mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 21:36:54 +01:00
shader: preserve scalar buffer offset alignment and carry
This commit is contained in:
@@ -206,8 +206,9 @@ bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span<uint32_t>
|
||||
}
|
||||
|
||||
std::unique_ptr<Fixture>
|
||||
MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
|
||||
bool memory_backed_material = false) {
|
||||
MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 4,
|
||||
bool memory_backed_material = false, uint32_t member_offset = 0,
|
||||
uint32_t material_stride = 224) {
|
||||
auto fixture = std::make_unique<Fixture>();
|
||||
std::array<Value, 4> material_words;
|
||||
std::array<Value, 4> heap_words;
|
||||
@@ -249,9 +250,9 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
|
||||
const auto selector = fixture->Emit(ValueOpcode::ReadFirstLane,
|
||||
{invocation, Value(true)});
|
||||
const auto record =
|
||||
fixture->Emit(ValueOpcode::IMul32, {selector, Value(224u)});
|
||||
const auto member = fixture->Emit(ValueOpcode::IAdd32, {record, Value(4u)});
|
||||
fixture->Emit(ValueOpcode::ReferenceU32, {record});
|
||||
fixture->Emit(ValueOpcode::IMul32, {selector, Value(material_stride)});
|
||||
const auto member = member_offset == 0 ? record :
|
||||
fixture->Emit(ValueOpcode::IAdd32, {record, Value(member_offset)});
|
||||
fixture->Emit(ValueOpcode::ReferenceU32, {member});
|
||||
MemoryInfo material_scalar;
|
||||
material_scalar.kind = ResourceKind::ScalarBuffer;
|
||||
@@ -497,7 +498,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
rebound_specialization != collapsed_specialization,
|
||||
"larger indirect candidate topology reused the old specialization");
|
||||
|
||||
auto memory_backed = MakeIndirectImageFixture(false, 0u, true);
|
||||
auto memory_backed = MakeIndirectImageFixture(false, 4u, true);
|
||||
memory_backed->PlanAndTrack();
|
||||
auto memory_backed_plan = ExtractResourcePlan(memory_backed->program);
|
||||
EliminateDeadCode(memory_backed->program.blocks);
|
||||
@@ -550,13 +551,34 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
malformed->program.descriptor_sources.empty(),
|
||||
"malformed indirect image pattern was partially accepted");
|
||||
|
||||
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
|
||||
|
||||
CheckFatal([&] { wrapped_immediate->PlanAndTrack(); },
|
||||
auto negative_immediate = MakeIndirectImageFixture(false, 0xfffffffcu);
|
||||
CheckFatal([&] { negative_immediate->PlanAndTrack(); },
|
||||
"not a valid runtime value",
|
||||
"wrapped scalar immediate entered the invariant image proof");
|
||||
Check(!wrapped_immediate->program.resource_tracking_complete,
|
||||
"wrapped scalar immediate entered the invariant image proof");
|
||||
"negative scalar immediate entered the indirect image proof");
|
||||
|
||||
for (const auto [immediate, member, first, stride] :
|
||||
{std::array{0u, 4u, 4u, 224u}, std::array{4u, 4u, 8u, 224u},
|
||||
std::array{36u, 0u, 36u, 224u}, std::array{1u, 3u, 0u, 224u},
|
||||
std::array{1u, 3u, 0u, 1u}, std::array{1u, 3u, 0u, 2u}}) {
|
||||
auto split_offset = MakeIndirectImageFixture(false, immediate, false, member, stride);
|
||||
split_offset->PlanAndTrack();
|
||||
const auto split_plan = ExtractResourcePlan(split_offset->program);
|
||||
LinearTestMemory split_memory;
|
||||
std::copy(image_descriptor.begin(), image_descriptor.end(),
|
||||
split_memory.words.begin() + 0x1000u / 4u);
|
||||
split_memory.fail_address = first == 36u ? 0x1004u : UINT64_MAX;
|
||||
split_memory.watched_address = split_memory.base + first;
|
||||
user_data[1] = stride << 16u;
|
||||
user_data[2] = stride < 4u ? 8u / stride : 1u;
|
||||
SrtRuntime split_runtime{.user_data = user_data,
|
||||
.userdata = &split_memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
Check(MaterializeResources(split_plan, split_runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 1 &&
|
||||
snapshot.images[0].dwords == image_descriptor &&
|
||||
split_memory.watched_reads == 1u,
|
||||
"indirect scalar offsets did not align and bound their components independently");
|
||||
}
|
||||
}
|
||||
|
||||
void TestGuardedDirectImageTable() {
|
||||
|
||||
@@ -488,35 +488,34 @@ void TestSharedIntegerRuntimeDependencies() {
|
||||
}
|
||||
|
||||
void TestConstantBufferBounds() {
|
||||
Fixture fixture;
|
||||
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer);
|
||||
const auto buffer =
|
||||
fixture.Emit(ValueOpcode::GetBufferResource,
|
||||
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
||||
const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer,
|
||||
{buffer, Value(12u)}, memory);
|
||||
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
|
||||
fixture.Plan();
|
||||
struct Case {
|
||||
uint32_t offset;
|
||||
uint32_t immediate;
|
||||
bool valid;
|
||||
uint32_t expected;
|
||||
};
|
||||
for (const auto &test : {Case{12u, 0u, true, 0xa5a5a5a5u},
|
||||
Case{3u, 1u, true, 0x12345678u},
|
||||
Case{0xfffffffcu, 4u, false, 0u},
|
||||
Case{16u, 0u, false, 0u}}) {
|
||||
Fixture fixture;
|
||||
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer, test.immediate);
|
||||
const auto buffer =
|
||||
fixture.Emit(ValueOpcode::GetBufferResource,
|
||||
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
||||
const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer,
|
||||
{buffer, Value(test.offset)}, memory);
|
||||
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
|
||||
fixture.Plan();
|
||||
|
||||
TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) &&
|
||||
flat == std::vector<uint32_t>{0xa5a5a5a5u},
|
||||
"constant-buffer SRT walk failed");
|
||||
|
||||
Fixture overflow;
|
||||
const auto overflow_memory = overflow.AddMemory(ResourceKind::ScalarBuffer);
|
||||
const auto overflow_buffer =
|
||||
overflow.Emit(ValueOpcode::GetBufferResource,
|
||||
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
||||
const auto overflow_read =
|
||||
overflow.EmitMemory(ValueOpcode::ReadConstBuffer,
|
||||
{overflow_buffer, Value(16u)}, overflow_memory);
|
||||
LoadBuffer(overflow, {overflow_read, Value(0u), Value(16u), Value(0u)});
|
||||
overflow.Plan();
|
||||
Check(!SrtWalker(overflow.program, runtime).RefreshFlatBuffer(flat),
|
||||
"out-of-bounds constant-buffer walk was accepted");
|
||||
TestMemory memory_image{{{0x3000u, 0x12345678u}, {0x300cu, 0xa5a5a5a5u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) == test.valid,
|
||||
"constant-buffer walk misaligned or wrapped its offset components");
|
||||
Check(test.valid ? flat == std::vector<uint32_t>{test.expected} : memory_image.reads == 0,
|
||||
"constant-buffer walk read the wrong word or accessed an out-of-bounds address");
|
||||
}
|
||||
}
|
||||
|
||||
void TestReadLaneElimination() {
|
||||
|
||||
@@ -23772,6 +23772,35 @@ TestCase ScalarMemoryLoadVariants() {
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarBufferOffsetAlignmentAndCarry() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code;
|
||||
AppendSMovLiteral(&code, 12, 0xfffffffcu);
|
||||
code.push_back(EncodeSop2(0x00, 12, 12, 4));
|
||||
code.push_back(EncodeSmem0(0x08, 20));
|
||||
code.push_back(EncodeSmem1(4, 12));
|
||||
AppendSMovLiteral(&code, 12, 3u);
|
||||
code.push_back(EncodeSop2(0x00, 12, 12, 4));
|
||||
code.push_back(EncodeSmem0(0x08, 21));
|
||||
code.push_back(EncodeSmem1(1, 12));
|
||||
AppendStoreSgpr(&code, 20, 0);
|
||||
AppendStoreSgpr(&code, 21, 1);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test{"ScalarBufferOffsetAlignmentAndCarry", code,
|
||||
{0x11111111u, 0x22222222u}, {0u, 0x11111111u},
|
||||
{O::S_MOV_B32, O::S_ADD_U32, O::S_BUFFER_LOAD_DWORD,
|
||||
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
test.forbidden_spirv = {"flattened_srt"};
|
||||
test.compute_info.threads_num[0] = 1;
|
||||
test.compute_info.threads_num[1] = 1;
|
||||
test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.group_id[0] = true;
|
||||
test.compute_info.workgroup_register = 4;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ScalarLoadSignedImmediateOffsetAddsSoffset() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -29888,6 +29917,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
||||
AddCase(ScalarMemRealtimeCapturedPlaceholder);
|
||||
AddCase(ScalarMemoryLoadVariants);
|
||||
AddCase(ScalarBufferOffsetAlignmentAndCarry);
|
||||
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
|
||||
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
|
||||
AddCase(ScalarLoadAlignsDynamicBase);
|
||||
|
||||
@@ -12822,10 +12822,10 @@ void TestSrtWalkerRealSBufferTranslation() {
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||
Check(walked, "SRT walk failed");
|
||||
Check(flat.size() == 4 &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin() + 1),
|
||||
"real S_BUFFER_LOAD walk used the wrong final alignment");
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
"real S_BUFFER_LOAD walk did not align offset components independently");
|
||||
|
||||
user_data[10] = 4 * sizeof(uint32_t);
|
||||
user_data[10] = 3 * sizeof(uint32_t);
|
||||
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||
Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds");
|
||||
CheckFlattenedReadSlots(
|
||||
|
||||
Reference in New Issue
Block a user