mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
shader: preserve descriptor bounds through guarded branch joins
This commit is contained in:
1 parent
a287a54f26
commit
25e1cc7ec9
3 files changed
+71
-38
No files matched your search
@@ -1030,23 +1030,13 @@ private:
|
||||
LaneQuantifier lanes = LaneQuantifier::All;
|
||||
};
|
||||
|
||||
bool HasEntryGuard(const Block* block, const auto& accepts, const Block* definition = nullptr) const {
|
||||
// Each unique predecessor must execute before this use. Stop at joins and
|
||||
// definitions so a guard from an earlier iteration cannot bound a new value.
|
||||
for (size_t depth = 0; block != nullptr && depth < m_program.blocks.size(); ++depth) {
|
||||
if (block == definition || block->ImmPredecessors().size() != 1u) {
|
||||
return false;
|
||||
}
|
||||
const auto* previous = block->ImmPredecessors()[0];
|
||||
const auto edge = ConditionalEdge(previous, block);
|
||||
if (edge && edge->lanes == LaneQuantifier::All && accepts(*edge)) return true;
|
||||
block = previous;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||
return HasEntryGuard(block, [&](const EdgePredicate& edge) {
|
||||
if (block == nullptr) return false;
|
||||
bool witnessed = false;
|
||||
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
|
||||
return current->ImmPredecessors().size() != 1u;
|
||||
}, [&](const EdgePredicate& edge) {
|
||||
if (edge.lanes != LaneQuantifier::All) return false;
|
||||
const auto* test = edge.condition.TryInstruction();
|
||||
if (test != nullptr && test->NumArgs() == 2u &&
|
||||
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
|
||||
@@ -1054,11 +1044,12 @@ private:
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
uint32_t immediate;
|
||||
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u &&
|
||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return true;
|
||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return witnessed = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return guarded && witnessed;
|
||||
}
|
||||
|
||||
uint32_t WorkgroupAxis(Value key) const {
|
||||
@@ -1679,11 +1670,18 @@ private:
|
||||
Value UnsignedBoundOnEntry(Value key, const Block* block) const {
|
||||
const auto* definition = key.Resolve().TryInstruction();
|
||||
Value bound;
|
||||
const bool found = HasEntryGuard(block, [&](const EdgePredicate& edge) {
|
||||
bound = UnsignedBound(edge.condition, key, edge.positive);
|
||||
return !bound.IsEmpty() && ValidateRuntimeValue(m_program, bound, RuntimeValueType::Integer);
|
||||
}, definition != nullptr ? definition->Parent() : nullptr);
|
||||
return found ? bound : Value {};
|
||||
const bool guarded = GuardedOnEntry(block, [&](const Block* current) {
|
||||
return definition != nullptr && current == definition->Parent();
|
||||
}, [&](const EdgePredicate& edge) {
|
||||
if (edge.lanes != LaneQuantifier::All) return false;
|
||||
const auto candidate = UnsignedBound(edge.condition, key, edge.positive);
|
||||
if (candidate.IsEmpty() ||
|
||||
!ValidateRuntimeValue(m_program, candidate, RuntimeValueType::Integer)) return false;
|
||||
if (bound.IsEmpty()) bound = candidate;
|
||||
// Every incoming path must prove the same bound for this key definition.
|
||||
return EquivalentValue(m_program, candidate, bound);
|
||||
});
|
||||
return guarded ? bound : Value {};
|
||||
}
|
||||
|
||||
Value SharedLoadBound(Value value, Value active) {
|
||||
|
||||
@@ -563,7 +563,8 @@ void TestGuardedDirectImageTable() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
enum class Guard {
|
||||
Nonzero, SccNonZero, Plain, Zero, Unrelated, Bypass, ExecZero, VccZero,
|
||||
Bounded, BoundMissing, BoundUnrelated, BoundBypass, BoundAnyLane
|
||||
Bounded, BoundMissing, BoundUnrelated, BoundBypass, BoundAnyLane,
|
||||
BoundDiamond, BoundBranchSpecific, BoundMismatched
|
||||
};
|
||||
const auto make_plan = [](Guard guard) {
|
||||
const bool bounded = guard >= Guard::Bounded;
|
||||
@@ -575,12 +576,6 @@ void TestGuardedDirectImageTable() {
|
||||
auto *before_sample = fixture.AddBlock();
|
||||
auto *sample = fixture.AddBlock();
|
||||
auto *exit = fixture.AddBlock();
|
||||
entry->AddBranch(middle);
|
||||
entry->AddBranch(exit);
|
||||
middle->AddBranch(before_sample);
|
||||
before_sample->AddBranch(sample);
|
||||
sample->AddBranch(exit);
|
||||
if (bypass) exit->AddBranch(sample);
|
||||
const auto varying = bounded
|
||||
? fixture.Emit(ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)})
|
||||
@@ -631,6 +626,32 @@ void TestGuardedDirectImageTable() {
|
||||
fixture.program.blocks[4]->terminator = {
|
||||
.kind = bypass ? CFG::TerminatorKind::Branch : CFG::TerminatorKind::Return,
|
||||
.true_block = fixture.program.blocks[3u]};
|
||||
if (guard == Guard::BoundDiamond) {
|
||||
middle->condition = fixture.Emit(ValueOpcode::IEqual32,
|
||||
{mask, Value(1u)});
|
||||
middle->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = before_sample, .false_block = sample};
|
||||
} else if (guard == Guard::BoundBranchSpecific || guard == Guard::BoundMismatched) {
|
||||
entry->condition = fixture.Emit(ValueOpcode::IEqual32,
|
||||
{mask, Value(1u)});
|
||||
entry->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = middle, .false_block = before_sample};
|
||||
middle->condition = condition;
|
||||
middle->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = exit, .false_block = sample};
|
||||
if (guard == Guard::BoundMismatched) {
|
||||
before_sample->condition = fixture.Emit(ValueOpcode::UGreaterThanEqual32,
|
||||
{key, fixture.UserData(2)});
|
||||
before_sample->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = exit, .false_block = sample};
|
||||
}
|
||||
}
|
||||
for (auto *block : fixture.program.blocks) {
|
||||
if (block->terminator.kind == CFG::TerminatorKind::Return) continue;
|
||||
block->AddBranch(block->terminator.true_block);
|
||||
if (block->terminator.kind == CFG::TerminatorKind::ConditionalBranch)
|
||||
block->AddBranch(block->terminator.false_block);
|
||||
}
|
||||
std::array<Value, 2> pointer;
|
||||
for (uint32_t word = 0; word < pointer.size(); ++word) {
|
||||
if (bounded) {
|
||||
@@ -702,7 +723,8 @@ void TestGuardedDirectImageTable() {
|
||||
make_plan(Guard::Plain);
|
||||
for (const auto guard : {Guard::Zero, Guard::Unrelated, Guard::Bypass,
|
||||
Guard::ExecZero, Guard::VccZero, Guard::BoundMissing,
|
||||
Guard::BoundUnrelated, Guard::BoundBypass, Guard::BoundAnyLane}) {
|
||||
Guard::BoundUnrelated, Guard::BoundBypass, Guard::BoundAnyLane,
|
||||
Guard::BoundBranchSpecific, Guard::BoundMismatched}) {
|
||||
CheckFatal([&] { make_plan(guard); }, "not a valid runtime value",
|
||||
"direct table accepted a selector without a dominating range guard");
|
||||
}
|
||||
@@ -779,7 +801,8 @@ void TestGuardedDirectImageTable() {
|
||||
endpoint.watched_reads == 0u,
|
||||
"batched descriptor read crossed the 48-bit endpoint");
|
||||
|
||||
auto bounded = make_plan(Guard::Bounded);
|
||||
make_plan(Guard::Bounded);
|
||||
auto bounded = make_plan(Guard::BoundDiamond);
|
||||
Check(bounded.capture_specialization_reads && bounded.info.images[1].written,
|
||||
"bounded table with image writes omitted specialization read capture");
|
||||
LinearTestMemory records;
|
||||
|
||||
@@ -36419,12 +36419,14 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
test.name = wave == 32u ? "GuardedRecordImageTable32" : "GuardedRecordImageTable64";
|
||||
test.initial.resize((table + offset + stride * count) / 4u, 0xdeadbeefu);
|
||||
test.initial[table / 4u + 1u] = count;
|
||||
for (u32 row = 0; row < count; ++row) {
|
||||
for (u32 row = 0; row < count; ++row)
|
||||
for (const u32 member : {0u, 1u}) {
|
||||
auto image = MakeSampledTextureData(Prospero::BufferFormat::k32Float);
|
||||
image[0] = row + 1u;
|
||||
image[0] = (row + 1u) * (member == 0u ? 1u : 10u);
|
||||
image[3] = DstSel(4, 0, 0, 1) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28u);
|
||||
std::copy_n(image.begin(), 8, test.initial.begin() + (table + offset + row * stride) / 4u);
|
||||
std::copy_n(image.begin(), 8,
|
||||
test.initial.begin() + (table + offset + row * stride + member * 32u) / 4u);
|
||||
}
|
||||
test.user_data[0] = table;
|
||||
auto destination = MakeSampledTextureData(Prospero::BufferFormat::k32Float);
|
||||
@@ -36454,6 +36456,15 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
const auto range_exit = code.size(); code.push_back(0u);
|
||||
code.insert(code.end(), {EncodeSop2(0x26, 23, 22, 255), stride,
|
||||
EncodeSop2(0x02, 23, 23, 255), offset,
|
||||
EncodeSopc(0x0d, 22, InlineU32(0))});
|
||||
const auto skip_first = code.size(); code.push_back(0u);
|
||||
code.insert(code.end(), {
|
||||
EncodeSmem0(0x03, 8), EncodeSmem1(0, 23),
|
||||
EncodeMimg0(0x27, 1), EncodeMimg1(4, 20, 2, 10),
|
||||
EncodeVop2(0x03, 0, Vgpr(4), 0)});
|
||||
code[skip_first] = EncodeSopp(0x05, code.size() - skip_first - 1u);
|
||||
// The second descriptor is reached through both sides of the optional first sample.
|
||||
code.insert(code.end(), {EncodeSop2(0x02, 23, 23, InlineU32(32)),
|
||||
EncodeSmem0(0x03, 8), EncodeSmem1(0, 23),
|
||||
EncodeMimg0(0x27, 1), EncodeMimg1(4, 20, 2, 10),
|
||||
EncodeVop2(0x03, 0, Vgpr(4), 0),
|
||||
@@ -36468,7 +36479,7 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::S_LSHR_B32, O::S_LOAD_DWORD, O::V_MOV_B32, O::S_FF1_I32_B32,
|
||||
O::S_CMP_LG_U32, O::S_CMP_GE_U32, O::S_CBRANCH_SCC0, O::S_CBRANCH_SCC1,
|
||||
O::S_MUL_I32, O::S_ADD_I32, O::S_LOAD_DWORDX8, O::IMAGE_SAMPLE,
|
||||
O::S_MUL_I32, O::S_ADD_I32, O::S_BITCMP1_B32, O::S_LOAD_DWORDX8, O::IMAGE_SAMPLE,
|
||||
O::V_ADD_F32, O::S_LSHL_B32, O::S_ANDN2_B32, O::S_BRANCH,
|
||||
O::IMAGE_STORE, O::S_ENDPGM};
|
||||
auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||
@@ -36476,7 +36487,8 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
[](const auto &image) {
|
||||
return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
});
|
||||
Require(test.name, "bounded descriptor rows", sampled_count == std::max(1u, count),
|
||||
Require(test.name, "bounded descriptor rows",
|
||||
count == 0u ? sampled_count <= 2u : sampled_count == count * 2u,
|
||||
"the runtime count lost a row or probed record padding");
|
||||
std::vector<VulkanHarness::Image> images(compiled.program.info.images.size());
|
||||
for (u32 resource = 0; resource < images.size(); ++resource) {
|
||||
@@ -36494,8 +36506,8 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
auto sampler = vulkan.CreateSampler(test.name, 1);
|
||||
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, &storage, nullptr, sampler, images);
|
||||
const std::vector<u32> expected = count == 0 ? std::vector<u32>(4, 0u) :
|
||||
std::vector<u32>{std::bit_cast<u32>(count == 3 ? 4.0f : 1.0f),
|
||||
std::bit_cast<u32>(2.0f), std::bit_cast<u32>(1.0f), 0u};
|
||||
std::vector<u32>{std::bit_cast<u32>(count == 3 ? 44.0f : 11.0f),
|
||||
std::bit_cast<u32>(20.0f), std::bit_cast<u32>(11.0f), 0u};
|
||||
CompareWords(test, "GPU worklist, guarded count and image writes", expected,
|
||||
vulkan.ReadImage(test.name, &storage));
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
|
||||
Reference in new issue
Block a user