diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index b0d983b5e..a8b6d56ca 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2937,7 +2937,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec if (pushStages != 0 && state->properties.limits.maxPushConstantsSize < Graphics::PipelinePushConstantBytes) { throw std::runtime_error("Vulkan dispatch: compute push constant range exceeds device limit"); } - const auto pushBytes = Graphics::AssemblePushConstants(shaders); + auto pushBytes = Graphics::AssemblePushConstants(shaders); const auto context = graphicsContext(); const auto* limit = state->properties.limits.maxComputeWorkGroupCount; if (arguments == 0 && (x > limit[0] || y > limit[1] || z > limit[2])) { @@ -3067,6 +3067,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec if (resources->Reusable()) insert(); } } + resources->PatchPushConstants(pushBytes); if (profile) timer.add(PhaseResourcesHookWaits, Graphics::Recorder::ThreadWaitedMs() - waitedBefore); // The lookup outcomes of this call: rows with counts on the [indirect] line, and a summary for // the longest call. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index aca08e13e..5677b2a1b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -163,7 +163,10 @@ public: void Upload(bool addressable); void UploadPrepare(bool addressable); void UploadFinish(bool addressable); - VkDescriptorBufferInfo Descriptor(std::uint64_t address, std::size_t bytes) const; + // A view off minStorageBufferOffsetAlignment in its GPU owner (a view inside a merged or + // imported range) is bound from the aligned offset below it; `adjustment` is the difference, + // which the shader adds to every access (RecompileResult::memoryOffsetDword). + VkDescriptorBufferInfo Descriptor(std::uint64_t address, std::size_t bytes, std::uint32_t& adjustment) const; std::vector AddressRanges() const; // The BDA table of the cached address space when it serves this upload alone (an address-based // build with no region outside it): its ranges, immutable while the space lives, and the diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index ef98ccc0f..4e74c0cac 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -51,8 +51,7 @@ public: // The same inside a render pass another pipeline of the same attachments began (compatible by // construction: the attachment formats alone decide). void Continue(VkCommandBuffer commands, const VkViewport& viewport, const VkRect2D& scissor) const; - void PushConstants(VkCommandBuffer commands, std::span shaders) const; - // The same from an assembled block (a draw recipe's, made by AssemblePushConstants once). + // An assembled block (AssemblePushConstants, then ShaderResources::PatchPushConstants). void PushConstants(VkCommandBuffer commands, VkShaderStageFlags stages, std::span bytes) const; // Forgets the Vulkan objects without destroying them: for entries of a device that is already gone. void Abandon() noexcept; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 2092a2b68..136607e0a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -185,6 +185,11 @@ public: // compiled shader's descriptors. A recipe hit refreshes the template iff the two differ. std::uint64_t DataWordsHash() const { return dataWordsHash; } static std::uint64_t DataWordsHash(const CompiledShader& shader); + // Stores the byte offsets of the guest buffers bound below their views (GuestBufferMemory:: + // Descriptor) into a push constant block assembled from the shaders this object was built for. + void PatchPushConstants(std::span bytes) const { + for (const auto& [position, adjustment] : pushPatches) bytes[position] = static_cast(adjustment); + } // Whether RefreshData would record anything for `shader` (the per-word compare; verification). bool DataWordsDiffer(const CompiledShader& shader) const; // Why the fast proof of a Revalidate left the object to the full walk (the [rescache] @@ -237,6 +242,11 @@ private: bool written = true; // Data buffers: the words the buffer holds once the recorded work ran (see RefreshData). std::vector dataWords; + // Guest buffers: how far below the view the descriptor starts (GuestBufferMemory:: + // Descriptor), and where in the push constant block the shader reads that (-1: the + // shader data is a buffer). + std::uint32_t adjustment = 0; + std::int32_t pushByte = -1; }; struct Binding { @@ -308,7 +318,7 @@ private: void forgetDeferredInputs(); void release() noexcept; void prepareAddressBindings(std::span shaders, std::span snapshots); - VkDescriptorBufferInfo descriptor(const Allocation& allocation) const; + VkDescriptorBufferInfo descriptor(Allocation& allocation); void noteReusable(); void reportDescriptorCaches() const; // What a sampled texture was proved current against when the build (or the last full Revalidate) @@ -400,6 +410,8 @@ private: // FNV-1a offset basis: the hash of no data buffers (DataWordsHash). std::uint64_t dataWordsHash = 14695981039346656037ull; void rehashDataWords(); + // The nonzero guest buffer adjustments by push constant byte (PatchPushConstants). + std::vector> pushPatches; BuildTiming timing; // Build state carried from stage A to stage B: the bindings in plan order, the image bindings // still to look up (index into `bindings`; the DescriptorBinding lives in the compiled shader), diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index e6b9ae5c5..4cd4dbac5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1159,7 +1159,11 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); if (record.pushBytes != nullptr) record.pipeline->PushConstants(commands, record.pushStages, *record.pushBytes); - else record.pipeline->PushConstants(commands, shaders); + else { + auto bytes = AssemblePushConstants(shaders); + resources.PatchPushConstants(bytes); + record.pipeline->PushConstants(commands, PushConstantStages(shaders), bytes); + } APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten); @@ -1488,7 +1492,10 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& recipe->passKey = passKey; recipe->vertexInput = inputs.vertexInput; recipe->pushStages = PushConstantStages(shaders); - if (recipe->pushStages != 0) recipe->pushBytes = AssemblePushConstants(shaders); + if (recipe->pushStages != 0) { + recipe->pushBytes = AssemblePushConstants(shaders); + resources->PatchPushConstants(recipe->pushBytes); + } recipe->masked = masked; recipe->fragmentOutputs = inputs.fragmentOutputs; recipe->shaderStages = inputs.shaderStages; @@ -1577,7 +1584,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK"); resources->Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->Layout()); APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); - pipeline->PushConstants(commands, shaders); + auto pushBytes = AssemblePushConstants(shaders); + resources->PatchPushConstants(pushBytes); + pipeline->PushConstants(commands, PushConstantStages(shaders), pushBytes); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); if (args != nullptr) CountIndirectDraw(indirect.path, indirect.readMs, rewritten); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 60ddf5a79..ca290d06f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1927,7 +1927,7 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { recorder.EndGpuTiming(timing, copiedBytes); } -VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std::size_t bytes) const { +VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std::size_t bytes, std::uint32_t& adjustment) const { Require(uploaded && !committed, "guest GPU memory is not available"); Require(bytes != 0 && bytes <= std::numeric_limits::max() - address, "invalid guest buffer view"); const auto* found = owner(address); @@ -1936,10 +1936,13 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std: Require(address >= region.begin && address + bytes <= region.end && (region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr), "guest buffer view exceeds its GPU owner"); const auto base = region.direct != nullptr ? region.direct->base : region.mirror != nullptr ? region.mirror->base : region.begin; const auto offset = address - base; - Require(context.limits.minStorageBufferOffsetAlignment != 0 && offset % context.limits.minStorageBufferOffsetAlignment == 0, "guest buffer view violates storage buffer offset alignment"); - Require(bytes <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit"); + Require(context.limits.minStorageBufferOffsetAlignment != 0, "no storage buffer offset alignment"); + // At most 255 (the alignment is at most 256); the shader adds it in DWORDs. + adjustment = static_cast(offset % context.limits.minStorageBufferOffsetAlignment); + Require(adjustment % 4 == 0, "guest buffer view off the storage buffer offset alignment is not DWORD aligned"); + Require(bytes + adjustment <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit"); const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? region.mirror->buffer->Handle() : region.buffer->Handle(); - return {handle, offset, bytes}; + return {handle, offset - adjustment, bytes + adjustment}; } ShaderRecompiler::BdaAbi::Range GuestBufferMemory::addressRange(const Region& region) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index c06f4471b..558cefb4f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -277,13 +277,6 @@ void Pipeline::Continue(VkCommandBuffer commands, const VkViewport& viewport, co context.Resolved(&DeviceFunctions::cmdSetScissor, "vkCmdSetScissor")(commands, 0, 1, &scissor); } -void Pipeline::PushConstants(VkCommandBuffer commands, std::span shaders) const { - const auto stages = PushConstantStages(shaders); - if (stages == 0) return; - const auto bytes = AssemblePushConstants(shaders); - context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, stages, 0, PipelinePushConstantBytes, bytes.data()); -} - void Pipeline::PushConstants(VkCommandBuffer commands, VkShaderStageFlags stages, std::span bytes) const { if (stages == 0) return; context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, stages, 0, PipelinePushConstantBytes, bytes.data()); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp index 630eba344..895fa359d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp @@ -32,8 +32,8 @@ void ShaderResources::prepareAddressBindings(std::span sha } } -VkDescriptorBufferInfo ShaderResources::descriptor(const Allocation& allocation) const { - if (allocation.guest) return guestMemory.Descriptor(allocation.address, allocation.size); +VkDescriptorBufferInfo ShaderResources::descriptor(Allocation& allocation) { + if (allocation.guest) return guestMemory.Descriptor(allocation.address, allocation.size, allocation.adjustment); if (allocation.role == ShaderRecompiler::DescriptorRole::GuestBuffers) { Require(context.emptyBuffer != VK_NULL_HANDLE, "no placeholder buffer for a null V#"); return {context.emptyBuffer, 0, allocation.size}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index ef3cfe76d..097b53ade 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -828,7 +828,15 @@ void ShaderResources::buildPrepare(std::span shaders, cons // vector) counts as written, like imageWritten below. const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element]; const bool atomic = element < binding.bufferAtomic.size() && binding.bufferAtomic[element]; - item.allocations.push_back(addGuestBuffer(std::span(binding.guestDescriptor).subspan(static_cast(element) * 4, 4), target, indexAddress, indexBytes, written, atomic)); + const auto index = addGuestBuffer(std::span(binding.guestDescriptor).subspan(static_cast(element) * 4, 4), target, indexAddress, indexBytes, written, atomic); + // The element's byte offset in the shader data (RecompileResult::memoryOffsetDword). + const auto& push = shader.program->pushConstants; + if (!push.empty()) { + const auto position = shader.program->memoryOffsetDword * 4u + element; + Require(position < push.size(), "guest buffer offset lies outside the shader's push constants"); + allocations[index].pushByte = static_cast(shader.pushConstantOffset + position); + } + item.allocations.push_back(index); } } else if (addressRole) { item.allocations.push_back(allocations.size()); @@ -966,6 +974,11 @@ void ShaderResources::buildComplete() { } context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); } + for (const auto& allocation : allocations) { + if (allocation.adjustment == 0) continue; + Require(allocation.pushByte >= 0, "a guest buffer off the storage buffer offset alignment in a shader whose shader data is a buffer is not implemented"); + pushPatches.emplace_back(static_cast(allocation.pushByte), allocation.adjustment); + } timing.descriptorsMs += phase(BuildPhase::Descriptors); noteReusable(); completed = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index d5c87f22e..c1b432a7f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -36,9 +36,11 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { memory.AddWritable(address, sizeof(guest)); memory.AddWritable(address + 16, 16); memory.Upload(true); - const auto first = memory.Descriptor(address, sizeof(guest)); - const auto alias = memory.Descriptor(address + 16, 16); - Require(first.buffer == alias.buffer && alias.offset == 16, "aliased guest buffers have different owners"); + std::uint32_t adjustment = 0; + const auto first = memory.Descriptor(address, sizeof(guest), adjustment); + Require(adjustment == 0, "a view at its owner's start is bound off it"); + const auto alias = memory.Descriptor(address + 16, 16, adjustment); + Require(first.buffer == alias.buffer && alias.offset + adjustment == 16 && alias.range == 16 + adjustment, "aliased guest buffers have different owners"); const auto ranges = memory.AddressRanges(); Require(ranges.size() == 1 && ranges[0].begin == address && ranges[0].end == address + sizeof(guest), "incorrect BDA range bounds"); Require(ranges[0].deviceAddress != 0 && ranges[0].permissions == ShaderRecompiler::BdaAbi::Read, "incorrect BDA address or permissions"); @@ -118,8 +120,10 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { GuestBufferMemory unaligned(aligned); unaligned.AddWritable(address, sizeof(guest)); unaligned.Upload(true); - reject([&] { unaligned.Descriptor(address + 4, 4); }, "offset alignment"); - reject([&] { unaligned.Descriptor(address + sizeof(guest), 4); }, "exceeds its GPU owner"); + std::uint32_t adjustment = 0; + const auto view = unaligned.Descriptor(address + 4, 4, adjustment); + Require(adjustment == 4 && view.offset == 0 && view.range == 8, "a view off the offset alignment binds from below it"); + reject([&] { unaligned.Descriptor(address + sizeof(guest), 4, adjustment); }, "exceeds its GPU owner"); } { // The cached address space: a second build in an unchanged registry takes the first one's @@ -139,8 +143,9 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { leased.AcquireRegistered(); Require(leased.HoldsLease(), "address-based build holds no lease"); leased.Upload(true); - const auto view = leased.Descriptor(blockAddress, 64); - Require(view.range == 64, "leased block has no descriptor"); + std::uint32_t adjustment = 0; + const auto view = leased.Descriptor(blockAddress, 64, adjustment); + Require(view.range == 64 + adjustment, "leased block has no descriptor"); const auto ranges = leased.AddressRanges(); Require(std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin == blockAddress && range.end == blockAddress + 64; }), "leased block is missing from the BDA table"); leased.WriteBack(); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index e237d71c2..584303d94 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -314,6 +314,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra #endif result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u; + result.memoryOffsetDword = bindings.layout.memoryOffsetDword; result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr; result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr; result.vertexOffsetShared = program.Info().vertexOffsetShared; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 68faec109..4d13c5c86 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -327,6 +327,10 @@ struct RecompileResult { SharedSpirv spirv; std::vector bindings; std::vector pushConstants; + // The shader data dword of the guest buffers' byte offsets, one byte per GuestBuffers element + // (four per dword), zero as populated: a driver binding an element below its view (the storage + // buffer offset alignment) stores the difference there, the shader adds it to every access. + std::uint32_t memoryOffsetDword = 0; std::uint32_t bdaAbiVersion = 0; std::vector vertexAttributes; std::int32_t vertexOffsetSgpr = -1;