diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 3440e6cd2..35b6e4102 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -689,68 +689,58 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul } const auto selected = EmitIndirectResourceIndex( state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u); - struct ImageRun { - uint32_t first; - uint32_t count; - uint32_t resource; - uint32_t slot_bias; + const auto SameType = [&](uint32_t lhs, uint32_t rhs) { + const auto& first = state.program.info.images[lhs]; + const auto& second = state.program.info.images[rhs]; + return first.dimension == second.dimension && first.cube == second.cube && + IR::DescriptorBindingForImage(first) == IR::DescriptorBindingForImage(second) && + (inst.GetOpcode() != IR::ValueOpcode::ImageWrite || + first.shader_swizzle == second.shader_swizzle) && + first.mip_count == 1u && second.mip_count == 1u; }; - std::vector runs; - for (uint32_t ordinal = 0; ordinal < image.indirect_resources.size(); ++ordinal) { - const auto resource = image.indirect_resources[ordinal]; - const auto& candidate = state.program.info.images[resource]; - const auto kind = *IR::DescriptorBindingForImage(candidate); - if (!runs.empty()) { - auto& run = runs.back(); - const auto& first = state.program.info.images[run.resource]; - const auto next_slot = run.slot_bias + ordinal; - if (candidate.dimension == first.dimension && candidate.cube == first.cube && - IR::DescriptorBindingForImage(first) == kind && - (inst.GetOpcode() != IR::ValueOpcode::ImageWrite || - candidate.shader_swizzle == first.shader_swizzle) && - candidate.mip_count == 1u && first.mip_count == 1u && - (ordinal == 1u || candidate.descriptor_index == next_slot)) { - // Native roots precede appended children; only the root may have a slot gap. - if (ordinal == 1u) - run.slot_bias = candidate.descriptor_index - ordinal; - ++run.count; - continue; - } - } - runs.push_back({ordinal, 1u, resource, - candidate.descriptor_index - ordinal}); + // Descriptor order is unrelated to image type. Share each typed access and + // select its descriptor slot independently, including gaps between native roots. + struct ImageGroup { uint32_t resource; uint32_t count; }; + std::vector groups; + std::vector ordinal_groups; + for (const auto resource: image.indirect_resources) { + const auto found = std::ranges::find_if(groups, [&](const ImageGroup& group) { + return SameType(resource, group.resource); + }); + ordinal_groups.push_back(static_cast(found - groups.begin())); + if (found == groups.end()) groups.push_back({resource, 1u}); + else ++found->count; } - const auto EmitRun = [&](uint32_t index) { - const auto& run = runs[index]; - if (run.count == 1u) return emit(run.resource, 0u); - auto slot = Binary(state, spv::OpIAdd, TypeU32(state), selected, - ConstantU32(state, run.slot_bias)); - if (run.first == 0u) { - const auto root_slot = image.descriptor_index; - if (root_slot != run.slot_bias) { - const auto is_root = Binary(state, spv::OpIEqual, TypeBool(state), selected, - ConstantU32(state, 0u)); - const auto root_or_child = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpSelect, TypeU32(state), root_or_child, - is_root, ConstantU32(state, root_slot), slot); - slot = root_or_child; - } - } - return emit(run.resource, slot); - }; - auto run_index = ConstantU32(state, 0u); - for (uint32_t index = 1; index < runs.size(); ++index) { + const bool dynamic_slot = std::ranges::any_of(groups, [](const ImageGroup& group) { + return group.count > 1u; + }); + auto group_index = ConstantU32(state, 0u); + auto slot_bias = ConstantU32(state, image.descriptor_index); + auto previous_bias = image.descriptor_index; + for (uint32_t ordinal = 1; ordinal < image.indirect_resources.size(); ++ordinal) { + const auto& candidate = state.program.info.images[image.indirect_resources[ordinal]]; + const auto bias = candidate.descriptor_index - ordinal; + const bool select_group = ordinal_groups[ordinal] != ordinal_groups[ordinal - 1u]; + const bool select_slot = dynamic_slot && bias != previous_bias; + previous_bias = bias; + if (!select_group && !select_slot) continue; const auto at_or_after = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), - selected, ConstantU32(state, runs[index].first)); - const auto next = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpSelect, TypeU32(state), next, at_or_after, - ConstantU32(state, index), run_index); - run_index = next; + selected, ConstantU32(state, ordinal)); + if (select_group) group_index = Select(state, TypeU32(state), at_or_after, + ConstantU32(state, ordinal_groups[ordinal]), group_index); + if (select_slot) slot_bias = Select(state, TypeU32(state), at_or_after, + ConstantU32(state, bias), slot_bias); } - return runs.size() == 1u - ? EmitRun(0u) - : EmitIndexSwitch(state, run_index, static_cast(runs.size()), - result_type, EmitRun); + const auto slot = dynamic_slot + ? Binary(state, spv::OpIAdd, TypeU32(state), selected, slot_bias) : 0u; + const auto EmitGroup = [&](uint32_t index) { + const auto& group = groups[index]; + return emit(group.resource, group.count == 1u ? 0u : slot); + }; + return groups.size() == 1u + ? EmitGroup(0u) + : EmitIndexSwitch(state, group_index, static_cast(groups.size()), + result_type, EmitGroup); } template diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index decf78eb4..a3e9959c2 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -40262,9 +40262,26 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { CollectShaderInfo(program, {.compute = &compute}); spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute}); ValidateSpirv(name, spirv); - Require(name, "mixed contiguous image runs", tools.Disassemble(spirv, &text) && - CountText(text, "OpImageSampleExplicitLod") == 3, - "one cube split the native arrays into per-image samples"); + Require(name, "mixed image types", tools.Disassemble(spirv, &text) && + CountText(text, "OpImageSampleExplicitLod") == 2, + "one cube duplicated the surrounding 2D samples"); + + for (u32 ordinal = 1; ordinal < root.indirect_resources.size(); ordinal += 2u) { + auto &alternating = program.info.images[root.indirect_resources[ordinal]]; + alternating.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; + alternating.cube = true; + } + program.shader_info_complete = false; + CollectShaderInfo(program, {.compute = &compute}); + spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute}); + ValidateSpirv(name, spirv); + Require(name, "alternating image types", tools.Disassemble(spirv, &text) && + CountText(text, "OpImageSampleExplicitLod") == 2, + "alternating cube and 2D descriptors expanded the typed samples"); + // The descriptor selector may grow, but 189 candidates still need only two + // sample bodies and should fit comfortably within a 32 KiB module. + Require(name, "alternating image emitted size", spirv.size() <= 8192u, + "alternating descriptors exceeded the 32 KiB SPIR-V regression budget"); program.memory_info[0].image_dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; @@ -40464,7 +40481,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 : 3u) * halves, + CountText(text, image_opcode) == (homogeneous ? 1u : 2u) * halves, "mixed runs expanded into per-image accesses"); if (homogeneous) { Require(test.name, "homogeneous gather array", text.find("OpSwitch") == std::string::npos, @@ -40486,7 +40503,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { Require(test.name, "nonuniform image operands", std::ranges::count_if(image_operands, [&](u32 operand) { return std::ranges::find(decorated, operand) != decorated.end(); - }) == (homogeneous ? 1u : 2u) * halves, + }) == halves, "a dynamically selected image operand lacks its nonuniform decoration"); } test.initial.assign(wave_size, 0xdeadbeefu); @@ -40533,7 +40550,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { } Require(test.name, "mixed numeric image result", raw_join && tools.Disassemble(mixed_spirv, &text) && - CountText(text, image_opcode) == (mode == Access::Fetch1D ? 3u : 4u) * halves && + CountText(text, image_opcode) == (mode == Access::Fetch1D ? 2u : 3u) * halves && CountText(text, "OpConvertUToF") == coordinate_conversions, "mixed image classes lost raw result bits or expanded per image"); typed.numeric_class = Prospero::TextureNumericClass::Float; @@ -40598,7 +40615,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u; Require(test.name, "native array writes per dimension and swizzle run", tools.Disassemble(compiled.spirv, &text) && - CountText(text, "OpImageWrite") == (mixed ? 5u : 1u) * halves && + CountText(text, "OpImageWrite") == (mixed ? 4u : 1u) * halves && (mixed || text.find("OpSwitch") == std::string::npos) && text.find("StorageImageArrayNonUniformIndexing") != std::string::npos, "storage candidates lost their typed native array runs");