diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 130508267e2e..e82d394a0082 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -1355,7 +1355,7 @@ IrResourcePlan handBuiltPlan() { DescriptorSource table; table.dwordCount = 8u; table.dwords[0] = &readConst(3); - table.indirectImage = DescriptorSource::IndirectImage{3u, 2u, 16u, 4u, 2u, 32u, true}; + table.indirectImage = DescriptorSource::IndirectImage{3u, 2u, 16u, 4u, 2u, 32u, 48u, true}; DescriptorSource heap; heap.dwordCount = 4u; heap.dwords[1] = &readConst(6); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 902164752015..cb6d15a55cd4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1076,6 +1076,58 @@ void verifyBindlessTable() { #if ANYPS5_ENABLE_SPIRV_TOOLS static_cast(ValidateAndOptimizeSpirv(queried->spirv, query.target.vulkanVersion, query.target.spirvVersion)); #endif + static std::array strided{}; + for (std::uint32_t entry = 0; entry < 4u; entry++) { + std::fill_n(strided.begin() + entry * 12u, 4u, 0xdead0000u + entry); + std::copy(heap[entry].begin(), heap[entry].end(), strided.begin() + entry * 12u + 4u); + } + const std::vector stridedCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x9310ff10u, 0x00000030u, 0xf42c0502u, 0x20000010u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; + const auto stridedHeapV = bufferDescriptor(strided.data(), 48u, 4u); + std::copy(stridedHeapV.begin(), stridedHeapV.end(), srt.begin()); + auto stridedRequest = makeRequest(stridedCode); + const auto stridedPlan = GetResourcePlan(stridedRequest); + std::uint32_t stridedTables = 0; + for (const auto& source : stridedPlan->descriptorSources) { + if (!source.indirectImage.has_value()) continue; + stridedTables++; + require(!source.indirectImage->hasMaterial && source.indirectImage->entryStride == 48u && source.indirectImage->entryOffset == 16u, "bindless: the 48-byte table entries were not recorded"); + } + require(stridedTables == 1, "bindless: a 48-byte table was not planned"); + AgcDriver::ShaderMemory stridedMemory({}); + const auto stridedCapture = stridedMemory.Capture(stridedRequest); + const auto stridedDirect = static_cast(stridedPlan->info.images.size()); + const auto stridedRoot = tableRoot(*stridedCapture, stridedDirect); + const auto stridedMapping = mappingOf(stridedCapture->snapshot); + require(std::vector(stridedMapping.begin(), stridedMapping.begin() + 7) == std::vector{3u, 0u, 0u, 1u, 1u, 3u, 3u}, "bindless: the 48-byte table is not the identity mapping"); + require(stridedCapture->snapshot.images[stridedRoot].dwords == heap[0] && stridedCapture->snapshot.images[stridedDirect].dwords == heap[1] && stridedCapture->snapshot.images[stridedDirect + 1u].dwords == heap[2] && stridedCapture->snapshot.images[stridedDirect + 2u].dwords == heap[3], "bindless: the 48-byte table slots are wrong"); + stridedRequest.context.memory = stridedMemory.Regions(); + require(!Recompile(stridedRequest, *stridedCapture)->spirv.empty(), "bindless: the 48-byte table did not compile"); + + static std::array leading{}; + for (std::uint32_t entry = 0; entry < 4u; entry++) { + std::copy(heap[entry].begin(), heap[entry].end(), leading.begin() + entry * 16u); + std::fill_n(leading.begin() + entry * 16u + 8u, 8u, 0xdead0000u + entry); + } + const std::vector leadingCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x9310ff10u, 0x00000040u, 0xf42c0502u, 0x20000000u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; + const auto leadingHeapV = bufferDescriptor(leading.data(), 64u, 4u); + std::copy(leadingHeapV.begin(), leadingHeapV.end(), srt.begin()); + auto leadingRequest = makeRequest(leadingCode); + const auto leadingPlan = GetResourcePlan(leadingRequest); + std::uint32_t leadingTables = 0; + for (const auto& source : leadingPlan->descriptorSources) { + if (!source.indirectImage.has_value()) continue; + leadingTables++; + require(!source.indirectImage->hasMaterial && source.indirectImage->entryStride == 64u && source.indirectImage->entryOffset == 0u, "bindless: the 64-byte table entries were not recorded"); + } + require(leadingTables == 1, "bindless: a 64-byte table was not planned"); + AgcDriver::ShaderMemory leadingMemory({}); + const auto leadingCapture = leadingMemory.Capture(leadingRequest); + const auto leadingDirect = static_cast(leadingPlan->info.images.size()); + const auto leadingRoot = tableRoot(*leadingCapture, leadingDirect); + require(leadingCapture->snapshot.images[leadingRoot].dwords == heap[0] && leadingCapture->snapshot.images[leadingDirect].dwords == heap[1] && leadingCapture->snapshot.images[leadingDirect + 1u].dwords == heap[2] && leadingCapture->snapshot.images[leadingDirect + 2u].dwords == heap[3], "bindless: the 64-byte table with the T# first and data behind it has wrong slots"); + leadingRequest.context.memory = leadingMemory.Regions(); + require(!Recompile(leadingRequest, *leadingCapture)->spirv.empty(), "bindless: the 64-byte table did not compile"); + fillSrt(4u); // A table wider than the slots without a material pattern is rejected. fillSrt(100u); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp index f9df378d48d8..7817e319e701 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp @@ -31,6 +31,7 @@ struct DescriptorSource { std::uint32_t selectorOffset = 0; std::uint32_t keyArg = 0; std::uint32_t entryOffset = 0; + std::uint32_t entryStride = 32; bool hasMaterial = false; bool operator==(const IndirectImage& other) const = default; diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 37da1a30cfe8..e946e75c8d7b 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -333,8 +333,8 @@ void resolveTableImage(const IrResourcePlan& plan, std::uint32_t imageIndex, con const ShaderBufferResource heap = decodeBufferDescriptor(heapValue); const std::uint64_t heapSize = heap.GetSize(); if (heap.Type() != 0u || heap.Base48() > std::numeric_limits::max() - heapSize) throw std::runtime_error("bindless heap buffer is invalid"); - if (heapSize != 0u && (heapSize < table.entryOffset || (heapSize - table.entryOffset) % TableEntryBytes != 0u)) throw std::runtime_error("bindless heap contains a partial descriptor"); - const auto entries = heapSize > table.entryOffset ? static_cast(std::min((heapSize - table.entryOffset) / TableEntryBytes, std::numeric_limits::max())) : 0u; + if (heapSize != 0u && (heapSize < table.entryOffset + TableEntryBytes || ((heapSize - table.entryOffset) % table.entryStride != 0u && (heapSize - table.entryOffset) % table.entryStride < TableEntryBytes))) throw std::runtime_error("bindless heap contains a partial descriptor"); + const auto entries = heapSize >= table.entryOffset + TableEntryBytes ? static_cast(std::min((heapSize - table.entryOffset - TableEntryBytes) / table.entryStride + 1u, std::numeric_limits::max())) : 0u; const auto readWord = [&](std::uint64_t address, std::uint32_t& word) { if (!runtime.readMemory(runtime.userContext, address, &word)) { throw std::runtime_error("failed to read a bindless image table from memory"); @@ -392,7 +392,7 @@ void resolveTableImage(const IrResourcePlan& plan, std::uint32_t imageIndex, con for (std::size_t i = 0u; i < keys.size(); ++i) { auto& candidate = candidates[i]; candidate.dwordCount = 8u; - const std::uint64_t address = heapBase + table.entryOffset + static_cast(keys[i]) * TableEntryBytes; + const std::uint64_t address = heapBase + table.entryOffset + static_cast(keys[i]) * table.entryStride; for (std::uint32_t dword = 0u; dword < 8u; ++dword) readWord(address + dword * sizeof(std::uint32_t), candidate.dwords[dword]); if (nullImageDescriptor(candidate)) { ++paddedNull; diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index ee4eaee5f81d..1d9109fbf33e 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -460,7 +460,7 @@ class Tracker { } // The table entry offset `key * 32` (a shift or a multiply), optionally plus an immediate. - static bool MatchTableOffset(IrValue* value, IrValue*& key, std::uint32_t& entryOffset) { + static bool MatchTableOffset(IrValue* value, IrValue*& key, std::uint32_t& entryOffset, std::uint32_t& entryStride) { value = value->Resolve(); entryOffset = 0; if (value->Opcode() == IrOpcode::IAdd32 && value->ArgumentCount() == 2u) { @@ -483,6 +483,7 @@ class Tracker { return false; } key = value->Argument(0)->Resolve(); + entryStride = 32u; return true; } if (value->Opcode() != IrOpcode::IMul32) { @@ -495,7 +496,8 @@ class Tracker { } else { return false; } - return scale == 32u; + entryStride = scale; + return scale >= 32u && scale % sizeof(std::uint32_t) == 0u; } // A T# whose eight dwords are scalar reads of one V# (the table) at `entryOffset + key * 32` @@ -538,10 +540,22 @@ class Tracker { IrValue* key = nullptr; std::uint32_t entryOffset = 0; - if (!MatchTableOffset(heapOffset, key, entryOffset) || key->Type() != IrType::U32) { + std::uint32_t entryStride = 0; + if (!MatchTableOffset(heapOffset, key, entryOffset, entryStride) || key->Type() != IrType::U32) { return false; } entryOffset += immediateOffset; + if (entryOffset + 8u * sizeof(std::uint32_t) > entryStride) { + return false; + } + if (entryStride != 32u) { + DescriptorSource direct; + MakeSource(handle, 8u, false, false, direct); + std::uint32_t badDword = 0; + if (ValidateSource(direct, badDword)) { + return false; + } + } for (const auto* read : heapReads) { if (!usedOnlyAsImage(*read, heapReads)) { @@ -559,6 +573,7 @@ class Tracker { table.heapSource = heapSourceIndex; table.materialSource = heapSourceIndex; table.entryOffset = entryOffset; + table.entryStride = entryStride; DescriptorSource materialSource = heapSource; std::uint32_t materialMemoryIndex = 0; const MemoryInfo* materialMemory = ScalarReadMemory(*key, materialMemoryIndex); diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 355e720e7670..e30e07ca31f4 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -108,7 +108,7 @@ static_assert(sizeof(IrValue) == 152, "IrValue changed: update the plan value en static_assert(sizeof(GuestRegister) == 8, "GuestRegister changed: update the plan value encoder"); static_assert(sizeof(MemoryInfo) == 80, "MemoryInfo changed: update the plan encoder"); static_assert(sizeof(DescriptorSource) == 104, "DescriptorSource changed: update the plan encoder"); -static_assert(sizeof(DescriptorSource::IndirectImage) == 28, "IndirectImage changed: update the plan encoder"); +static_assert(sizeof(DescriptorSource::IndirectImage) == 32, "IndirectImage changed: update the plan encoder"); static_assert(sizeof(ResourceBlock) == 80, "ResourceBlock changed: update the plan encoder"); static_assert(sizeof(SrtRead) == 16, "SrtRead changed: update the plan encoder"); static_assert(sizeof(UniformFill) == 28, "UniformFill changed: update the plan encoder"); @@ -147,7 +147,7 @@ static_assert(fieldCount() == 21, "IrResourcePlan changed: updat static_assert(fieldCount() == 2, "GuestRegister changed: update the plan value encoder"); static_assert(fieldCount() == 30, "MemoryInfo changed: update the plan encoder"); static_assert(fieldCount() == 3, "DescriptorSource changed: update the plan encoder"); -static_assert(fieldCount() == 7, "IndirectImage changed: update the plan encoder"); +static_assert(fieldCount() == 8, "IndirectImage changed: update the plan encoder"); static_assert(fieldCount() == 4, "ResourceBlock changed: update the plan encoder"); static_assert(fieldCount() == 2, "SrtRead changed: update the plan encoder"); static_assert(fieldCount() == 5, "UniformFill changed: update the plan encoder"); @@ -972,6 +972,7 @@ bool encodePlan(Writer& writer, const IrResourcePlan& plan) { out.Value(table.selectorOffset); out.Value(table.keyArg); out.Value(table.entryOffset); + out.Value(table.entryStride); out.Value(table.hasMaterial); }); writer.List(plan.controlFlow, [&](Writer& out, const ResourceBlock& block) { @@ -1103,7 +1104,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { } } reader.List(plan.memoryInfo, 78, decodeMemoryInfo); - reader.List(plan.descriptorSources, 62, [&](Reader& in, DescriptorSource& source) { + reader.List(plan.descriptorSources, 66, [&](Reader& in, DescriptorSource& source) { for (auto& dword : source.dwords) dword = valueAt(in.Get()); in.Value(source.dwordCount); const bool indirect = in.Get(); @@ -1114,6 +1115,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { in.Value(table.selectorOffset); in.Value(table.keyArg); in.Value(table.entryOffset); + in.Value(table.entryStride); in.Value(table.hasMaterial); source.indirectImage = indirect ? std::optional(table) : std::nullopt; });