From 2f9fcc63c33405f80e52cb20b792c5aef1db0ef8 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 4 Aug 2026 19:12:11 +0200 Subject: [PATCH] fix (agc): implement DCB context state operations Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com> --- CMakeLists.txt | 2 + .../command_processor/commandProcessor.h | 10 ++ .../command_processor/pm4Dispatch.cpp | 1 + .../guest_gpu/command_processor/pm4Dispatch.h | 1 + .../command_processor/pm4Handlers.cpp | 13 ++ src/graphics/guest_gpu/graphicsRun.cpp | 26 ++++ src/graphics/guest_gpu/pm4.cpp | 1 + src/graphics/guest_gpu/pm4.h | 1 + src/libs/agc.cpp | 70 +++++++-- src/libs/agc.h | 4 +- src/libs/libGraphicsDriver.cpp | 3 +- tests/ShaderRecompilerComputeTests.cpp | 137 ++++++++++++++++++ 12 files changed, 253 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96c71029a..603d3886e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -507,6 +507,8 @@ if(BUILD_TESTING) COMMAND $ --stream-buffer-only) add_test(NAME gpu_command_lane COMMAND $ --gpu-command-lane-only) + add_test(NAME pm4_context_state + COMMAND $ --context-state-only) add_test(NAME gpu_tiler COMMAND $ --gpu-tiler-only) diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index 899b34e55..b1e15c8a2 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -15,6 +15,13 @@ bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t f enum class Pm4ProcessResult { Complete, Blocked }; +enum class ContextStateOperation : uint32_t { + Clear = 0, + Push = 1, + Pop = 2, + PushClear = 3, +}; + class Pm4Execution { public: [[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; } @@ -49,6 +56,7 @@ public: KYTY_CLASS_NO_COPY(CommandProcessor); void Reset(); + void ApplyContextStateOperation(ContextStateOperation operation); void BufferInit(); void BufferFlush(); @@ -149,6 +157,8 @@ private: RenderContext& m_renderer; HW::Context m_ctx; + HW::Context m_saved_ctx; + bool m_context_state_pushed = false; HW::UserConfig m_ucfg; HW::Shader m_sh_ctx; HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown; diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp index 9a7a91bf9..7a180041f 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp @@ -254,6 +254,7 @@ constexpr auto MakeCustomOpcodeDispatchTable() { g_cp_op_custom_func[Pm4::R_WAIT_MEM_64] = CpOpWaitRegMem64; g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip; g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem; + g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState; return g_cp_op_custom_func; } diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h index fb398bbfd..8f9bff81d 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h @@ -121,6 +121,7 @@ uint32_t HwUcSetGeIndexOffset(CommandProcessor&, uint32_t, uint32_t, const uint3 uint32_t HwUcSetGdsOaRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t CpOpNop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); +uint32_t CpOpContextState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpClearState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndexBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index 89a54db7c..33e632c2b 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -2107,6 +2107,19 @@ KYTY_CP_OP_PARSER(CpOpClearState) { return 1; } +KYTY_CP_OP_PARSER(CpOpContextState) { + KYTY_PROFILER_FUNCTION(); + + const auto packet_size_dw = KYTY_PM4_LEN(cmd_id); + EXIT_NOT_IMPLEMENTED(KYTY_PM4_R(cmd_id) != Pm4::R_CONTEXT_STATE); + EXIT_NOT_IMPLEMENTED(packet_size_dw != 3u && packet_size_dw != 5u); + EXIT_NOT_IMPLEMENTED(buffer[0] > static_cast(ContextStateOperation::PushClear)); + + cp.ApplyContextStateOperation(static_cast(buffer[0])); + + return packet_size_dw - 1u; +} + KYTY_CP_OP_PARSER(CpOpDumpConstRam) { KYTY_PROFILER_FUNCTION(); diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index ffc9941fc..001a7794f 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -312,6 +312,8 @@ void CommandProcessor::Reset() { m_sh_ctx.Reset(); m_ucfg.Reset(); m_ctx.Reset(); + m_saved_ctx.Reset(); + m_context_state_pushed = false; m_index_type_and_size = 0; m_index_buffer_size = 0; m_user_data_marker = HW::UserSgprType::Unknown; @@ -321,6 +323,30 @@ void CommandProcessor::Reset() { std::memset(m_const_ram, 0, sizeof(m_const_ram)); } +void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) { + switch (operation) { + case ContextStateOperation::Clear: m_ctx.Reset(); break; + case ContextStateOperation::Push: + EXIT_IF(m_context_state_pushed); + m_saved_ctx = m_ctx; + m_context_state_pushed = true; + break; + case ContextStateOperation::Pop: + EXIT_IF(!m_context_state_pushed); + m_ctx = m_saved_ctx; + m_saved_ctx = {}; + m_context_state_pushed = false; + break; + case ContextStateOperation::PushClear: + EXIT_IF(m_context_state_pushed); + m_saved_ctx = m_ctx; + m_context_state_pushed = true; + m_ctx.Reset(); + break; + default: EXIT("unknown context state operation: %u\n", static_cast(operation)); + } +} + void CommandProcessor::BufferInit() { GetScheduler().Begin(m_ctx, m_ucfg, m_sh_ctx); } diff --git a/src/graphics/guest_gpu/pm4.cpp b/src/graphics/guest_gpu/pm4.cpp index 8167b2623..8b9472b71 100644 --- a/src/graphics/guest_gpu/pm4.cpp +++ b/src/graphics/guest_gpu/pm4.cpp @@ -30,6 +30,7 @@ constexpr auto MakeRegisterNames() { names[R_FLIP] = "R_FLIP"; names[R_RELEASE_MEM] = "R_RELEASE_MEM"; names[R_DMA_DATA] = "R_DMA_DATA"; + names[R_CONTEXT_STATE] = "R_CONTEXT_STATE"; return names; } diff --git a/src/graphics/guest_gpu/pm4.h b/src/graphics/guest_gpu/pm4.h index 2d1fd5b09..c83807155 100644 --- a/src/graphics/guest_gpu/pm4.h +++ b/src/graphics/guest_gpu/pm4.h @@ -87,6 +87,7 @@ constexpr uint32_t R_WAIT_MEM_64 = 0x16; constexpr uint32_t R_FLIP = 0x17; constexpr uint32_t R_RELEASE_MEM = 0x18; constexpr uint32_t R_DMA_DATA = 0x19; +constexpr uint32_t R_CONTEXT_STATE = 0x1A; constexpr uint32_t R_NUM = 0x3F + 1; diff --git a/src/libs/agc.cpp b/src/libs/agc.cpp index b3b50d738..af23e8a0e 100644 --- a/src/libs/agc.cpp +++ b/src/libs/agc.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/stringUtils.h" #include "common/virtualMemory.h" +#include "graphics/guest_gpu/command_processor/commandProcessor.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" @@ -1444,29 +1445,72 @@ int KYTY_SYSV_ABI GraphicsSuspendPoint() { return OK; } -uint32_t* KYTY_SYSV_ABI GraphicsUnknownQj7QZpgr9Uw(CommandBuffer* buf, uint32_t mode, - uint32_t value) { - PRINT_NAME(); - // ResetContextState? - LOGF("\t mode = 0x%08" PRIx32 "\n" - "\t value = 0x%08" PRIx32 "\n", - mode, value); +[[nodiscard]] static constexpr uint32_t context_state_op_size_dw(uint32_t operation) { + switch (static_cast(operation)) { + case ContextStateOperation::Clear: return 5; + case ContextStateOperation::Push: + case ContextStateOperation::Pop: return 27; + case ContextStateOperation::PushClear: return 32; + default: return 0; + } +} - if (buf == nullptr) { +uint32_t* KYTY_SYSV_ABI GraphicsDcbContextStateOp(CommandBuffer* buf, uint32_t operation) { + PRINT_NAME(); + LOGF("\t operation = 0x%08" PRIx32 "\n", operation); + + const auto size_dw = context_state_op_size_dw(operation); + if (buf == nullptr || size_dw == 0) { return nullptr; } buf->DbgDump(); - auto* cmd = buf->AllocateDW(1); + uint32_t* first = nullptr; + const auto append = [&](uint32_t segment_size_dw) { + auto* segment = buf->AllocateDW(segment_size_dw); + if (segment == nullptr) { + return false; + } + std::fill_n(segment, segment_size_dw, 0u); + if (first == nullptr) { + first = segment; + segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_CONTEXT_STATE); + segment[1] = operation; + } else { + segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_ZERO); + } + return true; + }; + const auto append_save_restore = [&] { + // The native helper reserves 22 dwords, then emits 5-, 8-, and 9-dword packets. + return buf->ReserveDW(22) && append(5) && append(8) && append(9); + }; - if (cmd == nullptr) { - return nullptr; + // Preserve native packet sizes, allocation boundaries, Push/Pop ordering, and + // returned first-packet address. The native packets reference process-private state buffers, + // so their net Cx operation is represented by the first HLE packet and type-3 NOPs. + bool complete = false; + switch (static_cast(operation)) { + case ContextStateOperation::Clear: complete = append(5); break; + case ContextStateOperation::Push: + complete = append_save_restore() && append(3) && append(2); + break; + case ContextStateOperation::Pop: + complete = append(3) && append_save_restore() && append(2); + break; + case ContextStateOperation::PushClear: + complete = append_save_restore() && append(3) && append(2) && append(5); + break; + default: break; } - cmd[0] = 0x80000000u; + return complete ? first : nullptr; +} - return cmd; +uint64_t KYTY_SYSV_ABI GraphicsDcbContextStateOpGetSize(uint32_t operation) { + PRINT_NAME(); + return static_cast(context_state_op_size_dw(operation)) * sizeof(uint32_t); } uint64_t KYTY_SYSV_ABI GraphicsGetIsTrinityMode() { diff --git a/src/libs/agc.h b/src/libs/agc.h index da51411f7..78a3d15fd 100644 --- a/src/libs/agc.h +++ b/src/libs/agc.h @@ -67,8 +67,8 @@ int KYTY_SYSV_ABI GraphicsUnknownJumpPatchSetTarget(uint32_t* cmd, const volatil int KYTY_SYSV_ABI GraphicsJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* target, uint32_t size_in_dwords); int KYTY_SYSV_ABI GraphicsSuspendPoint(); -uint32_t* KYTY_SYSV_ABI GraphicsUnknownQj7QZpgr9Uw(CommandBuffer* buf, uint32_t mode, - uint32_t value); +uint32_t* KYTY_SYSV_ABI GraphicsDcbContextStateOp(CommandBuffer* buf, uint32_t operation); +uint64_t KYTY_SYSV_ABI GraphicsDcbContextStateOpGetSize(uint32_t operation); uint64_t KYTY_SYSV_ABI GraphicsGetIsTrinityMode(); uint32_t KYTY_SYSV_ABI GraphicsDriverGetDefaultOwner(); diff --git a/src/libs/libGraphicsDriver.cpp b/src/libs/libGraphicsDriver.cpp index 7fb56e73b..ec9e807a6 100644 --- a/src/libs/libGraphicsDriver.cpp +++ b/src/libs/libGraphicsDriver.cpp @@ -178,7 +178,8 @@ LIB_DEFINE(InitGraphicsDriver_1) { LIB_FUNC("bxca0BK4FNg", Gen5::GraphicsUnknownJumpPatchSetTarget); LIB_FUNC("2BS4EtAaF28", Gen5::GraphicsJumpPatchSetTarget); LIB_FUNC("h9z6+0hEydk", Gen5::GraphicsSuspendPoint); - LIB_FUNC("qj7QZpgr9Uw", Gen5::GraphicsUnknownQj7QZpgr9Uw); + LIB_FUNC("qj7QZpgr9Uw", Gen5::GraphicsDcbContextStateOp); + LIB_FUNC("H6vHS5cidSA", Gen5::GraphicsDcbContextStateOpGetSize); LIB_FUNC("BfBDZGbti7A", Gen5::GraphicsGetIsTrinityMode); LIB_FUNC("dbOlWdppb4o", LibGen5::Graphics5UnknownDb); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index bf34b51b8..1b5dc6a14 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -42,6 +42,7 @@ #include "graphics/shader/rectListShader.h" #include "graphics/shader/shader.h" #include "kernel/memory.h" +#include "libs/agc.h" #include "spirv-tools/libspirv.hpp" #if __has_include("graphics/host_gpu/renderer/renderTargetBarriers.h") @@ -17653,6 +17654,136 @@ void CheckPm4StencilInfoValueLane(RenderContext& renderer) { std::printf("[host] %-32s ok\n", "Pm4StencilInfoValueLane"); } +void CheckPm4ContextStateOperations(RenderContext& renderer) { + GraphicsInitJmpTables(); + CommandProcessor processor(renderer); + struct AgcCommandBufferLayout { + using Callback = KYTY_SYSV_ABI bool (*)(Gen5::CommandBuffer*, uint32_t, void*); + + uint32_t* bottom; + uint32_t* top; + uint32_t* cursor_up; + uint32_t* cursor_down; + Callback callback; + void* user_data; + uint32_t reserved_dw; + }; + static_assert(offsetof(AgcCommandBufferLayout, cursor_up) == 0x10); + static_assert(offsetof(AgcCommandBufferLayout, reserved_dw) == 0x30); + + HW::RenderControl original_control {}; + original_control.depth_clear_enable = true; + original_control.stencil_clear_enable = true; + processor.GetCtx().SetRenderControl(original_control); + processor.GetCtx().SetRenderTargetMask(0x1234abcd); + processor.GetUcfg().SetPrimitiveType(0x35); + g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_PS](processor, Pm4::SPI_SHADER_PGM_LO_PS, + 0x00123456); + const auto shader_address = processor.GetShCtx().GetPs().ps_regs.data_addr; + const auto invoke = [&](ContextStateOperation operation) { + std::array packet {}; + std::array segment_sizes {}; + AgcCommandBufferLayout dcb {packet.data(), packet.data() + packet.size(), packet.data(), + packet.data() + packet.size(), nullptr, nullptr, 0}; + const auto operation_value = static_cast(operation); + size_t segment_count = 0; + switch (operation) { + case ContextStateOperation::Clear: segment_sizes = {5}; segment_count = 1; break; + case ContextStateOperation::Push: + segment_sizes = {5, 8, 9, 3, 2}; + segment_count = 5; + break; + case ContextStateOperation::Pop: + segment_sizes = {3, 5, 8, 9, 2}; + segment_count = 5; + break; + case ContextStateOperation::PushClear: + segment_sizes = {5, 8, 9, 3, 2, 5}; + segment_count = 6; + break; + } + const auto size_dw = + static_cast(Gen5::GraphicsDcbContextStateOpGetSize(operation_value) / 4); + auto* emitted = Gen5::GraphicsDcbContextStateOp( + reinterpret_cast(&dcb), operation_value); + bool packet_matches = true; + size_t segment_offset = 0; + for (size_t i = 0; i < segment_count; i++) { + const auto segment_size = segment_sizes[i]; + const auto selector = (i == 0 ? Pm4::R_CONTEXT_STATE : Pm4::R_ZERO); + packet_matches &= + packet[segment_offset] == KYTY_PM4(segment_size, Pm4::IT_NOP, selector); + for (size_t j = 1; j < segment_size; j++) { + packet_matches &= packet[segment_offset + j] == + (i == 0 && j == 1 ? operation_value : 0u); + } + segment_offset += segment_size; + } + Require("Pm4ContextState", "HLE packet", + emitted == packet.data() && dcb.cursor_up == packet.data() + size_dw && + segment_offset == size_dw && packet_matches, + "context-state HLE packet does not match its real allocation size"); + Pm4Execution execution; + return processor.Process(execution, packet.data(), size_dw); + }; + + Require("Pm4ContextState", "push-clear", + invoke(ContextStateOperation::PushClear) == Pm4ProcessResult::Complete && + !processor.GetCtx().GetRenderControl().depth_clear_enable && + !processor.GetCtx().GetRenderControl().stencil_clear_enable && + processor.GetCtx().GetRenderTargetMask() == HW::Context {}.GetRenderTargetMask() && + processor.GetUcfg().GetPrimType() == 0x35 && + processor.GetShCtx().GetPs().ps_regs.data_addr == shader_address, + "push-clear did not save and clear only Cx state"); + + processor.GetCtx().SetRenderTargetMask(0x55aa55aa); + Require("Pm4ContextState", "pop", + invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete && + processor.GetCtx().GetRenderControl().depth_clear_enable && + processor.GetCtx().GetRenderControl().stencil_clear_enable && + processor.GetCtx().GetRenderTargetMask() == 0x1234abcd, + "pop did not restore the complete saved Cx state"); + + Require("Pm4ContextState", "push", + invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete && + processor.GetCtx().GetRenderTargetMask() == 0x1234abcd, + "push changed live Cx state"); + processor.GetCtx().SetRenderTargetMask(0x01020304); + Require("Pm4ContextState", "push-pop restore", + invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete && + processor.GetCtx().GetRenderTargetMask() == 0x1234abcd, + "ordinary push/pop did not restore Cx state"); + + Require("Pm4ContextState", "clear", + invoke(ContextStateOperation::Clear) == Pm4ProcessResult::Complete && + !processor.GetCtx().GetRenderControl().depth_clear_enable && + processor.GetCtx().GetRenderTargetMask() == HW::Context {}.GetRenderTargetMask() && + processor.GetUcfg().GetPrimType() == 0x35 && + processor.GetShCtx().GetPs().ps_regs.data_addr == shader_address, + "clear reset state outside the Cx register domain"); + + processor.GetCtx().SetRenderTargetMask(0xabcdef01); + Require("Pm4ContextState", "push before processor reset", + invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete, + "push before reset failed"); + processor.Reset(); + processor.GetCtx().SetRenderTargetMask(0x76543210); + Require("Pm4ContextState", "processor reset discards push", + invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete && + invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete && + processor.GetCtx().GetRenderTargetMask() == 0x76543210, + "processor reset retained an invalid saved Cx state"); + + Require("Pm4ContextState", "HLE packet size", + Gen5::GraphicsDcbContextStateOpGetSize(0) == 20 && + Gen5::GraphicsDcbContextStateOpGetSize(1) == 108 && + Gen5::GraphicsDcbContextStateOpGetSize(2) == 108 && + Gen5::GraphicsDcbContextStateOpGetSize(3) == 128 && + Gen5::GraphicsDcbContextStateOpGetSize(4) == 0, + "context-state HLE sizes do not match libSceAgc"); + std::printf("[host] %-32s ok\n", "Pm4ContextState"); +} + void CheckPm4WaitResume(RenderContext& renderer) { GraphicsInitJmpTables(); CommandProcessor processor(renderer); @@ -17789,6 +17920,11 @@ int main(int argc, char** argv) { vulkan.CheckGpuCommandLane(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--context-state-only") == 0) { + VulkanHarness vulkan; + CheckPm4ContextStateOperations(vulkan.RuntimeRenderer()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) { CheckDepthAttachmentWrites(); CheckDynamicRenderingState(); @@ -17923,6 +18059,7 @@ int main(int argc, char** argv) { CheckVulkan13FeatureRequirements(); CheckPm4AcquireMemNoOp(vulkan.RuntimeRenderer()); CheckPm4StencilInfoValueLane(vulkan.RuntimeRenderer()); + CheckPm4ContextStateOperations(vulkan.RuntimeRenderer()); CheckPm4WaitResume(vulkan.RuntimeRenderer()); CheckPm4CeCompletion(vulkan.RuntimeRenderer()); CheckEmbeddedFetchVertexOffset();