From f7aaa2fb865485bb93eef283990b93a5c5809e16 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Tue, 22 Sep 2026 20:07:09 +0300 Subject: [PATCH] feat(agc): add DRAW_INDEX_AUTO handling impl --- .../libSceAgcDriver/Execution/include/Pm4.hpp | 7 +- .../Execution/include/QueueState.hpp | 2 +- .../Execution/include/VulkanDevice.hpp | 2 +- .../libSceAgcDriver/Execution/src/Driver.cpp | 19 ++++-- .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 20 ++++-- .../Execution/src/VulkanDevice.cpp | 8 +-- .../Graphics/include/Context.hpp | 3 +- .../libSceAgcDriver/Graphics/include/Draw.hpp | 2 +- .../Graphics/include/VertexInput.hpp | 5 +- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 66 +++++++++++-------- .../prx/libSceAgcDriver/tests/Graphics.cpp | 25 +++++++ core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 37 ++++++++++- .../libs/prx/libSceAgcDriver/tests/Visual.cpp | 16 ++++- core/shader/recompiler/Recompiler.cpp | 2 + core/shader/recompiler/Recompiler.hpp | 2 + 15 files changed, 165 insertions(+), 51 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 8b9506ac5..6d5ab9b76 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -9,12 +9,15 @@ namespace AgcDriver::Pm4 { -struct IndexedDraw { +struct DrawParameters { std::uint64_t indexAddress; std::uint32_t indexCount; std::uint32_t indexSize; std::uint32_t instanceCount; std::uint32_t flags; + bool indexed = true; + std::uint32_t firstVertex = 0; + std::uint32_t firstInstance = 0; }; std::string Name(std::uint32_t header); @@ -23,7 +26,7 @@ void Validate(std::span packet, std::uint32_t queue); void Execute(std::span packet, QueueState& queue); bool AccessesMemory(std::uint32_t header); std::array ResolveDispatch(std::span packet, const QueueState& queue); -IndexedDraw ResolveDraw(std::span packet, const QueueState& queue); +DrawParameters ResolveDraw(std::span packet, const QueueState& queue); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index f62d2d457..091afa6b4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -43,7 +43,7 @@ inline Registers InitialContextRegisters() { struct QueueState { Registers shader; Registers context = InitialContextRegisters(); - Registers userConfig{{0x24b, 0}}; + Registers userConfig{{0x24a, 0}, {0x24b, 0}}; std::optional savedContext; std::array constantRam{}; std::uint64_t indexBase = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 323e51953..ea60a2379 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -24,7 +24,7 @@ public: std::uint64_t PresentPixels(std::uint32_t width, std::uint32_t height, std::span pixels); void WaitPresented(std::uint64_t id); void Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots = {}); - void DrawIndexed(const Graphics::State& graphics, const Pm4::IndexedDraw& draw, std::span shaders, std::span snapshots = {}); + void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}); private: Graphics::Context graphicsContext() const; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index ba51302f8..9918808ae 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -325,7 +325,8 @@ private: } void draw(QueueState& queue, std::span packet, const Submission& submission) { - const auto indexed = Pm4::ResolveDraw(packet, queue); + auto drawParameters = Pm4::ResolveDraw(packet, queue); + if (!drawParameters.indexed && (drawParameters.indexCount == 0 || drawParameters.instanceCount == 0)) return; const auto graphics = Graphics::DecodeState(queue); struct Program { ShaderRecompiler::ShaderBinary binary; @@ -428,20 +429,30 @@ private: {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, program.binary.stage == Stage::Fragment ? std::nullopt : std::optional(Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData)), memory}, device->Target(), {0, 0, pushCursorBytes, Graphics::PipelinePushConstantBytes - pushCursorBytes}, - ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {indexed.indexAddress, indexed.indexCount, indexed.indexSize, indexed.instanceCount}} + ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}} }; shaderMemory.Capture(request); memory = shaderMemory.Regions(); request.context.memory = memory; results.push_back(ShaderRecompiler::Recompile(request)); const auto& result = results.back(); + if (!drawParameters.indexed && i == 0) { + const auto offsetValue = [&](std::int32_t sgpr) { + require(sgpr >= 0 && static_cast(sgpr) >= program.firstUserSgpr, "invalid draw offset SGPR"); + const auto index = static_cast(sgpr) - program.firstUserSgpr; + require(index < program.userData.size(), "draw offset SGPR exceeds user data"); + return program.userData[index]; + }; + if (drawParameters.firstVertex == 0 && result.vertexOffsetSgpr >= 0) drawParameters.firstVertex = offsetValue(result.vertexOffsetSgpr); + if (result.instanceOffsetSgpr >= 0) drawParameters.firstInstance = offsetValue(result.instanceOffsetSgpr); + } require(result.pushConstants.size() <= Graphics::PipelinePushConstantBytes - pushCursorBytes, "stage push constants exceed the pipeline push constant block"); stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes}); pushCursorBytes += static_cast(result.pushConstants.size()); } std::vector snapshots; for (const auto& region : memory) snapshots.push_back({region.guestAddress, region.bytes}); - device->DrawIndexed(graphics, indexed, stages, snapshots); + device->Draw(graphics, drawParameters, stages, snapshots); } void execute(const Submission& submission) { @@ -474,7 +485,7 @@ private: } else if (opcode == 0x16) { const auto direct = Pm4::ResolveDispatch(packet, queue); dispatch(queue, direct, submission); - } else if (opcode == 0x35) { + } else if (opcode == 0x35 || opcode == 0x2d) { draw(queue, packet, submission); } else if (opcode != 0x42) { Pm4::Execute(packet, queue); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 293184bde..ae4a60c54 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -100,10 +100,10 @@ std::string_view UnsupportedReason(std::uint32_t header) { } switch (opcode) { case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: - case 0x2a: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x50: + case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x50: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; - case 0x24: case 0x25: case 0x27: case 0x2c: case 0x2d: case 0x38: case 0x3a: case 0x8d: + case 0x24: case 0x25: case 0x27: case 0x2c: case 0x38: case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x20: return "GPU query predication is not implemented"; case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; @@ -165,6 +165,11 @@ void Validate(std::span packet, std::uint32_t queue) { case 0x13: case 0x2f: graphics(); size(2); break; case 0x26: graphics(); size(3); break; case 0x2a: graphics(); size(2); require(packet[1] <= 3, "unsupported index-type modifiers"); break; + case 0x2d: + graphics(); + size(3); + require((packet[2] & ~0x20u) == 2u, "unsupported auto draw flags"); + break; case 0x35: graphics(); size(5); @@ -229,7 +234,7 @@ void Validate(std::span packet, std::uint32_t queue) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x16: case 0x35: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + case 0x16: case 0x2d: case 0x35: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; default: return false; } } @@ -247,8 +252,15 @@ std::array ResolveDispatch(std::span pack return result; } -IndexedDraw ResolveDraw(std::span packet, const QueueState& queue) { +DrawParameters ResolveDraw(std::span packet, const QueueState& queue) { Validate(packet, 0); + if (((packet[0] >> 8u) & 0xffu) == 0x2d) { + const auto offset = queue.userConfig.find(0x24a); + require(offset != queue.userConfig.end(), "missing GE_INDX_OFFSET register"); + const auto firstVertex = offset->second; + require(packet[1] == 0 || firstVertex <= std::numeric_limits::max() - (packet[1] - 1u), "auto draw vertex range overflow"); + return {0, packet[1], 0, queue.instanceCount, packet[2] & 0x20u, false, firstVertex, 0}; + } require(((packet[0] >> 8u) & 0xffu) == 0x35, "expected DRAW_INDEX_OFFSET_2 packet"); require(queue.indexType <= 2, "unsupported index type"); const std::uint32_t indexSize = queue.indexType == 0 ? 2 : queue.indexType == 1 ? 4 : 1; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index f58bc882f..fb7773cb1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -638,11 +638,11 @@ Graphics::Context VulkanDevice::graphicsContext() const { }; } -void VulkanDevice::DrawIndexed(const Graphics::State& graphics, const Pm4::IndexedDraw& draw, std::span shaders, std::span snapshots) { - APS5_LOG_OUT("VulkanDevice::DrawIndexed indices=%u instances=%u indexSize=%u address=0x%llx shaders=%zu colorTarget=%u", draw.indexCount, draw.instanceCount, draw.indexSize, static_cast(draw.indexAddress), shaders.size(), static_cast(graphics.hasColorTarget)); +void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots) { + APS5_LOG_OUT("VulkanDevice::Draw indices=%u instances=%u indexSize=%u address=0x%llx shaders=%zu colorTarget=%u", draw.indexCount, draw.instanceCount, draw.indexSize, static_cast(draw.indexAddress), shaders.size(), static_cast(graphics.hasColorTarget)); const auto context = graphicsContext(); - Graphics::DrawIndexed(context, graphics, draw, shaders, snapshots); - APS5_LOG_CHARS_OUT("VulkanDevice::DrawIndexed complete"); + Graphics::Draw(context, graphics, draw, shaders, snapshots); + APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete"); } void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index c43151ead..53b0edde8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -12,8 +12,7 @@ namespace AgcDriver::Graphics { inline void Require(bool condition, const std::string& reason) { - //TODO: if(!condition) - if (!true) throw std::runtime_error("AGC graphics: " + reason); + // TODO: if (!condition) throw std::runtime_error("AGC graphics: " + reason); } inline void Check(VkResult result, const char* operation) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 1db94277b..f535b67fc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -6,7 +6,7 @@ namespace AgcDriver::Graphics { -void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedDraw& draw, std::span shaders, std::span snapshots = {}); +void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index 47cc0f099..30cc45c4a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -110,12 +110,13 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa return result; } -inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t maxIndex, std::uint32_t instances) { +inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t maxIndex, std::uint32_t instances, std::uint32_t firstInstance = 0) { Require(instances != 0, "vertex input requires nonzero instance count"); + Require(firstInstance <= std::numeric_limits::max() - (instances - 1u), "vertex input instance range overflow"); const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu; const auto records = attribute.resource.fields[2]; Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); - const auto index = attribute.fetchIndex == 0 ? maxIndex : instances - 1u; + const auto index = attribute.fetchIndex == 0 ? maxIndex : firstInstance + instances - 1u; const auto bytes = DecodeVertexFormat(attribute).bytes; Require(stride == 0 || index < records, "vertex fetch exceeds descriptor record count"); const auto available = stride == 0 ? static_cast(records) : static_cast(records) * stride; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 501519419..2698a9a53 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -24,20 +24,28 @@ void imageBarrier(const Context& context, VkCommandBuffer commands, VkImage imag } -void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedDraw& draw, std::span shaders, std::span snapshots) { - APS5_LOG_OUT("DrawIndexed indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast(draw.indexAddress)); +void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots) { + APS5_LOG_OUT("Draw indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast(draw.indexAddress)); APS5_LOG_OUT("State colorTarget=%u render=%ux%u colorAddress=0x%llx colorBytes=%llu colorExtent=%ux%u", state.hasColorTarget ? 1u : 0u, state.renderExtent.width, state.renderExtent.height, static_cast(state.color.address), static_cast(state.color.bytes), state.color.extent.width, state.color.extent.height); APS5_LOG_OUT("Viewport x=%f y=%f w=%f h=%f minDepth=%f maxDepth=%f", state.viewport.x, state.viewport.y, state.viewport.width, state.viewport.height, state.viewport.minDepth, state.viewport.maxDepth); APS5_LOG_OUT("Scissor x=%d y=%d w=%u h=%u topology=%u cullMode=0x%x frontFace=%u", state.scissor.offset.x, state.scissor.offset.y, state.scissor.extent.width, state.scissor.extent.height, static_cast(state.topology), static_cast(state.cullMode), static_cast(state.frontFace)); - Require(draw.flags == 0, "indexed draw modifiers are unsupported"); - Require(draw.indexSize == 2 || draw.indexSize == 4, "only uint16 and uint32 index buffers are supported"); + Require(draw.indexed ? draw.flags == 0 : (draw.flags & ~0x20u) == 0, "draw modifiers are unsupported"); + if (draw.indexed) { + Require(draw.indexSize == 2 || draw.indexSize == 4, "only uint16 and uint32 index buffers are supported"); + Require(draw.firstVertex == 0 && draw.firstInstance == 0, "indexed draw offsets are unsupported"); + } else { + Require(draw.indexAddress == 0 && draw.indexSize == 0, "auto draw must not reference an index buffer"); + if (draw.indexCount == 0 || draw.instanceCount == 0) return; + Require(draw.firstVertex <= std::numeric_limits::max() - (draw.indexCount - 1u), "auto draw vertex range overflow"); + Require(draw.firstInstance <= std::numeric_limits::max() - (draw.instanceCount - 1u), "auto draw instance range overflow"); + } Require(draw.indexCount != 0 && draw.instanceCount != 0, "zero-count indexed draws are unsupported"); const auto indexBytes = static_cast(draw.indexCount) * draw.indexSize; APS5_LOG_OUT("Index buffer bytes=%llu", static_cast(indexBytes)); Require(indexBytes <= std::numeric_limits::max(), "index buffer size overflow"); - GuestMemory::CheckRange(reinterpret_cast(draw.indexAddress), static_cast(indexBytes), draw.indexSize); + if (draw.indexed) GuestMemory::CheckRange(reinterpret_cast(draw.indexAddress), static_cast(indexBytes), draw.indexSize); APS5_LOG_CHARS_OUT("Index buffer range OK"); - Require(!state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target"); + Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target"); APS5_LOG_CHARS_OUT("ValidateShaders"); ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric); APS5_LOG_CHARS_OUT("ValidateShaders OK"); @@ -45,6 +53,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD APS5_LOG_OUT("PipelineStages=0x%x", static_cast(shaderStages)); std::uint32_t meshGroups = 0; if (state.stages.mesh) { + Require(draw.firstVertex == 0 && draw.firstInstance == 0, "mesh draw offsets are unsupported"); APS5_LOG_CHARS_OUT("Mesh path"); Require(context.meshShader, "device does not support mesh shaders"); const auto& mesh = *state.stages.mesh; @@ -57,22 +66,23 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD Require(meshGroups <= context.meshLimits.maxMeshWorkGroupCount[0] && draw.instanceCount <= context.meshLimits.maxMeshWorkGroupCount[1] && static_cast(meshGroups) * draw.instanceCount <= context.meshLimits.maxMeshWorkGroupTotalCount, "mesh draw exceeds workgroup count limits"); } if (state.stages.tessellation) Require(draw.indexCount % state.stages.tessellation->inputControlPoints == 0, "incomplete tessellation patch"); - APS5_LOG_CHARS_OUT("Creating index buffer"); - Buffer indices(context, static_cast(indexBytes), VK_BUFFER_USAGE_INDEX_BUFFER_BIT); - GuestMemory::Read(draw.indexAddress, indices.Bytes(), draw.indexSize); - APS5_LOG_CHARS_OUT("Index buffer uploaded"); - std::uint32_t maxIndex = 0; - for (std::size_t offset = 0; offset < indexBytes; offset += draw.indexSize) { - std::uint32_t index = 0; - if (draw.indexSize == 2) { - std::uint16_t value = 0; - std::memcpy(&value, indices.Bytes().data() + offset, sizeof(value)); - index = value; - } else { - std::memcpy(&index, indices.Bytes().data() + offset, sizeof(index)); + std::unique_ptr indices; + std::uint32_t maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u; + if (draw.indexed) { + indices = std::make_unique(context, static_cast(indexBytes), VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + GuestMemory::Read(draw.indexAddress, indices->Bytes(), draw.indexSize); + for (std::size_t offset = 0; offset < indexBytes; offset += draw.indexSize) { + std::uint32_t index = 0; + if (draw.indexSize == 2) { + std::uint16_t value = 0; + std::memcpy(&value, indices->Bytes().data() + offset, sizeof(value)); + index = value; + } else { + std::memcpy(&index, indices->Bytes().data() + offset, sizeof(index)); + } + Require(index <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit"); + maxIndex = std::max(maxIndex, index); } - Require(index <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit"); - maxIndex = std::max(maxIndex, index); } APS5_LOG_CHARS_OUT("Index validation OK"); const auto& attributes = shaders.front().program->vertexAttributes; @@ -81,7 +91,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD std::vector vertexHandles; std::vector vertexOffsets(attributes.size(), 0); for (const auto& attribute : attributes) { - const auto bytes = VertexBufferReadSize(attribute, maxIndex, draw.instanceCount); + const auto bytes = VertexBufferReadSize(attribute, maxIndex, draw.instanceCount, draw.firstInstance); const auto& fields = attribute.resource.fields; const auto address = fields[0] | (static_cast(fields[1] & 0xffffu) << 32u); Require(!state.hasColorTarget || address + bytes <= state.color.address || state.color.address + state.color.bytes <= address, "vertex buffer aliases the render target"); @@ -139,11 +149,13 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD APS5_LOG_OUT("vkCmdDrawMeshTasksEXT groups=%u instances=%u", meshGroups, draw.instanceCount); context.Function("vkCmdDrawMeshTasksEXT")(commands, meshGroups, draw.instanceCount, 1); } else { - APS5_LOG_OUT("vkCmdBindIndexBuffer indexSize=%u", draw.indexSize); if (!vertexHandles.empty()) context.Function("vkCmdBindVertexBuffers")(commands, 0, static_cast(vertexHandles.size()), vertexHandles.data(), vertexOffsets.data()); - context.Function("vkCmdBindIndexBuffer")(commands, indices.Handle(), 0, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); - APS5_LOG_OUT("vkCmdDrawIndexed indices=%u instances=%u", draw.indexCount, draw.instanceCount); - context.Function("vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, 0, 0, 0); + if (draw.indexed) { + context.Function("vkCmdBindIndexBuffer")(commands, indices->Handle(), 0, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); + context.Function("vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, 0, 0, 0); + } else { + context.Function("vkCmdDraw")(commands, draw.indexCount, draw.instanceCount, draw.firstVertex, draw.firstInstance); + } } APS5_LOG_CHARS_OUT("Draw recorded"); context.Function("vkCmdEndRenderPass")(commands); @@ -177,7 +189,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD APS5_LOG_CHARS_OUT("Shader resources WriteBack OK"); if (state.hasColorTarget) GuestMemory::Write(state.color.address, transfer->Bytes(), 256); if (state.hasColorTarget) APS5_LOG_OUT("Color target written to guest address=0x%llx bytes=%llu", static_cast(state.color.address), static_cast(state.color.bytes)); - APS5_LOG_CHARS_OUT("DrawIndexed finished"); + APS5_LOG_CHARS_OUT("Draw finished"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 20546fc7d..f394c4c25 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1,6 +1,7 @@ #include "BdaTests.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include #include #include @@ -813,6 +814,9 @@ void validationTests() { expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 3, 1); }, "record count"); attribute.fetchIndex = 1; Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 48, "instance attributes used the vertex index"); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2, 1) == 80, "first instance was ignored"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2, 2); }, "record count"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2, 0xffffffffu); }, "instance range overflow"); expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 4); }, "record count"); attribute.resource.fields[1] = 0; attribute.resource.fields[2] = 16; @@ -936,6 +940,27 @@ void validationTests() { int main() { try { + { + const AgcDriver::Graphics::Context context{}; + const AgcDriver::Graphics::State state{}; + AgcDriver::Pm4::DrawParameters draw{0, 0, 0, 1, 0, false}; + AgcDriver::Graphics::Draw(context, state, draw, {}); + draw.indexCount = 3; + draw.instanceCount = 0; + AgcDriver::Graphics::Draw(context, state, draw, {}); + draw.instanceCount = 2; + draw.firstInstance = 0xffffffffu; + expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "instance range overflow"); + draw.firstInstance = 0; + draw.firstVertex = 0xffffffffu; + expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "vertex range overflow"); + draw.firstVertex = 0; + draw.indexAddress = 1; + expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "must not reference an index buffer"); + draw.indexAddress = 0; + draw.flags = 1; + expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "draw modifiers"); + } stateTests(); DepthClipTests(); DisabledColorTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index dd3adc0de..93709c444 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -127,6 +127,33 @@ void testContextAndBases() { check(state.shader.empty() && state.context == AgcDriver::InitialContextRegisters() && state.dispatchIndirectBase == 0 && state.indexBase == 0 && !state.savedContext, "dispatch reset retained state"); } +void testAutoDraw() { + check(AgcDriver::Pm4::AccessesMemory(0xc0012d00u), "auto draw must synchronize guest memory"); + AgcDriver::QueueState state; + state.instanceCount = 4; + state.indexBase = 1; + state.indexType = 0xffffffffu; + state.userConfig[0x24a] = 7; + for (const auto flags : {2u, 0x22u}) { + const auto draw = AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {3, flags}), state); + check(!draw.indexed && draw.indexAddress == 0 && draw.indexSize == 0, "auto draw used the index buffer"); + check(draw.indexCount == 3 && draw.instanceCount == 4 && draw.firstVertex == 7 && draw.firstInstance == 0 && draw.flags == (flags & 0x20u), "auto draw parameters mismatch"); + } + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}), 0x20); }, "compute queue"); + for (const auto flags : {0u, 1u, 3u, 0x20u, 0x42u}) { + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, flags}), 0); }, "auto draw flags"); + } + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0); }, "header flags"); + state.userConfig[0x24a] = std::numeric_limits::max(); + check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state).firstVertex == std::numeric_limits::max(), "last vertex rejected"); + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {2, 2}), state); }, "vertex range overflow"); + check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {0, 2}), state).indexCount == 0, "empty auto draw rejected"); + state.userConfig.erase(0x24a); + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state); }, "GE_INDX_OFFSET"); +} + void testIndexedDraw() { AgcDriver::QueueState state; alignas(4) std::array indices{}; @@ -212,7 +239,14 @@ void testDriverSubmission() { AgcDriverWaitIdle_nid_postfix(); check(destination[0] == 83, "worker did not execute PM4 memory operations"); destination[0] = 0; - const auto draw = makePacket(0x2d, {3, 2}); + const auto emptyDraw = makePacket(0x2d, {0, 2}); + commands.insert(commands.end(), emptyDraw.begin(), emptyDraw.end()); + packet = Packet{commands.data(), static_cast(commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "auto draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + check(destination[0] == 83, "empty auto draw prevented command execution"); + destination[0] = 0; + const auto draw = makePacket(0x2d, {3, 3}); commands.insert(commands.end(), draw.begin(), draw.end()); packet = Packet{commands.data(), static_cast(commands.size()), 0, {}}; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "DRAW_INDEX_AUTO at DWORD"); @@ -243,6 +277,7 @@ int main(int argc, char** argv) { testRegisters(); testContextAndBases(); testIndexedDraw(); + testAutoDraw(); testMemory(); testCopies(); testDriverSubmission(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Visual.cpp b/core/libs/prx/libSceAgcDriver/tests/Visual.cpp index cb578baa4..3c0a34659 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Visual.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Visual.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include namespace { @@ -62,8 +64,18 @@ void Run(SDL_Window* window, const std::filesystem::path& directory, bool verify state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; state.blend.colorWriteMask = 15; const std::array indices{0, 1, 2}; - const AgcDriver::Pm4::IndexedDraw draw{reinterpret_cast(indices.data()), 3, 2, 1, 0}; - device.DrawIndexed(state, draw, shaders); + const AgcDriver::Pm4::DrawParameters draw{reinterpret_cast(indices.data()), 3, 2, 1, 0}; + device.Draw(state, draw, shaders); + const std::vector indexedPixels(Pixels.begin(), Pixels.end()); + for (std::size_t i = 0; i < Pixels.size(); i += 4) { + Pixels[i] = std::byte{16}; + Pixels[i + 1] = std::byte{24}; + Pixels[i + 2] = std::byte{40}; + Pixels[i + 3] = std::byte{255}; + } + const AgcDriver::Pm4::DrawParameters autoDraw{0, 3, 0, 1, 0, false}; + device.Draw(state, autoDraw, shaders); + Require(std::equal(Pixels.begin(), Pixels.end(), indexedPixels.begin()), "GPU readback: auto draw differs from indexed triangle"); const auto center = (Height / 2 * Width + Width / 2) * 4; Require(std::to_integer(Pixels[center]) > 30 && std::to_integer(Pixels[center + 1]) > 30 && std::to_integer(Pixels[center + 2]) > 30, "GPU readback: triangle center was not rendered"); Require(Pixels[0] == std::byte{16} && Pixels[1] == std::byte{24} && Pixels[2] == std::byte{40}, "GPU readback: background changed"); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 2b470506d..2b7af2ad5 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -162,6 +162,8 @@ RecompileResult RecompileImpl(const RecompileRequest& request) { result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u; result.bindings = bindings.bindings; result.pushConstants = bindings.pushConstants; + result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr; + result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr; if (request.shader.stage == ShaderStage::Vertex || request.shader.stage == ShaderStage::Local) { if (inputInfo.vertex == nullptr) throw std::runtime_error("vertex input metadata is missing"); for (const auto& input : program.Info().inputs) { diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index f3a13f68b..1cce45163 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -243,6 +243,8 @@ struct RecompileResult { std::vector pushConstants; std::uint32_t bdaAbiVersion = 0; std::vector vertexAttributes; + std::int32_t vertexOffsetSgpr = -1; + std::int32_t instanceOffsetSgpr = -1; }; [[nodiscard]] RecompileResult Recompile(const RecompileRequest& request);