diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 47f45285..6f246a66 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -61,10 +61,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR:: : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); } -ImageSampleLayout Layout(const IR::MemoryInfo& mem) { +ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) { ImageSampleLayout layout; uint32_t cursor = 0; - const auto& info = ImageDimensionInfoFor(mem.image_dimension); + const auto& info = ImageDimensionInfoFor(dimension); if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++; if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++; if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++; @@ -80,6 +80,48 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) { return layout; } +void ValidateIndirectImageOperands(ValueEmitContext& ctx, const IR::Inst& inst) { + const auto op = inst.GetOpcode(); + const auto& memory = ctx.Memory(inst); + const auto& images = ctx.state.program.info.images; + const auto& root = images.at(memory.resource); + if (root.indirect_root != memory.resource) return; + if (op != IR::ValueOpcode::ImageSampleRaw && + op != IR::ValueOpcode::ImageGatherRaw && op != IR::ValueOpcode::ImageRead) { + ctx.Fail(inst, "requires an unsupported indirect image operation"); + } + const auto& native = ImageDimensionInfoFor(memory.image_dimension); + const auto native_layout = Layout(memory, memory.image_dimension); + const bool sampling = op == IR::ValueOpcode::ImageSampleRaw || + op == IR::ValueOpcode::ImageGatherRaw; + const auto validate = [&](const IR::ImageResource& image) { + const auto& candidate = ImageDimensionInfoFor(image.dimension); + // Sample/fetch ignore the native array slice for a non-array T# while + // keeping the original positions of later operands. + const bool projected_2d = memory.image_dimension == ImageDimension::Dim2DArray && + image.dimension == ImageDimension::Dim2D; + bool compatible = projected_2d || + (candidate.coordinate_components == native.coordinate_components && + candidate.multisampled == native.multisampled); + if (sampling) { + compatible &= candidate.multisampled == 0u && native.multisampled == 0u; + compatible &= projected_2d || Layout(memory, image.dimension) == native_layout; + if (HasFlag(memory, Decoder::ImageSampleFlagDerivative)) { + compatible &= candidate.spatial_components == native.spatial_components; + } + if (op == IR::ValueOpcode::ImageGatherRaw || + HasFlag(memory, Decoder::ImageSampleFlagCompare)) { + compatible &= image.dimension != ImageDimension::Dim3D; + } + } + if (!compatible) ctx.Fail(inst, "has incompatible image operand layout"); + }; + validate(root); + for (const auto resource: root.indirect_resources) { + if (resource != memory.resource) validate(images.at(resource)); + } +} + uint32_t ZeroF32(EmitterState& state) { return ConstantF32(state, 0); } @@ -736,6 +778,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto& mem = ctx.Memory(inst); const auto image_arg = inst.Arg(0); ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource); + ValidateIndirectImageOperands(ctx, inst); const auto& image = state.program.info.images.at(mem.resource); const auto* address = ctx.ImageAddress(inst.Arg(image_info.needs_sampler ? 2 : 1)); if (op == IR::ValueOpcode::ImageQueryDimensions) { @@ -817,7 +860,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) { const auto dimension = image.dimension; const auto& dimension_info = ImageDimensionInfoFor(dimension); - const auto layout = Layout(mem); + const auto layout = Layout(mem, mem.image_dimension); const auto numeric_class = image.numeric_class; const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); IR::Value offset_value; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index e3611f0d..9e8f8b74 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -272,6 +272,8 @@ struct ImageSampleLayout { uint32_t lod = NoImageComponent; uint32_t grad_x = NoImageComponent; uint32_t grad_y = NoImageComponent; + + bool operator==(const ImageSampleLayout&) const = default; }; struct F32Class { diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 81d4bf91..8e68135d 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -621,10 +621,6 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna return SpecializationFail("indirect image specialization has no typed candidate"); } const auto& image_class = specialization.images[exemplar]; - const auto is_2d = [](Decoder::ImageDimension dimension) { - return dimension == Decoder::ImageDimension::Dim2D || - dimension == Decoder::ImageDimension::Dim2DArray; - }; for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) { auto& image = specialization.images[candidate]; if (image.indirect_root != root_index) { @@ -638,10 +634,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna image.shader_swizzle = image_class.shader_swizzle; image.cube = image_class.cube; } - const bool same_coordinates = image.dimension == image_class.dimension && - image.cube == image_class.cube; - if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) || - image.mip_count != image_class.mip_count || + if (image.mip_count != image_class.mip_count || image.conversion_format != image_class.conversion_format || image.shader_swizzle != image_class.shader_swizzle) { return SpecializationFail( @@ -1414,10 +1407,6 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& memory.sampler = sampler_plan.mapping[memory.sampler][type]; EXIT_IF(memory.sampler == UINT32_MAX); } - EXIT_IF(image.indirect_root == memory.resource && - inst.GetOpcode() != ValueOpcode::ImageSampleRaw && - inst.GetOpcode() != ValueOpcode::ImageGatherRaw && - inst.GetOpcode() != ValueOpcode::ImageRead); } } for (auto& memory: memory_info) { diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index ef0db04e..9d56f5d3 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -434,8 +434,18 @@ void TestBoundedImageViewEligibility() { plan.descriptor_sources.push_back(source); } plan.descriptor_sources[image_source].indirect_descriptor->sources = std::move(selected); + Check(MaterializeResources(plan, runtime, snapshot, specialization) && + snapshot.images.size() == 2u && + snapshot.images[0].dwords == descriptor(0x30u, Type::kColor2D) && + snapshot.images[1].dwords == descriptor(0x30u, Type::kColor3D) && + specialization.images[0].dimension == Decoder::ImageDimension::Dim2D && + specialization.images[1].dimension == Decoder::ImageDimension::Dim3D, + "explicitly selected image dimensions were discarded before operand validation"); + const auto converted = plan.descriptor_sources[image_source].indirect_descriptor->sources.back(); + plan.descriptor_sources[converted].dwords[1] = Value( + static_cast(Libs::Graphics::Prospero::BufferFormat::k11_11_10UInt) << 20u); Check(!MaterializeResources(plan, runtime, snapshot, specialization), - "explicitly selected incompatible descriptor was silently normalized to null"); + "explicitly selected incompatible conversion was silently normalized to null"); } void TestWaterfallImageTable() { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3d85a320..0e89201b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1987,6 +1987,7 @@ public: vk::ImageLayout layout = vk::ImageLayout::eUndefined; u32 width = 0; u32 height = 0; + u32 depth = 1; u32 layers = 1; u32 mip_levels = 1; u32 dwords_per_pixel = 0; @@ -15451,11 +15452,12 @@ public: u32 view_layers = 0, vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1, vk::Format view_format = vk::Format::eUndefined, - u32 view_base_mip = 0) { + u32 view_base_mip = 0, u32 depth = 1) { Image ret; ret.format = format; ret.width = width; ret.height = height; + ret.depth = depth; ret.layers = layers; ret.mip_levels = std::max(static_cast(initial_mips.size()), 1u); ret.dwords_per_pixel = dwords_per_pixel; @@ -15466,7 +15468,7 @@ public: image_info.format = format; image_info.extent.width = width; image_info.extent.height = height; - image_info.extent.depth = 1; + image_info.extent.depth = depth; image_info.mipLevels = ret.mip_levels; image_info.arrayLayers = layers; image_info.samples = samples; @@ -15531,13 +15533,13 @@ public: size_t total_dwords = 0; for (u32 level = 0; level < ret.mip_levels; level++) { total_dwords += - ImageMipDwordCount(width, height, dwords_per_pixel, level, layers); + ImageMipDwordCount(width, height, dwords_per_pixel, level, layers) * MipExtent(depth, level); } std::vector contents(total_dwords, 0); size_t offset = 0; for (u32 level = 0; level < ret.mip_levels; level++) { const auto level_dwords = - ImageMipDwordCount(width, height, dwords_per_pixel, level, layers); + ImageMipDwordCount(width, height, dwords_per_pixel, level, layers) * MipExtent(depth, level); const auto &src = initial_mips[level]; for (size_t i = 0; i < src.size() && i < level_dwords; i++) { contents[offset + i] = src[i]; @@ -15681,7 +15683,8 @@ public: if (image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { supported = supported || image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D || - image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray; + image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray || + image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D; } Require(test.name, "dispatch", supported, "unsupported image dimension needs a matching Vulkan test view"); @@ -19202,11 +19205,11 @@ private: copy.imageSubresource.layerCount = image->layers; copy.imageExtent.width = MipExtent(image->width, level); copy.imageExtent.height = MipExtent(image->height, level); - copy.imageExtent.depth = 1; + copy.imageExtent.depth = MipExtent(image->depth, level); copies.push_back(copy); offset += static_cast( ImageMipDwordCount(image->width, image->height, - image->dwords_per_pixel, level, image->layers) * + image->dwords_per_pixel, level, image->layers) * MipExtent(image->depth, level) * sizeof(u32)); } cmd.copyBufferToImage(staging, image->image, @@ -36134,6 +36137,9 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { program.info.images[root.indirect_resources[103]].dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; program.info.images[root.indirect_resources[103]].cube = true; + program.memory_info[0].image_dimension = + ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; + program.memory_info[0].image_address_components = 4; program.shader_info_complete = false; CollectShaderInfo(program, {.compute = &compute}); spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute}); @@ -36142,9 +36148,6 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { CountText(text, "OpImageSampleExplicitLod") == 3, "one cube split the native arrays into per-image samples"); - program.memory_info[0].image_dimension = - ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; - program.memory_info[0].image_address_components = 4; for (u32 component = 0; component < 4u; component++) { constexpr std::array values{1.375f, 1.625f, 10.0f, 2.0f}; address.SetArg(component, Value(std::bit_cast(values[component]))); @@ -36198,6 +36201,83 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { Require(name, "mixed sample count", samples == 3u, "mixed candidate switch did not retain every image"); } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + enum class RejectedAccess { LodPosition, Derivatives, Gather, Depth, Multisampled, Query, Write, Atomic }; + for (const auto [rejected, label] : { + std::pair{RejectedAccess::LodPosition, "IndirectImageLodPositionRejected"}, + std::pair{RejectedAccess::Derivatives, "IndirectImageDerivativeLayoutRejected"}, + std::pair{RejectedAccess::Gather, "IndirectImageVolumeGatherRejected"}, + std::pair{RejectedAccess::Depth, "IndirectImageVolumeDrefRejected"}, + std::pair{RejectedAccess::Multisampled, "IndirectImageMsaaSampleRejected"}, + std::pair{RejectedAccess::Query, "IndirectImageQueryRejected"}, + std::pair{RejectedAccess::Write, "IndirectImageWriteRejected"}, + std::pair{RejectedAccess::Atomic, "IndirectImageAtomicRejected"}}) { + ExpectFatal(label, [&] { + using Dimension = ShaderRecompiler::Decoder::ImageDimension; + for (auto &resource : program.info.images) { + resource.dimension = Dimension::Dim2DArray; + resource.cube = false; + } + program.info.images[2].dimension = Dimension::Dim3D; + auto &mem = program.memory_info[0]; + const auto position = std::ranges::find_if(*block, [&](const Inst &inst) { return &inst == &sample; }); + if (rejected == RejectedAccess::LodPosition) { + // Four available operands do not make native x,y,LOD compatible with x,y,z,LOD. + mem.image_dimension = Dimension::Dim2D; + program.info.images[0].dimension = program.info.images[1].dimension = Dimension::Dim2D; + } else if (rejected == RejectedAccess::Derivatives) { + mem.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagDerivative; + mem.image_address_components = 9u; // Capacity alone must not change the native gradient layout. + } else if (rejected == RejectedAccess::Gather) { + mem.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLevelZero; + mem.image_address_components = 3u; + const auto gather = block->PrependNewInst(position, ValueOpcode::ImageGatherRaw, + {Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u), + Value(0u), Value(0u), Value(true)}, memory_flag_bits); + sample.ReplaceUsesWith(Value(&*gather), false); + } else if (rejected == RejectedAccess::Depth) { + mem.image_sample_flags |= ShaderRecompiler::Decoder::ImageSampleFlagCompare; + mem.image_address_components = 5u; + for (auto &resource : program.info.images) resource.depth_compare = true; + program.info.samplers[0].depth_compare = true; + } else if (rejected == RejectedAccess::Multisampled) { + program.info.images[2].dimension = Dimension::Dim2DMsaaArray; + } else if (rejected == RejectedAccess::Query) { + const auto query = block->PrependNewInst(position, ValueOpcode::ImageQueryDimensions, + {Value(&image), Value(&address)}, memory_flag_bits); + sample.ReplaceUsesWith(Value(&*query), false); + } else { + sample_x.ReplaceUsesWith(Value(0u), false); + sample.Invalidate(); + sampler.Invalidate(); + program.info.samplers.clear(); + program.info.sampled_pairs.clear(); + mem.image_sample_flags = 0u; + mem.image_address_components = 3u; + const bool atomic = rejected == RejectedAccess::Atomic; + for (auto &resource : program.info.images) { + resource.resource_class = ImageResourceClass::Storage; + resource.numeric_class = atomic ? Prospero::TextureNumericClass::Uint + : Prospero::TextureNumericClass::Float; + resource.written = true; + resource.atomic = atomic; + } + if (atomic) { + block->PrependNewInst(position, ValueOpcode::ImageAtomicIAdd32, + {Value(&image), Value(&address), Value(1u), Value(true)}, memory_flag_bits); + } else { + const auto texel = block->PrependNewInst(position, ValueOpcode::CompositeConstructU32x4, + {Value(0u), Value(0u), Value(0u), Value(0u)}); + block->PrependNewInst(position, ValueOpcode::ImageWrite, + {Value(&image), Value(&address), Value(&*texel), Value(true)}, memory_flag_bits); + } + } + program.shader_info_complete = false; + CollectShaderInfo(program, {.compute = &compute}); + ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute}); + }); + } +#endif // Keep an unrelated native root between the null image and appended children, // then select both sides of a cube boundary and the unmapped suffix on the GPU. root.indirect_resources = {0u, 2u, 3u, 4u, 5u, 6u}; @@ -36248,6 +36328,24 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { resource == 4u ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, layers)); } const auto native_sampler = vulkan.CreateSampler(test.name); + const auto mip_sampler = vulkan.CreateSampler(test.name, 2u); + constexpr u32 volume_mip_bits = 0x43800001u; + constexpr u32 array_mip_bits = 0x42800001u; + const auto mip_pixels = [](u32 texels, u32 first) { + std::vector pixels(texels * 4u); + for (u32 texel = 0; texel < texels; ++texel) + std::fill_n(pixels.begin() + texel * 4u, 4u, first + texel); + return pixels; + }; + auto volume_texture = vulkan.CreateImageMips( + test.name, 4, 4, vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eSampled, + {mip_pixels(4u * 4u * 8u, 0x44000001u), mip_pixels(2u * 2u * 4u, volume_mip_bits)}, 4u, + vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageType::e3D, vk::ImageViewType::e3D, 1u, + 0u, 0u, vk::SampleCountFlagBits::e1, vk::Format::eUndefined, 0u, 8u); + auto array_texture = vulkan.CreateImageMips( + test.name, 4, 4, vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eSampled, + {mip_pixels(4u * 4u * 2u, 0x43000001u), mip_pixels(2u * 2u * 2u, array_mip_bits)}, 4u, + vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageType::e2D, vk::ImageViewType::e2DArray, 2u); constexpr u32 uint_texel = 0x80000106u; auto uint_texture = vulkan.CreateImageMips( test.name, 2, 2, vk::Format::eR32G32B32A32Uint, vk::ImageUsageFlagBits::eSampled, @@ -36267,21 +36365,35 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { &integer_sampler_info, nullptr, &integer_border_sampler), "vkCreateSampler"); const std::array native_samplers{native_sampler, native_sampler, native_sampler, integer_sampler, integer_sampler, integer_border_sampler}; - enum class Access { Sample, Gather, GatherHomogeneous, MixedSample, MixedGather, MixedFetch, Fetch }; + enum class Access { Sample, MixedDimensions, Gather, GatherHomogeneous, MixedSample, MixedGather, MixedFetch, Fetch }; auto *access = &sample; bool uint_bound = false; - for (const auto mode : {Access::Sample, Access::Gather, + bool volume_bound = false; + for (const auto mode : {Access::Sample, Access::MixedDimensions, Access::Gather, Access::GatherHomogeneous, Access::MixedSample, Access::MixedGather, Access::MixedFetch, Access::Fetch}) { const bool mixed = mode == Access::MixedSample || mode == Access::MixedGather || mode == Access::MixedFetch; + const bool dimensions = mode == Access::MixedDimensions; const bool fetch = mode == Access::Fetch || mode == Access::MixedFetch; const bool gather = mode == Access::Gather || mode == Access::GatherHomogeneous || mode == Access::MixedGather; const bool homogeneous = mode == Access::GatherHomogeneous; - test.name = mixed ? (fetch ? "IndirectImageMixedFetch" : gather ? "IndirectImageMixedGather" + test.name = dimensions ? "IndirectImageArrayCubeVolumeLod" + : mixed ? (fetch ? "IndirectImageMixedFetch" : gather ? "IndirectImageMixedGather" : "IndirectImageMixedSample") : fetch ? "IndirectImageFetchContiguousRuns" : homogeneous ? "IndirectImageGatherHomogeneous" : gather ? "IndirectImageGatherContiguousRuns" : "IndirectImageContiguousRuns"; + if (volume_bound != dimensions) { + std::swap(textures[6], volume_texture); + std::swap(textures[3], array_texture); + compiled.program.info.images[6].dimension = dimensions + ? ShaderRecompiler::Decoder::ImageDimension::Dim3D + : ShaderRecompiler::Decoder::ImageDimension::Dim2D; + compiled.program.info.images[3].dimension = dimensions + ? ShaderRecompiler::Decoder::ImageDimension::Dim2DArray + : ShaderRecompiler::Decoder::ImageDimension::Dim2D; + volume_bound = dimensions; + } if (uint_bound != mixed) { std::swap(textures[6], uint_texture); compiled.program.info.images[6].numeric_class = mixed ? Prospero::TextureNumericClass::Uint @@ -36321,7 +36433,20 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { access->ReplaceUsesWith(Value(&read), false); access = &read; } - if (gather) { + if (dimensions) { + const auto position = std::ranges::find_if(*block, [&](const Inst &inst) { return &inst == &address; }); + const auto parity = block->PrependNewInst(position, ValueOpcode::BitwiseAnd32, {Value(&key), Value(1u)}); + const auto odd = block->PrependNewInst(position, ValueOpcode::INotEqual32, {Value(&*parity), Value(0u)}); + const auto z = block->PrependNewInst(position, ValueOpcode::SelectU32, + {Value(&*odd), Value(std::bit_cast(0.75f)), Value(std::bit_cast(0.25f))}); + const auto cube_lane = block->PrependNewInst(position, ValueOpcode::IEqual32, {Value(&key), Value(3u)}); + const auto coordinate_z = block->PrependNewInst(position, ValueOpcode::SelectU32, + {Value(&*cube_lane), Value(0u), Value(&*z)}); + address.SetArg(0, Value(std::bit_cast(1.25f))); + address.SetArg(1, Value(std::bit_cast(1.75f))); + address.SetArg(2, Value(&*coordinate_z)); + address.SetArg(3, Value(std::bit_cast(1.0f))); + } else if (gather) { compiled.program.info.images[4].cube = false; auto &gather_memory = compiled.program.memory_info[0]; gather_memory.dmask = 1; @@ -36342,7 +36467,8 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { selected_resources = homogeneous ? std::vector{0u, 2u, 3u, 5u, 6u} : root.indirect_resources; compiled.resources.flattened_srt[6] = homogeneous ? 4u : ordinals[5]; - compiled.resources.flattened_srt[7] = mixed ? 5u : ordinals[6]; + compiled.resources.flattened_srt[7] = mixed || dimensions ? 5u : ordinals[6]; + compiled.resources.flattened_srt[8] = dimensions ? 2u : ordinals[7]; if (fetch) { sampler.Invalidate(); compiled.program.info.samplers.clear(); @@ -36371,7 +36497,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather" : "OpImageSampleExplicitLod"; Require(test.name, "native array image runs", tools.Disassemble(compiled.spirv, &text) && - CountText(text, image_opcode) == (homogeneous ? 1u : mixed ? 4u : 3u) * halves, + CountText(text, image_opcode) == (homogeneous ? 1u : dimensions ? 5u : mixed ? 4u : 3u) * halves, "mixed runs expanded into per-image accesses"); if (homogeneous) { Require(test.name, "homogeneous gather array", text.find("OpSwitch") == std::string::npos, @@ -36400,13 +36526,16 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { test.expected.assign(wave_size, 0u); for (u32 lane = 0; lane < ordinals.size(); ++lane) { const auto resource = selected_resources[compiled.resources.flattened_srt[1u + lane]]; - test.expected[lane] = mixed && resource == 6u + test.expected[lane] = dimensions && resource == 6u + ? volume_mip_bits + ((lane & 1u) ? 3u : 1u) * 4u + 3u + : dimensions && resource == 3u ? array_mip_bits + (lane & 1u) * 4u + 3u + : mixed && resource == 6u ? (fetch ? uint_texel + 10u : (lane & 1u) ? 1u : uint_texel) - : std::bit_cast(float(resource + (fetch && resource != 0u ? 10u : 0u))); + : std::bit_cast(float(resource + ((fetch || dimensions) && resource != 0u ? 10u : 0u))); } auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size()); vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr, - fetch ? vk::Sampler{} : native_sampler, textures, + fetch ? vk::Sampler{} : dimensions ? mip_sampler : native_sampler, textures, mixed && !fetch ? std::span(native_samplers) : std::span{}); const auto actual = vulkan.ReadBuffer(test.name, buffer, test.expected.size()); vulkan.DestroyBuffer(&buffer); @@ -36417,7 +36546,10 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { vulkan.Device().destroySampler(integer_sampler); vulkan.Device().destroySampler(integer_border_sampler); vulkan.Device().destroySampler(native_sampler); + vulkan.Device().destroySampler(mip_sampler); vulkan.DestroyImage(&uint_texture); + vulkan.DestroyImage(&volume_texture); + vulkan.DestroyImage(&array_texture); for (auto &texture : textures) vulkan.DestroyImage(&texture); } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 04d21f38..41378ea8 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -14258,6 +14258,7 @@ void TestBoundedScalarMaterialImageKeys() { bool clean = true; bool mixed = false; bool mixed_dimension = false; + bool mixed_conversion = false; bool same_key = false; uint32_t keys = 0; uint32_t images = 0; @@ -14286,8 +14287,10 @@ void TestBoundedScalarMaterialImageKeys() { std::ranges::fill(words, 0u); words[0] = key; const bool integer = key == 5573u || key == 5588u; - words[1] = static_cast(integer && !(m.mixed && key == 5573u) - ? Prospero::BufferFormat::k32UInt : Prospero::BufferFormat::k32Float) << 20u; + words[1] = static_cast(m.mixed_conversion && key == 5573u + ? Prospero::BufferFormat::k11_11_10UInt + : integer && !(m.mixed && key == 5573u) + ? Prospero::BufferFormat::k32UInt : Prospero::BufferFormat::k32Float) << 20u; words[3] = DstSel(4, 5, 6, 7) | (static_cast(m.mixed_dimension && key == 5573u ? Prospero::ImageType::kColor2D : Prospero::ImageType::kColor3D) << 28u); @@ -14344,8 +14347,15 @@ void TestBoundedScalarMaterialImageKeys() { }) == 1, "selected material fields lost their mixed float/uint image classes"); memory = {.mixed_dimension = true}; + Check(MaterializeResources(plan, runtime, snapshot, specialization) && + snapshot.images.size() == 4u && + std::ranges::count_if(specialization.images, [](const auto& image) { + return image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D; + }) == 1, + "selected material fields lost their per-candidate image dimensions"); + memory = {.mixed_conversion = true}; Check(!MaterializeResources(plan, runtime, snapshot, specialization), - "selected material fields bypassed image coordinate compatibility"); + "selected material fields bypassed image conversion compatibility"); memory = {}; Check(MaterializeResources(plan, runtime, snapshot, specialization), "native scalar material image selection did not recover after rejected candidates");