mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
shader: share bounded material-key scans across scalar and vector loads
This commit is contained in:
@@ -249,6 +249,19 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
const auto descriptor_bytes = dword_count * sizeof(uint32_t);
|
||||
const auto& sources = indirect.sources;
|
||||
auto& keys = program.material_keys;
|
||||
const auto read_keys = [&](const ShaderBufferResource& material, uint64_t first,
|
||||
uint64_t step, uint64_t count) {
|
||||
keys.resize(count);
|
||||
if (step == 4u) {
|
||||
return ReadScalarTable(material.Base48(), material.GetSize(), first, runtime, keys);
|
||||
}
|
||||
for (auto& key: keys) {
|
||||
if (!ReadScalarTable(material.Base48(), material.GetSize(), first, runtime, {&key, 1}))
|
||||
return false;
|
||||
first += step;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
uint64_t table_base = 0;
|
||||
uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds.
|
||||
if (sources.empty()) {
|
||||
@@ -297,20 +310,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
indirect.selector_offset + 4u >
|
||||
uint64_t {UINT32_MAX} + 1u)
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (!read_keys(material, uint64_t {first} * material.Stride() + indirect.selector_offset,
|
||||
material.Stride(), count)) return false;
|
||||
} else if (!indirect.selector_mask.IsEmpty()) {
|
||||
uint32_t mask = 0;
|
||||
uint32_t count = 0;
|
||||
@@ -336,8 +337,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
}
|
||||
} else {
|
||||
ShaderBufferResource material;
|
||||
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u ||
|
||||
material.Stride() != indirect.selector_stride) {
|
||||
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u) {
|
||||
return false;
|
||||
}
|
||||
// The first aligned offset includes the immediate added after shader U32 arithmetic.
|
||||
@@ -350,15 +350,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
if (probe_count > MaxIndirectDescriptorProbes) {
|
||||
return false;
|
||||
}
|
||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
keys.push_back(0u);
|
||||
for (uint64_t probe = 0, offset = first; probe < probe_count; ++probe, offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarTable(material.Base48(), size, offset, runtime, {&key, 1})) {
|
||||
return false;
|
||||
}
|
||||
keys.push_back(key);
|
||||
}
|
||||
if (!read_keys(material, first, step, probe_count)) return false;
|
||||
if (indirect.selector_shift == 0u) keys.push_back(0u);
|
||||
}
|
||||
if (indirect.material_source != UINT32_MAX) {
|
||||
if (indirect.selector_shift != 0u) {
|
||||
@@ -1011,8 +1004,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads |= !indirect.selector_mask.IsEmpty() ||
|
||||
!indirect.selector_first.IsEmpty() || !indirect.sources.empty();
|
||||
capture_indirect_reads |= indirect.material_source != UINT32_MAX || !indirect.sources.empty();
|
||||
MarkCleanFlatSlots(plan, Source(plan, indirect.material_source), plan.clean_flat_slots,
|
||||
indirect.selector_mask);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_first);
|
||||
|
||||
@@ -923,38 +923,39 @@ private:
|
||||
return ValidateSource(descriptor, bad_dword);
|
||||
}
|
||||
|
||||
bool MatchMaterialOffset(Value value, Value& selector, uint32_t& stride,
|
||||
uint32_t& offset) const {
|
||||
value = value.Resolve();
|
||||
offset = 0;
|
||||
auto* candidate = value.TryInstruction();
|
||||
if (candidate != nullptr && candidate->GetOpcode() == ValueOpcode::IAdd32 &&
|
||||
candidate->NumArgs() == 2u) {
|
||||
uint32_t immediate = 0;
|
||||
if (ImmediateU32(candidate->Arg(0), immediate)) {
|
||||
value = candidate->Arg(1).Resolve();
|
||||
} else if (ImmediateU32(candidate->Arg(1), immediate)) {
|
||||
value = candidate->Arg(0).Resolve();
|
||||
bool MatchMaterialOffset(Value value, uint32_t& stride, uint32_t& offset) const {
|
||||
stride = 1u;
|
||||
offset = 0u;
|
||||
value = value.Resolve();
|
||||
while (const auto* inst = value.TryInstruction()) {
|
||||
if (inst->NumArgs() != 2u) break;
|
||||
uint32_t constant = 0;
|
||||
Value remaining;
|
||||
if (inst->GetOpcode() == ValueOpcode::ShiftLeftLogical32 &&
|
||||
ImmediateU32(inst->Arg(1), constant) && constant < 32u) {
|
||||
stride *= 1u << constant;
|
||||
remaining = inst->Arg(0);
|
||||
} else if (inst->GetOpcode() == ValueOpcode::IAdd32 ||
|
||||
inst->GetOpcode() == ValueOpcode::IMul32) {
|
||||
if (ImmediateU32(inst->Arg(0), constant)) remaining = inst->Arg(1);
|
||||
else if (ImmediateU32(inst->Arg(1), constant)) remaining = inst->Arg(0);
|
||||
else break;
|
||||
if (inst->GetOpcode() == ValueOpcode::IAdd32) offset += stride * constant;
|
||||
else stride *= constant;
|
||||
} else {
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
offset = immediate;
|
||||
value = remaining.Resolve();
|
||||
}
|
||||
const auto* multiply = value.TryInstruction();
|
||||
if (multiply == nullptr || multiply->GetOpcode() != ValueOpcode::IMul32 ||
|
||||
multiply->NumArgs() != 2u) {
|
||||
return false;
|
||||
}
|
||||
if (ImmediateU32(multiply->Arg(0), stride)) {
|
||||
selector = multiply->Arg(1).Resolve();
|
||||
} else if (ImmediateU32(multiply->Arg(1), stride)) {
|
||||
selector = multiply->Arg(0).Resolve();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const auto* selector_inst = selector.TryInstruction();
|
||||
return stride != 0u && selector_inst != nullptr &&
|
||||
selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane;
|
||||
// Treat the remaining GPU-derived scalar as any U32 value.
|
||||
return stride != 0u && value.GetType() == Type::U32 && !value.IsImmediate();
|
||||
}
|
||||
|
||||
const Inst* MaterialKeyShift(Value key, uint32_t& amount) const {
|
||||
const auto* shift = key.Resolve().TryInstruction();
|
||||
return shift != nullptr && shift->GetOpcode() == ValueOpcode::ShiftRightLogical32 &&
|
||||
ImmediateU32(shift->Arg(1), amount) && amount != 0u && amount < 32u
|
||||
? shift : nullptr;
|
||||
}
|
||||
|
||||
enum class LaneQuantifier { Any, All };
|
||||
@@ -1558,10 +1559,9 @@ private:
|
||||
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* shift = MaterialKeyShift(selected->Arg(1), amount);
|
||||
if (shift == nullptr) 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;
|
||||
@@ -1611,7 +1611,9 @@ private:
|
||||
if ((table_offset & 3u) != 0u ||
|
||||
(bitscan && table_offset > UINT32_MAX - (32u * 32u - 1u))) return false;
|
||||
} else if (!MatchUniformizedBufferKey(key, handle, indirect, material_source)) {
|
||||
auto* material_read = key.Resolve().TryInstruction();
|
||||
const auto* key_shift = MaterialKeyShift(key, indirect.selector_shift);
|
||||
auto* material_read =
|
||||
(key_shift != nullptr ? key_shift->Arg(0) : key).Resolve().TryInstruction();
|
||||
uint32_t material_memory_index = 0;
|
||||
const auto* memory = material_read != nullptr
|
||||
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
|
||||
@@ -1619,8 +1621,7 @@ private:
|
||||
memory->offset > INT32_MAX || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||
return false;
|
||||
}
|
||||
Value selector;
|
||||
if (!MatchMaterialOffset(material_read->Arg(1), selector, indirect.selector_stride,
|
||||
if (!MatchMaterialOffset(material_read->Arg(1), indirect.selector_stride,
|
||||
indirect.selector_offset)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1628,8 +1629,11 @@ private:
|
||||
indirect.selector_offset =
|
||||
(static_cast<uint32_t>(indirect.selector_offset % step) & ~3u) + (memory->offset & ~3u);
|
||||
const auto* shift = plan.reads[0]->Arg(1).Resolve().TryInstruction();
|
||||
const std::array<const Inst*, 1> material_users {shift};
|
||||
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, plan.reads)) {
|
||||
const std::array<const Inst*, 1> material_users {key_shift != nullptr ? key_shift : shift};
|
||||
const std::array<const Inst*, 1> key_users {shift};
|
||||
if (!UsesOnly(*material_read, material_users) ||
|
||||
(key_shift != nullptr && !UsesOnly(*key_shift, key_users)) ||
|
||||
!UsesOnly(*shift, plan.reads)) {
|
||||
return false;
|
||||
}
|
||||
const auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
|
||||
|
||||
@@ -35604,9 +35604,9 @@ void CheckImageSamplerSpecialization() {
|
||||
"a shared float/unsigned/signed sampler lost its border or filtering "
|
||||
"class in the compiled bindings, instruction or descriptor snapshot");
|
||||
|
||||
{
|
||||
for (const bool scalar : {false, true}) {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
constexpr const char *name = "GpuMaterialImageDomain";
|
||||
const char *name = scalar ? "ScalarMaterialImageDomain" : "GpuMaterialImageDomain";
|
||||
constexpr u32 count = 70, table_base = 0x1000, material_base = 0x2000;
|
||||
std::vector<u32> memory(4096);
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
@@ -35628,29 +35628,44 @@ void CheckImageSamplerSpecialization() {
|
||||
std::copy_n(output.begin(), 4, user_data.begin() + 48);
|
||||
user_data[48] = 0x3000u;
|
||||
std::vector<u32> code{EncodeVopc(0xc5, InlineU32(0), 0),
|
||||
EncodeSop1(0x24, 24, 106), EncodeSopp(0x08, 0),
|
||||
EncodeSop1(0x04, 26, 126), EncodeVop1(0x01, 16, InlineU32(1))};
|
||||
// An earlier descriptor waterfall separates the EXECZ witness from this material load.
|
||||
const auto previous_loop = code.size();
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 28, 126),
|
||||
EncodeVop1(0x02, 22, Vgpr(0)), EncodeVopc(0xd2, 22, 0),
|
||||
EncodeVop1(0x01, 16, InlineU32(0)),
|
||||
EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)});
|
||||
code.push_back(EncodeSopp(0x09, static_cast<int16_t>(previous_loop - code.size() - 1u)));
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 126, 26),
|
||||
EncodeMubuf0(0x0c, 0, true, false), EncodeMubuf1(15, 1, 0),
|
||||
EncodeVop2(0x16, 15, InlineU32(4), 15),
|
||||
EncodeVop1(0x01, 16, InlineU32(1))});
|
||||
EncodeSop1(0x24, 24, 106), EncodeSopp(0x08, 0)};
|
||||
size_t scalar_shift = 0;
|
||||
if (scalar) {
|
||||
code.insert(code.end(), {EncodeVop1(0x02, 20, Vgpr(0)),
|
||||
EncodeSop2(0x00, 20, 20, 21),
|
||||
EncodeSop2(0x26, 20, 20, InlineU32(3)),
|
||||
EncodeSop2(0x1e, 20, 20, InlineU32(2)),
|
||||
EncodeSop2(0x00, 20, 20, InlineU32(8)),
|
||||
EncodeSmem0(0x08, 20, 2), EncodeSmem1(0, 20)});
|
||||
scalar_shift = code.size();
|
||||
code.push_back(EncodeSop2(0x20, 20, 20, InlineU32(4)));
|
||||
} else {
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 26, 126), EncodeVop1(0x01, 16, InlineU32(1))});
|
||||
// An earlier descriptor waterfall separates the EXECZ witness from the material load.
|
||||
const auto previous_loop = code.size();
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 28, 126),
|
||||
EncodeVop1(0x02, 22, Vgpr(0)), EncodeVopc(0xd2, 22, 0),
|
||||
EncodeVop1(0x01, 16, InlineU32(0)),
|
||||
EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)});
|
||||
code.push_back(EncodeSopp(0x09, static_cast<int16_t>(previous_loop - code.size() - 1u)));
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 126, 26),
|
||||
EncodeMubuf0(0x0c, 0, true, false), EncodeMubuf1(15, 1, 0),
|
||||
EncodeVop2(0x16, 15, InlineU32(4), 15),
|
||||
EncodeVop1(0x01, 16, InlineU32(1))});
|
||||
}
|
||||
const auto image_loop = code.size();
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 28, 126),
|
||||
EncodeVop1(0x02, 20, Vgpr(15)), EncodeVopc(0xd2, 20, 15),
|
||||
EncodeSop2(0x1e, 20, 20, InlineU32(5)),
|
||||
if (!scalar)
|
||||
code.insert(code.end(), {EncodeSop1(0x04, 28, 126),
|
||||
EncodeVop1(0x02, 20, Vgpr(15)), EncodeVopc(0xd2, 20, 15)});
|
||||
code.insert(code.end(), {EncodeSop2(0x1e, 20, 20, InlineU32(5)),
|
||||
EncodeSmem0(0x0b, 12), EncodeSmem1(0, 20),
|
||||
EncodeMimg0(0x24, 1), EncodeMimg1(4, 0, 3, 2)});
|
||||
AppendBufferStoreDword(&code, 4, 0);
|
||||
code.insert(code.end(), {EncodeVop1(0x01, 16, InlineU32(0)),
|
||||
EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)});
|
||||
code.push_back(EncodeSopp(0x09, static_cast<int16_t>(image_loop - code.size() - 1u)));
|
||||
if (!scalar) {
|
||||
code.insert(code.end(), {EncodeVop1(0x01, 16, InlineU32(0)),
|
||||
EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)});
|
||||
code.push_back(EncodeSopp(0x09, static_cast<int16_t>(image_loop - code.size() - 1u)));
|
||||
}
|
||||
code[2] = EncodeSopp(0x08, static_cast<int16_t>(code.size() - 3u));
|
||||
AppendEnd(&code);
|
||||
ShaderComputeInputInfo compute{};
|
||||
@@ -35695,21 +35710,34 @@ void CheckImageSamplerSpecialization() {
|
||||
"descriptor addresses were frozen or entered the shader specialization");
|
||||
auto compiled = ShaderRecompiler::CompileProgram(std::move(translated), options, specialization);
|
||||
ValidateSpirv(name, compiled.spirv);
|
||||
user_data[4] += 1u;
|
||||
Require(name, "misaligned material rejection",
|
||||
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"unaligned raw buffer base was treated as separately aligned scalar memory");
|
||||
user_data[4] -= 1u;
|
||||
if (!scalar) {
|
||||
user_data[4] += 1u;
|
||||
Require(name, "misaligned material rejection",
|
||||
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"unaligned raw buffer base was treated as separately aligned scalar memory");
|
||||
user_data[4] -= 1u;
|
||||
}
|
||||
auto dirty = runtime;
|
||||
dirty.read_specialization_memory = +[](void*, uint64_t, std::span<u32>) { return false; };
|
||||
Require(name, "dirty material rejection",
|
||||
!MaterializeResources(plan, dirty, snapshot, specialization),
|
||||
"GPU-selected texture materialization bypassed strict read provenance");
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
ExpectFatal("MaterialImageWithoutEntryWitness", [&] {
|
||||
code[2] = EncodeSopp(0x00, 0); // S_NOP removes the original nonempty-EXEC witness.
|
||||
(void)ShaderRecompiler::TranslateProgram(code, options);
|
||||
});
|
||||
if (scalar) {
|
||||
ExpectFatal("ScalarMaterialNumericConsumer", [&] {
|
||||
code.insert(code.begin() + scalar_shift, EncodeVop1(0x01, 17, 20));
|
||||
code.pop_back();
|
||||
AppendBufferStoreDword(&code, 17, 0);
|
||||
code[2] = EncodeSopp(0x08, static_cast<int16_t>(code.size() - 3u));
|
||||
AppendEnd(&code);
|
||||
(void)ShaderRecompiler::TranslateProgram(code, options);
|
||||
});
|
||||
} else {
|
||||
ExpectFatal("MaterialImageWithoutEntryWitness", [&] {
|
||||
code[2] = EncodeSopp(0x00, 0); // S_NOP removes the original nonempty-EXEC witness.
|
||||
(void)ShaderRecompiler::TranslateProgram(code, options);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user