shader: resolve bounded GPU material keys through sampled image arrays

Co-authored-by: TheCruZ <thecruzwtf@gmail.com>
This commit is contained in:
nmzik
2026-10-04 06:05:03 +04:00
parent 139b564c01
commit 843b577880
8 changed files with 266 additions and 43 deletions
@@ -487,6 +487,7 @@ struct DescriptorSource {
uint32_t selector_stride = 0;
uint32_t selector_offset = 0;
uint32_t table_offset = 0;
uint32_t selector_shift = 0;
Value key_count;
Value selector_first;
Value selector_mask;
@@ -285,6 +285,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
ShaderBufferResource material;
uint32_t first = 0, count = 0;
if (!DecodeBufferDescriptor(material_value, material) || material.Type() != 0u ||
(indirect.selector_shift != 0u && (material.Base48() & 3u) != 0u) ||
material.SwizzleEnabled() ||
material.AddTid() || material.OutOfBounds() != 0u ||
uint64_t {indirect.selector_offset} + 4u > material.Stride() ||
@@ -296,17 +297,20 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
indirect.selector_offset + 4u >
uint64_t {UINT32_MAX} + 1u)
return false;
keys.reserve(count);
for (uint64_t index = first; index < uint64_t {first} + count; ++index) {
uint32_t key = 0;
const auto offset = index * material.Stride() + indirect.selector_offset;
if (!ReadScalarTable(material.Base48(), material.GetSize(), offset, runtime,
{&key, 1}))
return false;
keys.push_back(key);
if (material.Stride() == 4u && indirect.selector_offset == 0u) {
keys.resize(count);
if (!ReadScalarTable(material.Base48(), material.GetSize(), uint64_t {first} * 4u,
runtime, keys)) return false;
} else {
keys.reserve(count);
for (uint64_t index = first; index < uint64_t {first} + count; ++index) {
uint32_t key = 0;
const auto offset = index * material.Stride() + indirect.selector_offset;
if (!ReadScalarTable(material.Base48(), material.GetSize(), offset, runtime,
{&key, 1})) return false;
keys.push_back(key);
}
}
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
} else if (!indirect.selector_mask.IsEmpty()) {
uint32_t mask = 0;
uint32_t count = 0;
@@ -330,8 +334,6 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
keys.push_back(key);
mask &= mask - 1u;
}
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
} else {
ShaderBufferResource material;
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u ||
@@ -357,6 +359,12 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
}
keys.push_back(key);
}
}
if (indirect.material_source != UINT32_MAX) {
if (indirect.selector_shift != 0u) {
for (auto& key: keys) key >>= indirect.selector_shift;
keys.push_back(0u); // An out-of-range material load returns zero.
}
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
}
@@ -426,10 +434,21 @@ struct SamplerPlan {
uint32_t sampler_count = 0;
};
template <typename T, typename Keep>
void CompactImages(std::vector<T>& images, Keep&& keep) {
size_t count = 0;
for (size_t index = 0; index < images.size(); ++index) {
if (!keep(index)) continue;
if (count != index) images[count] = std::move(images[index]);
++count;
}
images.resize(count);
}
struct ImageRemap {
explicit ImageRemap(const ResourceSpecialization& specialization)
: source_count(static_cast<uint32_t>(specialization.images.size())) {
EXIT_IF(specialization.images.size() > indices.size());
: indices(specialization.images.size()),
source_count(static_cast<uint32_t>(specialization.images.size())) {
for (uint32_t index = 0; index < source_count; index++) {
indices[index] = specialization.images[index].fmask ? UINT32_MAX : count++;
}
@@ -446,16 +465,11 @@ struct ImageRemap {
if (count == source_count) {
return;
}
for (uint32_t index = 0; index < source_count; index++) {
if (indices[index] != UINT32_MAX && indices[index] != index) {
images[indices[index]] = std::move(images[index]);
}
}
images.resize(count);
CompactImages(images, [&](size_t index) { return indices[index] != UINT32_MAX; });
}
private:
std::array<uint32_t, ShaderInfo::MaxImages> indices;
std::vector<uint32_t> indices;
uint32_t source_count;
uint32_t count = 0;
};
@@ -602,7 +616,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
}
}
}
ImageRemap(specialization).Apply(snapshot.images);
CompactImages(snapshot.images, [&](size_t index) { return !specialization.images[index].fmask; });
return true;
}
@@ -1146,7 +1160,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
const auto& indirect = *source->indirect_descriptor;
if (!MaterializeIndirectDescriptor(
program, indirect, i, 8u, observed, clean, snapshot, snapshot.images,
specialization.images, ShaderInfo::MaxImages, [&](DescriptorValue& value) {
specialization.images, UINT32_MAX, [&](DescriptorValue& value) {
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, image.r128))
value.dwords.fill(0u);
return true;
@@ -826,6 +826,7 @@ private:
const auto& b = *descriptor.indirect_descriptor;
if (a.material_source != b.material_source || a.table_source != b.table_source ||
a.selector_stride != b.selector_stride || a.selector_offset != b.selector_offset ||
a.selector_shift != b.selector_shift ||
a.table_offset != b.table_offset || a.sources != b.sources ||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
a.selector_first.IsEmpty() != b.selector_first.IsEmpty() ||
@@ -1549,6 +1550,37 @@ private:
return true;
}
bool MatchUniformizedBufferKey(Value key, const Inst& image,
DescriptorSource::IndirectDescriptor& indirect,
DescriptorSource& material_source) {
const auto* selected = UniformizedMaterialValue(key, image);
if (selected == nullptr) return false;
const auto* shift = selected->Arg(1).Resolve().TryInstruction();
uint32_t amount = 0;
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftRightLogical32 ||
!ImmediateU32(shift->Arg(1), amount) || amount == 0u || amount >= 32u) return false;
const auto* loaded = shift->Arg(0).Resolve().TryInstruction();
if (loaded == nullptr || loaded->GetOpcode() != ValueOpcode::SelectU32 ||
!EquivalentValue(m_program, selected->Arg(0), loaded->Arg(0))) return false;
const auto* read = loaded->Arg(1).Resolve().TryInstruction();
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadBufferU32) return false;
const auto& memory = m_program.memory_info[read->Flags<MemoryFlags>().index];
uint32_t offset = 1u, scalar = 1u;
if (memory.kind != ResourceKind::Buffer || memory.typed || memory.formatted ||
!memory.idxen || memory.offen ||
!ImmediateU32(read->Arg(2), offset) || offset != 0u ||
!ImmediateU32(read->Arg(3), scalar) || scalar != 0u ||
!EquivalentValue(m_program, selected->Arg(0), read->Arg(4)) ||
!MakeRuntimeTableSource(*read, material_source)) return false;
// GPU index arithmetic may wrap. Every record in the bounded V# is a candidate.
indirect.material_source = InternSource(material_source);
indirect.selector_first = Value(0u);
indirect.key_count = material_source.dwords[2];
indirect.selector_offset = memory.offset;
indirect.selector_shift = amount;
return true;
}
bool TryMakeIndirectImage(Inst& handle, IndirectDescriptorPlan& plan) {
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) return false;
DescriptorSource table_source;
@@ -1575,7 +1607,7 @@ private:
}
if ((table_offset & 3u) != 0u ||
(bitscan && table_offset > UINT32_MAX - (32u * 32u - 1u))) return false;
} else {
} else if (!MatchUniformizedBufferKey(key, handle, indirect, material_source)) {
auto* material_read = key.Resolve().TryInstruction();
uint32_t material_memory_index = 0;
const auto* memory = material_read != nullptr