From 9f83fd5f0ca23d1e968340f4f67b3113040158b9 Mon Sep 17 00:00:00 2001 From: Claxten Date: Wed, 7 Oct 2026 19:19:14 +0400 Subject: [PATCH] graphics: apply indirect draw offsets to guest user SGPRs --- .../command_processor/commandProcessor.h | 11 ++- .../command_processor/pm4Handlers.cpp | 12 +++- src/graphics/guest_gpu/graphicsRun.cpp | 29 ++++++-- src/graphics/host_gpu/renderer/renderDraw.cpp | 12 ++-- tests/ShaderRecompilerComputeTests.cpp | 71 +++++++++++++++++++ 5 files changed, 123 insertions(+), 12 deletions(-) diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index eee971ed6..c976bd8f6 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -23,6 +23,12 @@ enum class ContextStateOperation : uint32_t { PushClear = 3, }; +struct IndirectDrawRegisters { + uint32_t vertex_offset; + uint32_t instance_offset; + uint32_t index_offset; +}; + class Pm4Execution { public: [[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; } @@ -79,10 +85,11 @@ public: void DrawIndex(DrawIndexArgs args); void DrawIndexOffset(uint32_t index_offset, uint32_t index_count); void DrawIndexAuto(DrawAutoArgs args); - void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed); + void DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers, + uint32_t draw_initiator, bool indexed); void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count, const volatile uint32_t* count_addr, uint32_t stride_in_bytes, - uint32_t draw_initiator, bool indexed); + IndirectDrawRegisters registers, uint32_t draw_initiator, bool indexed); void WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index, uint32_t event_write_source, void* dst_gpu_addr, uint32_t value, diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index dbaffcceb..f059a0a47 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -1641,8 +1641,12 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) { const auto data_offset = buffer[0]; const auto draw_initiator = buffer[3]; const bool indexed = (cmd_id == 0xc0032500); + // The native indexed packet enables the high-half first-index destination in word 3. + const IndirectDrawRegisters registers { + buffer[1] & 0xffffu, buffer[2] & 0xffffu, + indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP}; - cp.DrawIndirect(data_offset, draw_initiator, indexed); + cp.DrawIndirect(data_offset, registers, draw_initiator, indexed); return 4; } @@ -1660,13 +1664,17 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirectMulti) { const auto stride_in_bytes = buffer[7]; const auto draw_initiator = buffer[8]; const bool indexed = (cmd_id == 0xc0083800); + // Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3. + const IndirectDrawRegisters registers { + buffer[1] & 0xffffu, buffer[2] & 0xffffu, + indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP}; if (count_indirect == 0) { count_addr = nullptr; } cp.DrawIndirectMulti(data_offset, max_count_or_count, count_addr, stride_in_bytes, - draw_initiator, indexed); + registers, draw_initiator, indexed); return 9; } diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 8e8cefb56..3b8315307 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -875,15 +875,32 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou DrawIndex({.index_count = index_count, .index_addr = index_addr}); } -void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) { +static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers, + uint32_t vertex_offset, uint32_t instance_offset, + uint32_t first_index) { + const auto write = [&](uint32_t location, uint32_t value) { + if (location == Pm4::SH_NOP) { + return; + } + EXIT_NOT_IMPLEMENTED(location >= Pm4::SH_NUM || + g_hw_sh_indirect_func[location] == nullptr); + g_hw_sh_indirect_func[location](cp, location, value); + }; + write(registers.vertex_offset, vertex_offset); + write(registers.instance_offset, instance_offset); + write(registers.index_offset, first_index); +} + +void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers, + uint32_t draw_initiator, bool indexed) { const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs); - DrawIndirectMulti(data_offset, 1, nullptr, args_size, draw_initiator, indexed); + DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed); } void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count, const volatile uint32_t* count_addr, - uint32_t stride_in_bytes, uint32_t draw_initiator, - bool indexed) { + uint32_t stride_in_bytes, IndirectDrawRegisters registers, + uint32_t draw_initiator, bool indexed) { EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u)); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); @@ -918,6 +935,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun if (!indexed) { auto* args = reinterpret_cast(args_addr); + PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location, + args->start_instance_location, 0); m_num_instances = args->instance_count; DrawIndexAuto({.vertex_count = args->vertex_count_per_instance, .instance_count = args->instance_count, @@ -928,6 +947,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun } auto* args = reinterpret_cast(args_addr); + PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location, + args->start_instance_location, args->start_index_location); auto* index_addr = reinterpret_cast( m_index_base_addr + static_cast(args->start_index_location) * index_size); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 17a724ace..9b59b21ba 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -1284,8 +1284,10 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]); DrawEmitInfo emit {}; - emit.vertex_offset = vertex_offset + args.base_vertex; - emit.first_instance = instance_offset; + // Native mesh prologs consume the offsets written to the packet's user SGPRs. + const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh; + emit.vertex_offset = mesh_indirect ? 0 : vertex_offset + args.base_vertex; + emit.first_instance = mesh_indirect ? 0 : instance_offset; ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, primitive_restart); @@ -1373,8 +1375,10 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D indirect ? std::pair {0, args.first_instance} : ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]); DrawEmitInfo emit {}; - emit.first_vertex = static_cast(vertex_offset + static_cast(args.first_vertex)); - emit.first_instance = instance_offset; + const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh; + emit.first_vertex = mesh_indirect ? 0 : + static_cast(vertex_offset + static_cast(args.first_vertex)); + emit.first_instance = mesh_indirect ? 0 : instance_offset; DrawIndexBufferSource index_source {}; ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 1dd501103..a67039fbe 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -40613,6 +40613,77 @@ void CheckPm4DrawIndirectMultiPacket(RenderContext &renderer) { } } renderer.GetCommandScheduler().Finish(); + processor.BufferInit(); + auto &shaders = processor.GetShCtx(); + const auto &gs = shaders.GetVs().gs_user_sgpr.value; + const auto &hs = shaders.GetVs().hs_user_sgpr.value; + const auto check_offsets = [&](auto &commands, const auto &sgprs, + std::initializer_list expected_offsets, + const char *label) { + Require("Pm4DrawIndirectMulti", label, + execute(commands.data(), commands.size()) && + std::equal(expected_offsets.begin(), expected_offsets.end(), sgprs + 9), + "indirect draw did not write the expected user SGPR offsets"); + }; + // These locations match the captured NGG prolog: user SGPR9/10 become s17/s18. + shaders.SetGsUserSgpr(9, 17821, HW::UserSgprType::Unknown); + shaders.SetGsUserSgpr(10, 0x898f3f24u, HW::UserSgprType::Unknown); + arguments = {0, 1}; + std::array indexed_packet{ + KYTY_PM4(5, Pm4::IT_DRAW_INDEX_INDIRECT, 0), 0, + Pm4::SPI_SHADER_USER_DATA_GS_0 + 9, + Pm4::SPI_SHADER_USER_DATA_GS_0 + 10, 0}; + check_offsets(indexed_packet, gs, {0, 0}, "clear stale offsets"); + arguments = {0, 1, 5, static_cast(-17), 3}; + indexed_packet[2] |= (Pm4::SPI_SHADER_USER_DATA_GS_0 + 11) << 16u; + // Native sceAgcDcbDrawIndexIndirect (0x5a00) emits first-index enable in word3 bit28. + indexed_packet[3] |= 1u << 28u; + check_offsets(indexed_packet, gs, {static_cast(-17), 3, 5}, "indexed offsets"); + indexed_packet[3] ^= (1u << 28u) | (1u << 27u); + arguments[2] = 6; + check_offsets(indexed_packet, gs, {static_cast(-17), 3, 5}, + "index destination disabled"); + + arguments = {0, 1, 31, 5}; + std::array auto_packet{ + KYTY_PM4(5, Pm4::IT_DRAW_INDIRECT, 0), 0, + Pm4::SPI_SHADER_USER_DATA_HS_0 + 9, + Pm4::SPI_SHADER_USER_DATA_HS_0 + 10, 2}; + check_offsets(auto_packet, hs, {31, 5}, "non-indexed offsets"); + Require("Pm4DrawIndirectMulti", "shader stage", gs[9] == static_cast(-17), + "non-indexed draw patched the wrong shader stage"); + + arguments = {0, 1, 6, 41, 7, 0, 1, 8, 43, 9, 0, 1, 10, 45, 11}; + uint32_t count = 2; + const auto multi_count_address = reinterpret_cast(&count); + // Native sceAgcDcbDrawIndexIndirectMulti (0x5800) puts this enable in word4 bit28. + std::array multi_packet{ + KYTY_PM4(10, Pm4::IT_DRAW_INDEX_INDIRECT_MULTI, 0), 0, + indexed_packet[2], Pm4::SPI_SHADER_USER_DATA_GS_0 + 10, + (1u << 30u) | (1u << 28u) | Pm4::SH_NOP, 3, + static_cast(multi_count_address), + static_cast(multi_count_address >> 32u), 20, 0}; + check_offsets(multi_packet, gs, {43, 9, 8}, "counted multi-draw offsets"); + count = 0; + check_offsets(multi_packet, gs, {43, 9, 8}, "empty multi-draw"); + count = 3; + multi_packet[5] = 1; + multi_packet[3] |= 1u << 28u; + multi_packet[4] &= ~(1u << 28u); + check_offsets(multi_packet, gs, {41, 7, 8}, "multi-draw enable word and count clamp"); + + arguments = {0, 1, 51, 13, 0, 1, 53, 15}; + std::array auto_multi_packet{ + KYTY_PM4(10, Pm4::IT_DRAW_INDIRECT_MULTI, 0), 0, + auto_packet[2], auto_packet[3], Pm4::SH_NOP, 2, 0, 0, 16, 2}; + check_offsets(auto_multi_packet, hs, {53, 15}, "non-indexed multi-draw offsets"); + arguments = {0, 1, 99, 101}; + auto_packet[2] = Pm4::SH_NOP; + check_offsets(auto_packet, hs, {53, 101}, "independent destinations"); + auto_packet[3] = Pm4::SH_NOP; + arguments[3] = 103; + check_offsets(auto_packet, hs, {53, 101}, "disabled destinations"); + processor.BufferFlush(); std::printf("[host] %-32s ok\n", "Pm4DrawIndirectMulti"); }