shader: share indirect image accesses by type and guard emitted size

This commit is contained in:
nmzik
2026-10-18 20:16:49 +02:00
parent 7675579e79
commit 8639e0de81
2 changed files with 72 additions and 65 deletions
@@ -689,68 +689,58 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul
} }
const auto selected = EmitIndirectResourceIndex( const auto selected = EmitIndirectResourceIndex(
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u); state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
struct ImageRun { const auto SameType = [&](uint32_t lhs, uint32_t rhs) {
uint32_t first; const auto& first = state.program.info.images[lhs];
uint32_t count; const auto& second = state.program.info.images[rhs];
uint32_t resource; return first.dimension == second.dimension && first.cube == second.cube &&
uint32_t slot_bias; IR::DescriptorBindingForImage(first) == IR::DescriptorBindingForImage(second) &&
};
std::vector<ImageRun> 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 || (inst.GetOpcode() != IR::ValueOpcode::ImageWrite ||
candidate.shader_swizzle == first.shader_swizzle) && first.shader_swizzle == second.shader_swizzle) &&
candidate.mip_count == 1u && first.mip_count == 1u && first.mip_count == 1u && second.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});
}
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); // Descriptor order is unrelated to image type. Share each typed access and
for (uint32_t index = 1; index < runs.size(); ++index) { // select its descriptor slot independently, including gaps between native roots.
const auto at_or_after = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), struct ImageGroup { uint32_t resource; uint32_t count; };
selected, ConstantU32(state, runs[index].first)); std::vector<ImageGroup> groups;
const auto next = state.builder.AllocateId(); std::vector<uint32_t> ordinal_groups;
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next, at_or_after, for (const auto resource: image.indirect_resources) {
ConstantU32(state, index), run_index); const auto found = std::ranges::find_if(groups, [&](const ImageGroup& group) {
run_index = next; return SameType(resource, group.resource);
});
ordinal_groups.push_back(static_cast<uint32_t>(found - groups.begin()));
if (found == groups.end()) groups.push_back({resource, 1u});
else ++found->count;
} }
return runs.size() == 1u const bool dynamic_slot = std::ranges::any_of(groups, [](const ImageGroup& group) {
? EmitRun(0u) return group.count > 1u;
: EmitIndexSwitch(state, run_index, static_cast<uint32_t>(runs.size()), });
result_type, EmitRun); 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, 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);
}
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<uint32_t>(groups.size()),
result_type, EmitGroup);
} }
template <typename Emit> template <typename Emit>
+24 -7
View File
@@ -40262,9 +40262,26 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
CollectShaderInfo(program, {.compute = &compute}); CollectShaderInfo(program, {.compute = &compute});
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute}); spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv(name, spirv); ValidateSpirv(name, spirv);
Require(name, "mixed contiguous image runs", tools.Disassemble(spirv, &text) && Require(name, "mixed image types", tools.Disassemble(spirv, &text) &&
CountText(text, "OpImageSampleExplicitLod") == 3, CountText(text, "OpImageSampleExplicitLod") == 2,
"one cube split the native arrays into per-image samples"); "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 = program.memory_info[0].image_dimension =
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
@@ -40464,7 +40481,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather" const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather"
: "OpImageSampleExplicitLod"; : "OpImageSampleExplicitLod";
Require(test.name, "native array image runs", tools.Disassemble(compiled.spirv, &text) && 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"); "mixed runs expanded into per-image accesses");
if (homogeneous) { if (homogeneous) {
Require(test.name, "homogeneous gather array", text.find("OpSwitch") == std::string::npos, 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", Require(test.name, "nonuniform image operands",
std::ranges::count_if(image_operands, [&](u32 operand) { std::ranges::count_if(image_operands, [&](u32 operand) {
return std::ranges::find(decorated, operand) != decorated.end(); return std::ranges::find(decorated, operand) != decorated.end();
}) == (homogeneous ? 1u : 2u) * halves, }) == halves,
"a dynamically selected image operand lacks its nonuniform decoration"); "a dynamically selected image operand lacks its nonuniform decoration");
} }
test.initial.assign(wave_size, 0xdeadbeefu); test.initial.assign(wave_size, 0xdeadbeefu);
@@ -40533,7 +40550,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
} }
Require(test.name, "mixed numeric image result", raw_join && Require(test.name, "mixed numeric image result", raw_join &&
tools.Disassemble(mixed_spirv, &text) && 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, CountText(text, "OpConvertUToF") == coordinate_conversions,
"mixed image classes lost raw result bits or expanded per image"); "mixed image classes lost raw result bits or expanded per image");
typed.numeric_class = Prospero::TextureNumericClass::Float; 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; const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u;
Require(test.name, "native array writes per dimension and swizzle run", Require(test.name, "native array writes per dimension and swizzle run",
tools.Disassemble(compiled.spirv, &text) && 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) && (mixed || text.find("OpSwitch") == std::string::npos) &&
text.find("StorageImageArrayNonUniformIndexing") != std::string::npos, text.find("StorageImageArrayNonUniformIndexing") != std::string::npos,
"storage candidates lost their typed native array runs"); "storage candidates lost their typed native array runs");