From 516e61a960a1ea947b436f66211a5784b2723ce5 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Sat, 17 Jan 2026 22:44:38 -0500 Subject: [PATCH] Fix flickering and other graphics --- Src/Model3/Model3.cpp | 53 +++++++++------ Src/Model3/Real3D.cpp | 90 +++++++++++++++++-------- Src/Model3/Real3D.h | 25 +++++-- android/app/src/main/cpp/native-lib.cpp | 1 + 4 files changed, 117 insertions(+), 52 deletions(-) diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp index 096b239..3ffbe5e 100644 --- a/Src/Model3/Model3.cpp +++ b/Src/Model3/Model3.cpp @@ -1772,7 +1772,10 @@ void CModel3::Write32(UINT32 addr, UINT32 data) } else if ((addr>=0xF1180000) && (addr<0xF1180100)) { - TileGen.WriteRegister(addr&0xFF,FLIPENDIAN32(data)); + UINT32 flipped = FLIPENDIAN32(data); + TileGen.WriteRegister(addr&0xFF, flipped); + if (addr == 0xF118000C) + GPU.TilegenDrawFrame(flipped); break; } @@ -2048,19 +2051,9 @@ void CModel3::RunMainBoardFrame(void) // Compute display and VBlank timings unsigned ppcCycles = m_config["PowerPCFrequency"].ValueAs() * 1000000; unsigned frameCycles = (unsigned)((float)ppcCycles / 57.524160f); - unsigned gapCycles = (unsigned)((float)frameCycles * 2.5f / 100.0f); // we need a gap between asserting irq2 & irq 0x40 - unsigned offsetCycles = (unsigned)((float)frameCycles * 33.f / 100.0f); - unsigned dispCycles = frameCycles - gapCycles - offsetCycles; - unsigned statusCycles = (unsigned)((float)frameCycles * (0.005f)); - - // we think a frame looks like this on the model 2 - // 66% of frame - // [irq2------------------ping_pong_flips------] - // - // Games will start writing a new frame at the ping_pong time. It could be the buffer swaps here. - // Need more h/w testing to confirm. - // What we are doing here is asserting IRQ2 at 33% of the frame, and treating the ping_pong flip as the front/back buffer swap - // This way the data for the correct frames, ends up in the right frames! + unsigned lineCycles = frameCycles / 424; + unsigned vBlankCycles = lineCycles * 40; + unsigned dispCycles = lineCycles * 384; // Scale PPC timer ratio according to speed at which the PowerPC is being emulated so that the observed running frequency of the PPC timer // registers is more or less correct. This is needed to get the Virtua Striker 2 series of games running at the right speed (they are @@ -2072,11 +2065,8 @@ void CModel3::RunMainBoardFrame(void) if (gpusReady) { TileGen.BeginVBlank(); - GPU.BeginVBlank(statusCycles); // Games poll the ping_pong at startup. Values aren't 100% accurate so we stretch the frame a bit to ensure writes happen in the correct frame - - ppc_execute(offsetCycles); - IRQ.Assert(0x02); // start at 33% of the frame - ppc_execute(gapCycles); // need a gap between asserting irqs + GPU.BeginVBlank(); + ppc_execute(vBlankCycles); /* * Sound: @@ -2100,7 +2090,10 @@ void CModel3::RunMainBoardFrame(void) // Process MIDI interrupt IRQ.Assert(0x40); ppc_execute(500); // give PowerPC time to acknowledge IR - dispCycles -= 500; + if (dispCycles >= 500) + dispCycles -= 500; + else + dispCycles = 0; ++irqCount; if (irqCount > 128) @@ -2117,7 +2110,25 @@ void CModel3::RunMainBoardFrame(void) } // Run the PowerPC for the active display part of the frame - ppc_execute(dispCycles); + unsigned pingPongFlipLine = TileGen.ReadRegister(0x08); + for (unsigned i = 0; i < 384; i++) + { + if (i == pingPongFlipLine) + GPU.FlipPingPongBit(); + + if (i == 383) + IRQ.Assert(0x02); + + unsigned cycles = lineCycles; + if (dispCycles < cycles) + cycles = dispCycles; + + if (cycles > 0) + ppc_execute(cycles); + + if (dispCycles >= cycles) + dispCycles -= cycles; + } timings.ppcTicks = CThread::GetTicks() - start; } diff --git a/Src/Model3/Real3D.cpp b/Src/Model3/Real3D.cpp index 058baf1..4e3e1bb 100644 --- a/Src/Model3/Real3D.cpp +++ b/Src/Model3/Real3D.cpp @@ -140,6 +140,8 @@ void CReal3D::LoadState(CBlockFile *SaveState) UpdateRenderConfig(Render3D, m_internalRenderConfig); SaveState->Read(&commandPortWritten); SaveState->Read(&m_pingPong, sizeof(m_pingPong)); + m_pingPongCopy = m_pingPong; + m_tilegenDrawFrame = false; for (int i = 0; i < 39; i++) { uint8_t nul; @@ -162,13 +164,15 @@ static void UpdateRenderConfig(IRender3D *Render3D, uint64_t internalRenderConfi Render3D->SetSignedShade(shadeIsSigned); } -void CReal3D::BeginVBlank(int statusCycles) +void CReal3D::BeginVBlank(void) { - // Calculate point at which status bit should change value. Currently the same timing is used for both the status bit in ReadRegister - // and in WriteDMARegister32/ReadDMARegister32, however it may be that they are completely unrelated. It appears that step 1.x games - // access just the former while step 2.x access the latter. It is not known yet what this bit/these bits actually represent. - statusChange = ppc_total_cycles() + statusCycles; - m_evenFrame = !m_evenFrame; + m_pingPongCopy = m_pingPong; + + if (commandPortWritten) + FlushTextures(); + + if (m_tilegenDrawFrame && commandPortWritten) + DrawFrame(); } void CReal3D::EndVBlank(void) @@ -176,12 +180,15 @@ void CReal3D::EndVBlank(void) error = false; // clear error (just needs to be done once per frame) } +void CReal3D::FlipPingPongBit(void) +{ + m_pingPong = !m_pingPong; + m_tilegenDrawFrame = false; + commandPortWritten = false; +} + uint32_t CReal3D::SyncSnapshots(void) { - // Update read-only copy of command port flag - commandPortWrittenRO = commandPortWritten; - commandPortWritten = false; - if (!m_gpuMultiThreaded) return 0; @@ -268,8 +275,7 @@ void CReal3D::BeginFrame(void) void CReal3D::RenderFrame(void) { - //if (commandPortWrittenRO) - Render3D->RenderFrame(); + Render3D->RenderFrame(); } void CReal3D::EndFrame(void) @@ -641,11 +647,8 @@ void CReal3D::WriteDMARegister32(unsigned reg, uint32_t data) Basic Emulation Functions, Registers, Memory, and Texture FIFO ******************************************************************************/ -void CReal3D::Flush(void) +void CReal3D::FlushTextures() { - commandPortWritten = true; - DebugLog("Real3D 88000000 written @ PC=%08X\n", ppc_get_pc()); - // Upload textures (if any) if (fifoIdx > 0) { @@ -673,6 +676,42 @@ void CReal3D::Flush(void) fifoIdx = 0; } +bool CReal3D::PollPingPong() +{ + return m_pingPong != m_pingPongCopy; +} + +void CReal3D::DrawFrame() +{ + m_tilegenDrawFrame = false; + commandPortWritten = false; +} + +void CReal3D::TilegenDrawFrame(uint32_t flags) +{ + (void)flags; + m_tilegenDrawFrame = true; + + if (!PollPingPong()) + { + if (commandPortWritten) + DrawFrame(); + } +} + +void CReal3D::Flush(void) +{ + commandPortWritten = true; + DebugLog("Real3D 88000000 written @ PC=%08X\n", ppc_get_pc()); + + if (!PollPingPong()) + { + FlushTextures(); + if (m_tilegenDrawFrame) + DrawFrame(); + } +} + void CReal3D::WriteTextureFIFO(uint32_t data) { if (fifoIdx >= (0x100000/4)) @@ -789,16 +828,8 @@ uint32_t CReal3D::ReadRegister(unsigned reg) DebugLog("Real3D: Read reg %X\n", reg); if (reg == 0) { - uint32_t ping_pong; - - if (m_evenFrame) { - ping_pong = (ppc_total_cycles() >= statusChange ? 0x0 : 0x02000000); - } - else { - ping_pong = (ppc_total_cycles() >= statusChange ? 0x02000000 : 0x0); - } - - return 0xfdffffff | ping_pong; + uint32_t ping_pong = (m_pingPong ? 0x02000000 : 0x0); + return 0xfdffffff | ping_pong; } else if (reg >= 20 && reg<=32) { // line of sight registers @@ -867,8 +898,9 @@ void CReal3D::Reset(void) error = false; m_pingPong = 0; + m_pingPongCopy = 0; commandPortWritten = false; - commandPortWrittenRO = false; + m_tilegenDrawFrame = false; queuedUploadTextures.clear(); queuedUploadTexturesRO.clear(); @@ -1029,6 +1061,10 @@ CReal3D::CReal3D(const Util::Config::Node &config) vrom = NULL; error = false; fifoIdx = 0; + commandPortWritten = false; + m_tilegenDrawFrame = false; + m_pingPong = 0; + m_pingPongCopy = 0; m_vromTextureFIFO[0] = 0; m_vromTextureFIFO[1] = 0; m_vromTextureFIFOIdx = 0; diff --git a/Src/Model3/Real3D.h b/Src/Model3/Real3D.h index 707e074..ca91fb0 100644 --- a/Src/Model3/Real3D.h +++ b/Src/Model3/Real3D.h @@ -121,7 +121,7 @@ public: * * Must be called before the VBlank starts. */ - void BeginVBlank(int statusCycles); + void BeginVBlank(void); /* * EndVBlank(void) @@ -130,6 +130,13 @@ public: */ void EndVBlank(void); + /* + * FlipPingPongBit(void) + * + * Any writes that happen after the ping_pong bit flips but before VBlank are buffered. + */ + void FlipPingPongBit(void); + /* * SyncSnapshots(void): * @@ -338,6 +345,14 @@ public: * data Data. */ void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, uint32_t data); + + /* + * TilegenDrawFrame(flags): + * + * The tilegen controls the frame timing. A write to reg 0x0C appears to + * trigger the 3D hardware to draw the frame. + */ + void TilegenDrawFrame(uint32_t flags); /* * Reset(void): @@ -436,6 +451,9 @@ private: void UploadTexture(uint32_t header, const uint16_t *texData); uint32_t UpdateSnapshots(bool copyWhole); uint32_t UpdateSnapshot(bool copyWhole, uint8_t *src, uint8_t *dst, unsigned size, uint8_t *dirty); + void FlushTextures(); + bool PollPingPong(); + void DrawFrame(); // Config const Util::Config::Node &m_config; @@ -497,12 +515,11 @@ private: // Command port bool commandPortWritten; - bool commandPortWrittenRO; // Read-only copy of flag + bool m_tilegenDrawFrame; // Status and command registers uint32_t m_pingPong; - uint64_t statusChange = 0; - bool m_evenFrame = false; + uint32_t m_pingPongCopy; // Internal ASIC state std::unordered_map m_asicID; diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index a4b2774..19bad3d 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -326,6 +326,7 @@ struct Super3Host { // Keep unsupported rendering knobs clamped on Android. config.Set("QuadRendering", false); + config.Set("New3DEngine", true); config.Set("New3DAccurate", false); // Allow user-specified framebuffer sizes. Clamp to sane bounds so we don't