mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
shader: bind bounded GPU-selected buffer store destinations
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
This commit is contained in:
@@ -675,7 +675,7 @@ void CheckAmprOrdering(Loader::SymbolDatabase &symbols, uint32_t file_id) {
|
||||
"initialize AMPR command buffer");
|
||||
}
|
||||
};
|
||||
// SDK WaitCompare order: ==, unsigned >/<, !=, wrapped >=, signed >/<.
|
||||
// WaitCompare order: ==, unsigned >/<, !=, wrapped >=, signed >/<.
|
||||
struct Comparison { uint8_t compare; uint64_t blocked, reference, released; };
|
||||
constexpr std::array comparisons {
|
||||
Comparison{0, 1, 2, 2}, Comparison{1, 0x40000000000019c3, 0x40000000000019c3,
|
||||
|
||||
@@ -306,7 +306,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
const auto source = fixture->program.info.images[0].source;
|
||||
Check(source < fixture->program.descriptor_sources.size() &&
|
||||
fixture->program.descriptor_sources[source]
|
||||
.indirect_image.has_value(),
|
||||
.indirect_descriptor.has_value(),
|
||||
"indirect image source was not retained for runtime proof");
|
||||
const auto image_handle =
|
||||
std::ranges::find_if(*fixture->block, [](const Inst &inst) {
|
||||
@@ -668,7 +668,7 @@ void TestGuardedDirectImageTable() {
|
||||
fixture.AddMemory(memory, 0x128));
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(indirect && indirect->material_source == UINT32_MAX &&
|
||||
indirect->selector_stride == 0u && indirect->table_offset == 344u &&
|
||||
indirect->key_count.Resolve().IsImmediate() &&
|
||||
@@ -966,7 +966,7 @@ void TestBoundedComputeImageLoop() {
|
||||
fixture.AddMemory(sample, 0x29c));
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(indirect && indirect->material_source == UINT32_MAX &&
|
||||
indirect->table_offset == 0x6b0u &&
|
||||
indirect->key_count.Resolve() == count.Resolve(),
|
||||
@@ -1321,7 +1321,7 @@ void TestUniformizedMaterialImageKeys() {
|
||||
fixture.AddMemory(output_memory, 0x1b00u), done);
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
|
||||
const auto &indirect = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(indirect && indirect->selector_stride == 0x90u &&
|
||||
indirect->selector_offset == 0xc00u &&
|
||||
indirect->table_offset == 0x20e0u &&
|
||||
@@ -1340,7 +1340,7 @@ void TestUniformizedMaterialImageKeys() {
|
||||
plan = make_plan(Variant::FirstLaneAndNot);
|
||||
Check(plan.requires_specialization_memory &&
|
||||
plan.descriptor_sources[plan.info.images[0].source]
|
||||
.indirect_image->selector_mask.Resolve().TryInstruction() != nullptr,
|
||||
.indirect_descriptor->selector_mask.Resolve().TryInstruction() != nullptr,
|
||||
"uniformized image mask did not survive extraction");
|
||||
LinearTestMemory memory;
|
||||
memory.words.resize(0x23000u / 4u);
|
||||
@@ -2577,7 +2577,7 @@ void TestFiniteImagePhiCycle() {
|
||||
fixture.AddMemory(memory, 4));
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_image;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(finite && finite->sources.size() == 2,
|
||||
"cyclic image selection did not retain two complete descriptor candidates");
|
||||
const auto *key = image.Instruction()->Arg(0).Instruction();
|
||||
@@ -2659,7 +2659,7 @@ void TestFiniteImageBitScanSentinel() {
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_image;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(finite && finite->sources.size() == 1 &&
|
||||
image.Instruction()->Arg(0).Instruction()->NumPhiBlocks() == 2,
|
||||
"nonzero bit scan retained its impossible sentinel or removed a Phi edge");
|
||||
|
||||
@@ -9202,7 +9202,7 @@ public:
|
||||
rendering.width == width && rendering.height == height &&
|
||||
rendering.num_layers == view_layers &&
|
||||
rendering.num_color_attachments == 1,
|
||||
"SDK dimension=0 slice range did not create a Vulkan 1D-array "
|
||||
"dimension=0 slice range did not create a Vulkan 1D-array "
|
||||
"attachment view");
|
||||
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
@@ -30569,6 +30569,117 @@ TestCase ImageD16StoreUnpacksHalfPairs() {
|
||||
return test;
|
||||
}
|
||||
|
||||
void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
TestCase test;
|
||||
test.name = "IndirectBufferStore";
|
||||
test.initial.assign(64, 0xdeadbeefu);
|
||||
test.storage_buffer_range_dwords = 16;
|
||||
test.storage_buffer_offsets = {12, 108};
|
||||
test.required_spirv = {"OpSwitch"};
|
||||
test.forbidden_spirv = {"PhysicalStorageBuffer"};
|
||||
|
||||
CompiledShader compiled;
|
||||
auto &program = compiled.program;
|
||||
program.stage = ShaderType::Compute;
|
||||
program.wave_size = 32;
|
||||
program.srt_plan_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
program.shader_info_complete = true;
|
||||
program.block_storage.push_back(std::make_unique<Block>());
|
||||
auto *block = program.block_storage.back().get();
|
||||
program.blocks.push_back(block);
|
||||
program.block_info.push_back({.id = 0});
|
||||
|
||||
auto &lane = block->AppendNewInst(ValueOpcode::LaneId);
|
||||
auto &selector = block->AppendNewInst(ValueOpcode::BitwiseAnd32,
|
||||
{Value(&lane), Value(7u)});
|
||||
auto &key = block->AppendNewInst(ValueOpcode::IAdd32,
|
||||
{Value(&selector), Value(8u)});
|
||||
auto &handle = block->AppendNewInst(
|
||||
ValueOpcode::GetBufferResource,
|
||||
{Value(&key), Value(0u), Value(0u), Value(0u)});
|
||||
handle.SetFlags<uint32_t>(0u);
|
||||
auto &offset = block->AppendNewInst(ValueOpcode::IMul32,
|
||||
{Value(&lane), Value(4u)});
|
||||
auto &data = block->AppendNewInst(ValueOpcode::IAdd32,
|
||||
{Value(&lane), Value(0x1000u)});
|
||||
auto &exec = block->AppendNewInst(ValueOpcode::INotEqual32,
|
||||
{Value(&lane), Value(4u)});
|
||||
block->AppendNewInst(ValueOpcode::StoreBufferU32,
|
||||
{Value(&handle), Value(0u), Value(&offset), Value(0u),
|
||||
Value(&data), Value(&exec)});
|
||||
program.memory_info.push_back({.kind = ResourceKind::Buffer, .offen = true});
|
||||
|
||||
BufferResource root;
|
||||
root.packed_stride = 1;
|
||||
root.written = true;
|
||||
root.indirect_root = 0;
|
||||
root.indirect_mapping_offset = 4;
|
||||
root.indirect_search_iterations = std::bit_width(3u);
|
||||
root.indirect_resources = {0u, 1u};
|
||||
auto candidate = root;
|
||||
candidate.indirect_search_iterations = 0;
|
||||
candidate.indirect_resources.clear();
|
||||
program.descriptor_sources.resize(1);
|
||||
program.descriptor_sources[0].indirect_descriptor.emplace();
|
||||
ShaderComputeInputInfo compute;
|
||||
compute.wave_size = 32;
|
||||
compute.host_subgroup_size = vulkan.SubgroupSize();
|
||||
compute.threads_num[0] = 32;
|
||||
compute.threads_num[1] = compute.threads_num[2] = 1;
|
||||
for (const u32 null_buffers : {0u, 1u, 2u}) {
|
||||
program.info.buffers = {root, candidate};
|
||||
if (null_buffers != 0u) program.info.buffers[1].packed_stride = 0;
|
||||
if (null_buffers == 2u) {
|
||||
// Materialization deduplicates all null destinations into one unmapped root.
|
||||
program.info.buffers.resize(1);
|
||||
program.info.buffers[0].packed_stride = 0;
|
||||
program.info.buffers[0].indirect_root = BufferResource::NoIndirectBuffer;
|
||||
program.info.buffers[0].indirect_search_iterations = 0;
|
||||
program.info.buffers[0].indirect_resources.clear();
|
||||
}
|
||||
program.binding_layout_complete = false;
|
||||
AllocateBindings(program);
|
||||
const auto *buffers = FindBinding(program.bindings, DescriptorBindingKind::Buffers);
|
||||
Require(test.name, "candidate bindings",
|
||||
buffers != nullptr && buffers->resources ==
|
||||
(null_buffers == 2u ? std::vector<u32>{0u} : std::vector<u32>{0u, 1u}),
|
||||
"indirect store lost a buffer binding");
|
||||
compiled.packed_user_data.resize(program.bindings.ShaderDataDwords());
|
||||
compiled.packed_user_data[program.bindings.memory_offset_dword] = 12u | (108u << 8u);
|
||||
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
|
||||
ValidateSpirv(test.name, compiled.spirv);
|
||||
if (null_buffers == 2u) {
|
||||
test.required_spirv.clear();
|
||||
test.forbidden_spirv.push_back("OpSwitch");
|
||||
}
|
||||
CheckSpirvText(test, compiled.spirv);
|
||||
|
||||
for (const bool swapped : {false, true}) {
|
||||
if (null_buffers != 0u && swapped) continue;
|
||||
// Repeated keys select two distinct logical buffers within one native allocation.
|
||||
// Keys below, between and above the live keys must not select either buffer.
|
||||
compiled.resources.flattened_srt = {0u, 0u, 0u, 0u, 3u,
|
||||
10u, swapped ? 0u : 1u,
|
||||
12u, swapped ? 1u : 0u,
|
||||
14u, swapped ? 0u : 1u};
|
||||
test.expected = test.initial;
|
||||
for (const u32 lane : {2u, 6u, 10u, 12u, 14u}) {
|
||||
const u32 resource = (lane & 7u) == 4u ? swapped : !swapped;
|
||||
if (null_buffers == 2u || (null_buffers == 1u && resource == 1u)) continue;
|
||||
test.expected[test.storage_buffer_offsets[resource] / 4u + lane] = 0x1000u + lane;
|
||||
}
|
||||
auto output = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||
vulkan.Dispatch(test, compiled, output);
|
||||
const auto actual = vulkan.ReadBuffer(test.name, output, test.expected.size());
|
||||
vulkan.DestroyBuffer(&output);
|
||||
CompareWords(test, "key selection, EXEC, bounds and null descriptors", test.expected, actual);
|
||||
}
|
||||
}
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
}
|
||||
|
||||
void CheckIndirectImageKeySwitch() {
|
||||
constexpr const char *name = "IndirectImageKeySwitch";
|
||||
constexpr uint32_t mapping_capacity = 1793u;
|
||||
@@ -30621,8 +30732,8 @@ void CheckIndirectImageKeySwitch() {
|
||||
|
||||
program.descriptor_sources.resize(2);
|
||||
program.descriptor_sources[0].dword_count = 8;
|
||||
program.descriptor_sources[0].indirect_image =
|
||||
DescriptorSource::IndirectImage{0u, 0u, 224u, 12u, 0u};
|
||||
program.descriptor_sources[0].indirect_descriptor =
|
||||
DescriptorSource::IndirectDescriptor{0u, 0u, 224u, 12u, 0u};
|
||||
program.descriptor_sources[1].dword_count = 4;
|
||||
|
||||
ImageResource root{};
|
||||
@@ -30656,7 +30767,7 @@ void CheckIndirectImageKeySwitch() {
|
||||
text.find("OpSwitch") != std::string::npos &&
|
||||
text.find("OpPhi") != std::string::npos &&
|
||||
CountText(text, "OpImageSampleExplicitLod") == 2 &&
|
||||
CountText(text, "OpIEqual") == 11,
|
||||
CountText(text, "OpIEqual") == 1,
|
||||
"dynamic image key did not use a compact two-sample switch");
|
||||
|
||||
program.memory_info[0].image_dimension =
|
||||
@@ -37292,6 +37403,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--indirect-buffer-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
CheckIndirectBufferStore(vulkan);
|
||||
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
|
||||
RunCase(&vulkan, BufferLoadDwordx3GpuSelectedDescriptors());
|
||||
RunCase(&vulkan, BufferLoadDwordx4SnapshotsOverlappingAddress());
|
||||
@@ -37857,6 +37969,7 @@ int main(int argc, char **argv) {
|
||||
CheckTessellationPrograms();
|
||||
CheckPixelParameterAliases();
|
||||
CheckRectListShaders();
|
||||
CheckIndirectBufferStore(vulkan);
|
||||
CheckIndirectImageKeySwitch();
|
||||
CheckWave64WholeWaveResults();
|
||||
CheckPs5GameExampleImageClearRuntimeShape();
|
||||
|
||||
@@ -8785,6 +8785,112 @@ void TestNativeGuardedSamplerSource() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestBoundedMaterialBufferStores(bool scalar_key) {
|
||||
// Captured batch search and descriptor waterfall, with deformation arithmetic removed.
|
||||
const uint32_t shader[] = {
|
||||
0xbfa00003u, 0xd7460002u, 0x04010a04u, 0xf4080101u,
|
||||
0xfa000080u, 0xbe8e0380u, 0xbe8f037eu, 0xbf8cc07fu,
|
||||
0xf4240302u, 0xfa000014u, 0xf4080201u, 0xfa000020u,
|
||||
0xbf880019u, 0x7e3602c1u, 0xbf8cc07fu, 0xbf0a0d0eu,
|
||||
0xbf800000u, 0x8506807eu, 0xbeea0306u, 0xbefe0306u,
|
||||
0xbf860011u, 0x816b0e0cu, 0x810e810eu, 0x936aff6bu,
|
||||
0x00000050u, 0x7e36026bu, 0x816aa46au, 0xf4240104u,
|
||||
0xd4000000u, 0xbf8cc07fu, 0xbeea0404u, 0x816a0405u,
|
||||
0x7d8604f9u, 0x06868405u, 0x7d88046au, 0x876a6a04u,
|
||||
0x8a7e6a06u, 0xbf89ffe6u, 0xbefe030fu, 0x7daa36c1u,
|
||||
0xbf880013u, 0xbf8cc07fu, 0xe0342030u, 0x8002171bu,
|
||||
0xf4080100u, 0xfa000020u, 0x7e020281u, 0x7ed40518u,
|
||||
0xbeeb037eu, 0x7da4306au, 0x8f6a846au, 0x7e020280u,
|
||||
0xf4280002u, 0xd4000000u, 0xbf8cc07fu, 0xe0701000u,
|
||||
0x80003405u, 0xbefe036bu, 0x7daa0280u, 0xbf89fff3u,
|
||||
0xbf810000u,
|
||||
};
|
||||
std::array<uint32_t, 8 * 20> material{};
|
||||
std::array<uint32_t, 16 * 4> table{};
|
||||
std::array<uint32_t, 8> batch{};
|
||||
std::array<uint32_t, 12> table_root{};
|
||||
std::array<uint32_t, 36> batch_root{};
|
||||
const auto buffer = [](uint32_t* words, const void* data, uint32_t stride,
|
||||
uint32_t count, uint32_t mode = 0u) {
|
||||
const auto address = reinterpret_cast<uint64_t>(data);
|
||||
words[0] = static_cast<uint32_t>(address);
|
||||
words[1] = static_cast<uint32_t>(address >> 32u) | (stride << 16u);
|
||||
words[2] = count;
|
||||
words[3] = 0x5004u | (mode << 28u);
|
||||
};
|
||||
batch[5] = 1u;
|
||||
batch[6] = 2u;
|
||||
material[20 + 9] = 32u;
|
||||
material[20 + 13] = 3u;
|
||||
material[40 + 9] = 32u;
|
||||
material[40 + 10] = 32u;
|
||||
material[40 + 13] = 5u;
|
||||
buffer(table.data() + 12u, reinterpret_cast<void*>(0x100000u), 1u, 128u, 3u);
|
||||
buffer(table.data() + 20u, reinterpret_cast<void*>(0x200000u), 1u, 128u, 3u);
|
||||
buffer(table_root.data() + 8u, table.data(), 16u, 16u);
|
||||
buffer(batch_root.data() + 8u, material.data(), 80u, 8u);
|
||||
buffer(batch_root.data() + 32u, batch.data(), 16u, 2u);
|
||||
const auto table_address = reinterpret_cast<uint64_t>(table_root.data());
|
||||
const auto batch_address = reinterpret_cast<uint64_t>(batch_root.data());
|
||||
const std::array<uint32_t, 4> user_data{
|
||||
static_cast<uint32_t>(table_address), static_cast<uint32_t>(table_address >> 32u),
|
||||
static_cast<uint32_t>(batch_address), static_cast<uint32_t>(batch_address >> 32u)};
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.wave_size = 32u;
|
||||
options.user_data = user_data;
|
||||
auto code = std::to_array(shader);
|
||||
if (scalar_key) {
|
||||
code[0xa8u / 4u] = EncodeMubuf0(0x0cu, 52u);
|
||||
code[0xacu / 4u] = EncodeMubuf1(24u, 2u, 27u);
|
||||
}
|
||||
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
||||
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = user_data, .read_memory = ReadHostTestMemory,
|
||||
.read_specialization_memory = ReadHostTestMemory};
|
||||
const auto materialize = [&] {
|
||||
return ShaderRecompiler::IR::MaterializeResources(plan, runtime, snapshot, specialization);
|
||||
};
|
||||
Check(materialize(), "bounded material range failed to materialize");
|
||||
const auto& buffers = specialization.buffers;
|
||||
const auto root = std::ranges::find_if(buffers, [](const auto& resource) {
|
||||
return resource.indirect_root != ShaderRecompiler::IR::BufferResource::NoIndirectBuffer;
|
||||
});
|
||||
Check(root != buffers.end() && snapshot.flattened_srt[root->indirect_mapping_offset] == 2u &&
|
||||
buffers.size() == plan.info.buffers.size() + 1u,
|
||||
"bounded material range did not select exactly its two writable buffers");
|
||||
const auto root_index = static_cast<size_t>(root - buffers.begin());
|
||||
const auto first_specialization = specialization;
|
||||
table[12] += 0x1000u;
|
||||
Check(materialize() && specialization == first_specialization,
|
||||
"buffer address changes created a shader permutation");
|
||||
batch[6] = 0u;
|
||||
Check(materialize() && specialization.buffers[root_index].indirect_root ==
|
||||
ShaderRecompiler::IR::BufferResource::NoIndirectBuffer &&
|
||||
std::ranges::all_of(snapshot.buffers[root_index].dwords,
|
||||
[](uint32_t word) { return word == 0u; }) &&
|
||||
specialization.buffers.size() == plan.info.buffers.size(),
|
||||
"empty material batch retained writable candidates");
|
||||
batch[6] = 2u;
|
||||
batch[5] = 7u;
|
||||
Check(!materialize(), "material batch exceeded the descriptor record count");
|
||||
batch[5] = 1u;
|
||||
batch_root[11] |= 1u << 30u;
|
||||
Check(!materialize(), "material selection accepted a non-buffer descriptor type");
|
||||
batch_root[11] &= ~(1u << 30u);
|
||||
Check(materialize(), "restored material batch failed to materialize");
|
||||
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options, specialization, 0u);
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
auto unbounded = code;
|
||||
unbounded[0x3cu / 4u] = EncodeSopc(0x0bu, 14u, 13u); // s_cmp_le_u32 admits wraparound.
|
||||
ExpectFatal([&] { ShaderRecompiler::TranslateProgram(unbounded, options); },
|
||||
"material selection accepted an inclusive induction bound");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestNativeDescriptorProvenanceRejectsGpuSelection() {
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
const std::array<uint32_t, 13> selected{
|
||||
@@ -14383,6 +14489,7 @@ int main() {
|
||||
TestNestedSelectionPreservesDescriptorSources();
|
||||
TestNativeScalarReadDescriptorPlanning();
|
||||
TestNativeGuardedSamplerSource();
|
||||
for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key);
|
||||
TestNativeDescriptorProvenanceRejectsGpuSelection();
|
||||
TestCfgSiblingSharedExit();
|
||||
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
||||
|
||||
Reference in New Issue
Block a user