mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
shader: skip resources in proven empty unsigned loops
This commit is contained in:
1 parent
56d03f16e9
commit
c2173801d4
3 files changed
+73
-28
No files matched your search
@@ -1409,7 +1409,8 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
|||||||
}
|
}
|
||||||
if (op != ValueOpcode::LogicalAnd && op != ValueOpcode::LogicalOr &&
|
if (op != ValueOpcode::LogicalAnd && op != ValueOpcode::LogicalOr &&
|
||||||
op != ValueOpcode::LogicalNot && op != ValueOpcode::IEqual32 &&
|
op != ValueOpcode::LogicalNot && op != ValueOpcode::IEqual32 &&
|
||||||
op != ValueOpcode::INotEqual32 && op != ValueOpcode::ULessThanEqual32 &&
|
op != ValueOpcode::INotEqual32 && op != ValueOpcode::ULessThan32 &&
|
||||||
|
op != ValueOpcode::ULessThanEqual32 &&
|
||||||
op != ValueOpcode::SelectU1 && op != ValueOpcode::SelectU32) return finish(opaque());
|
op != ValueOpcode::SelectU1 && op != ValueOpcode::SelectU32) return finish(opaque());
|
||||||
auto& node = plan.value_storage.emplace_back(op);
|
auto& node = plan.value_storage.emplace_back(op);
|
||||||
for (size_t index = 0; index < inst->NumArgs(); ++index)
|
for (size_t index = 0; index < inst->NumArgs(); ++index)
|
||||||
|
|||||||
@@ -1054,7 +1054,13 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case ValueOpcode::ULessThan32:
|
case ValueOpcode::ULessThan32:
|
||||||
if (binary()) {
|
// No U32 is below zero, including an unknown loop-carried value.
|
||||||
|
if (!Arg(inst, 1, b)) return false;
|
||||||
|
if (static_cast<uint32_t>(b) == 0u) {
|
||||||
|
result = 0u;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Arg(inst, 0, a)) {
|
||||||
result = static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
|
result = static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -874,7 +874,7 @@ void TestFiniteImageRefreshReusesScalarReads() {
|
|||||||
"finite image refresh grew reusable resource storage after warmup");
|
"finite image refresh grew reusable resource storage after warmup");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask, bool counted_loop = false) {
|
||||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||||
using Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
using Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
||||||
Program program;
|
Program program;
|
||||||
@@ -898,54 +898,87 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
|||||||
block.AddBranch(&target);
|
block.AddBranch(&target);
|
||||||
if (other) block.AddBranch(other);
|
if (other) block.AddBranch(other);
|
||||||
};
|
};
|
||||||
const auto lane = emit(entry, ValueOpcode::GetBuiltin,
|
Value lane, active, initial;
|
||||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
if (!counted_loop) {
|
||||||
const auto active = emit(entry, ValueOpcode::INotEqual32, {lane, Value(0u)});
|
lane = emit(entry, ValueOpcode::GetBuiltin,
|
||||||
const auto initial_mask = independent_mask
|
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
||||||
? emit(entry, ValueOpcode::IEqual32, {lane, Value(0u)}) : active;
|
active = emit(entry, ValueOpcode::INotEqual32, {lane, Value(0u)});
|
||||||
const auto initial = emit(entry, ValueOpcode::SelectU32,
|
const auto initial_mask = independent_mask
|
||||||
{initial_mask, Value(UINT32_MAX), Value(0u)});
|
? emit(entry, ValueOpcode::IEqual32, {lane, Value(0u)}) : active;
|
||||||
|
initial = emit(entry, ValueOpcode::SelectU32,
|
||||||
|
{initial_mask, Value(UINT32_MAX), Value(0u)});
|
||||||
|
}
|
||||||
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress, .planning_only = true});
|
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress, .planning_only = true});
|
||||||
const auto address = emit(entry, ValueOpcode::GetAddressResource, {Value(0x1000u), Value(0u)});
|
const auto address = emit(entry, ValueOpcode::GetAddressResource, {Value(0x1000u), Value(0u)});
|
||||||
const auto count = emit(entry, ValueOpcode::LoadAddressU32,
|
const auto count = emit(entry, ValueOpcode::LoadAddressU32,
|
||||||
{address, Value(0u), Value(0u), Value(true)});
|
{address, Value(0u), Value(0u), Value(true)});
|
||||||
count.Instruction()->SetFlags(MemoryFlags{.index = 0});
|
count.Instruction()->SetFlags(MemoryFlags{.index = 0});
|
||||||
const auto rejected = emit(entry, ValueOpcode::ULessThanEqual32, {count, lane});
|
if (counted_loop) {
|
||||||
const auto accepted = emit(entry, ValueOpcode::LogicalNot, {rejected});
|
branch(entry, loop);
|
||||||
branch(entry, loop, emit(entry, ValueOpcode::LogicalAnd, {active, accepted}), &merge);
|
} else {
|
||||||
|
const auto rejected = emit(entry, ValueOpcode::ULessThanEqual32, {count, lane});
|
||||||
|
const auto accepted = emit(entry, ValueOpcode::LogicalNot, {rejected});
|
||||||
|
branch(entry, loop, emit(entry, ValueOpcode::LogicalAnd, {active, accepted}), &merge);
|
||||||
|
}
|
||||||
|
|
||||||
// A nonempty traversal contains a varying loop-carried value. Its cycle must
|
// A nonempty traversal contains a varying loop-carried value. Its cycle must
|
||||||
// remain unknown; only the proven empty traversal can select the bypass arm.
|
// remain unknown; only the proven empty traversal can select the bypass arm.
|
||||||
auto &carried = loop.AppendNewInst(ValueOpcode::Phi);
|
auto &carried = loop.AppendNewInst(ValueOpcode::Phi);
|
||||||
carried.SetFlags(Type::U32);
|
carried.SetFlags(Type::U32);
|
||||||
const auto next = emit(body, ValueOpcode::IAdd32, {Value(&carried), Value(1u)});
|
const auto next = emit(body, ValueOpcode::IAdd32, {Value(&carried), Value(1u)});
|
||||||
carried.AddPhiOperand(&entry, lane);
|
carried.AddPhiOperand(&entry, counted_loop ? Value(0u) : lane);
|
||||||
carried.AddPhiOperand(&body, next);
|
carried.AddPhiOperand(&body, next);
|
||||||
branch(loop, body, emit(loop, ValueOpcode::INotEqual32, {Value(&carried), Value(UINT32_MAX)}), &merge);
|
const auto condition = counted_loop
|
||||||
|
? emit(loop, ValueOpcode::ULessThan32, {Value(&carried), count})
|
||||||
|
: emit(loop, ValueOpcode::INotEqual32, {Value(&carried), Value(UINT32_MAX)});
|
||||||
|
branch(loop, counted_loop ? use : body, condition, &merge);
|
||||||
loop.terminator.loop_header = true;
|
loop.terminator.loop_header = true;
|
||||||
loop.terminator.continue_block = &body;
|
loop.terminator.continue_block = &body;
|
||||||
loop.terminator.merge_block = &merge;
|
loop.terminator.merge_block = &merge;
|
||||||
branch(body, loop);
|
branch(body, loop);
|
||||||
auto &selected = merge.AppendNewInst(ValueOpcode::Phi);
|
if (counted_loop) {
|
||||||
selected.SetFlags(Type::U32);
|
branch(merge, exit);
|
||||||
selected.AddPhiOperand(&entry, initial);
|
branch(use, body);
|
||||||
selected.AddPhiOperand(&loop, Value(&carried));
|
} else {
|
||||||
const auto valid = emit(merge, ValueOpcode::INotEqual32, {Value(&selected), Value(UINT32_MAX)});
|
auto &selected = merge.AppendNewInst(ValueOpcode::Phi);
|
||||||
branch(merge, use, emit(merge, ValueOpcode::LogicalAnd, {active, valid}), &exit);
|
selected.SetFlags(Type::U32);
|
||||||
branch(use, exit);
|
selected.AddPhiOperand(&entry, initial);
|
||||||
|
selected.AddPhiOperand(&loop, Value(&carried));
|
||||||
|
const auto valid = emit(merge, ValueOpcode::INotEqual32, {Value(&selected), Value(UINT32_MAX)});
|
||||||
|
branch(merge, use, emit(merge, ValueOpcode::LogicalAnd, {active, valid}), &exit);
|
||||||
|
branch(use, exit);
|
||||||
|
}
|
||||||
DescriptorSource source;
|
DescriptorSource source;
|
||||||
source.dwords = {Value(0x8000u), Value(1u << 16u), Value(16u), Value(3u << 28u)};
|
const auto descriptor_address = emit(use, ValueOpcode::GetAddressResource,
|
||||||
|
{Value(0x2000u), Value(0u)});
|
||||||
|
for (uint32_t i = 0; i < 4; ++i) {
|
||||||
|
const auto index = static_cast<uint32_t>(program.memory_info.size());
|
||||||
|
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress, .planning_only = true});
|
||||||
|
source.dwords[i] = emit(use, ValueOpcode::LoadAddressU32,
|
||||||
|
{descriptor_address, Value(i * 4u), Value(0u), Value(true)});
|
||||||
|
source.dwords[i].Instruction()->SetFlags(MemoryFlags{.index = index});
|
||||||
|
}
|
||||||
source.dword_count = 4;
|
source.dword_count = 4;
|
||||||
program.descriptor_sources.push_back(source);
|
program.descriptor_sources.push_back(source);
|
||||||
program.info.buffers.push_back({.source = 0, .read = true});
|
program.info.buffers.push_back({.source = 0, .read = true});
|
||||||
|
const auto buffer_index = static_cast<uint32_t>(program.memory_info.size());
|
||||||
program.memory_info.push_back({.kind = ResourceKind::Buffer, .resource = 0});
|
program.memory_info.push_back({.kind = ResourceKind::Buffer, .resource = 0});
|
||||||
const auto buffer = emit(use, ValueOpcode::GetBufferResource,
|
const auto buffer = emit(use, ValueOpcode::GetBufferResource,
|
||||||
{source.dwords[0], source.dwords[1], source.dwords[2], source.dwords[3]});
|
{source.dwords[0], source.dwords[1], source.dwords[2], source.dwords[3]});
|
||||||
emit(use, ValueOpcode::LoadBufferU32, {buffer, Value(0u), Value(0u), Value(0u), Value(true)})
|
emit(use, ValueOpcode::LoadBufferU32, {buffer, Value(0u), Value(0u), Value(0u), Value(true)})
|
||||||
.Instruction()->SetFlags(MemoryFlags{.index = 1});
|
.Instruction()->SetFlags(MemoryFlags{.index = buffer_index});
|
||||||
auto plan = ExtractResourcePlan(program);
|
auto plan = ExtractResourcePlan(program);
|
||||||
struct Reads { uint32_t count = 0; bool clean = true; } reads;
|
struct Reads { uint32_t count = 0; bool clean = true; uint32_t descriptors = 0; } reads;
|
||||||
const SrtRuntime runtime{
|
const SrtRuntime runtime{
|
||||||
|
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||||
|
constexpr std::array<uint32_t, 4> descriptor{0x8000u, 1u << 16u, 16u,
|
||||||
|
(3u << 28u) | 0x4dfacu};
|
||||||
|
if (address < 0x2000u || address >= 0x2010u || address % 4u || words.size() != 1)
|
||||||
|
return false;
|
||||||
|
++static_cast<Reads *>(data)->descriptors;
|
||||||
|
words[0] = descriptor[(address - 0x2000u) / 4u];
|
||||||
|
return true;
|
||||||
|
},
|
||||||
.userdata = &reads,
|
.userdata = &reads,
|
||||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||||
const auto &reads = *static_cast<Reads *>(data);
|
const auto &reads = *static_cast<Reads *>(data);
|
||||||
@@ -957,9 +990,11 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
|||||||
ResourceSpecialization specialization;
|
ResourceSpecialization specialization;
|
||||||
for (const auto state : {Reads{0u, true}, Reads{4u, true}, Reads{0u, false}, Reads{0u, true}}) {
|
for (const auto state : {Reads{0u, true}, Reads{4u, true}, Reads{0u, false}, Reads{0u, true}}) {
|
||||||
reads = state;
|
reads = state;
|
||||||
uint32_t condition = 99;
|
if (!counted_loop) {
|
||||||
Check(!SrtWalker(plan, runtime).Evaluate(plan.control_flow[3].condition, condition),
|
uint32_t condition = 99;
|
||||||
"direct merge evaluation used missing or previous runtime reachability");
|
Check(!SrtWalker(plan, runtime).Evaluate(plan.control_flow[3].condition, condition),
|
||||||
|
"direct merge evaluation used missing or previous runtime reachability");
|
||||||
|
}
|
||||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||||
"partial traversal proof rejected a valid resource snapshot");
|
"partial traversal proof rejected a valid resource snapshot");
|
||||||
const bool inactive = !independent_mask && reads.clean && reads.count == 0u;
|
const bool inactive = !independent_mask && reads.clean && reads.count == 0u;
|
||||||
@@ -967,6 +1002,8 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
|||||||
"empty traversal kept its descriptor or an uncertain traversal lost it");
|
"empty traversal kept its descriptor or an uncertain traversal lost it");
|
||||||
Check(specialization.buffers[0].zero_stride_oob == inactive,
|
Check(specialization.buffers[0].zero_stride_oob == inactive,
|
||||||
"inactive traversal did not expose dead vector reads to specialization");
|
"inactive traversal did not expose dead vector reads to specialization");
|
||||||
|
Check(reads.descriptors == (inactive ? 0u : 4u),
|
||||||
|
"inactive traversal read its descriptor or an uncertain traversal skipped it");
|
||||||
if (inactive) {
|
if (inactive) {
|
||||||
Check(!snapshot.specialization_reads.empty(), "empty traversal lost its clean scalar read");
|
Check(!snapshot.specialization_reads.empty(), "empty traversal lost its clean scalar read");
|
||||||
for (const auto &read : snapshot.specialization_reads)
|
for (const auto &read : snapshot.specialization_reads)
|
||||||
@@ -1331,6 +1368,7 @@ int main() {
|
|||||||
TestFiniteImageRefreshReusesScalarReads();
|
TestFiniteImageRefreshReusesScalarReads();
|
||||||
for (const bool independent_mask : {false, true})
|
for (const bool independent_mask : {false, true})
|
||||||
TestEmptyTraversalSkipsUnmappedDescriptor(independent_mask);
|
TestEmptyTraversalSkipsUnmappedDescriptor(independent_mask);
|
||||||
|
TestEmptyTraversalSkipsUnmappedDescriptor(false, true);
|
||||||
TestCyclicControlPredicatesRemainUnknown();
|
TestCyclicControlPredicatesRemainUnknown();
|
||||||
TestEmptyScalarBufferSpecialization();
|
TestEmptyScalarBufferSpecialization();
|
||||||
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
||||||
|
|||||||
Reference in new issue
Block a user