mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 19:36:55 +02:00
shader: resolve typed phis and ReadFirstLane in the SRT walker
Two descriptor chains this title uses could not be resolved: - A descriptor whose address arrives through a phi. Walk the phi to its leaves and accept the result when they agree, or when exactly one is a real address and the rest are null/sentinel. - A descriptor read through ReadFirstLane. This is safe for non-image descriptors, where every lane holds the same handle, so accept it there. Image descriptors keep the old restriction, gated by the dword count.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -109,9 +110,15 @@ public:
|
||||
Fail(0, "SRT plan is not ready");
|
||||
}
|
||||
PlanIndirectImages();
|
||||
if (m_failed) {
|
||||
return;
|
||||
}
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
Collect(inst);
|
||||
if (m_failed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
LinkImageAliases();
|
||||
@@ -140,7 +147,7 @@ public:
|
||||
std::erase_if(m_program.dynamic_reads, [&](Value value) {
|
||||
const auto* inst = value.Resolve().TryInstruction();
|
||||
return std::any_of(m_indirect_images.begin(), m_indirect_images.end(),
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
return std::ranges::find(plan.reads, inst) != plan.reads.end();
|
||||
});
|
||||
});
|
||||
@@ -171,14 +178,23 @@ private:
|
||||
std::array<const Inst*, 8> reads {};
|
||||
};
|
||||
|
||||
[[noreturn]] void Fail(uint32_t pc, const std::string& reason) const {
|
||||
void Fail(uint32_t pc, const std::string& reason) const {
|
||||
if (Frontend::TranslationNonFatal()) {
|
||||
m_failed = true;
|
||||
return;
|
||||
}
|
||||
const auto message =
|
||||
fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
||||
m_program.shader_hash, StageName(m_program.stage), pc, reason);
|
||||
EXIT("%s", message.c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
mutable bool m_failed = false;
|
||||
|
||||
public:
|
||||
[[nodiscard]] bool Failed() const { return m_failed; }
|
||||
private:
|
||||
|
||||
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
||||
DescriptorSource& descriptor, uint32_t pc) const {
|
||||
if (handle.NumArgs() != width) {
|
||||
@@ -206,7 +222,8 @@ private:
|
||||
if (descriptor.dwords[i].Resolve().GetType() != Type::U32) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) {
|
||||
const bool allow_read_first_lane = descriptor.dword_count != 8;
|
||||
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i], allow_read_first_lane)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -328,6 +345,46 @@ private:
|
||||
selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane;
|
||||
}
|
||||
|
||||
bool TryDetectIndirectBuffer(const Inst& handle, uint32_t pc, uint32_t& stride_out,
|
||||
uint32_t& offset_out) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetBufferResource || handle.NumArgs() != 4u) {
|
||||
return false;
|
||||
}
|
||||
const Inst* table_handle = nullptr;
|
||||
Value table_offset;
|
||||
for (uint32_t dword = 0; dword < 4u; dword++) {
|
||||
const auto* read = handle.Arg(dword).Resolve().TryInstruction();
|
||||
if (read == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = ScalarReadMemory(*read, memory_index);
|
||||
if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) ||
|
||||
!MemoryIndexBelongsTo(memory_index, *read)) {
|
||||
return false;
|
||||
}
|
||||
const auto* current = read->Arg(0).Resolve().TryInstruction();
|
||||
if (current == nullptr || (table_handle != nullptr && current != table_handle)) {
|
||||
return false;
|
||||
}
|
||||
table_handle = current;
|
||||
if (dword == 0u) {
|
||||
table_offset = read->Arg(1).Resolve();
|
||||
} else if (!EquivalentValue(m_program, table_offset, read->Arg(1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Value selector;
|
||||
uint32_t stride = 0;
|
||||
uint32_t offset = 0;
|
||||
if (!MatchMaterialOffset(table_offset, selector, stride, offset)) {
|
||||
return false;
|
||||
}
|
||||
stride_out = stride;
|
||||
offset_out = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) {
|
||||
return false;
|
||||
@@ -434,7 +491,7 @@ private:
|
||||
const IndirectImagePlan* FindIndirectImage(const Inst& handle) const {
|
||||
const auto found =
|
||||
std::find_if(m_indirect_images.begin(), m_indirect_images.end(),
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
return plan.handle == &handle;
|
||||
});
|
||||
return found == m_indirect_images.end() ? nullptr : &*found;
|
||||
@@ -442,7 +499,7 @@ private:
|
||||
|
||||
bool IsIndirectPlanningMemory(uint32_t index) const {
|
||||
return std::any_of(m_indirect_images.begin(), m_indirect_images.end(),
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
[&](const IndirectImagePlan& plan) {
|
||||
return std::ranges::find(plan.memory, index) != plan.memory.end();
|
||||
});
|
||||
}
|
||||
@@ -756,8 +813,10 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
void TrackResources(Program& program) {
|
||||
Tracker(program).Run();
|
||||
bool TrackResources(Program& program) {
|
||||
Tracker tracker(program);
|
||||
tracker.Run();
|
||||
return !tracker.Failed();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in
|
||||
// descriptor_sources, then writes dense indices to handle flags and MemoryInfo.
|
||||
void TrackResources(Program& program);
|
||||
bool TrackResources(Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <atomic>
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -50,6 +51,30 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollectPhiLeaves(Value value, std::vector<Value>& out) {
|
||||
std::vector<Value> pending {value};
|
||||
std::unordered_set<const Inst*> seen;
|
||||
while (!pending.empty()) {
|
||||
const auto current = pending.back().Resolve();
|
||||
pending.pop_back();
|
||||
const auto* inst = current.TryInstruction();
|
||||
if (inst != nullptr && inst->GetOpcode() == ValueOpcode::Phi) {
|
||||
if (!seen.insert(inst).second) {
|
||||
continue;
|
||||
}
|
||||
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
||||
pending.push_back(inst->Arg(index));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (current.IsImmediate() && current.TryInstruction() == nullptr &&
|
||||
current.GetType() == Type::U32 && current.U32() == 0u) {
|
||||
continue;
|
||||
}
|
||||
out.push_back(current);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRawRead(const Program& values, const Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) {
|
||||
@@ -118,7 +143,8 @@ bool IsRuntimeIntegerOp(ValueOpcode op) {
|
||||
|
||||
class RuntimeValidator {
|
||||
public:
|
||||
explicit RuntimeValidator(const Program& program): m_program(program) {}
|
||||
RuntimeValidator(const Program& program, bool allow_read_first_lane)
|
||||
: m_program(program), m_allow_read_first_lane(allow_read_first_lane) {}
|
||||
|
||||
bool Run(Value value) { return Validate(value); }
|
||||
|
||||
@@ -222,7 +248,8 @@ private:
|
||||
return finish(false);
|
||||
}
|
||||
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
|
||||
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) {
|
||||
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op) &&
|
||||
!(m_allow_read_first_lane && op == ValueOpcode::ReadFirstLane)) {
|
||||
return finish(false);
|
||||
}
|
||||
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
||||
@@ -234,6 +261,7 @@ private:
|
||||
}
|
||||
|
||||
const Program& m_program;
|
||||
bool m_allow_read_first_lane = false;
|
||||
std::unordered_set<const Inst*> m_visiting;
|
||||
};
|
||||
|
||||
@@ -440,7 +468,44 @@ private:
|
||||
|
||||
bool EvaluatePhi(const Inst& inst, uint64_t& result) {
|
||||
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
|
||||
return !value.IsEmpty() && EvaluateWide(value, result);
|
||||
if (!value.IsEmpty()) {
|
||||
return EvaluateWide(value, result);
|
||||
}
|
||||
std::vector<Value> leaves;
|
||||
CollectPhiLeaves(Value(const_cast<Inst*>(&inst)), leaves);
|
||||
std::vector<uint64_t> values;
|
||||
for (const auto& leaf: leaves) {
|
||||
uint64_t leaf_value = 0;
|
||||
if (EvaluateWide(leaf, leaf_value)) {
|
||||
values.push_back(leaf_value);
|
||||
}
|
||||
}
|
||||
if (values.empty()) {
|
||||
return false;
|
||||
}
|
||||
bool all_same = true;
|
||||
for (const auto v: values) {
|
||||
all_same = all_same && v == values.front();
|
||||
}
|
||||
if (all_same) {
|
||||
result = values.front();
|
||||
return true;
|
||||
}
|
||||
uint64_t only_real = 0;
|
||||
uint32_t real_count = 0;
|
||||
for (const auto v: values) {
|
||||
if (v != 0 && static_cast<uint32_t>(v) != 0xffffffffu) {
|
||||
if (real_count == 0 || v != only_real) {
|
||||
real_count++;
|
||||
}
|
||||
only_real = v;
|
||||
}
|
||||
}
|
||||
if (real_count != 1u) {
|
||||
return false;
|
||||
}
|
||||
result = only_real;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateExtract(const Inst& inst, uint64_t& result) {
|
||||
@@ -885,8 +950,8 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateRuntimeValue(const Program& program, Value value) {
|
||||
return RuntimeValidator(program).Run(value);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value, bool allow_read_first_lane) {
|
||||
return RuntimeValidator(program, allow_read_first_lane).Run(value);
|
||||
}
|
||||
|
||||
void BuildSrtPlan(Program& program) {
|
||||
|
||||
@@ -26,7 +26,8 @@ struct DescriptorSourceRequest {
|
||||
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
|
||||
// dynamic offsets remain explicit and are never assigned a fake slot.
|
||||
void BuildSrtPlan(Program& program);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value,
|
||||
bool allow_read_first_lane = false);
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
||||
DescriptorValue& result);
|
||||
|
||||
Reference in New Issue
Block a user