From d8bef418a752c18e984fe572d5f21e2f1df26ce2 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 10 Jul 2025 23:01:53 +0100 Subject: [PATCH] pica: Fix irq request register behaviour (#1216) --- src/video_core/pica/pica_core.cpp | 33 +++++++++++++++++++++++---- src/video_core/pica/pica_core.h | 2 +- src/video_core/pica/regs_internal.cpp | 5 +++- src/video_core/pica/regs_internal.h | 17 ++++++++++---- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/video_core/pica/pica_core.cpp b/src/video_core/pica/pica_core.cpp index f9f2f4bfb..d12a986c1 100644 --- a/src/video_core/pica/pica_core.cpp +++ b/src/video_core/pica/pica_core.cpp @@ -54,6 +54,10 @@ PicaCore::PicaCore(Memory::MemorySystem& memory_, std::shared_ptr PicaCore::~PicaCore() = default; void PicaCore::InitializeRegs() { + // Values initialized by GSP + regs.internal.irq_autostop = 1; + regs.internal.irq_mask = 0xFFFFFFF0; + auto& framebuffer_top = regs.framebuffer_config[0]; auto& framebuffer_sub = regs.framebuffer_config[1]; @@ -100,7 +104,11 @@ void PicaCore::ProcessCmdList(PAddr list, u32 size, bool ignore_list) { const u8* head = memory.GetPhysicalPointer(list); cmd_list.Reset(list, head, size); + bool stop_requested = false; while (cmd_list.current_index < cmd_list.length) { + if (stop_requested) [[unlikely]] { + break; + } // Align read pointer to 8 bytes if (cmd_list.current_index % 2 != 0) { cmd_list.current_index++; @@ -111,18 +119,26 @@ void PicaCore::ProcessCmdList(PAddr list, u32 size, bool ignore_list) { const CommandHeader header{cmd_list.head[cmd_list.current_index++]}; // Write to the requested PICA register. - WriteInternalReg(header.cmd_id, value, header.parameter_mask); + WriteInternalReg(header.cmd_id, value, header.parameter_mask, stop_requested); // Write any extra paramters as well. for (u32 i = 0; i < header.extra_data_length; ++i) { + if (stop_requested) [[unlikely]] { + break; + } const u32 cmd = header.cmd_id + (header.group_commands ? i + 1 : 0); const u32 extra_value = cmd_list.head[cmd_list.current_index++]; - WriteInternalReg(cmd, extra_value, header.parameter_mask); + WriteInternalReg(cmd, extra_value, header.parameter_mask, stop_requested); } } } -void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask) { +static bool any_byte_match(u32 a, u32 b) { + return ((a & 0xFF) == (b & 0xFF)) || (((a >> 8) & 0xFF) == ((b >> 8) & 0xFF)) || + (((a >> 16) & 0xFF) == ((b >> 16) & 0xFF)) || (((a >> 24) & 0xFF) == ((b >> 24) & 0xFF)); +} + +void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask, bool& stop_requested) { if (id >= RegsInternal::NUM_REGS) { LOG_ERROR( HW_GPU, @@ -153,8 +169,15 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask) { switch (id) { // Trigger IRQ - case PICA_REG_INDEX(trigger_irq): - signal_interrupt(Service::GSP::InterruptId::P3D); + case PICA_REG_INDEX(irq_request): + // TODO(PabloMK7): This logic is not fully accurate, but close enough: + // https://problemkaputt.de/gbatek-3ds-gpu-internal-registers-finalize-interrupt-registers.htm + if (any_byte_match(regs.internal.reg_array[id], regs.internal.irq_compare)) [[likely]] { + signal_interrupt(Service::GSP::InterruptId::P3D); + if (regs.internal.irq_autostop) [[likely]] { + stop_requested = true; + } + } break; case PICA_REG_INDEX(pipeline.triangle_topology): diff --git a/src/video_core/pica/pica_core.h b/src/video_core/pica/pica_core.h index ba71c43d9..8cdcb493d 100644 --- a/src/video_core/pica/pica_core.h +++ b/src/video_core/pica/pica_core.h @@ -43,7 +43,7 @@ public: private: void InitializeRegs(); - void WriteInternalReg(u32 id, u32 value, u32 mask); + void WriteInternalReg(u32 id, u32 value, u32 mask, bool& stop_requested); void SubmitImmediate(u32 data); diff --git a/src/video_core/pica/regs_internal.cpp b/src/video_core/pica/regs_internal.cpp index 9a528c654..7fca1ae80 100644 --- a/src/video_core/pica/regs_internal.cpp +++ b/src/video_core/pica/regs_internal.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -13,6 +13,9 @@ namespace Pica { static constexpr std::pair register_names[] = { {0x010, "GPUREG_FINALIZE"}, + {0x020, "GPUREG_IRQ_CMP"}, + {0x020, "GPUREG_IRQ_MASK"}, + {0x034, "GPUREG_IRQ_AUTOSTOP"}, {0x040, "GPUREG_FACECULLING_CONFIG"}, {0x041, "GPUREG_VIEWPORT_WIDTH"}, diff --git a/src/video_core/pica/regs_internal.h b/src/video_core/pica/regs_internal.h index 1634ec92c..930a36e29 100644 --- a/src/video_core/pica/regs_internal.h +++ b/src/video_core/pica/regs_internal.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -21,8 +21,14 @@ struct RegsInternal { union { struct { INSERT_PADDING_WORDS(0x10); - u32 trigger_irq; - INSERT_PADDING_WORDS(0x2f); + u32 irq_request; + INSERT_PADDING_WORDS(0xf); + u32 irq_compare; + INSERT_PADDING_WORDS(0xf); + u32 irq_mask; + INSERT_PADDING_WORDS(0x3); + u32 irq_autostop; + INSERT_PADDING_WORDS(0xb); RasterizerRegs rasterizer; TexturingRegs texturing; FramebufferRegs framebuffer; @@ -46,7 +52,10 @@ static_assert(sizeof(RegsInternal) == RegsInternal::NUM_REGS * sizeof(u32), static_assert(offsetof(RegsInternal, field_name) == position * 4, \ "Field " #field_name " has invalid position") -ASSERT_REG_POSITION(trigger_irq, 0x10); +ASSERT_REG_POSITION(irq_request, 0x10); +ASSERT_REG_POSITION(irq_compare, 0x20); +ASSERT_REG_POSITION(irq_mask, 0x30); +ASSERT_REG_POSITION(irq_autostop, 0x34); ASSERT_REG_POSITION(rasterizer, 0x40); ASSERT_REG_POSITION(rasterizer.cull_mode, 0x40);