shader: query dimensions through shared indirect image selection

This commit is contained in:
nmzik
2026-10-18 21:35:02 +02:00
parent de81300b28
commit 09c993dfe0
4 changed files with 99 additions and 10 deletions
+11 -5
View File
@@ -3167,7 +3167,7 @@ void TestUniformizedMaterialImageKeys() {
void TestIndexedMaterialImageKeys() {
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
const auto make_plan = [](ShaderType stage = ShaderType::Compute, bool shared = false) {
const auto make_program = [](ShaderType stage = ShaderType::Compute, bool shared = false) {
Fixture f(stage);
auto *entry = f.block, *body = f.AddBlock();
auto *later = shared ? f.AddBlock() : nullptr;
@@ -3251,9 +3251,10 @@ void TestIndexedMaterialImageKeys() {
offset == 12994u * 32u,
"inherited indexed selector changed GPU byte offsets into raw material keys");
}
return ExtractResourcePlan(f.program);
return std::move(f.program);
};
auto plan = make_plan();
auto program = make_program();
auto plan = ExtractResourcePlan(program);
Check(plan.info.images.size() == 3u && plan.selector_keys.size() == 2u,
"indexed material fields did not share their query/gather selector domain");
for (const auto &image : plan.info.images) {
@@ -3304,6 +3305,9 @@ void TestIndexedMaterialImageKeys() {
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
memory.selectors == 2u && memory.descriptors == 6u && snapshot.images.size() == 9u,
"indexed selector scanned the broad heap or did not share its captured keys");
ApplyResourceSpecialization(program, specialization);
Check(program.info.images[0].indirect_resources.size() == 3u,
"indirect dimension query lost its specialized candidates");
for (uint32_t root = 0; root < plan.info.images.size(); ++root) {
const auto &image = specialization.images[root];
const auto offset = image.indirect_mapping_offset;
@@ -3319,7 +3323,8 @@ void TestIndexedMaterialImageKeys() {
Check(std::ranges::find(snapshot.specialization_reads,
std::pair<uint64_t, uint64_t>{base + field, 868u}) != snapshot.specialization_reads.end(),
"indexed selector batch escaped writable-alias tracking");
auto shared = make_plan(ShaderType::Compute, true);
auto shared_program = make_program(ShaderType::Compute, true);
auto shared = ExtractResourcePlan(shared_program);
Check(MaterializeResources(shared, runtime, snapshot, specialization) && snapshot.images.size() == 9u,
"inherited indexed selector lost its bounded material domain");
memory.words[64u / 4u] = 12996u;
@@ -3349,7 +3354,8 @@ void TestIndexedMaterialImageKeys() {
"unsupported geometry or writable selector lost the original heap domain");
}
user_data = original;
auto graphics = make_plan(ShaderType::Pixel);
auto graphics_program = make_program(ShaderType::Pixel);
auto graphics = ExtractResourcePlan(graphics_program);
Check(MaterializeResources(graphics, runtime, snapshot, specialization) && memory.selectors == 0u,
"graphics material lookup acquired unproved selector read dependencies");
memory.unreadable = false;
+80 -1
View File
@@ -40583,13 +40583,92 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
}
std::printf("[compute] %-32s ok\n", test.name);
}
test.name = "IndirectImageQueryDimensions";
const auto address_position = std::ranges::find_if(*block, [&](const Inst &inst) { return &inst == &address; });
auto &query_lod = *block->PrependNewInst(address_position, ValueOpcode::BitwiseAnd32,
{Value(&key), Value(1u)});
address.SetArg(0, Value(&query_lod));
const auto access_position = std::ranges::find_if(*block, [&](const Inst &inst) { return &inst == access; });
auto &query = *block->PrependNewInst(access_position, ValueOpcode::ImageQueryDimensions,
{Value(&image), Value(&address)}, memory_flag_bits);
access->ReplaceUsesWith(Value(&query), false);
store.Invalidate();
auto &query_offset = block->AppendNewInst(ValueOpcode::IMul32, {Value(&key), Value(16u)});
auto &query_store = block->AppendNewInst(ValueOpcode::StoreBufferU32x4,
{Value(&output), Value(0u), Value(&query_offset), Value(0u), Value(&query), Value(true)});
query_store.SetFlags(MemoryFlags{1u, 0u});
compiled.program.memory_info[1].data_dwords = 4u;
compiled.program.memory_info[0].image_address_components = 1u;
compiled.program.memory_info[0].image_has_mip = false;
compiled.program.info.images[3].numeric_class = Prospero::TextureNumericClass::Uint;
compiled.program.info.images[4].dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
constexpr std::array<std::array<u32, 4>, 7> geometry{{
{2, 2, 0, 2}, {2, 2, 0, 2}, {4, 2, 0, 3}, {8, 0, 0, 4},
{2, 2, 6, 2}, {2, 2, 0, 2}, {2, 2, 0, 2}}};
for (const u32 resource : {2u, 3u, 4u}) {
const auto &size = geometry[resource];
const u32 height = std::max(size[1], 1u), layers = std::max(size[2], 1u);
std::vector<std::vector<u32>> mips;
for (u32 mip = 0; mip < size[3]; ++mip)
mips.emplace_back(std::max(size[0] >> mip, 1u) * std::max(height >> mip, 1u) * layers * 4u);
vulkan.DestroyImage(&textures[resource]);
textures[resource] = vulkan.CreateImageMips(test.name, size[0], height,
resource == 3u ? vk::Format::eR32G32B32A32Uint : vk::Format::eR32G32B32A32Sfloat,
vk::ImageUsageFlagBits::eSampled, mips, 4u, vk::ImageLayout::eShaderReadOnlyOptimal,
resource == 3u ? vk::ImageType::e1D : vk::ImageType::e2D,
resource == 3u ? vk::ImageViewType::e1D
: resource == 4u ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, layers);
}
compiled.program.shader_info_complete = false;
CollectShaderInfo(compiled.program, {.compute = &compute});
for (const u32 wave_size : {32u, 64u}) {
compute.wave_size = wave_size;
compute.threads_num[0] = wave_size;
compiled.program.wave_size = wave_size;
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &compute});
compiled.packed_user_data.resize(compiled.program.bindings.ShaderDataDwords());
ValidateSpirv(test.name, compiled.spirv);
const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u;
Require(test.name, "typed query body count", tools.Disassemble(compiled.spirv, &text) &&
CountText(text, "OpImageQuerySizeLod") == 3u * halves &&
CountText(text, "OpImageQueryLevels") == 3u * halves &&
compiled.spirv.size() <= 8192u * halves,
"indirect geometry queries exceeded their per-type body or emitted-size budget");
std::vector<u32> decorated, queried;
for (size_t offset = 5; offset < compiled.spirv.size();) {
const auto *words = compiled.spirv.data() + offset;
const auto op = static_cast<spv::Op>(words[0] & 0xffffu);
if (op == spv::OpDecorate && words[2] == spv::DecorationNonUniform) decorated.push_back(words[1]);
if (op == spv::OpImageQuerySizeLod) queried.push_back(words[3]);
offset += words[0] >> 16u;
}
Require(test.name, "nonuniform query image", std::ranges::count_if(queried, [&](u32 operand) {
return std::ranges::find(decorated, operand) != decorated.end();
}) == halves, "the dynamic query image lacks its nonuniform decoration");
test.initial.assign(wave_size * 4u, 0xdeadbeefu);
test.expected.clear();
for (u32 lane = 0; lane < wave_size; ++lane) {
const auto resource = lane < ordinals.size() ? root.indirect_resources[ordinals[lane]] : 0u;
auto size = geometry[resource];
size[0] = std::max(size[0] >> (lane & 1u), 1u);
if (size[1] != 0u) size[1] = std::max(size[1] >> (lane & 1u), 1u);
test.expected.insert(test.expected.end(), size.begin(), size.end());
}
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr, {}, textures);
const auto actual = vulkan.ReadBuffer(test.name, buffer, test.expected.size());
vulkan.DestroyBuffer(&buffer);
CompareWords(test, "selected dimensions, mip levels and unmapped root", test.expected, actual);
}
std::printf("[compute] %-32s ok\n", test.name);
query_store.Invalidate();
compiled.program.info.images[3].numeric_class = Prospero::TextureNumericClass::Float;
vulkan.Device().destroySampler(native_sampler);
for (auto &texture : textures) vulkan.DestroyImage(&texture);
textures.clear();
// Reuse the GPU key map, including the separated root slot and unmapped suffix,
// for storage writes. Every lane has a distinct texel address.
store.Invalidate();
for (auto &inst : *block)
if (inst.GetOpcode() == ValueOpcode::ReferenceU32) inst.Invalidate();
compiled.program.info.buffers.clear();