mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
shader: materialize compact image and paired sampler tables
Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
@@ -2933,6 +2933,143 @@ void TestUniformScalarBufferImage() {
|
||||
"unavailable nested image table was accepted");
|
||||
}
|
||||
|
||||
void TestCompactScalarImageTable() {
|
||||
using F = Libs::Graphics::Prospero::BufferFormat;
|
||||
constexpr uint32_t stride = 0x330u, image_offset = 0x98u, sampler_offset = 0x88u;
|
||||
const auto make_plan = [&](bool compact, uint32_t invalid = 0u) {
|
||||
Fixture fixture;
|
||||
const auto list = invalid == 1u
|
||||
? fixture.Address(fixture.UserData(0), fixture.UserData(1))
|
||||
: fixture.Buffer({fixture.UserData(0), fixture.UserData(1),
|
||||
fixture.UserData(2), fixture.UserData(3)});
|
||||
const auto table = fixture.Buffer({fixture.UserData(4), fixture.UserData(5),
|
||||
fixture.UserData(6), fixture.UserData(7)});
|
||||
// Uniform on the GPU, but no CPU-evaluable row or workgroup-prefix proof.
|
||||
const auto group = fixture.Emit(ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)), Value(0u)});
|
||||
const auto index = fixture.Emit(ValueOpcode::BitwiseXor32, {group, Value(1u)});
|
||||
const auto offset = fixture.Emit(invalid == 2u ? ValueOpcode::UDiv32
|
||||
: ValueOpcode::ShiftLeftLogical32,
|
||||
{index, Value(2u)});
|
||||
MemoryInfo scalar;
|
||||
scalar.kind = invalid == 1u ? ResourceKind::ScalarAddress : ResourceKind::ScalarBuffer;
|
||||
const auto key = invalid == 1u
|
||||
? fixture.Emit(ValueOpcode::LoadAddressU32, {list, offset, Value(0u), Value(true)},
|
||||
fixture.AddMemory(scalar, 0x570u))
|
||||
: fixture.Emit(ValueOpcode::ReadConstBuffer, {list, offset},
|
||||
fixture.AddMemory(scalar, 0x570u));
|
||||
const auto record = fixture.Emit(ValueOpcode::IMul32, {key, Value(stride)});
|
||||
scalar.kind = ResourceKind::ScalarBuffer;
|
||||
std::array<Value, 8> image_words;
|
||||
std::array<Value, 4> sampler_words;
|
||||
for (uint32_t word = 0; word < 8u; ++word) {
|
||||
scalar.offset = image_offset + word * 4u;
|
||||
image_words[word] = compact && word >= 4u ? Value(0u)
|
||||
: fixture.Emit(ValueOpcode::ReadConstBuffer, {table, record},
|
||||
fixture.AddMemory(scalar, 0x590u));
|
||||
if (word < 4u) {
|
||||
scalar.offset = sampler_offset + word * 4u;
|
||||
sampler_words[word] = fixture.Emit(ValueOpcode::ReadConstBuffer, {table, record},
|
||||
fixture.AddMemory(scalar, 0x590u));
|
||||
}
|
||||
}
|
||||
if (invalid == 3u) image_words[7] = Value(1u);
|
||||
const auto image = fixture.Image(image_words, 0x59cu);
|
||||
MemoryInfo sample;
|
||||
sample.kind = ResourceKind::Image;
|
||||
sample.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
sample.image_r128 = compact;
|
||||
const auto sampler = fixture.Sampler(sampler_words, 0x59cu);
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw,
|
||||
{image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(sample, 0x59cu));
|
||||
if (invalid == 4u) {
|
||||
sample.image_r128 = false;
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw,
|
||||
{image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(sample, 0x5a0u));
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
const auto &source = fixture.program.descriptor_sources[fixture.program.info.samplers[0].source];
|
||||
Check(source.indirect_descriptor && source.indirect_descriptor->selector &&
|
||||
source.indirect_descriptor->selector->stride == 4u &&
|
||||
source.indirect_descriptor->selector_first.IsEmpty() &&
|
||||
source.indirect_descriptor->key_count.IsEmpty() &&
|
||||
source.indirect_descriptor->table_stride == stride &&
|
||||
source.indirect_descriptor->table_immediate == sampler_offset,
|
||||
"compact image sampler lost its complete bounded index-list domain");
|
||||
return ExtractResourcePlan(fixture.program);
|
||||
};
|
||||
for (const bool compact : {false, true}) {
|
||||
auto plan = make_plan(compact);
|
||||
const uint32_t width = compact ? 4u : 8u;
|
||||
const auto bytes = 2u * stride + image_offset + width * 4u;
|
||||
std::array<uint32_t, 8> data{0x1000u, 0u, 8u, 0u, 0x2000u, 0u, bytes, 0u};
|
||||
struct Memory : LinearTestMemory { uint32_t selector_attempts = 0; } memory;
|
||||
memory.words.resize((0x1000u + bytes) / 4u, 0xdeadbeefu);
|
||||
memory.words[0] = 2u;
|
||||
memory.words[1] = 1u;
|
||||
for (uint32_t row = 0; row < 3u; ++row) {
|
||||
auto at = memory.words.begin() + (0x1000u + row * stride + sampler_offset) / 4u;
|
||||
std::fill_n(at, 4u, 0u);
|
||||
at[0] = row;
|
||||
at += 4;
|
||||
std::fill_n(at, width, 0u);
|
||||
at[0] = 0x100u + row;
|
||||
at[1] = static_cast<uint32_t>(F::k32_32_32_32Float) << 20u;
|
||||
at[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
if (!compact) at[5] = 0x00700000u;
|
||||
}
|
||||
memory.fail_address = 0x2000u + bytes; // Last descriptor ends at the readable boundary.
|
||||
SrtRuntime runtime{.user_data = data, .userdata = &memory,
|
||||
.read_specialization_memory = [](void *userdata, uint64_t address,
|
||||
std::span<uint32_t> words) {
|
||||
auto &memory = *static_cast<Memory *>(userdata);
|
||||
if (address == memory.base) ++memory.selector_attempts;
|
||||
return ReadLinearTestMemory(&memory, address, words);
|
||||
}};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] {
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"paired compact image/sampler table did not materialize");
|
||||
Check(snapshot.images.size() == 4u && snapshot.samplers.size() == 3u,
|
||||
"paired table omitted an image, index-list value or OOB zero key");
|
||||
for (const auto &image : snapshot.images) {
|
||||
Check(image.dword_count == 8u, "compact image escaped normalized eight-DWORD ABI");
|
||||
if (compact) Check(std::ranges::all_of(std::span(image.dwords).subspan(4u),
|
||||
[](uint32_t word) { return word == 0u; }),
|
||||
"R128 image copied poisoned adjacent record fields");
|
||||
}
|
||||
};
|
||||
materialize();
|
||||
const auto original = specialization;
|
||||
memory.words[(0x1000u + stride + image_offset) / 4u] = 0x201u;
|
||||
materialize();
|
||||
Check(specialization == original && std::ranges::any_of(snapshot.images,
|
||||
[](const auto &image) { return image.dwords[0] == 0x201u; }),
|
||||
"compact descriptor payload refresh changed specialization or stayed stale");
|
||||
data[2] = 4u;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.samplers.size() == 2u,
|
||||
"index-list count refresh retained a stale sampler candidate");
|
||||
data[2] = 0u;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.samplers.size() == 1u &&
|
||||
snapshot.samplers[0].dwords[0] == 0u,
|
||||
"empty index list lost the scalar OOB zero key");
|
||||
data[2] = 8u;
|
||||
memory.fail_address = 0x1004u;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"unreadable selected index-list bytes were accepted");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
data[2] = (MaxIndirectDescriptorProbes + 1u) * 4u;
|
||||
memory.selector_attempts = 0u;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.selector_attempts == 0u,
|
||||
"oversized index list was read before the existing probe cap");
|
||||
}
|
||||
for (uint32_t invalid : {1u, 2u, 3u, 4u})
|
||||
CheckFatal([&] { make_plan(true, invalid); }, "not a valid runtime value",
|
||||
"compact table accepted an unproved selector or inconsistent descriptor width");
|
||||
}
|
||||
|
||||
void TestScalarBufferSelectorDomain() {
|
||||
const auto make_plan = [](bool active = true, bool sparse = false, bool uniformize_first = false) {
|
||||
Fixture fixture;
|
||||
@@ -7313,6 +7450,7 @@ int main() {
|
||||
Run("uniformized material image keys", TestUniformizedMaterialImageKeys);
|
||||
Run("image descriptor fields", TestImageDescriptorFields);
|
||||
Run("draw-uniform scalar image", TestUniformScalarBufferImage);
|
||||
Run("compact scalar image table", TestCompactScalarImageTable);
|
||||
Run("scalar buffer selector domain", TestScalarBufferSelectorDomain);
|
||||
Run("private LDS forwarding", TestPrivateLdsForwarding);
|
||||
Run("counted row image selector", TestCountedRowImageSelector);
|
||||
|
||||
@@ -38696,33 +38696,46 @@ void CheckPointerImageTable(VulkanHarness &vulkan) {
|
||||
|
||||
void CheckNestedPointerImageTable(VulkanHarness &vulkan) {
|
||||
using O = ShaderOpcode;
|
||||
constexpr u32 keys_address = 128, table = 256, stride = 592, objects = 4096;
|
||||
constexpr u32 keys_address = 128, list_address = 160, table = 256, objects = 4096;
|
||||
constexpr std::array keys{5u, 0u, 3u, 1u, 4u, 2u};
|
||||
constexpr std::array list{2u, 1u, 4u, 0u, 5u, 3u};
|
||||
for (const bool compact : {false, true})
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
const u32 stride = compact ? 0x330u : 592u;
|
||||
const u32 table_bytes = (keys.size() - 1u) * stride + 0x98u + 16u;
|
||||
TestCase test;
|
||||
test.name = "NestedPointerImageSamplerTable";
|
||||
test.initial.resize((objects + keys.size() * 0x430u) / 4u, 0);
|
||||
test.name = compact ? "CompactRecordImageSamplerTable" : "NestedPointerImageSamplerTable";
|
||||
test.initial.resize((compact ? table + table_bytes : objects + keys.size() * 0x430u) / 4u,
|
||||
compact ? 0xdeadbeefu : 0u);
|
||||
std::fill_n(test.initial.begin(), keys.size(), 0xdeadbeefu);
|
||||
std::fill_n(test.initial.begin() + keys_address / 4u, keys.size(), UINT32_MAX);
|
||||
if (compact) std::copy(list.begin(), list.end(), test.initial.begin() + list_address / 4u);
|
||||
for (u32 row = 0; row < keys.size(); ++row) {
|
||||
const u32 object = objects + row * 0x430u;
|
||||
test.initial[(table + row * stride) / 4u] = object;
|
||||
const u32 object = compact ? table + row * stride : objects + row * 0x430u;
|
||||
if (!compact) test.initial[(table + row * stride) / 4u] = object;
|
||||
auto image = MakeSampledTextureData(Prospero::BufferFormat::k32Float);
|
||||
image[0] = 10u + row;
|
||||
image[1] |= 1u << 30u; // Two texels: zero, then the row's value.
|
||||
image[3] = DstSel(4, 0, 0, 1) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28u);
|
||||
std::copy_n(image.begin(), 8, test.initial.begin() + (object + 0x3d0u) / 4u);
|
||||
std::copy_n(image.begin(), compact ? 4u : 8u,
|
||||
test.initial.begin() + (object + (compact ? 0x98u : 0x3d0u)) / 4u);
|
||||
const std::array sampler{0u, 0x00fff000u, (row & 1u) ? 0x0a500000u : 0u, 0u};
|
||||
std::copy(sampler.begin(), sampler.end(),
|
||||
test.initial.begin() + (object + 0x3f8u) / 4u);
|
||||
test.initial.begin() + (object + (compact ? 0x88u : 0x3f8u)) / 4u);
|
||||
}
|
||||
auto records = MakeStructuredStorageBufferData(stride, keys.size());
|
||||
auto records = MakeStructuredStorageBufferData(compact ? 0u : stride,
|
||||
compact ? table_bytes : keys.size());
|
||||
records[0] = table;
|
||||
std::copy_n(records.begin(), 4, test.user_data.begin() + 32);
|
||||
auto key_buffer = MakeStructuredStorageBufferData(0, keys.size() * 4u);
|
||||
key_buffer[0] = keys_address;
|
||||
std::copy_n(key_buffer.begin(), 4, test.user_data.begin() + 4);
|
||||
if (compact) {
|
||||
auto indices = MakeStructuredStorageBufferData(0, list.size() * 4u);
|
||||
indices[0] = list_address;
|
||||
std::copy_n(indices.begin(), 4, test.user_data.begin() + 36);
|
||||
}
|
||||
const auto output = MakeStructuredStorageBufferData(0, keys.size() * 4u);
|
||||
std::copy_n(output.begin(), 4, test.user_data.begin() + 48);
|
||||
test.bda_mappings = {{0u, 0u}};
|
||||
@@ -38739,20 +38752,39 @@ void CheckNestedPointerImageTable(VulkanHarness &vulkan) {
|
||||
code.insert(code.end(), {EncodeMubuf0(0x0c), EncodeMubuf1(4, 1, 30),
|
||||
EncodeVop2(0x16, 6, InlineU32(16), 4)});
|
||||
AppendVop3(&code, 0x360, 22, Vgpr(6), InlineU32(0));
|
||||
code.insert(code.end(), {EncodeSop2(0x26, 23, 22, 255), stride,
|
||||
if (compact) {
|
||||
code.insert(code.end(), {EncodeSop2(0x1e, 23, 22, InlineU32(2)),
|
||||
EncodeSmem0(0x08, 22, 18), EncodeSmem1(0, 23),
|
||||
EncodeSop2(0x26, 23, 22, 255), stride,
|
||||
EncodeSmem0(0x0b, 8, 16), EncodeSmem1(0x88u, 23)});
|
||||
for (u32 reg = 16u; reg < 20u; ++reg) AppendSMovLiteral(&code, reg, 0xdeadbeefu);
|
||||
} else code.insert(code.end(), {EncodeSop2(0x26, 23, 22, 255), stride,
|
||||
EncodeSmem0(0x09, 20, 16), EncodeSmem1(0, 23),
|
||||
EncodeSmem0(0x03, 8, 10), EncodeSmem1(0x3d0, 125),
|
||||
EncodeSmem0(0x02, 16, 10), EncodeSmem1(0x3f8, 125)});
|
||||
AppendVMovLiteral(&code, 20, std::bit_cast<u32>(0.5f));
|
||||
AppendVMovLiteral(&code, 21, std::bit_cast<u32>(0.5f));
|
||||
code.insert(code.end(), {EncodeMimg0(0x27, 1), EncodeMimg1(5, 20, 2, 4)});
|
||||
code.insert(code.end(), {EncodeMimg0(0x27, 1, 0, false, 1, compact),
|
||||
EncodeMimg1(5, 20, compact ? 3u : 2u, compact ? 2u : 4u)});
|
||||
AppendBufferStoreDword(&code, 5, 30);
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::S_LSHL_B32, O::V_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_LSHRREV_B32, O::V_READLANE_B32, O::S_MUL_I32,
|
||||
O::S_BUFFER_LOAD_DWORDX2, O::S_LOAD_DWORDX8, O::S_LOAD_DWORDX4,
|
||||
O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
if (compact) {
|
||||
test.opcodes.insert(test.opcodes.end(), {O::S_BUFFER_LOAD_DWORD,
|
||||
O::S_BUFFER_LOAD_DWORDX8, O::S_MOV_B32});
|
||||
test.decoded_counts = {{"r128=1", 1}};
|
||||
} else test.opcodes.insert(test.opcodes.end(),
|
||||
{O::S_BUFFER_LOAD_DWORDX2, O::S_LOAD_DWORDX8, O::S_LOAD_DWORDX4});
|
||||
const auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||
if (compact) Require(test.name, "physical R128 descriptor boundary",
|
||||
compiled.resources.images.size() == keys.size() + 1u &&
|
||||
std::ranges::all_of(compiled.resources.images, [](const auto &image) {
|
||||
return image.dword_count == 8u &&
|
||||
std::ranges::all_of(std::span(image.dwords).subspan(4u),
|
||||
[](u32 word) { return word == 0u; });
|
||||
}), "compact descriptor copied adjacent poison or omitted the trailing 16-byte image");
|
||||
for (const auto &value : compiled.resources.buffers) {
|
||||
ShaderBufferResource descriptor{};
|
||||
std::copy_n(value.dwords.begin(), 4, descriptor.fields);
|
||||
@@ -38792,8 +38824,10 @@ void CheckNestedPointerImageTable(VulkanHarness &vulkan) {
|
||||
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, images, samplers);
|
||||
std::vector<u32> expected;
|
||||
for (const auto key : keys)
|
||||
for (auto key : keys) {
|
||||
if (compact) key = list[key];
|
||||
expected.push_back(std::bit_cast<u32>(float(10u + key) * ((key & 1u) ? 0.5f : 1.0f)));
|
||||
}
|
||||
CompareWords(test, "GPU-selected nested image and sampler", expected,
|
||||
vulkan.ReadBuffer(test.name, buffer, expected.size()));
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
|
||||
Reference in New Issue
Block a user