From d74a3ce1396ca8b2e1092b807aec0e62133aec83 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Thu, 13 Aug 2026 22:49:04 -0300 Subject: [PATCH 1/3] Feature/gs refactor (#204) * refactor: from guest threads to EE scheduler * feat: bad wip mpeg fix for code veronica * feat: cheap copy from host feat: small perf o vsync tick * feat: added EE clock Hz fix: fix MPEG out of sync with new EE refactor * fix: fix lotr tests * fix: fix cri dtx loading fix: fix wrong mmi instruction translation fix: fix thread info params feat: added EE timers decoder and consumer feat: split SFI and IOP memory to prevent collision and overrides * feat: revert wrong changes * refactor: change GS architecture --- ps2xRuntime/CMakeLists.txt | 8 +- ps2xRuntime/include/ps2_runtime.h | 4 +- ps2xRuntime/include/runtime/gs/gs_backend.h | 33 + .../include/runtime/gs/gs_cpu_backend.h | 75 + .../{ps2_gs_gpu.h => gs/gs_frontend.h} | 290 +- ps2xRuntime/include/runtime/gs/gs_types.h | 315 ++ .../runtime/{ => gs}/ps2_gif_arbiter.h | 0 .../include/runtime/{ => gs}/ps2_gs_common.h | 2 +- .../include/runtime/{ => gs}/ps2_gs_memory.h | 0 .../include/runtime/{ => gs}/ps2_gs_psmct16.h | 0 .../include/runtime/{ => gs}/ps2_gs_psmct32.h | 0 .../include/runtime/{ => gs}/ps2_gs_psmt4.h | 0 .../include/runtime/{ => gs}/ps2_gs_psmt8.h | 0 .../include/runtime/ps2_gs_rasterizer.h | 22 - ps2xRuntime/include/runtime/ps2_memory.h | 2 +- ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp | 4 +- ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp | 1894 +++++++++++ ps2xRuntime/src/lib/gs/gs_frontend.cpp | 1733 ++++++++++ .../src/lib/{ => gs}/ps2_gif_arbiter.cpp | 2 +- .../src/lib/{ => gs}/ps2_gs_memory.cpp | 2 +- ps2xRuntime/src/lib/ps2_gs_gpu.cpp | 2961 ----------------- ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp | 1102 ------ ps2xRuntime/src/lib/ps2_memory.cpp | 2 +- ps2xRuntime/src/lib/ps2_runtime.cpp | 2 +- ps2xRuntime/src/lib/ps2_vu1.cpp | 1 - ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp | 4 +- ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp | 4 +- ps2xTest/src/ps2_gs_tests.cpp | 58 +- ps2xTest/src/ps2_memory_tests.cpp | 4 +- ps2xTest/src/ps2_runtime_expansion_tests.cpp | 4 +- ps2xTest/src/ps2_vu1_tests.cpp | 6 +- 31 files changed, 4146 insertions(+), 4388 deletions(-) create mode 100644 ps2xRuntime/include/runtime/gs/gs_backend.h create mode 100644 ps2xRuntime/include/runtime/gs/gs_cpu_backend.h rename ps2xRuntime/include/runtime/{ps2_gs_gpu.h => gs/gs_frontend.h} (56%) create mode 100644 ps2xRuntime/include/runtime/gs/gs_types.h rename ps2xRuntime/include/runtime/{ => gs}/ps2_gif_arbiter.h (100%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_common.h (97%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_memory.h (100%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_psmct16.h (100%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_psmct32.h (100%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_psmt4.h (100%) rename ps2xRuntime/include/runtime/{ => gs}/ps2_gs_psmt8.h (100%) delete mode 100644 ps2xRuntime/include/runtime/ps2_gs_rasterizer.h create mode 100644 ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp create mode 100644 ps2xRuntime/src/lib/gs/gs_frontend.cpp rename ps2xRuntime/src/lib/{ => gs}/ps2_gif_arbiter.cpp (98%) rename ps2xRuntime/src/lib/{ => gs}/ps2_gs_memory.cpp (99%) delete mode 100644 ps2xRuntime/src/lib/ps2_gs_gpu.cpp delete mode 100644 ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp delete mode 100644 ps2xRuntime/src/lib/ps2_vu1.cpp diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 0a80a98..e4dc195 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -377,12 +377,12 @@ endfunction() add_library(ps2_runtime STATIC src/lib/game_overrides.cpp - src/lib/ps2_gif_arbiter.cpp + src/lib/gs/ps2_gif_arbiter.cpp src/lib/ps2_audio.cpp src/lib/ps2_audio_vag.cpp - src/lib/ps2_gs_gpu.cpp - src/lib/ps2_gs_memory.cpp - src/lib/ps2_gs_rasterizer.cpp + src/lib/gs/ps2_gs_memory.cpp + src/lib/gs/gs_frontend.cpp + src/lib/gs/gs_cpu_backend.cpp src/lib/ps2_iop_host.cpp src/lib/ps2_memory.cpp src/lib/ps2_pad.cpp diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 4259e12..a899408 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -24,9 +24,9 @@ #include "ps2_log.h" #include "runtime/ps2_address.h" -#include "runtime/ps2_gif_arbiter.h" +#include "runtime/gs/ps2_gif_arbiter.h" #include "runtime/ps2_memory.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ps2_vu1.h" #include "runtime/ps2_audio.h" #include "runtime/ps2_pad.h" diff --git a/ps2xRuntime/include/runtime/gs/gs_backend.h b/ps2xRuntime/include/runtime/gs/gs_backend.h new file mode 100644 index 0000000..9419cc2 --- /dev/null +++ b/ps2xRuntime/include/runtime/gs/gs_backend.h @@ -0,0 +1,33 @@ +#pragma once + +#include "runtime/gs/gs_types.h" + +#include +#include + +class GSRasterBackend +{ +public: + virtual ~GSRasterBackend() = default; + + virtual void Initialize(uint8_t *vram, uint32_t vramSize) = 0; + virtual void Reset() = 0; + + virtual void Submit(const GSPrimitiveBatch &batch) = 0; + + virtual void BeginTransfer(const GSTransferCommand &command) = 0; + virtual void UploadImage(const uint8_t *data, uint32_t sizeBytes) = 0; + + virtual void Flush() = 0; + virtual void TextureFlush() = 0; + virtual void Sync(GSSyncReason reason) = 0; + virtual PresentationFrame Present(const GSPresentationRequest &request) = 0; + + virtual bool ClearFramebuffer(const GSContext &context, uint32_t rgba) = 0; + virtual uint32_t ConsumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) = 0; + + virtual uint32_t ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const = 0; + virtual void WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) = 0; + virtual void SnapshotVram(std::vector &out) const = 0; + virtual GSTransferSnapshot GetTransferSnapshot() const = 0; +}; diff --git a/ps2xRuntime/include/runtime/gs/gs_cpu_backend.h b/ps2xRuntime/include/runtime/gs/gs_cpu_backend.h new file mode 100644 index 0000000..71e8032 --- /dev/null +++ b/ps2xRuntime/include/runtime/gs/gs_cpu_backend.h @@ -0,0 +1,75 @@ +#pragma once + +#include "runtime/gs/gs_backend.h" + +#include +#include +#include +#include + +class GSCpuBackend final : public GSRasterBackend +{ +public: + GSCpuBackend(); + + void Initialize(uint8_t *vram, uint32_t vramSize) override; + void Reset() override; + + void Submit(const GSPrimitiveBatch &batch) override; + void BeginTransfer(const GSTransferCommand &command) override; + void UploadImage(const uint8_t *data, uint32_t sizeBytes) override; + + void Flush() override; + void TextureFlush() override; + void Sync(GSSyncReason reason) override; + PresentationFrame Present(const GSPresentationRequest &request) override; + + bool ClearFramebuffer(const GSContext &context, uint32_t rgba) override; + uint32_t ConsumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) override; + + uint32_t ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const override; + void WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) override; + void SnapshotVram(std::vector &out) const override; + GSTransferSnapshot GetTransferSnapshot() const override; + +private: + void ResetUnlocked(); + uint32_t ReadVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const; + void WriteVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value); + + void DrawPrimitive(const GSPrimitiveBatch &batch); + void DrawSprite(const GSPrimitiveBatch &batch); + void DrawTriangle(const GSPrimitiveBatch &batch); + void DrawLine(const GSPrimitiveBatch &batch); + void WritePixel(const GSDrawState &state, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog); + uint32_t SampleTexture(const GSDrawState &state, float s, float t, float q, uint16_t u, uint16_t v); + uint32_t LookupCLUT(const GSDrawState &state, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm); + + void PerformLocalToLocalTransfer(); + void PerformLocalToHostTransfer(); + PresentationFrame PresentFromLocalMemory(const GSPresentationRequest &request); + bool CopyFrameToHostRgba(const GSFrameReg &frame, + uint32_t width, + uint32_t height, + std::vector &outPixels, + bool preserveAlpha, + bool useLocalMemoryLayout, + bool frameBaseIsPages, + uint32_t sourceOriginX, + uint32_t sourceOriginY) const; + + using WriteVramFunc = std::function; + using ReadVramFunc = std::function; + + static constexpr size_t kPsmHandlerCount = 1u << 6u; + mutable std::mutex m_mutex; + uint8_t *m_vram = nullptr; + uint32_t m_vramSize = 0; + std::array m_readVramFuncs{}; + std::array m_writeVramFuncs{}; + + GSTransferCommand m_transfer{}; + GSTransferSnapshot m_transferState{}; + std::vector m_localToHostBuffer; + size_t m_localToHostReadPos = 0; +}; diff --git a/ps2xRuntime/include/runtime/ps2_gs_gpu.h b/ps2xRuntime/include/runtime/gs/gs_frontend.h similarity index 56% rename from ps2xRuntime/include/runtime/ps2_gs_gpu.h rename to ps2xRuntime/include/runtime/gs/gs_frontend.h index d968326..4c3f641 100644 --- a/ps2xRuntime/include/runtime/ps2_gs_gpu.h +++ b/ps2xRuntime/include/runtime/gs/gs_frontend.h @@ -1,229 +1,14 @@ -#ifndef PS2_GS_GPU_H -#define PS2_GS_GPU_H +#ifndef PS2_GS_FRONTEND_H +#define PS2_GS_FRONTEND_H #include #include -#include #include -#include +#include #include #include -#include "ps2_gs_rasterizer.h" -#include "ps2_gs_memory.h" - -enum GSPrimType : uint8_t -{ - GS_PRIM_POINT = 0, - GS_PRIM_LINE = 1, - GS_PRIM_LINESTRIP = 2, - GS_PRIM_TRIANGLE = 3, - GS_PRIM_TRISTRIP = 4, - GS_PRIM_TRIFAN = 5, - GS_PRIM_SPRITE = 6, -}; - -enum GSPsm : uint8_t -{ - GS_PSM_CT32 = 0, - GS_PSM_CT24 = 1, - GS_PSM_CT16 = 2, - GS_PSM_CT16S = 10, - GS_PSM_T8 = 19, - GS_PSM_T4 = 20, - GS_PSM_T8H = 27, - GS_PSM_T4HL = 36, - GS_PSM_T4HH = 44, - GS_PSM_Z32 = 48, - GS_PSM_Z24 = 49, - GS_PSM_Z16 = 50, - GS_PSM_Z16S = 58, -}; - -enum GSGifFormat : uint8_t -{ - GIF_FMT_PACKED = 0, - GIF_FMT_REGLIST = 1, - GIF_FMT_IMAGE = 2, - GIF_FMT_DISABLED = 3, -}; - -enum GSRegId : uint8_t -{ - GS_REG_PRIM = 0x00, - GS_REG_RGBAQ = 0x01, - GS_REG_ST = 0x02, - GS_REG_UV = 0x03, - GS_REG_XYZF2 = 0x04, - GS_REG_XYZ2 = 0x05, - GS_REG_TEX0_1 = 0x06, - GS_REG_TEX0_2 = 0x07, - GS_REG_CLAMP_1 = 0x08, - GS_REG_CLAMP_2 = 0x09, - GS_REG_FOG = 0x0A, - GS_REG_XYZF3 = 0x0C, - GS_REG_XYZ3 = 0x0D, - GS_REG_AD = 0x0F, - - GS_REG_TEX1_1 = 0x14, - GS_REG_TEX1_2 = 0x15, - GS_REG_TEX2_1 = 0x16, - GS_REG_TEX2_2 = 0x17, - GS_REG_XYOFFSET_1 = 0x18, - GS_REG_XYOFFSET_2 = 0x19, - GS_REG_PRMODECONT = 0x1A, - GS_REG_PRMODE = 0x1B, - GS_REG_TEXCLUT = 0x1C, - GS_REG_SCANMSK = 0x22, - GS_REG_MIPTBP1_1 = 0x34, - GS_REG_MIPTBP1_2 = 0x35, - GS_REG_MIPTBP2_1 = 0x36, - GS_REG_MIPTBP2_2 = 0x37, - GS_REG_TEXA = 0x3B, - GS_REG_FOGCOL = 0x3D, - GS_REG_TEXFLUSH = 0x3F, - GS_REG_SCISSOR_1 = 0x40, - GS_REG_SCISSOR_2 = 0x41, - GS_REG_ALPHA_1 = 0x42, - GS_REG_ALPHA_2 = 0x43, - GS_REG_DIMX = 0x44, - GS_REG_DTHE = 0x45, - GS_REG_COLCLAMP = 0x46, - GS_REG_TEST_1 = 0x47, - GS_REG_TEST_2 = 0x48, - GS_REG_PABE = 0x49, - GS_REG_FBA_1 = 0x4A, - GS_REG_FBA_2 = 0x4B, - GS_REG_FRAME_1 = 0x4C, - GS_REG_FRAME_2 = 0x4D, - GS_REG_ZBUF_1 = 0x4E, - GS_REG_ZBUF_2 = 0x4F, - GS_REG_BITBLTBUF = 0x50, - GS_REG_TRXPOS = 0x51, - GS_REG_TRXREG = 0x52, - GS_REG_TRXDIR = 0x53, - GS_REG_HWREG = 0x54, - GS_REG_SIGNAL = 0x60, - GS_REG_FINISH = 0x61, - GS_REG_LABEL = 0x62, -}; - -struct GSVertex -{ - float x, y; - // double because float isnt accurate enough for values near UINT32_MAX - double z; - uint8_t r, g, b, a; - float q; - float s, t; - uint16_t u, v; - uint8_t fog; -}; - -struct GSFrameReg -{ - uint32_t fbp; - uint32_t fbw; - uint8_t psm; - uint32_t fbmsk; -}; - -struct GSZbufReg -{ - u32 zbp; - u8 psm; - bool zmask; -}; - -struct GSScissorReg -{ - uint16_t x0, x1, y0, y1; -}; - -struct GSTex0Reg -{ - uint32_t tbp0; - uint8_t tbw; - uint8_t psm; - uint8_t tw; - uint8_t th; - uint8_t tcc; - uint8_t tfx; - uint32_t cbp; - uint8_t cpsm; - uint8_t csm; - uint8_t csa; - uint8_t cld; -}; - -struct GSXYOffsetReg -{ - uint16_t ofx; - uint16_t ofy; -}; - -struct GSTexaReg -{ - uint8_t ta0; - bool aem; - uint8_t ta1; -}; - -struct GSTexClutReg -{ - uint8_t cbw; - uint8_t cou; - uint16_t cov; -}; - -struct GSContext -{ - GSFrameReg frame; - GSScissorReg scissor; - GSTex0Reg tex0; - GSXYOffsetReg xyoffset; - GSZbufReg zbuf; - uint64_t tex1; - uint64_t clamp; - uint64_t alpha; - uint64_t test; - uint64_t fba; -}; - -struct GSPrimReg -{ - GSPrimType type; - bool iip; - bool tme; - bool fge; - bool abe; - bool aa1; - bool fst; - bool ctxt; - bool fix; -}; - -struct GSBitBltBuf -{ - uint32_t sbp; - uint8_t sbw; - uint8_t spsm; - uint32_t dbp; - uint8_t dbw; - uint8_t dpsm; -}; - -struct GSTrxPos -{ - uint16_t ssax, ssay; - uint16_t dsax, dsay; - uint8_t dir; -}; - -struct GSTrxReg -{ - uint16_t rrw, rrh; -}; +#include "runtime/gs/gs_backend.h" struct GSDebugSnapshot { @@ -231,6 +16,10 @@ struct GSDebugSnapshot GSPrimReg prim{}; GSTexaReg texa{}; GSTexClutReg texclut{}; + uint64_t scanmsk = 0; + uint64_t dimx = 0; + uint64_t dthe = 0; + uint64_t colclamp = 0; GSBitBltBuf bitbltbuf{}; GSTrxPos trxpos{}; GSTrxReg trxreg{}; @@ -307,18 +96,15 @@ struct GSDebugHistoryEntry bool usedPreferred = false; }; -class GSRasterizer; - class GS { - friend class GSRasterizer; - public: GS(); ~GS() = default; void init(uint8_t *vram, uint32_t vramSize, struct GSRegisters *privRegs = nullptr); void reset(); + void setRasterBackend(std::unique_ptr backend); void processGIFPacket(const uint8_t *data, uint32_t sizeBytes); bool processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes); @@ -359,8 +145,8 @@ public: void refreshDisplaySnapshot(); - inline void WriteVram(u32 psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value); - inline u32 ReadVram(u32 psm, u32 base, u32 bw, u32 x, u32 y) const; + void WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value); + uint32_t ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const; private: void snapshotVRAM(); @@ -373,7 +159,6 @@ private: const uint8_t *data, uint32_t sizeBytes); void vertexKick(bool drawing); - void latchHostPresentationFrameUnlocked(); void recordDebugEventUnlocked(GSDebugHistoryEntry entry); GSDebugHistoryEntry makeDebugEventUnlocked(GSDebugEventKind kind) const; @@ -385,24 +170,18 @@ private: void processImageData(const uint8_t *data, uint32_t sizeBytes); bool tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes); - void performLocalToLocalTransfer(); - void performLocalToHostToBuffer(); - bool copyFrameToHostRgbaUnlocked(const GSFrameReg &frame, - uint32_t width, - uint32_t height, - std::vector &outPixels, - bool preserveAlpha = false, - bool useLocalMemoryLayout = false, - bool frameBaseIsPages = true, - uint32_t sourceOriginX = 0u, - uint32_t sourceOriginY = 0u) const; + GSPrimitiveBatch buildDrawBatch(int vertexCount) const; + void updatePreferredDisplaySourceForDraw(const GSPrimitiveBatch &batch); + GSPresentationRequest buildPresentationRequestUnlocked() const; + GSContext &activeContext(); - uint8_t *m_vram = nullptr; - uint32_t m_vramSize = 0; + uint8_t *m_localMemoryStorage = nullptr; + uint32_t m_localMemorySize = 0u; struct GSRegisters *m_privRegs = nullptr; mutable std::recursive_mutex m_stateMutex; + mutable std::mutex m_backendLifetimeMutex; mutable std::mutex m_presentationMutex; GSContext m_ctx[2]; @@ -419,6 +198,10 @@ private: bool m_prmodecont = true; bool m_pabe = false; + uint64_t m_scanmsk = 0; + uint64_t m_dimx = 0; + uint64_t m_dthe = 0; + uint64_t m_colclamp = 0; GSTexaReg m_texa{0u, false, 0u}; GSTexClutReg m_texclut{0u, 0u, 0u}; @@ -427,13 +210,6 @@ private: GSTrxReg m_trxreg{}; uint32_t m_trxdir = 3; - struct - { - uint32_t x{ 0 }; - uint32_t y{ 0 }; - uint32_t total_pixels{ 0 }; - uint32_t copied_pixels{ 0 }; - } m_transferState; static constexpr int kMaxVerts = 6; GSVertex m_vtxQueue[kMaxVerts]; @@ -456,9 +232,6 @@ private: uint64_t m_nativeImageUploadCount = 0; uint64_t m_nativePackedGIFPacketCount = 0; - std::vector m_localToHostBuffer; - size_t m_localToHostReadPos = 0; - static constexpr size_t kDebugHistoryCapacity = 512; std::array m_debugHistory{}; size_t m_debugHistoryWrite = 0; @@ -468,24 +241,7 @@ private: uint64_t m_debugLastVsyncTick = UINT64_MAX; bool m_debugHistoryPaused = true; - GSRasterizer m_rasterizer; - - using WriteVramFunc = std::function; - using ReadVramFunc = std::function; - - static constexpr size_t kPsmHandlerCount = 1u << 6u; - std::array m_read_vram_funcs{ }; - std::array m_write_vram_funcs{ }; + std::unique_ptr m_backend; }; -inline u32 GS::ReadVram(u32 psm, u32 base, u32 bw, u32 x, u32 y) const -{ - return m_read_vram_funcs[psm & 0x3F](m_vram, base, bw, x, y); -} - -inline void GS::WriteVram(u32 psm, u32 base, u32 bw, u32 x, u32 y, u32 value) -{ - m_write_vram_funcs[psm & 0x3F](m_vram, base, bw, x, y, value); -} - #endif diff --git a/ps2xRuntime/include/runtime/gs/gs_types.h b/ps2xRuntime/include/runtime/gs/gs_types.h new file mode 100644 index 0000000..836a3ff --- /dev/null +++ b/ps2xRuntime/include/runtime/gs/gs_types.h @@ -0,0 +1,315 @@ +#pragma once + +#include +#include +#include +#include + +enum GSPrimType : uint8_t +{ + GS_PRIM_POINT = 0, + GS_PRIM_LINE = 1, + GS_PRIM_LINESTRIP = 2, + GS_PRIM_TRIANGLE = 3, + GS_PRIM_TRISTRIP = 4, + GS_PRIM_TRIFAN = 5, + GS_PRIM_SPRITE = 6, +}; + +enum GSPsm : uint8_t +{ + GS_PSM_CT32 = 0, + GS_PSM_CT24 = 1, + GS_PSM_CT16 = 2, + GS_PSM_CT16S = 10, + GS_PSM_T8 = 19, + GS_PSM_T4 = 20, + GS_PSM_T8H = 27, + GS_PSM_T4HL = 36, + GS_PSM_T4HH = 44, + GS_PSM_Z32 = 48, + GS_PSM_Z24 = 49, + GS_PSM_Z16 = 50, + GS_PSM_Z16S = 58, +}; + +enum GSGifFormat : uint8_t +{ + GIF_FMT_PACKED = 0, + GIF_FMT_REGLIST = 1, + GIF_FMT_IMAGE = 2, + GIF_FMT_DISABLED = 3, +}; + +enum GSRegId : uint8_t +{ + GS_REG_PRIM = 0x00, + GS_REG_RGBAQ = 0x01, + GS_REG_ST = 0x02, + GS_REG_UV = 0x03, + GS_REG_XYZF2 = 0x04, + GS_REG_XYZ2 = 0x05, + GS_REG_TEX0_1 = 0x06, + GS_REG_TEX0_2 = 0x07, + GS_REG_CLAMP_1 = 0x08, + GS_REG_CLAMP_2 = 0x09, + GS_REG_FOG = 0x0A, + GS_REG_XYZF3 = 0x0C, + GS_REG_XYZ3 = 0x0D, + GS_REG_AD = 0x0F, + GS_REG_TEX1_1 = 0x14, + GS_REG_TEX1_2 = 0x15, + GS_REG_TEX2_1 = 0x16, + GS_REG_TEX2_2 = 0x17, + GS_REG_XYOFFSET_1 = 0x18, + GS_REG_XYOFFSET_2 = 0x19, + GS_REG_PRMODECONT = 0x1A, + GS_REG_PRMODE = 0x1B, + GS_REG_TEXCLUT = 0x1C, + GS_REG_SCANMSK = 0x22, + GS_REG_MIPTBP1_1 = 0x34, + GS_REG_MIPTBP1_2 = 0x35, + GS_REG_MIPTBP2_1 = 0x36, + GS_REG_MIPTBP2_2 = 0x37, + GS_REG_TEXA = 0x3B, + GS_REG_FOGCOL = 0x3D, + GS_REG_TEXFLUSH = 0x3F, + GS_REG_SCISSOR_1 = 0x40, + GS_REG_SCISSOR_2 = 0x41, + GS_REG_ALPHA_1 = 0x42, + GS_REG_ALPHA_2 = 0x43, + GS_REG_DIMX = 0x44, + GS_REG_DTHE = 0x45, + GS_REG_COLCLAMP = 0x46, + GS_REG_TEST_1 = 0x47, + GS_REG_TEST_2 = 0x48, + GS_REG_PABE = 0x49, + GS_REG_FBA_1 = 0x4A, + GS_REG_FBA_2 = 0x4B, + GS_REG_FRAME_1 = 0x4C, + GS_REG_FRAME_2 = 0x4D, + GS_REG_ZBUF_1 = 0x4E, + GS_REG_ZBUF_2 = 0x4F, + GS_REG_BITBLTBUF = 0x50, + GS_REG_TRXPOS = 0x51, + GS_REG_TRXREG = 0x52, + GS_REG_TRXDIR = 0x53, + GS_REG_HWREG = 0x54, + GS_REG_SIGNAL = 0x60, + GS_REG_FINISH = 0x61, + GS_REG_LABEL = 0x62, +}; + +struct GSVertex +{ + float x = 0.0f; + float y = 0.0f; + double z = 0.0; + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 0; + float q = 1.0f; + float s = 0.0f; + float t = 0.0f; + uint16_t u = 0; + uint16_t v = 0; + uint8_t fog = 0; +}; + +struct GSFrameReg +{ + uint32_t fbp = 0; + uint32_t fbw = 0; + uint8_t psm = 0; + uint32_t fbmsk = 0; +}; + +struct GSZbufReg +{ + uint32_t zbp = 0; + uint8_t psm = 0; + bool zmask = false; +}; + +struct GSScissorReg +{ + uint16_t x0 = 0; + uint16_t x1 = 0; + uint16_t y0 = 0; + uint16_t y1 = 0; +}; + +struct GSTex0Reg +{ + uint32_t tbp0 = 0; + uint8_t tbw = 0; + uint8_t psm = 0; + uint8_t tw = 0; + uint8_t th = 0; + uint8_t tcc = 0; + uint8_t tfx = 0; + uint32_t cbp = 0; + uint8_t cpsm = 0; + uint8_t csm = 0; + uint8_t csa = 0; + uint8_t cld = 0; +}; + +struct GSXYOffsetReg +{ + uint16_t ofx = 0; + uint16_t ofy = 0; +}; + +struct GSTexaReg +{ + uint8_t ta0 = 0; + bool aem = false; + uint8_t ta1 = 0; +}; + +struct GSTexClutReg +{ + uint8_t cbw = 0; + uint8_t cou = 0; + uint16_t cov = 0; +}; + +struct GSContext +{ + GSFrameReg frame; + GSScissorReg scissor; + GSTex0Reg tex0; + GSXYOffsetReg xyoffset; + GSZbufReg zbuf; + uint64_t tex1 = 0; + uint64_t miptbp1 = 0; + uint64_t miptbp2 = 0; + uint64_t clamp = 0; + uint64_t alpha = 0; + uint64_t test = 0; + uint64_t fba = 0; +}; + +struct GSPrimReg +{ + GSPrimType type = GS_PRIM_POINT; + bool iip = false; + bool tme = false; + bool fge = false; + bool abe = false; + bool aa1 = false; + bool fst = false; + bool ctxt = false; + bool fix = false; +}; + +struct GSBitBltBuf +{ + uint32_t sbp = 0; + uint8_t sbw = 0; + uint8_t spsm = 0; + uint32_t dbp = 0; + uint8_t dbw = 0; + uint8_t dpsm = 0; +}; + +struct GSTrxPos +{ + uint16_t ssax = 0; + uint16_t ssay = 0; + uint16_t dsax = 0; + uint16_t dsay = 0; + uint8_t dir = 0; +}; + +struct GSTrxReg +{ + uint16_t rrw = 0; + uint16_t rrh = 0; +}; + +struct GSDrawState +{ + GSContext context{}; + GSPrimReg prim{}; + GSTexaReg texa{}; + GSTexClutReg texclut{}; + bool pabe = false; + uint64_t scanmsk = 0; + uint64_t dimx = 0; + uint64_t dthe = 0; + uint64_t colclamp = 0; + uint8_t fogR = 0; + uint8_t fogG = 0; + uint8_t fogB = 0; + uint16_t textureWidth = 1; + uint16_t textureHeight = 1; + bool linearFilter = false; +}; + +struct GSPrimitiveBatch +{ + std::array vertices{}; + uint8_t vertexCount = 0; + GSDrawState state{}; +}; + +struct GSTransferCommand +{ + GSBitBltBuf bitbltbuf{}; + GSTrxPos trxpos{}; + GSTrxReg trxreg{}; + uint32_t direction = 3; +}; + +struct GSTransferSnapshot +{ + uint32_t x = 0; + uint32_t y = 0; + uint32_t totalPixels = 0; + uint32_t copiedPixels = 0; + uint32_t direction = 3; + size_t localToHostPendingBytes = 0; +}; + +struct GSPresentationRequest +{ + uint64_t pmode = 0; + uint64_t smode2 = 0; + uint64_t dispfb1 = 0; + uint64_t display1 = 0; + uint64_t dispfb2 = 0; + uint64_t display2 = 0; + uint64_t bgcolor = 0; + uint64_t vsyncTick = 0; + GSFrameReg contextFrames[2]{}; + GSFrameReg preferredSource{}; + uint32_t preferredDestFbp = 0; + bool hasPreferredSource = false; +}; + +struct PresentationFrame +{ + std::vector pixels; + uint32_t width = 0; + uint32_t height = 0; + uint32_t displayFbp = 0; + uint32_t sourceFbp = 0; + bool usedPreferred = false; + + explicit operator bool() const + { + return !pixels.empty() && width != 0u && height != 0u; + } +}; + +enum class GSSyncReason : uint8_t +{ + Finish, + LocalToHost, + Presentation, + DebugReadback, + Reset, +}; diff --git a/ps2xRuntime/include/runtime/ps2_gif_arbiter.h b/ps2xRuntime/include/runtime/gs/ps2_gif_arbiter.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gif_arbiter.h rename to ps2xRuntime/include/runtime/gs/ps2_gif_arbiter.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_common.h b/ps2xRuntime/include/runtime/gs/ps2_gs_common.h similarity index 97% rename from ps2xRuntime/include/runtime/ps2_gs_common.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_common.h index 3dfa8eb..03306d3 100644 --- a/ps2xRuntime/include/runtime/ps2_gs_common.h +++ b/ps2xRuntime/include/runtime/gs/ps2_gs_common.h @@ -1,7 +1,7 @@ #ifndef PS2_GS_COMMON_H #define PS2_GS_COMMON_H -#include "ps2_gs_gpu.h" +#include "runtime/gs/gs_types.h" #include namespace GSInternal diff --git a/ps2xRuntime/include/runtime/ps2_gs_memory.h b/ps2xRuntime/include/runtime/gs/ps2_gs_memory.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gs_memory.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_memory.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmct16.h b/ps2xRuntime/include/runtime/gs/ps2_gs_psmct16.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gs_psmct16.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_psmct16.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmct32.h b/ps2xRuntime/include/runtime/gs/ps2_gs_psmct32.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gs_psmct32.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_psmct32.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmt4.h b/ps2xRuntime/include/runtime/gs/ps2_gs_psmt4.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gs_psmt4.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_psmt4.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmt8.h b/ps2xRuntime/include/runtime/gs/ps2_gs_psmt8.h similarity index 100% rename from ps2xRuntime/include/runtime/ps2_gs_psmt8.h rename to ps2xRuntime/include/runtime/gs/ps2_gs_psmt8.h diff --git a/ps2xRuntime/include/runtime/ps2_gs_rasterizer.h b/ps2xRuntime/include/runtime/ps2_gs_rasterizer.h deleted file mode 100644 index 17547de..0000000 --- a/ps2xRuntime/include/runtime/ps2_gs_rasterizer.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PS2_GS_RASTERIZER_H -#define PS2_GS_RASTERIZER_H - -#include - -class GS; - -class GSRasterizer -{ -public: - void drawPrimitive(GS *gs); - void writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog); - uint32_t sampleTexture(GS *gs, float s, float t, float q, uint16_t u, uint16_t v); - uint32_t lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm); - -private: - void drawSprite(GS *gs); - void drawTriangle(GS *gs); - void drawLine(GS *gs); -}; - -#endif diff --git a/ps2xRuntime/include/runtime/ps2_memory.h b/ps2xRuntime/include/runtime/ps2_memory.h index 9972e42..cea5b98 100644 --- a/ps2xRuntime/include/runtime/ps2_memory.h +++ b/ps2xRuntime/include/runtime/ps2_memory.h @@ -11,7 +11,7 @@ #include #include -#include "ps2_gif_arbiter.h" +#include "gs/ps2_gif_arbiter.h" #if defined(_MSC_VER) #include #elif defined(USE_SSE2NEON) diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp index b29df1b..9c54919 100644 --- a/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp +++ b/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp @@ -1,8 +1,8 @@ #include "Common.h" #include "GS.h" #include "ps2_log.h" -#include "runtime/ps2_gs_common.h" -#include "runtime/ps2_gs_psmct16.h" +#include "runtime/gs/ps2_gs_common.h" +#include "runtime/gs/ps2_gs_psmct16.h" #include "runtime/ee_scheduler.h" namespace ps2_stubs diff --git a/ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp b/ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp new file mode 100644 index 0000000..9c39ae2 --- /dev/null +++ b/ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp @@ -0,0 +1,1894 @@ +#include "runtime/gs/gs_cpu_backend.h" +#include "runtime/gs/ps2_gs_common.h" +#include "runtime/gs/ps2_gs_psmct16.h" +#include "runtime/gs/ps2_gs_psmct32.h" +#include "runtime/gs/ps2_gs_psmt4.h" +#include "runtime/gs/ps2_gs_psmt8.h" +#include "runtime/gs/ps2_gs_memory.h" +#include "ps2_log.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace GSInternal; + +namespace +{ + float fabsQ(float q) + { + return (std::fabs(q) > 1.0e-8f) ? q : 1.0f; + } + + u16 Rgba8888ToRgba5551(u32 c) + { + uint32_t r = ((c >> 0) & 0xFF) >> 3; + uint32_t g = ((c >> 8) & 0xFF) >> 3; + uint32_t b = ((c >> 16) & 0xFF) >> 3; + uint32_t a = ((c >> 24) & 0xFF) >> 7; + + return (r | (g << 5) | (b << 10) | (a << 15)); + } + + u32 Rgba5551ToRgba8888(u16 c) + { + u32 r = ((c >> 0) & 0x1F) << 3; + u32 g = ((c >> 5) & 0x1F) << 3; + u32 b = ((c >> 10) & 0x1F) << 3; + u32 a = ((c >> 15) & 0x01) << 7; + + return (r | (g << 8) | (b << 16) | (a << 24)); + } + + u32 pack32(u8 r, u8 g, u8 b, u8 a) + { + return static_cast(r) | (g << 8) | (b << 16) | (a << 24); + } + + uint32_t applyTexa(const GSTexaReg &texa, uint8_t psm, uint32_t texel) + { + if (psm == GS_PSM_CT32) + return texel; + + const uint8_t r = static_cast(texel & 0xFFu); + const uint8_t g = static_cast((texel >> 8) & 0xFFu); + const uint8_t b = static_cast((texel >> 16) & 0xFFu); + const bool rgbZero = r == 0u && g == 0u && b == 0u; + uint8_t a = static_cast((texel >> 24) & 0xFFu); + + switch (psm) + { + case GS_PSM_CT24: + a = (texa.aem && rgbZero) ? 0u : texa.ta0; + break; + case GS_PSM_CT16: + case GS_PSM_CT16S: + if ((a & 0x80u) != 0u) + a = texa.ta1; + else + a = (texa.aem && rgbZero) ? 0u : texa.ta0; + break; + default: + break; + } + + return (texel & 0x00FFFFFFu) | (static_cast(a) << 24); + } + + uint32_t addrPSMCT16Family(uint32_t basePtr, uint32_t width, uint8_t psm, uint32_t x, uint32_t y) + { + switch (psm) + { + case GS_PSM_CT16: + return GSPSMCT16::addrPSMCT16(basePtr, width, x, y); + case GS_PSM_CT16S: + return GSPSMCT16::addrPSMCT16S(basePtr, width, x, y); + case GS_PSM_Z16: + return GSPSMCT16::addrPSMZ16(basePtr, width, x, y); + case GS_PSM_Z16S: + return GSPSMCT16::addrPSMZ16S(basePtr, width, x, y); + default: + return 0u; + } + } + + std::atomic s_debugPrimitiveCount{0}; + std::atomic s_debugPixelCount{0}; + std::atomic s_debugContext1PrimitiveCount{0}; + std::atomic s_debugFbp150PixelCount{0}; + + int wrapTextureCoordinate(int coordinate, + int textureSize, + uint8_t mode, + uint16_t regionMin, + uint16_t regionMax) + { + switch (mode & 0x3u) + { + case 0: // REPEAT + return static_cast(static_cast(coordinate) & static_cast(textureSize - 1)); + case 1: // CLAMP + return clampInt(coordinate, 0, textureSize - 1); + case 2: // REGION_CLAMP + return std::min(std::max(coordinate, static_cast(regionMin)), static_cast(regionMax)); + case 3: // REGION_REPEAT + return static_cast((static_cast(coordinate) & static_cast(regionMin)) | static_cast(regionMax)); + default: + return coordinate; + } + } + + bool passesAlphaTest(uint64_t testReg, uint8_t alpha) + { + if ((testReg & 0x1u) == 0u) + return true; + + const uint8_t atst = static_cast((testReg >> 1) & 0x7u); + const uint8_t aref = static_cast((testReg >> 4) & 0xFFu); + + switch (atst) + { + case 0: + return false; + case 1: + return true; + case 2: + return alpha < aref; + case 3: + return alpha <= aref; + case 4: + return alpha == aref; + case 5: + return alpha >= aref; + case 6: + return alpha > aref; + case 7: + return alpha != aref; + default: + return true; + } + } + + struct PixelWriteMask + { + bool writeRgb = true; + bool writeAlpha = true; + bool writeDepth = true; + + bool writesFramebuffer() const + { + return writeRgb || writeAlpha; + } + + bool writesAnything() const + { + return writesFramebuffer() || writeDepth; + } + }; + + PixelWriteMask classifyAlphaTest(uint64_t testReg, uint8_t alpha, uint8_t framePsm) + { + const bool pass = passesAlphaTest(testReg, alpha); + if (pass) + return {}; + + // TEST.AFAIL controls what happens when the alpha comparison fails. + switch (static_cast((testReg >> 12) & 0x3u)) + { + case 1: // FB_ONLY + return {true, true, false}; + case 2: // ZB_ONLY + return {false, false, true}; + case 3: // RGB_ONLY + // RGB_ONLY is only distinct for RGBA32. The GS treats it as + // FB_ONLY for RGB24 and RGBA16 framebuffers. + if (framePsm == GS_PSM_CT32) + return {true, false, false}; + return {true, true, false}; + case 0: // KEEP + default: + return {false, false, false}; + } + } + + bool passesDestinationAlphaTest(uint64_t testReg, uint8_t framePsm, uint32_t rawFramebufferPixel) + { + const bool date = ((testReg >> 14) & 0x1u) != 0u; + if (!date) + return true; + + const bool datm = ((testReg >> 15) & 0x1u) != 0u; + switch (framePsm) + { + case GS_PSM_CT32: + return (((rawFramebufferPixel >> 31) & 0x1u) != 0u) == datm; + case GS_PSM_CT16: + case GS_PSM_CT16S: + return (((rawFramebufferPixel >> 15) & 0x1u) != 0u) == datm; + case GS_PSM_CT24: + // RGB24 has no destination alpha, so DATE always passes. + return true; + default: + return true; + } + } + + struct TextureCombineResult + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + + TextureCombineResult combineTexture(const GSTex0Reg &tex, + uint8_t vr, + uint8_t vg, + uint8_t vb, + uint8_t va, + uint8_t tr, + uint8_t tg, + uint8_t tb, + uint8_t ta) + { + const bool textureHasAlpha = tex.tcc != 0u; + TextureCombineResult out{tr, tg, tb, textureHasAlpha ? ta : va}; + + switch (tex.tfx) + { + case 0: // MODULATE + out.r = clampU8((tr * vr) >> 7); + out.g = clampU8((tg * vg) >> 7); + out.b = clampU8((tb * vb) >> 7); + out.a = textureHasAlpha ? clampU8((ta * va) >> 7) : va; + break; + case 1: // DECAL + out.r = tr; + out.g = tg; + out.b = tb; + out.a = textureHasAlpha ? ta : va; + break; + case 2: // HIGHLIGHT + out.r = clampU8(((tr * vr) >> 7) + va); + out.g = clampU8(((tg * vg) >> 7) + va); + out.b = clampU8(((tb * vb) >> 7) + va); + out.a = textureHasAlpha ? clampU8(ta + va) : va; + break; + case 3: // HIGHLIGHT2 + out.r = clampU8(((tr * vr) >> 7) + va); + out.g = clampU8(((tg * vg) >> 7) + va); + out.b = clampU8(((tb * vb) >> 7) + va); + out.a = textureHasAlpha ? ta : va; + break; + default: + out.r = tr; + out.g = tg; + out.b = tb; + out.a = textureHasAlpha ? ta : va; + break; + } + + return out; + } + + uint32_t swizzleClutIndexCSM1(uint32_t index) + { + // CSM1 swaps address bits 3 and 4. Preserve the remaining bits: + // 16-bit CLUTs expose a ninth address bit through CSA[4]. + return (index & ~0x18u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u); + } + + // TODO: clut cache + uint32_t resolveClutIndex(uint8_t index, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm) + { + uint32_t clutIndex = static_cast(index); + + // CSM2 addresses the source directly through TEXCLUT. CSA is required + // to be zero there, so it must not offset the source coordinates. + if (csm != 0u) + return (sourcePsm == GS_PSM_T4 || + sourcePsm == GS_PSM_T4HH || + sourcePsm == GS_PSM_T4HL) + ? (clutIndex & 0x0Fu) + : clutIndex; + + const bool is16BitClut = cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S; + const uint32_t csaMask = is16BitClut ? 0x1Fu : 0x0Fu; + const uint32_t clutIndexMask = is16BitClut ? 0x1FFu : 0x0FFu; + const uint32_t clutBase = (static_cast(csa) & csaMask) << 4u; + + switch (sourcePsm) + { + case GS_PSM_T4: + case GS_PSM_T4HH: + case GS_PSM_T4HL: + clutIndex = clutBase + (clutIndex & 0x0Fu); + break; + case GS_PSM_T8: + case GS_PSM_T8H: + clutIndex = clutBase + clutIndex; + break; + default: + return clutIndex; + } + + return swizzleClutIndexCSM1(clutIndex & clutIndexMask); + } + + uint8_t lerpChannel(uint8_t c00, uint8_t c10, uint8_t c01, uint8_t c11, float fx, float fy) + { + const float top = static_cast(c00) + (static_cast(c10) - static_cast(c00)) * fx; + const float bottom = static_cast(c01) + (static_cast(c11) - static_cast(c01)) * fx; + return clampU8(static_cast(std::lround(top + (bottom - top) * fy))); + } +} + +namespace +{ + static constexpr uint32_t kDefaultDisplayWidth = 640u; + static constexpr uint32_t kDefaultDisplayHeight = 448u; + static constexpr uint32_t kHostFrameWidth = 640u; + static constexpr uint32_t kHostFrameHeight = 512u; + + uint16_t encodeFramePixelPSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + return static_cast(((r >> 3) & 0x1Fu) | + (((g >> 3) & 0x1Fu) << 5) | + (((b >> 3) & 0x1Fu) << 10) | + ((a >= 0x40u) ? 0x8000u : 0u)); + } + + void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight) + { + const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); + const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); + const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); + + outWidth = (dw + 1u) / (magh + 1u); + outHeight = dh + 1u; + if (outWidth < 64u || outHeight < 64u) + { + outWidth = kDefaultDisplayWidth; + outHeight = kDefaultDisplayHeight; + } + outWidth = std::min(outWidth, kHostFrameWidth); + outHeight = std::min(outHeight, kHostFrameHeight); + } + + GSFrameReg decodeDisplayFrame(uint64_t dispfb64) + { + GSFrameReg frame{}; + frame.fbp = static_cast(dispfb64 & 0x1FFu); + frame.fbw = static_cast((dispfb64 >> 9) & 0x3Fu); + frame.psm = static_cast((dispfb64 >> 15) & 0x1Fu); + return frame; + } + + struct GSDisplayReadOrigin + { + uint32_t x = 0u; + uint32_t y = 0u; + }; + + GSDisplayReadOrigin decodeDisplayReadOrigin(uint64_t dispfb64) + { + return { + static_cast((dispfb64 >> 32) & 0x7FFu), + static_cast((dispfb64 >> 43) & 0x7FFu)}; + } + + bool hasDisplaySetup(uint64_t display64, const GSFrameReg &frame) + { + const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); + const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); + const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); + return frame.fbw != 0u || dw != 0u || dh != 0u || magh != 0u; + } + + struct GSPmodeState + { + bool enableCrt1 = false; + bool enableCrt2 = false; + bool mmod = false; + bool amod = false; + bool slbg = false; + uint8_t alp = 0u; + }; + + GSPmodeState decodePmode(uint64_t pmode64) + { + return { + (pmode64 & 0x1ull) != 0ull, + (pmode64 & 0x2ull) != 0ull, + ((pmode64 >> 5) & 0x1ull) != 0ull, + ((pmode64 >> 6) & 0x1ull) != 0ull, + ((pmode64 >> 7) & 0x1ull) != 0ull, + static_cast((pmode64 >> 8) & 0xFFu)}; + } + + struct GSSmode2State + { + bool interlaced = false; + bool frameMode = true; + }; + + GSSmode2State decodeSMode2(uint64_t smode2) + { + return {(smode2 & 0x1ull) != 0ull, ((smode2 >> 1) & 0x1ull) != 0ull}; + } + + void applyFieldPresentation(std::vector &pixels, uint32_t width, uint32_t height, bool oddField) + { + if (pixels.empty() || width == 0u || height < 2u) + return; + const std::vector source = pixels; + for (uint32_t y = 0; y < height; ++y) + { + uint32_t sourceY = ((y >> 1u) << 1u) + (oddField ? 1u : 0u); + if (sourceY >= height) + sourceY = height - 1u; + std::memcpy(pixels.data() + y * kHostFrameWidth * 4u, + source.data() + sourceY * kHostFrameWidth * 4u, + width * 4u); + } + } + + void normalizePresentationAlpha(std::vector &pixels, uint32_t width, uint32_t height) + { + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *row = pixels.data() + y * kHostFrameWidth * 4u; + for (uint32_t x = 0; x < width; ++x) + row[x * 4u + 3u] = 255u; + } + } + + uint8_t blendPresentationChannel(uint8_t src, uint8_t dst, uint32_t factor) + { + const int delta = static_cast(src) - static_cast(dst); + return GSInternal::clampU8(static_cast(dst) + ((delta * static_cast(factor)) / 255)); + } + + uint32_t countNonBlackPixels(const std::vector &pixels, uint32_t width, uint32_t height) + { + uint32_t count = 0u; + for (uint32_t y = 0; y < height; ++y) + { + const uint8_t *row = pixels.data() + y * kHostFrameWidth * 4u; + for (uint32_t x = 0; x < width; ++x) + { + if (row[x * 4u] != 0u || row[x * 4u + 1u] != 0u || row[x * 4u + 2u] != 0u) + ++count; + } + } + return count; + } +} + +GSCpuBackend::GSCpuBackend() +{ + using namespace GSMem; + static std::once_flag lookupTablesOnce; + std::call_once(lookupTablesOnce, []() + { InitLookupTables(); }); + for (size_t i = 0; i < kPsmHandlerCount; ++i) + { + switch (i) + { + case GS_PSM_CT32: + m_readVramFuncs[i] = ReadCT32; + m_writeVramFuncs[i] = WriteCT32; + break; + case GS_PSM_CT24: + m_readVramFuncs[i] = ReadCT24; + m_writeVramFuncs[i] = WriteCT24; + break; + case GS_PSM_CT16: + m_readVramFuncs[i] = ReadCT16; + m_writeVramFuncs[i] = WriteCT16; + break; + case GS_PSM_CT16S: + m_readVramFuncs[i] = ReadCT16S; + m_writeVramFuncs[i] = WriteCT16S; + break; + case GS_PSM_T8: + m_readVramFuncs[i] = ReadP8; + m_writeVramFuncs[i] = WriteP8; + break; + case GS_PSM_T8H: + m_readVramFuncs[i] = ReadP8H; + m_writeVramFuncs[i] = WriteP8H; + break; + case GS_PSM_T4: + m_readVramFuncs[i] = ReadP4; + m_writeVramFuncs[i] = WriteP4; + break; + case GS_PSM_T4HH: + m_readVramFuncs[i] = ReadP4HH; + m_writeVramFuncs[i] = WriteP4HH; + break; + case GS_PSM_T4HL: + m_readVramFuncs[i] = ReadP4HL; + m_writeVramFuncs[i] = WriteP4HL; + break; + case GS_PSM_Z32: + m_readVramFuncs[i] = ReadZ32; + m_writeVramFuncs[i] = WriteZ32; + break; + case GS_PSM_Z24: + m_readVramFuncs[i] = ReadZ24; + m_writeVramFuncs[i] = WriteZ24; + break; + case GS_PSM_Z16: + m_readVramFuncs[i] = ReadZ16; + m_writeVramFuncs[i] = WriteZ16; + break; + case GS_PSM_Z16S: + m_readVramFuncs[i] = ReadZ16S; + m_writeVramFuncs[i] = WriteZ16S; + break; + default: + m_readVramFuncs[i] = ReadNull; + m_writeVramFuncs[i] = WriteNull; + break; + } + } + Reset(); +} + +void GSCpuBackend::Initialize(uint8_t *vram, uint32_t vramSize) +{ + std::lock_guard lock(m_mutex); + m_vram = vram; + m_vramSize = vramSize; + ResetUnlocked(); +} + +void GSCpuBackend::Reset() +{ + std::lock_guard lock(m_mutex); + ResetUnlocked(); +} + +void GSCpuBackend::ResetUnlocked() +{ + m_transfer = {}; + m_transfer.direction = 3u; + m_transferState = {}; + m_transferState.direction = 3u; + m_localToHostBuffer.clear(); + m_localToHostReadPos = 0u; +} + +void GSCpuBackend::Submit(const GSPrimitiveBatch &batch) +{ + std::lock_guard lock(m_mutex); + if (!m_vram || batch.vertexCount == 0u) + return; + DrawPrimitive(batch); +} + +void GSCpuBackend::Flush() +{ + // CPU backend is immediate. GPU backends may submit command buffers here. +} + +void GSCpuBackend::TextureFlush() +{ + // CPU texture reads are coherent with local memory. Future cached/GPU + // backends use this boundary to invalidate texture views. +} + +void GSCpuBackend::Sync(GSSyncReason) +{ + // CPU backend is immediate. GPU backends may wait on fences/readbacks here. +} + +uint32_t GSCpuBackend::ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const +{ + std::lock_guard lock(m_mutex); + return ReadVramUnlocked(psm, base, bw, x, y); +} + +uint32_t GSCpuBackend::ReadVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const +{ + if (!m_vram) + return 0u; + return m_readVramFuncs[psm & 0x3Fu](m_vram, base, bw, x, y); +} + +void GSCpuBackend::WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) +{ + std::lock_guard lock(m_mutex); + WriteVramUnlocked(psm, base, bw, x, y, value); +} + +void GSCpuBackend::WriteVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) +{ + if (!m_vram) + return; + m_writeVramFuncs[psm & 0x3Fu](m_vram, base, bw, x, y, value); +} + +void GSCpuBackend::SnapshotVram(std::vector &out) const +{ + std::lock_guard lock(m_mutex); + if (!m_vram || m_vramSize == 0u) + { + out.clear(); + return; + } + out.resize(m_vramSize); + std::memcpy(out.data(), m_vram, m_vramSize); +} + +GSTransferSnapshot GSCpuBackend::GetTransferSnapshot() const +{ + std::lock_guard lock(m_mutex); + GSTransferSnapshot result = m_transferState; + result.localToHostPendingBytes = m_localToHostReadPos < m_localToHostBuffer.size() + ? m_localToHostBuffer.size() - m_localToHostReadPos + : 0u; + return result; +} + +void GSCpuBackend::DrawPrimitive(const GSPrimitiveBatch &batch) +{ + const GSDrawState &state = batch.state; + const auto &ctx = state.context; + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t primitiveIndex = s_debugPrimitiveCount.fetch_add(1u, std::memory_order_relaxed); + if (primitiveIndex < 64u) + { + std::cout << "[gs:prim] idx=" << primitiveIndex + << " type=" << static_cast(state.prim.type) + << " tme=" << static_cast(state.prim.tme) + << " abe=" << static_cast(state.prim.abe) + << " fst=" << static_cast(state.prim.fst) + << " ctxt=" << static_cast(state.prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tw=" << static_cast(ctx.tex0.tw) + << " th=" << static_cast(ctx.tex0.th) + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(ctx.tex0.csa) + << ")" + << " texclut=(" + << "cbw=" << static_cast(state.texclut.cbw) + << " cou=" << static_cast(state.texclut.cou) + << " cov=" << state.texclut.cov + << ")" + << " ofx=" << (ctx.xyoffset.ofx >> 4) + << " ofy=" << (ctx.xyoffset.ofy >> 4) + << " scissor=(" << ctx.scissor.x0 + << "," << ctx.scissor.y0 + << ")-(" << ctx.scissor.x1 + << "," << ctx.scissor.y1 << ")" + << " test=0x" << std::hex << ctx.test + << " alpha=0x" << ctx.alpha + << std::dec + << " v0=(" << batch.vertices[0].x << "," << batch.vertices[0].y << ")" + << " uv0=(" << (batch.vertices[0].u >> 4) << "," << (batch.vertices[0].v >> 4) << ")" + << " stq0=(" << batch.vertices[0].s << "," << batch.vertices[0].t << "," << batch.vertices[0].q << ")" + << " v1=(" << batch.vertices[1].x << "," << batch.vertices[1].y << ")" + << " uv1=(" << (batch.vertices[1].u >> 4) << "," << (batch.vertices[1].v >> 4) << ")" + << " stq1=(" << batch.vertices[1].s << "," << batch.vertices[1].t << "," << batch.vertices[1].q << ")" + << " v2=(" << batch.vertices[2].x << "," << batch.vertices[2].y << ")" + << " uv2=(" << (batch.vertices[2].u >> 4) << "," << (batch.vertices[2].v >> 4) << ")" + << " stq2=(" << batch.vertices[2].s << "," << batch.vertices[2].t << "," << batch.vertices[2].q << ")" + << " rgba0=(" << static_cast(batch.vertices[0].r) << "," + << static_cast(batch.vertices[0].g) << "," + << static_cast(batch.vertices[0].b) << "," + << static_cast(batch.vertices[0].a) << ")" + << " rgba1=(" << static_cast(batch.vertices[1].r) << "," + << static_cast(batch.vertices[1].g) << "," + << static_cast(batch.vertices[1].b) << "," + << static_cast(batch.vertices[1].a) << ")" + << " rgba2=(" << static_cast(batch.vertices[2].r) << "," + << static_cast(batch.vertices[2].g) << "," + << static_cast(batch.vertices[2].b) << "," + << static_cast(batch.vertices[2].a) << ")" + << std::endl; + } + }); + + PS2_IF_AGRESSIVE_LOGS({ + if ((state.prim.ctxt != 0u || ctx.frame.fbp == 150u) && + s_debugContext1PrimitiveCount.fetch_add(1u, std::memory_order_relaxed) < 32u) + { + std::cout << "[gs:copy-prim]" + << " type=" << static_cast(state.prim.type) + << " tme=" << static_cast(state.prim.tme) + << " abe=" << static_cast(state.prim.abe) + << " fst=" << static_cast(state.prim.fst) + << " ctxt=" << static_cast(state.prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(ctx.tex0.csa) + << ")" + << " texclut=(" + << "cbw=" << static_cast(state.texclut.cbw) + << " cou=" << static_cast(state.texclut.cou) + << " cov=" << state.texclut.cov + << ")" + << " ofx=" << (ctx.xyoffset.ofx >> 4) + << " ofy=" << (ctx.xyoffset.ofy >> 4) + << " scissor=(" << ctx.scissor.x0 + << "," << ctx.scissor.y0 + << ")-(" << ctx.scissor.x1 + << "," << ctx.scissor.y1 << ")" + << " test=0x" << std::hex << ctx.test + << " alpha=0x" << ctx.alpha + << std::dec << std::endl; + } + }); + + switch (state.prim.type) + { + case GS_PRIM_SPRITE: + DrawSprite(batch); + break; + case GS_PRIM_TRIANGLE: + case GS_PRIM_TRISTRIP: + case GS_PRIM_TRIFAN: + DrawTriangle(batch); + break; + case GS_PRIM_LINE: + case GS_PRIM_LINESTRIP: + DrawLine(batch); + break; + case GS_PRIM_POINT: + { + const GSVertex &v = batch.vertices[0]; + const auto &ctx = state.context; + int px = static_cast(v.x) - (ctx.xyoffset.ofx >> 4); + int py = static_cast(v.y) - (ctx.xyoffset.ofy >> 4); + WritePixel(state, px, py, static_cast(v.z), v.r, v.g, v.b, v.a, v.fog); + break; + } + default: + break; + } +} + +void GSCpuBackend::WritePixel(const GSDrawState &state, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog) +{ + const auto &ctx = state.context; + if (x < ctx.scissor.x0 || x > ctx.scissor.x1 || y < ctx.scissor.y0 || y > ctx.scissor.y1) + return; + + if (state.prim.fge) + { + const uint32_t inverseFog = 255u - fog; + auto applyFog = [&](uint8_t input, uint8_t fogColor) -> uint8_t + { + return static_cast(((static_cast(fog) * input) >> 8) + ((inverseFog * fogColor) >> 8)); + }; + + r = applyFog(r, state.fogR); + g = applyFog(g, state.fogG); + b = applyFog(b, state.fogB); + } + + const u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp); + const u32 fbw = std::max(ctx.frame.fbw, 1u); + const u32 fpsm = ctx.frame.psm; + const u32 zbp = GSInternal::framePageBaseToBlock(ctx.zbuf.zbp); + const u32 zpsm = ctx.zbuf.psm; + + const PixelWriteMask writeMask = classifyAlphaTest(ctx.test, a, static_cast(fpsm)); + if (!writeMask.writesAnything()) + { + return; + } + + const uint32_t ztestMethod = static_cast((ctx.test >> 17) & 3u); + const bool alphaBlendEnabled = state.prim.abe; + const bool preserveDestinationAlpha = writeMask.writeRgb && !writeMask.writeAlpha && fpsm == GS_PSM_CT32; + const bool destinationAlphaTestNeedsRead = ((ctx.test >> 14) & 0x1u) != 0u && (fpsm == GS_PSM_CT32 || fpsm == GS_PSM_CT16 || fpsm == GS_PSM_CT16S); + + // small optimization, avoid reading the framebuffer for simple draws + // TODO: only one address lookup for rmw + const bool frmw = destinationAlphaTestNeedsRead || (writeMask.writesFramebuffer() && ((ctx.frame.fbmsk != 0) || alphaBlendEnabled || preserveDestinationAlpha)); + + u32 rawFramebufferPixel = 0; + u32 fbrgba = 0; + if (frmw) + { + rawFramebufferPixel = ReadVramUnlocked(fpsm, fbp, fbw, x, y); + fbrgba = rawFramebufferPixel; + + if (bitsPerPixel(fpsm) == 16) + { + fbrgba = Rgba5551ToRgba8888(fbrgba); + } + else if (fpsm == GS_PSM_CT24) + { + // The GS supplies 0x80 as destination alpha for RGB24 blending. + fbrgba |= 0x80000000u; + } + } + + if (!passesDestinationAlphaTest(ctx.test, static_cast(fpsm), rawFramebufferPixel)) + { + return; + } + + bool zpass = false; + uint32_t storedZ = 0u; + switch (ztestMethod) + { + case 0: + zpass = false; + break; + case 1: + zpass = true; + break; + case 2: + storedZ = ReadVramUnlocked(zpsm, zbp, fbw, x, y); + zpass = static_cast(z) >= storedZ; + break; + case 3: + storedZ = ReadVramUnlocked(zpsm, zbp, fbw, x, y); + zpass = static_cast(z) > storedZ; + break; + } + + if (!zpass) + { + return; + } + + if (writeMask.writesFramebuffer()) + { + const u8 srcR = r; + const u8 srcG = g; + const u8 srcB = b; + + if (state.prim.abe) + { + uint8_t dr = fbrgba & 0xFF; + uint8_t dg = (fbrgba >> 8) & 0xFF; + uint8_t db = (fbrgba >> 16) & 0xFF; + uint8_t da = (fbrgba >> 24) & 0xFF; + + // PABE disables alpha blending when the source alpha MSB is clear. + if (!(state.pabe && (a & 0x80u) == 0u)) + { + uint64_t alphaReg = ctx.alpha; + uint8_t asel = alphaReg & 3; + uint8_t bsel = (alphaReg >> 2) & 3; + uint8_t csel = (alphaReg >> 4) & 3; + uint8_t dsel = (alphaReg >> 6) & 3; + uint8_t fix = static_cast((alphaReg >> 32) & 0xFF); + + auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int + { + if (sel == 0) + return cs; + if (sel == 1) + return cd; + return 0; + }; + int cAlpha = (csel == 0) ? a : (csel == 1) ? da + : fix; + + r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr)); + g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg)); + b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db)); + } + else + { + r = srcR; + g = srcG; + b = srcB; + } + } + + if (writeMask.writeAlpha && (ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24) + { + a = static_cast(a | 0x80u); + } + + u32 pixel = pack32(r, g, b, a); + + if (ctx.frame.fbmsk != 0) + { + pixel = (pixel & ~ctx.frame.fbmsk) | (fbrgba & ctx.frame.fbmsk); + } + + if (preserveDestinationAlpha) + { + pixel = (pixel & 0x00FFFFFFu) | (fbrgba & 0xFF000000u); + } + + // format conversion + if (bitsPerPixel(fpsm) == 16) + { + pixel = Rgba8888ToRgba5551(pixel); + } + + WriteVramUnlocked(fpsm, fbp, fbw, x, y, pixel); + } + + if (writeMask.writeDepth && !ctx.zbuf.zmask) + { + WriteVramUnlocked(zpsm, zbp, fbw, x, y, z); + } +} + +uint32_t GSCpuBackend::LookupCLUT(const GSDrawState &state, + uint8_t index, + uint32_t cbp, + uint8_t cpsm, + uint8_t csm, + uint8_t csa, + uint8_t sourcePsm) +{ + const uint32_t clutIndex = resolveClutIndex(index, cpsm, csm, csa, sourcePsm); + const uint32_t clutWidth = (state.texclut.cbw != 0u) ? static_cast(state.texclut.cbw) : 1u; + const uint32_t clutX = static_cast(state.texclut.cou) + (clutIndex & 0x0Fu); + const uint32_t clutY = static_cast(state.texclut.cov) + (clutIndex >> 4); + + switch (cpsm) + { + case GS_PSM_CT32: + return applyTexa(state.texa, cpsm, GSMem::ReadCT32(m_vram, cbp, clutWidth, clutX, clutY)); + case GS_PSM_CT24: + return applyTexa(state.texa, cpsm, GSMem::ReadCT24(m_vram, cbp, clutWidth, clutX, clutY)); + case GS_PSM_CT16: + return applyTexa(state.texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16(m_vram, cbp, clutWidth, clutX, clutY))); + case GS_PSM_CT16S: + return applyTexa(state.texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16S(m_vram, cbp, clutWidth, clutX, clutY))); + default: + break; + } + + return 0xFFFF00FFu; +} + +uint32_t GSCpuBackend::SampleTexture(const GSDrawState &state, float s, float t, float q, uint16_t u, uint16_t v) +{ + const auto &ctx = state.context; + const auto &tex = ctx.tex0; + + const int texW = state.textureWidth; + const int texH = state.textureHeight; + const uint64_t clamp = ctx.clamp; + const uint8_t wrapU = static_cast(clamp & 0x3u); + const uint8_t wrapV = static_cast((clamp >> 2) & 0x3u); + const uint16_t minU = static_cast((clamp >> 4) & 0x3FFu); + const uint16_t maxU = static_cast((clamp >> 14) & 0x3FFu); + const uint16_t minV = static_cast((clamp >> 24) & 0x3FFu); + const uint16_t maxV = static_cast((clamp >> 34) & 0x3FFu); + + float texUf, texVf; + if (state.prim.fst) + { + texUf = static_cast(u) / 16.0f; + texVf = static_cast(v) / 16.0f; + } + else + { + const float invQ = 1.0f / fabsQ(q); + texUf = s * invQ * static_cast(texW); + texVf = t * invQ * static_cast(texH); + } + + auto samplePoint = [&](int sampleU, int sampleV) -> uint32_t + { + sampleU = wrapTextureCoordinate(sampleU, texW, wrapU, minU, maxU); + sampleV = wrapTextureCoordinate(sampleV, texH, wrapV, minV, maxV); + + u32 out = ReadVramUnlocked(tex.psm, tex.tbp0, tex.tbw, sampleU, sampleV); + + switch (tex.psm) + { + case GS_PSM_CT32: + case GS_PSM_Z32: + case GS_PSM_CT24: + case GS_PSM_Z24: + return applyTexa(state.texa, tex.psm, out); + case GS_PSM_CT16: + case GS_PSM_CT16S: + case GS_PSM_Z16: + case GS_PSM_Z16S: + return applyTexa(state.texa, tex.psm, Rgba5551ToRgba8888(out)); + case GS_PSM_T8: + case GS_PSM_T8H: + case GS_PSM_T4: + case GS_PSM_T4HL: + case GS_PSM_T4HH: + return LookupCLUT(state, static_cast(out), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + } + + return 0xFFFF00FFu; + }; + + if (!state.linearFilter) + { + return samplePoint(static_cast(texUf), static_cast(texVf)); + } + + const float sampleU = texUf - 0.5f; + const float sampleV = texVf - 0.5f; + const int u0 = static_cast(std::floor(sampleU)); + const int v0 = static_cast(std::floor(sampleV)); + const int u1 = u0 + 1; + const int v1 = v0 + 1; + const float fx = sampleU - static_cast(u0); + const float fy = sampleV - static_cast(v0); + + const uint32_t c00 = samplePoint(u0, v0); + const uint32_t c10 = samplePoint(u1, v0); + const uint32_t c01 = samplePoint(u0, v1); + const uint32_t c11 = samplePoint(u1, v1); + + const uint8_t r = lerpChannel(static_cast(c00 & 0xFFu), + static_cast(c10 & 0xFFu), + static_cast(c01 & 0xFFu), + static_cast(c11 & 0xFFu), + fx, fy); + const uint8_t g = lerpChannel(static_cast((c00 >> 8) & 0xFFu), + static_cast((c10 >> 8) & 0xFFu), + static_cast((c01 >> 8) & 0xFFu), + static_cast((c11 >> 8) & 0xFFu), + fx, fy); + const uint8_t b = lerpChannel(static_cast((c00 >> 16) & 0xFFu), + static_cast((c10 >> 16) & 0xFFu), + static_cast((c01 >> 16) & 0xFFu), + static_cast((c11 >> 16) & 0xFFu), + fx, fy); + const uint8_t a = lerpChannel(static_cast((c00 >> 24) & 0xFFu), + static_cast((c10 >> 24) & 0xFFu), + static_cast((c01 >> 24) & 0xFFu), + static_cast((c11 >> 24) & 0xFFu), + fx, fy); + + return static_cast(r) | + (static_cast(g) << 8) | + (static_cast(b) << 16) | + (static_cast(a) << 24); +} + +void GSCpuBackend::DrawSprite(const GSPrimitiveBatch &batch) +{ + const GSDrawState &state = batch.state; + const GSVertex &v0 = batch.vertices[0]; + const GSVertex &v1 = batch.vertices[1]; + const auto &ctx = state.context; + + int ofx = ctx.xyoffset.ofx >> 4; + int ofy = ctx.xyoffset.ofy >> 4; + + int x0 = static_cast(v0.x) - ofx; + int y0 = static_cast(v0.y) - ofy; + int x1 = static_cast(v1.x) - ofx; + int y1 = static_cast(v1.y) - ofy; + u32 z1 = static_cast(v1.z); + + if (x0 > x1) + std::swap(x0, x1); + if (y0 > y1) + std::swap(y0, y1); + + const int unclippedX0 = x0; + const int unclippedY0 = y0; + const int spanX = std::max(1, x1 - x0); + const int spanY = std::max(1, y1 - y0); + const int unclippedX1 = unclippedX0 + spanX - 1; + const int unclippedY1 = unclippedY0 + spanY - 1; + + // If the sprite rectangle is fully outside scissor, nothing should render. + if (unclippedX1 < ctx.scissor.x0 || unclippedX0 > ctx.scissor.x1 || + unclippedY1 < ctx.scissor.y0 || unclippedY0 > ctx.scissor.y1) + return; + + const int drawX0 = clampInt(unclippedX0, ctx.scissor.x0, ctx.scissor.x1); + const int drawY0 = clampInt(unclippedY0, ctx.scissor.y0, ctx.scissor.y1); + const int drawX1 = clampInt(unclippedX1, ctx.scissor.x0, ctx.scissor.x1); + const int drawY1 = clampInt(unclippedY1, ctx.scissor.y0, ctx.scissor.y1); + + const uint64_t alphaReg = ctx.alpha; + const uint8_t alphaMode = static_cast(alphaReg & 0xFFu); + const uint8_t alphaFix = static_cast((alphaReg >> 32) & 0xFFu); + + uint8_t r = v1.r, g = v1.g, b = v1.b, a = v1.a; + + if (state.prim.tme) + { + const auto &tex = ctx.tex0; + const int texW = state.textureWidth; + const int texH = state.textureHeight; + + float u0f, v0f, u1f, v1f; + if (state.prim.fst) + { + u0f = static_cast(v0.u >> 4); + v0f = static_cast(v0.v >> 4); + u1f = static_cast(v1.u >> 4); + v1f = static_cast(v1.v >> 4); + } + else + { + const float q0 = fabsQ(v0.q); + const float q1 = fabsQ(v1.q); + u0f = (v0.s / q0) * static_cast(texW); + v0f = (v0.t / q0) * static_cast(texH); + u1f = (v1.s / q1) * static_cast(texW); + v1f = (v1.t / q1) * static_cast(texH); + } + + float spriteW = static_cast(spanX); + float spriteH = static_cast(spanY); + if (spriteW < 1.0f) + spriteW = 1.0f; + if (spriteH < 1.0f) + spriteH = 1.0f; + + for (int y = drawY0; y <= drawY1; ++y) + { + float ty = (static_cast(y - unclippedY0) + 0.5f) / spriteH; + float texVf = v0f + (v1f - v0f) * ty; + + for (int x = drawX0; x <= drawX1; ++x) + { + float tx = (static_cast(x - unclippedX0) + 0.5f) / spriteW; + float texUf = u0f + (u1f - u0f) * tx; + uint32_t texel = 0xFFFF00FFu; + if (state.prim.fst) + { + const int fixedU = static_cast((texUf * 16.0f) + 0.5f); + const int fixedV = static_cast((texVf * 16.0f) + 0.5f); + const uint16_t sampleU = static_cast(clampInt(fixedU, 0, 0xFFFF)); + const uint16_t sampleV = static_cast(clampInt(fixedV, 0, 0xFFFF)); + texel = SampleTexture(state, 0.0f, 0.0f, 1.0f, sampleU, sampleV); + } + else + { + texel = SampleTexture(state, texUf / static_cast(texW), texVf / static_cast(texH), 1.0f, 0u, 0u); + } + + uint8_t tr = static_cast(texel & 0xFF); + uint8_t tg = static_cast((texel >> 8) & 0xFF); + uint8_t tb = static_cast((texel >> 16) & 0xFF); + uint8_t ta = static_cast((texel >> 24) & 0xFF); + + const TextureCombineResult color = combineTexture(tex, r, g, b, a, tr, tg, tb, ta); + WritePixel(state, x, y, z1, color.r, color.g, color.b, color.a, v1.fog); + } + } + } + else + { + for (int y = drawY0; y <= drawY1; ++y) + for (int x = drawX0; x <= drawX1; ++x) + WritePixel(state, x, y, z1, r, g, b, a, v1.fog); + } +} + +void GSCpuBackend::DrawTriangle(const GSPrimitiveBatch &batch) +{ + const GSDrawState &state = batch.state; + const GSVertex &v0 = batch.vertices[0]; + const GSVertex &v1 = batch.vertices[1]; + const GSVertex &v2 = batch.vertices[2]; + const auto &ctx = state.context; + + int ofx = ctx.xyoffset.ofx >> 4; + int ofy = ctx.xyoffset.ofy >> 4; + + float fx0 = v0.x - static_cast(ofx); + float fy0 = v0.y - static_cast(ofy); + float fx1 = v1.x - static_cast(ofx); + float fy1 = v1.y - static_cast(ofy); + float fx2 = v2.x - static_cast(ofx); + float fy2 = v2.y - static_cast(ofy); + + int minX = static_cast(std::floor(std::min({fx0, fx1, fx2}))); + int maxX = static_cast(std::ceil(std::max({fx0, fx1, fx2}))); + int minY = static_cast(std::floor(std::min({fy0, fy1, fy2}))); + int maxY = static_cast(std::ceil(std::max({fy0, fy1, fy2}))); + + minX = clampInt(minX, ctx.scissor.x0, ctx.scissor.x1); + maxX = clampInt(maxX, ctx.scissor.x0, ctx.scissor.x1); + minY = clampInt(minY, ctx.scissor.y0, ctx.scissor.y1); + maxY = clampInt(maxY, ctx.scissor.y0, ctx.scissor.y1); + + float denom = (fy1 - fy2) * (fx0 - fx2) + (fx2 - fx1) * (fy0 - fy2); + if (std::fabs(denom) < 0.001f) + return; + + const float winding = (denom < 0.0f) ? -1.0f : 1.0f; + const float invAbsDenom = 1.0f / std::fabs(denom); + constexpr float kEdgeEpsilon = 1.0e-4f; + + for (int y = minY; y <= maxY; ++y) + { + float py = static_cast(y) + 0.5f; + for (int x = minX; x <= maxX; ++x) + { + float px = static_cast(x) + 0.5f; + + float w0 = (((fy1 - fy2) * (px - fx2) + (fx2 - fx1) * (py - fy2)) * winding) * invAbsDenom; + float w1 = (((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (py - fy2)) * winding) * invAbsDenom; + float w2 = 1.0f - w0 - w1; + + if (w0 < -kEdgeEpsilon || w1 < -kEdgeEpsilon || w2 < -kEdgeEpsilon) + continue; + + double z = v0.z * w0 + v1.z * w1 + v2.z * w2; + + uint8_t r, g, b, a; + if (state.prim.iip) + { + r = clampU8(static_cast(v0.r * w0 + v1.r * w1 + v2.r * w2)); + g = clampU8(static_cast(v0.g * w0 + v1.g * w1 + v2.g * w2)); + b = clampU8(static_cast(v0.b * w0 + v1.b * w1 + v2.b * w2)); + a = clampU8(static_cast(v0.a * w0 + v1.a * w1 + v2.a * w2)); + } + else + { + r = v2.r; + g = v2.g; + b = v2.b; + a = v2.a; + } + + if (state.prim.tme) + { + float is, it, iq; + uint16_t iu, iv; + if (state.prim.fst) + { + iu = static_cast(v0.u * w0 + v1.u * w1 + v2.u * w2); + iv = static_cast(v0.v * w0 + v1.v * w1 + v2.v * w2); + is = 0.0f; + it = 0.0f; + iq = 1.0f; + } + else + { + // The GS DDA interpolates the homogeneous S, T and Q + // values. Texel coordinates are calculated from S/Q and + // T/Q only after interpolation. + is = v0.s * w0 + v1.s * w1 + v2.s * w2; + it = v0.t * w0 + v1.t * w1 + v2.t * w2; + iq = v0.q * w0 + v1.q * w1 + v2.q * w2; + iu = 0; + iv = 0; + } + + uint32_t texel = SampleTexture(state, is, it, iq, iu, iv); + + uint8_t tr = static_cast(texel & 0xFF); + uint8_t tg = static_cast((texel >> 8) & 0xFF); + uint8_t tb = static_cast((texel >> 16) & 0xFF); + uint8_t ta = static_cast((texel >> 24) & 0xFF); + + const auto &tex = ctx.tex0; + const uint8_t shadeR = r; + const uint8_t shadeG = g; + const uint8_t shadeB = b; + const uint8_t shadeA = a; + const TextureCombineResult color = combineTexture(tex, shadeR, shadeG, shadeB, shadeA, tr, tg, tb, ta); + + r = color.r; + g = color.g; + b = color.b; + a = color.a; + } + + const uint8_t fog = clampU8(static_cast(v0.fog * w0 + v1.fog * w1 + v2.fog * w2)); + WritePixel(state, x, y, static_cast(z + 0.5), r, g, b, a, fog); + } + } +} + +void GSCpuBackend::DrawLine(const GSPrimitiveBatch &batch) +{ + const GSDrawState &state = batch.state; + const GSVertex &v0 = batch.vertices[0]; + const GSVertex &v1 = batch.vertices[1]; + const auto &ctx = state.context; + + int ofx = ctx.xyoffset.ofx >> 4; + int ofy = ctx.xyoffset.ofy >> 4; + + int x0 = static_cast(v0.x) - ofx; + int y0 = static_cast(v0.y) - ofy; + int x1 = static_cast(v1.x) - ofx; + int y1 = static_cast(v1.y) - ofy; + + int dx = std::abs(x1 - x0); + int dy = -std::abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx + dy; + + int totalSteps = std::max(std::abs(x1 - x0), std::abs(y1 - y0)); + if (totalSteps == 0) + totalSteps = 1; + int step = 0; + + for (;;) + { + float t = static_cast(step) / static_cast(totalSteps); + uint8_t r, g, b, a; + if (state.prim.iip) + { + r = clampU8(static_cast(v0.r + (v1.r - v0.r) * t)); + g = clampU8(static_cast(v0.g + (v1.g - v0.g) * t)); + b = clampU8(static_cast(v0.b + (v1.b - v0.b) * t)); + a = clampU8(static_cast(v0.a + (v1.a - v0.a) * t)); + } + else + { + r = v1.r; + g = v1.g; + b = v1.b; + a = v1.a; + } + + double z = (v0.z + (v1.z - v0.z) * t); + const uint8_t fog = clampU8(static_cast(v0.fog + (v1.fog - v0.fog) * t)); + WritePixel(state, x0, y0, static_cast(z), r, g, b, a, fog); + + if (x0 == x1 && y0 == y1) + break; + + int e2 = 2 * err; + if (e2 >= dy) + { + err += dy; + x0 += sx; + } + if (e2 <= dx) + { + err += dx; + y0 += sy; + } + ++step; + } +} + +void GSCpuBackend::BeginTransfer(const GSTransferCommand &command) +{ + std::lock_guard lock(m_mutex); + m_transfer = command; + m_transferState.x = command.trxpos.dsax; + m_transferState.y = command.trxpos.dsay; + m_transferState.totalPixels = static_cast(command.trxreg.rrw) * static_cast(command.trxreg.rrh); + m_transferState.copiedPixels = 0u; + m_transferState.direction = command.direction; + m_transferState.localToHostPendingBytes = 0u; + + if (command.direction == 2u) + PerformLocalToLocalTransfer(); + else if (command.direction == 1u) + PerformLocalToHostTransfer(); +} + +void GSCpuBackend::UploadImage(const uint8_t *data, uint32_t sizeBytes) +{ + std::lock_guard lock(m_mutex); + if (!data || sizeBytes == 0u || !m_vram || m_transferState.direction != 0u) + return; + if (m_transfer.trxreg.rrw == 0u || m_transfer.trxreg.rrh == 0u || m_transferState.totalPixels == 0u) + return; + + const uint32_t dbp = m_transfer.bitbltbuf.dbp; + const uint32_t dbw = std::max(m_transfer.bitbltbuf.dbw, 1u); + const uint8_t dpsm = m_transfer.bitbltbuf.dpsm; + const uint32_t rrw = m_transfer.trxreg.rrw; + const uint32_t dsax = m_transfer.trxpos.dsax; + uint32_t offset = 0u; + + auto advancePixel = [&](uint32_t count) + { + const uint32_t totalPixels = m_transferState.totalPixels; + m_transferState.copiedPixels = + std::min(totalPixels, m_transferState.copiedPixels + count); + + if (m_transferState.copiedPixels >= totalPixels) + { + m_transferState.direction = 3u; + m_transferState.totalPixels = 0u; + return; + } + + m_transferState.x = dsax + (m_transferState.copiedPixels % rrw); + m_transferState.y = m_transfer.trxpos.dsay + (m_transferState.copiedPixels / rrw); + }; + + while (offset < sizeBytes && m_transferState.direction == 0u) + { + switch (dpsm) + { + case GS_PSM_CT32: + case GS_PSM_Z32: + { + if (sizeBytes - offset < 4u) + return; + uint32_t value = 0u; + std::memcpy(&value, data + offset, sizeof(value)); + WriteVramUnlocked(dpsm, dbp, dbw, m_transferState.x, m_transferState.y, value); + offset += 4u; + advancePixel(1u); + break; + } + case GS_PSM_CT24: + case GS_PSM_Z24: + { + if (sizeBytes - offset < 3u) + return; + const uint32_t value = static_cast(data[offset]) | + (static_cast(data[offset + 1u]) << 8u) | + (static_cast(data[offset + 2u]) << 16u); + WriteVramUnlocked(dpsm, dbp, dbw, m_transferState.x, m_transferState.y, value); + offset += 3u; + advancePixel(1u); + break; + } + case GS_PSM_CT16: + case GS_PSM_CT16S: + case GS_PSM_Z16: + case GS_PSM_Z16S: + { + if (sizeBytes - offset < 2u) + return; + uint16_t value = 0u; + std::memcpy(&value, data + offset, sizeof(value)); + WriteVramUnlocked(dpsm, dbp, dbw, m_transferState.x, m_transferState.y, value); + offset += 2u; + advancePixel(1u); + break; + } + case GS_PSM_T8: + case GS_PSM_T8H: + WriteVramUnlocked(dpsm, dbp, dbw, m_transferState.x, m_transferState.y, data[offset++]); + advancePixel(1u); + break; + case GS_PSM_T4: + case GS_PSM_T4HL: + case GS_PSM_T4HH: + { + const uint8_t packed = data[offset++]; + const uint32_t firstPixel = m_transferState.copiedPixels; + WriteVramUnlocked(dpsm, dbp, dbw, + dsax + (firstPixel % rrw), + m_transfer.trxpos.dsay + (firstPixel / rrw), + packed & 0x0Fu); + if (firstPixel + 1u < m_transferState.totalPixels) + { + const uint32_t secondPixel = firstPixel + 1u; + WriteVramUnlocked(dpsm, dbp, dbw, + dsax + (secondPixel % rrw), + m_transfer.trxpos.dsay + (secondPixel / rrw), + (packed >> 4u) & 0x0Fu); + } + advancePixel(std::min(2u, m_transferState.totalPixels - firstPixel)); + break; + } + default: + return; + } + } +} + +void GSCpuBackend::PerformLocalToLocalTransfer() +{ + if (!m_vram) + return; + + const uint32_t rrw = m_transfer.trxreg.rrw; + const uint32_t rrh = m_transfer.trxreg.rrh; + const uint32_t total = rrw * rrh; + if (total == 0u) + { + m_transferState.direction = 3u; + return; + } + + for (uint32_t pixel = 0; pixel < total; ++pixel) + { + uint32_t x = pixel % rrw; + uint32_t y = pixel / rrw; + if ((m_transfer.trxpos.dir & 0x2u) != 0u) + x = rrw - x - 1u; + if ((m_transfer.trxpos.dir & 0x1u) != 0u) + y = rrh - y - 1u; + + const uint32_t value = ReadVramUnlocked(m_transfer.bitbltbuf.spsm, + m_transfer.bitbltbuf.sbp, + std::max(m_transfer.bitbltbuf.sbw, 1u), + x + m_transfer.trxpos.ssax, + y + m_transfer.trxpos.ssay); + WriteVramUnlocked(m_transfer.bitbltbuf.dpsm, + m_transfer.bitbltbuf.dbp, + std::max(m_transfer.bitbltbuf.dbw, 1u), + x + m_transfer.trxpos.dsax, + y + m_transfer.trxpos.dsay, + value); + } + + m_transferState.copiedPixels = total; + m_transferState.direction = 3u; +} + +void GSCpuBackend::PerformLocalToHostTransfer() +{ + m_localToHostBuffer.clear(); + m_localToHostReadPos = 0u; + if (!m_vram) + return; + + const uint32_t rrw = m_transfer.trxreg.rrw; + const uint32_t rrh = m_transfer.trxreg.rrh; + const uint32_t sbw = std::max(m_transfer.bitbltbuf.sbw, 1u); + const uint8_t spsm = m_transfer.bitbltbuf.spsm; + const uint32_t bpp = static_cast(GSMem::BitsPerPixel(static_cast(spsm))); + const uint32_t total = rrw * rrh; + m_localToHostBuffer.reserve((static_cast(total) * bpp + 7u) / 8u); + + for (uint32_t pixel = 0u; pixel < total; ++pixel) + { + const uint32_t x = pixel % rrw; + const uint32_t y = pixel / rrw; + const uint32_t value = ReadVramUnlocked(spsm, + m_transfer.bitbltbuf.sbp, + sbw, + x + m_transfer.trxpos.ssax, + y + m_transfer.trxpos.ssay); + switch (bpp) + { + case 32: + m_localToHostBuffer.push_back(static_cast(value)); + m_localToHostBuffer.push_back(static_cast(value >> 8u)); + m_localToHostBuffer.push_back(static_cast(value >> 16u)); + m_localToHostBuffer.push_back(static_cast(value >> 24u)); + break; + case 24: + m_localToHostBuffer.push_back(static_cast(value)); + m_localToHostBuffer.push_back(static_cast(value >> 8u)); + m_localToHostBuffer.push_back(static_cast(value >> 16u)); + break; + case 16: + m_localToHostBuffer.push_back(static_cast(value)); + m_localToHostBuffer.push_back(static_cast(value >> 8u)); + break; + case 8: + m_localToHostBuffer.push_back(static_cast(value)); + break; + case 4: + { + if ((pixel & 1u) != 0u) + break; + uint32_t next = 0u; + if (pixel + 1u < total) + { + const uint32_t nextPixel = pixel + 1u; + const uint32_t nextX = nextPixel % rrw; + const uint32_t nextY = nextPixel / rrw; + next = ReadVramUnlocked(spsm, m_transfer.bitbltbuf.sbp, sbw, + nextX + m_transfer.trxpos.ssax, + nextY + m_transfer.trxpos.ssay); + } + m_localToHostBuffer.push_back(static_cast((value & 0x0Fu) | ((next & 0x0Fu) << 4u))); + break; + } + default: + break; + } + } + + m_transferState.copiedPixels = total; + m_transferState.localToHostPendingBytes = m_localToHostBuffer.size(); +} + +uint32_t GSCpuBackend::ConsumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) +{ + std::lock_guard lock(m_mutex); + if (!dst || maxBytes == 0u || m_localToHostReadPos >= m_localToHostBuffer.size()) + return 0u; + const size_t count = std::min(maxBytes, m_localToHostBuffer.size() - m_localToHostReadPos); + std::memcpy(dst, m_localToHostBuffer.data() + m_localToHostReadPos, count); + m_localToHostReadPos += count; + m_transferState.localToHostPendingBytes = m_localToHostBuffer.size() - m_localToHostReadPos; + return static_cast(count); +} + +bool GSCpuBackend::ClearFramebuffer(const GSContext &context, uint32_t rgba) +{ + std::lock_guard lock(m_mutex); + if (!m_vram || context.frame.fbw == 0u) + return false; + + const uint32_t x0 = context.scissor.x0; + const uint32_t x1 = std::max(x0, context.scissor.x1); + const uint32_t y0 = context.scissor.y0; + const uint32_t y1 = std::max(y0, context.scissor.y1); + uint8_t r = static_cast(rgba); + uint8_t g = static_cast(rgba >> 8u); + uint8_t b = static_cast(rgba >> 16u); + uint8_t a = static_cast(rgba >> 24u); + if ((context.fba & 1ull) != 0ull && context.frame.psm != GS_PSM_CT24) + a |= 0x80u; + + const uint32_t fbp = GSInternal::framePageBaseToBlock(context.frame.fbp); + const uint32_t fbw = std::max(context.frame.fbw, 1u); + if (context.frame.psm == GS_PSM_CT32 || context.frame.psm == GS_PSM_CT24) + { + const uint32_t source = static_cast(r) | + (static_cast(g) << 8u) | + (static_cast(b) << 16u) | + (static_cast(a) << 24u); + for (uint32_t y = y0; y <= y1; ++y) + for (uint32_t x = x0; x <= x1; ++x) + { + uint32_t pixel = source; + if (context.frame.fbmsk != 0u) + { + const uint32_t old = ReadVramUnlocked(context.frame.psm, fbp, fbw, x, y); + pixel = (pixel & ~context.frame.fbmsk) | (old & context.frame.fbmsk); + } + WriteVramUnlocked(context.frame.psm, fbp, fbw, x, y, pixel); + } + return true; + } + + if (context.frame.psm == GS_PSM_CT16 || context.frame.psm == GS_PSM_CT16S) + { + const uint16_t source = encodeFramePixelPSMCT16(r, g, b, a); + const uint16_t mask = static_cast(context.frame.fbmsk); + for (uint32_t y = y0; y <= y1; ++y) + for (uint32_t x = x0; x <= x1; ++x) + { + uint16_t pixel = source; + if (mask != 0u) + { + const uint16_t old = static_cast(ReadVramUnlocked(context.frame.psm, fbp, fbw, x, y)); + pixel = static_cast((pixel & ~mask) | (old & mask)); + } + WriteVramUnlocked(context.frame.psm, fbp, fbw, x, y, pixel); + } + return true; + } + return false; +} + +bool GSCpuBackend::CopyFrameToHostRgba(const GSFrameReg &frame, + uint32_t width, + uint32_t height, + std::vector &outPixels, + bool preserveAlpha, + bool useLocalMemoryLayout, + bool frameBaseIsPages, + uint32_t sourceOriginX, + uint32_t sourceOriginY) const +{ + if (!m_vram || m_vramSize == 0u) + return false; + + outPixels.assign(kHostFrameWidth * kHostFrameHeight * 4u, 0u); + const uint32_t baseBytes = frameBaseIsPages ? frame.fbp * 8192u : frame.fbp * 256u; + const uint32_t basePtr = frameBaseIsPages ? GSInternal::framePageBaseToBlock(frame.fbp) : frame.fbp; + const uint32_t fbw = frame.fbw ? frame.fbw : kHostFrameWidth / 64u; + const uint32_t bytesPerPixel = (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) ? 2u : 4u; + const uint32_t stride = fbw * 64u * bytesPerPixel; + + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *dst = outPixels.data() + y * kHostFrameWidth * 4u; + for (uint32_t x = 0; x < width; ++x) + { + const uint32_t sx = sourceOriginX + x; + const uint32_t sy = sourceOriginY + y; + if (frame.psm == GS_PSM_CT32 || frame.psm == GS_PSM_CT24) + { + uint32_t color = 0u; + if (useLocalMemoryLayout) + color = ReadVramUnlocked(frame.psm, basePtr, fbw, sx, sy); + else + { + const uint32_t pixelBytes = frame.psm == GS_PSM_CT24 ? 3u : 4u; + const uint64_t offset = static_cast(baseBytes) + static_cast(sy) * stride + static_cast(sx) * pixelBytes; + if (offset + pixelBytes > m_vramSize) + return false; + color = m_vram[offset] | (static_cast(m_vram[offset + 1u]) << 8u) | + (static_cast(m_vram[offset + 2u]) << 16u); + if (pixelBytes == 4u) + color |= static_cast(m_vram[offset + 3u]) << 24u; + } + dst[x * 4u] = static_cast(color); + dst[x * 4u + 1u] = static_cast(color >> 8u); + dst[x * 4u + 2u] = static_cast(color >> 16u); + dst[x * 4u + 3u] = preserveAlpha && frame.psm != GS_PSM_CT24 ? static_cast(color >> 24u) : 255u; + } + else if (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) + { + uint16_t color = 0u; + if (useLocalMemoryLayout) + color = static_cast(ReadVramUnlocked(frame.psm, basePtr, fbw, sx, sy)); + else + { + const uint64_t offset = static_cast(baseBytes) + static_cast(sy) * stride + static_cast(sx) * 2u; + if (offset + 2u > m_vramSize) + return false; + std::memcpy(&color, m_vram + offset, sizeof(color)); + } + const uint32_t r = color & 31u; + const uint32_t g = (color >> 5u) & 31u; + const uint32_t b = (color >> 10u) & 31u; + dst[x * 4u] = static_cast((r << 3u) | (r >> 2u)); + dst[x * 4u + 1u] = static_cast((g << 3u) | (g >> 2u)); + dst[x * 4u + 2u] = static_cast((b << 3u) | (b >> 2u)); + dst[x * 4u + 3u] = preserveAlpha ? ((color & 0x8000u) ? 0x80u : 0u) : 255u; + } + else + { + outPixels.clear(); + return false; + } + } + } + return true; +} + +PresentationFrame GSCpuBackend::Present(const GSPresentationRequest &request) +{ + // Snapshot local memory under the backend lock, then perform the expensive + // display conversion without holding the producer-side raster lock. + thread_local std::vector snapshot; + SnapshotVram(snapshot); + if (snapshot.empty()) + return {}; + + thread_local GSCpuBackend snapshotBackend; + snapshotBackend.Initialize(snapshot.data(), static_cast(snapshot.size())); + return snapshotBackend.PresentFromLocalMemory(request); +} + +PresentationFrame GSCpuBackend::PresentFromLocalMemory(const GSPresentationRequest &request) +{ + PresentationFrame result{}; + const GSPmodeState pmode = decodePmode(request.pmode); + const GSSmode2State smode2 = decodeSMode2(request.smode2); + const bool fieldMode = smode2.interlaced && !smode2.frameMode; + const bool oddField = (request.vsyncTick & 1ull) != 0ull; + const GSFrameReg displayFrame1 = decodeDisplayFrame(request.dispfb1); + const GSFrameReg displayFrame2 = decodeDisplayFrame(request.dispfb2); + const GSDisplayReadOrigin origin1 = decodeDisplayReadOrigin(request.dispfb1); + const GSDisplayReadOrigin origin2 = decodeDisplayReadOrigin(request.dispfb2); + uint32_t width1 = 0u, height1 = 0u, width2 = 0u, height2 = 0u; + decodeDisplaySize(request.display1, width1, height1); + decodeDisplaySize(request.display2, width2, height2); + const bool valid1 = pmode.enableCrt1 && hasDisplaySetup(request.display1, displayFrame1); + const bool valid2 = pmode.enableCrt2 && hasDisplaySetup(request.display2, displayFrame2); + if (!valid1 && !valid2) + return result; + + auto copySource = [&](const GSFrameReg &displayFrame, + const GSDisplayReadOrigin &origin, + uint32_t width, + uint32_t height, + bool allowPreferred, + bool preserveAlpha, + GSFrameReg &selected, + std::vector &pixels, + bool &usedPreferred) -> bool + { + selected = displayFrame; + pixels.clear(); + usedPreferred = false; + if (allowPreferred && request.hasPreferredSource && request.preferredDestFbp == displayFrame.fbp && + (request.preferredSource.fbw != 0u || request.preferredSource.fbp != displayFrame.fbp) && + CopyFrameToHostRgba(request.preferredSource, width, height, pixels, preserveAlpha, true, false, 0u, 0u)) + { + selected = request.preferredSource; + usedPreferred = true; + } + if (pixels.empty() && !CopyFrameToHostRgba(displayFrame, width, height, pixels, preserveAlpha, true, true, origin.x, origin.y)) + return false; + + if (!usedPreferred && displayFrame.fbp == 0u && countNonBlackPixels(pixels, width, height) == 0u) + { + for (const GSFrameReg &candidate : request.contextFrames) + { + if (candidate.fbp == selected.fbp && candidate.fbw == selected.fbw && candidate.psm == selected.psm) + continue; + std::vector candidatePixels; + if (!CopyFrameToHostRgba(candidate, width, height, candidatePixels, preserveAlpha, true, true, 0u, 0u)) + continue; + if (countNonBlackPixels(candidatePixels, width, height) == 0u) + continue; + selected = candidate; + pixels.swap(candidatePixels); + break; + } + } + return true; + }; + + if (valid1 && valid2) + { + GSFrameReg selected1{}, selected2{}; + std::vector crt1, crt2; + bool preferred1 = false, preferred2 = false; + if (copySource(displayFrame1, origin1, width1, height1, false, true, selected1, crt1, preferred1) && + copySource(displayFrame2, origin2, width2, height2, false, true, selected2, crt2, preferred2)) + { + result.width = std::max(width1, width2); + result.height = std::max(height1, height2); + result.pixels.assign(kHostFrameWidth * kHostFrameHeight * 4u, 0u); + const uint8_t bgR = static_cast(request.bgcolor); + const uint8_t bgG = static_cast(request.bgcolor >> 8u); + const uint8_t bgB = static_cast(request.bgcolor >> 16u); + for (uint32_t y = 0; y < result.height; ++y) + for (uint32_t x = 0; x < result.width; ++x) + { + uint8_t *dst = result.pixels.data() + (y * kHostFrameWidth + x) * 4u; + dst[0] = bgR; + dst[1] = bgG; + dst[2] = bgB; + dst[3] = pmode.alp; + } + if (!pmode.slbg) + for (uint32_t y = 0; y < height2; ++y) + std::memcpy(result.pixels.data() + y * kHostFrameWidth * 4u, crt2.data() + y * kHostFrameWidth * 4u, width2 * 4u); + for (uint32_t y = 0; y < height1; ++y) + for (uint32_t x = 0; x < width1; ++x) + { + const uint8_t *src = crt1.data() + (y * kHostFrameWidth + x) * 4u; + uint8_t *dst = result.pixels.data() + (y * kHostFrameWidth + x) * 4u; + const uint32_t factor = pmode.mmod ? pmode.alp : std::min(255u, static_cast(src[3]) * 2u); + dst[0] = blendPresentationChannel(src[0], dst[0], factor); + dst[1] = blendPresentationChannel(src[1], dst[1], factor); + dst[2] = blendPresentationChannel(src[2], dst[2], factor); + dst[3] = pmode.amod ? dst[3] : src[3]; + } + normalizePresentationAlpha(result.pixels, result.width, result.height); + if (fieldMode) + applyFieldPresentation(result.pixels, result.width, result.height, oddField); + result.displayFbp = displayFrame1.fbp; + result.sourceFbp = selected1.fbp; + return result; + } + } + + const GSFrameReg &displayFrame = valid1 ? displayFrame1 : displayFrame2; + const GSDisplayReadOrigin &origin = valid1 ? origin1 : origin2; + result.width = valid1 ? width1 : width2; + result.height = valid1 ? height1 : height2; + GSFrameReg selected = displayFrame; + if (!copySource(displayFrame, origin, result.width, result.height, true, false, selected, result.pixels, result.usedPreferred)) + return {}; + if (fieldMode) + applyFieldPresentation(result.pixels, result.width, result.height, oddField); + normalizePresentationAlpha(result.pixels, result.width, result.height); + result.displayFbp = displayFrame.fbp; + result.sourceFbp = selected.fbp; + return result; +} diff --git a/ps2xRuntime/src/lib/gs/gs_frontend.cpp b/ps2xRuntime/src/lib/gs/gs_frontend.cpp new file mode 100644 index 0000000..fd5a90d --- /dev/null +++ b/ps2xRuntime/src/lib/gs/gs_frontend.cpp @@ -0,0 +1,1733 @@ +#include "runtime/gs/gs_frontend.h" +#include "runtime/gs/gs_cpu_backend.h" +#include "ps2_log.h" +#include "runtime/ps2_memory.h" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + static constexpr uint32_t kHostFrameWidth = 640u; + + GSPrimReg decodePrimRegister(uint64_t value) + { + GSPrimReg prim{}; + prim.type = static_cast(value & 0x7u); + prim.iip = ((value >> 3) & 1u) != 0u; + prim.tme = ((value >> 4) & 1u) != 0u; + prim.fge = ((value >> 5) & 1u) != 0u; + prim.abe = ((value >> 6) & 1u) != 0u; + prim.aa1 = ((value >> 7) & 1u) != 0u; + prim.fst = ((value >> 8) & 1u) != 0u; + prim.ctxt = ((value >> 9) & 1u) != 0u; + prim.fix = ((value >> 10) & 1u) != 0u; + return prim; + } + + static inline uint64_t loadLE64(const uint8_t *p) + { + uint64_t v; + std::memcpy(&v, p, 8); + return v; + } + + struct PackedGifPacketTag + { + uint64_t lo = 0u; + uint64_t hi = 0u; + uint32_t payloadOffset = 0u; + uint32_t nloop = 0u; + uint32_t nreg = 0u; + uint8_t regs[16]{}; + }; + + template + bool visitPackedGifPacket(const uint8_t *data, uint32_t sizeBytes, Visitor &&visitor) + { + uint32_t offset = 0u; + while (offset + 16u <= sizeBytes) + { + PackedGifPacketTag tag{}; + tag.lo = loadLE64(data + offset); + tag.hi = loadLE64(data + offset + 8u); + + const uint8_t flg = static_cast((tag.lo >> 58u) & 0x3u); + if (flg != GIF_FMT_PACKED) + return false; + + tag.nloop = static_cast(tag.lo & 0x7FFFu); + tag.nreg = static_cast((tag.lo >> 60u) & 0xFu); + if (tag.nreg == 0u) + tag.nreg = 16u; + + const uint64_t payloadBytes64 = + static_cast(tag.nloop) * static_cast(tag.nreg) * 16ull; + if (payloadBytes64 > 0xFFFFFFFFull) + return false; + + offset += 16u; + const uint32_t payloadBytes = static_cast(payloadBytes64); + if (payloadBytes > sizeBytes - offset) + return false; + + tag.payloadOffset = offset; + for (uint32_t i = 0u; i < tag.nreg; ++i) + tag.regs[i] = static_cast((tag.hi >> (i * 4u)) & 0xFu); + + if (!visitor(tag)) + return false; + + offset += payloadBytes; + } + + return offset == sizeBytes; + } + + bool validatePackedGifPacket(const uint8_t *data, uint32_t sizeBytes) + { + return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &) + { return true; }); + } + + std::atomic s_debugGifPacketCount{0}; + std::atomic s_debugGsRegisterCount{0}; + std::atomic s_debugGsPackedVertexCount{0}; + std::atomic s_debugGsVertexKickCount{0}; + std::atomic s_debugCopyRegCount{0}; + std::atomic s_debugTexaWriteCount{0}; + std::atomic s_debugCvFontUploadCount{0}; + std::atomic s_debugLocalCopyCount{0}; +} + + +GS::GS() + : m_backend(std::make_unique()) +{ + reset(); +} + +void GS::init(uint8_t *vram, uint32_t vramSize, GSRegisters *privRegs) +{ + m_localMemoryStorage = vram; + m_localMemorySize = vramSize; + m_privRegs = privRegs; + if (!m_backend) + m_backend = std::make_unique(); + m_backend->Initialize(vram, vramSize); + reset(); +} + +void GS::reset() +{ + std::lock_guard lock(m_stateMutex); + std::memset(m_ctx, 0, sizeof(m_ctx)); + m_prim = {}; + m_primRegister = {}; + m_prmodeRegister = {}; + m_curR = 0x80; + m_curG = 0x80; + m_curB = 0x80; + m_curA = 0x80; + m_curQ = 1.0f; + m_curS = 0.0f; + m_curT = 0.0f; + m_curU = 0; + m_curV = 0; + m_curFog = 0; + m_fogR = 0; + m_fogG = 0; + m_fogB = 0; + m_prmodecont = true; + m_pabe = false; + m_scanmsk = 0u; + m_dimx = 0u; + m_dthe = 0u; + m_colclamp = 0u; + m_texa = {0u, false, 0u}; + m_texclut = {0u, 0u, 0u}; + m_bitbltbuf = {}; + m_trxpos = {}; + m_trxreg = {}; + m_trxdir = 3; + m_vtxCount = 0; + m_vtxIndex = 0; + m_preferredDisplaySourceFrame = {}; + m_preferredDisplayDestFbp = 0; + m_hasPreferredDisplaySource = false; + if (m_backend) + { + m_backend->Flush(); + m_backend->Sync(GSSyncReason::Reset); + m_backend->Reset(); + } + { + std::lock_guard presentationLock(m_presentationMutex); + m_hostPresentationFrame.clear(); + m_hostPresentationWidth = 0u; + m_hostPresentationHeight = 0u; + m_hostPresentationDisplayFbp = 0u; + m_hostPresentationSourceFbp = 0u; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = false; + } + + m_debugHistoryWrite = 0; + m_debugHistoryCount = 0; + m_debugNextSeq = 1; + m_debugFrameIndex = 0; + m_debugLastVsyncTick = UINT64_MAX; + + for (int i = 0; i < 2; ++i) + { + m_ctx[i].frame.fbw = 10; + m_ctx[i].scissor = {0, 639, 0, 447}; + m_ctx[i].xyoffset = {0, 0}; + } +} + +GSContext &GS::activeContext() +{ + return m_ctx[m_prim.ctxt ? 1 : 0]; +} + +void GS::snapshotVRAM() +{ + // Presentation/debug snapshots run outside m_stateMutex so the EE can keep + // feeding the GS while a backend performs host-side conversion. Keep the + // selected backend alive and unswappable for the duration of the call. + std::lock_guard backendLock(m_backendLifetimeMutex); + if (!m_backend) + return; + std::vector snapshot; + m_backend->Sync(GSSyncReason::DebugReadback); + m_backend->SnapshotVram(snapshot); + std::lock_guard lock(m_snapshotMutex); + m_displaySnapshot.swap(snapshot); +} + +const uint8_t *GS::lockDisplaySnapshot(uint32_t &outSize) +{ + m_snapshotMutex.lock(); + if (m_displaySnapshot.empty()) + { + outSize = 0; + return nullptr; + } + + outSize = static_cast(m_displaySnapshot.size()); + return m_displaySnapshot.data(); +} + +GSDebugSnapshot GS::getDebugSnapshot() const +{ + std::lock_guard lock(m_stateMutex); + + GSDebugSnapshot snapshot{}; + snapshot.ctx[0] = m_ctx[0]; + snapshot.ctx[1] = m_ctx[1]; + snapshot.prim = m_prim; + snapshot.texa = m_texa; + snapshot.texclut = m_texclut; + snapshot.scanmsk = m_scanmsk; + snapshot.dimx = m_dimx; + snapshot.dthe = m_dthe; + snapshot.colclamp = m_colclamp; + snapshot.bitbltbuf = m_bitbltbuf; + snapshot.trxpos = m_trxpos; + snapshot.trxreg = m_trxreg; + const GSTransferSnapshot transfer = m_backend ? m_backend->GetTransferSnapshot() : GSTransferSnapshot{}; + snapshot.trxdir = transfer.direction; + snapshot.transferX = transfer.x; + snapshot.transferY = transfer.y; + snapshot.transferTotalPixels = transfer.totalPixels; + snapshot.transferCopiedPixels = transfer.copiedPixels; + snapshot.lastDisplayBaseBytes = m_lastDisplayBaseBytes; + snapshot.preferredDisplaySourceFrame = m_preferredDisplaySourceFrame; + snapshot.preferredDisplayDestFbp = m_preferredDisplayDestFbp; + snapshot.hasPreferredDisplaySource = m_hasPreferredDisplaySource; + { + std::lock_guard presentationLock(m_presentationMutex); + snapshot.hostPresentationWidth = m_hostPresentationWidth; + snapshot.hostPresentationHeight = m_hostPresentationHeight; + snapshot.hostPresentationDisplayFbp = m_hostPresentationDisplayFbp; + snapshot.hostPresentationSourceFbp = m_hostPresentationSourceFbp; + snapshot.hostPresentationUsedPreferred = m_hostPresentationUsedPreferred; + snapshot.hasHostPresentationFrame = m_hasHostPresentationFrame; + } + snapshot.localToHostPendingBytes = transfer.localToHostPendingBytes; + return snapshot; +} + +std::vector GS::getDebugHistory() const +{ + std::lock_guard lock(m_stateMutex); + + std::vector out; + out.reserve(m_debugHistoryCount); + const size_t first = (m_debugHistoryWrite + kDebugHistoryCapacity - m_debugHistoryCount) % kDebugHistoryCapacity; + for (size_t i = 0; i < m_debugHistoryCount; ++i) + { + out.push_back(m_debugHistory[(first + i) % kDebugHistoryCapacity]); + } + return out; +} + +void GS::clearDebugHistory() +{ + std::lock_guard lock(m_stateMutex); + m_debugHistoryWrite = 0; + m_debugHistoryCount = 0; + m_debugNextSeq = 1; + m_debugFrameIndex = 0; + m_debugLastVsyncTick = UINT64_MAX; +} + +bool GS::isDebugHistoryPaused() const +{ + std::lock_guard lock(m_stateMutex); + return m_debugHistoryPaused; +} + +void GS::setDebugHistoryPaused(bool paused) +{ + std::lock_guard lock(m_stateMutex); + m_debugHistoryPaused = paused; +} + +GSDebugHistoryEntry GS::makeDebugEventUnlocked(GSDebugEventKind kind) const +{ + GSDebugHistoryEntry entry{}; + entry.kind = kind; + entry.prim = m_prim; + const uint32_t ci = m_prim.ctxt ? 1u : 0u; + entry.frame = m_ctx[ci].frame; + entry.zbuf = m_ctx[ci].zbuf; + entry.tex0 = m_ctx[ci].tex0; + entry.scissor = m_ctx[ci].scissor; + entry.test = m_ctx[ci].test; + entry.alpha = m_ctx[ci].alpha; + entry.bitbltbuf = m_bitbltbuf; + entry.trxpos = m_trxpos; + entry.trxreg = m_trxreg; + const GSTransferSnapshot transfer = m_backend ? m_backend->GetTransferSnapshot() : GSTransferSnapshot{}; + entry.trxdir = transfer.direction; + entry.transferPixels = transfer.totalPixels; + return entry; +} + +void GS::recordDebugEventUnlocked(GSDebugHistoryEntry entry) +{ + if (m_debugHistoryPaused) + { + return; + } + + const uint64_t tick = m_privRegs ? m_privRegs->vsyncTick.load(std::memory_order_acquire) : 0u; + if (m_debugLastVsyncTick == UINT64_MAX) + { + m_debugLastVsyncTick = tick; + } + else if (tick != m_debugLastVsyncTick) + { + ++m_debugFrameIndex; + m_debugLastVsyncTick = tick; + } + + entry.seq = m_debugNextSeq++; + entry.vsyncTick = tick; + entry.frameIndex = m_debugFrameIndex; + + m_debugHistory[m_debugHistoryWrite] = entry; + m_debugHistoryWrite = (m_debugHistoryWrite + 1u) % kDebugHistoryCapacity; + if (m_debugHistoryCount < kDebugHistoryCapacity) + { + ++m_debugHistoryCount; + } +} + +void GS::recordGifTagDebugEventUnlocked(uint32_t sizeBytes, uint32_t nloop, uint8_t flg, uint32_t nreg) +{ + if (m_debugHistoryPaused) + { + return; + } + + GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::GifTag); + entry.gifSizeBytes = sizeBytes; + entry.gifNloop = nloop; + entry.gifFlg = flg; + entry.gifNreg = static_cast(std::min(nreg, 16u)); + recordDebugEventUnlocked(entry); +} + +void GS::recordRegisterDebugEventUnlocked(uint8_t regAddr, uint64_t value) +{ + if (m_debugHistoryPaused) + { + return; + } + + switch (regAddr) + { + case GS_REG_PRIM: + case GS_REG_TEX0_1: + case GS_REG_TEX0_2: + case GS_REG_TEX2_1: + case GS_REG_TEX2_2: + case GS_REG_TEXA: + case GS_REG_TEXCLUT: + case GS_REG_FRAME_1: + case GS_REG_FRAME_2: + case GS_REG_ZBUF_1: + case GS_REG_ZBUF_2: + case GS_REG_ALPHA_1: + case GS_REG_ALPHA_2: + case GS_REG_TEST_1: + case GS_REG_TEST_2: + case GS_REG_SCISSOR_1: + case GS_REG_SCISSOR_2: + case GS_REG_XYOFFSET_1: + case GS_REG_XYOFFSET_2: + case GS_REG_BITBLTBUF: + case GS_REG_TRXPOS: + case GS_REG_TRXREG: + case GS_REG_TRXDIR: + break; + default: + return; + } + + GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Register); + entry.reg = regAddr; + entry.regValue = value; + recordDebugEventUnlocked(entry); +} + +void GS::recordDrawDebugEventUnlocked(int vertexCount) +{ + if (m_debugHistoryPaused) + { + return; + } + + if (vertexCount <= 0) + { + return; + } + + GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Draw); + entry.vertexCount = static_cast(vertexCount); + + const int count = std::min(vertexCount, kMaxVerts); + entry.xMin = entry.xMax = m_vtxQueue[0].x; + entry.yMin = entry.yMax = m_vtxQueue[0].y; + entry.zMin = entry.zMax = m_vtxQueue[0].z; + entry.aMin = entry.aMax = m_vtxQueue[0].a; + + for (int i = 1; i < count; ++i) + { + const GSVertex &v = m_vtxQueue[i]; + entry.xMin = std::min(entry.xMin, v.x); + entry.xMax = std::max(entry.xMax, v.x); + entry.yMin = std::min(entry.yMin, v.y); + entry.yMax = std::max(entry.yMax, v.y); + entry.zMin = std::min(entry.zMin, v.z); + entry.zMax = std::max(entry.zMax, v.z); + entry.aMin = std::min(entry.aMin, v.a); + entry.aMax = std::max(entry.aMax, v.a); + } + + recordDebugEventUnlocked(entry); +} + +void GS::recordTransferDebugEventUnlocked() +{ + if (m_debugHistoryPaused) + { + return; + } + + GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Transfer); + entry.transferPixels = m_backend ? m_backend->GetTransferSnapshot().totalPixels : 0u; + recordDebugEventUnlocked(entry); +} + +void GS::recordPresentDebugEventUnlocked(uint32_t displayFbp, uint32_t sourceFbp, uint32_t width, uint32_t height, bool usedPreferred) +{ + if (m_debugHistoryPaused) + { + return; + } + + GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Present); + entry.displayFbp = displayFbp; + entry.sourceFbp = sourceFbp; + entry.width = width; + entry.height = height; + entry.usedPreferred = usedPreferred; + recordDebugEventUnlocked(entry); +} + +bool GS::getPreferredDisplaySource(GSFrameReg &outSource, uint32_t &outDestFbp) const +{ + std::lock_guard lock(m_stateMutex); + if (!m_hasPreferredDisplaySource) + { + outSource = {}; + outDestFbp = 0u; + return false; + } + + outSource = m_preferredDisplaySourceFrame; + outDestFbp = m_preferredDisplayDestFbp; + return true; +} + +void GS::unlockDisplaySnapshot() +{ + m_snapshotMutex.unlock(); +} + +uint32_t GS::getLastDisplayBaseBytes() const +{ + return m_lastDisplayBaseBytes; +} + +void GS::refreshDisplaySnapshot() +{ + snapshotVRAM(); +} + +GSPresentationRequest GS::buildPresentationRequestUnlocked() const +{ + GSPresentationRequest request{}; + if (!m_privRegs) + return request; + request.pmode = m_privRegs->pmode; + request.smode2 = m_privRegs->smode2; + request.dispfb1 = m_privRegs->dispfb1; + request.display1 = m_privRegs->display1; + request.dispfb2 = m_privRegs->dispfb2; + request.display2 = m_privRegs->display2; + request.bgcolor = m_privRegs->bgcolor; + request.vsyncTick = m_privRegs->vsyncTick.load(std::memory_order_acquire); + request.contextFrames[0] = m_ctx[0].frame; + request.contextFrames[1] = m_ctx[1].frame; + request.preferredSource = m_preferredDisplaySourceFrame; + request.preferredDestFbp = m_preferredDisplayDestFbp; + request.hasPreferredSource = m_hasPreferredDisplaySource; + return request; +} + +void GS::latchHostPresentationFrame() +{ + GSPresentationRequest request{}; + { + std::lock_guard lock(m_stateMutex); + if (!m_backend || !m_privRegs) + { + std::lock_guard presentationLock(m_presentationMutex); + m_hostPresentationFrame.clear(); + m_hasHostPresentationFrame = false; + m_hostPresentationWidth = m_hostPresentationHeight = 0u; + return; + } + request = buildPresentationRequestUnlocked(); + } + + PresentationFrame frame{}; + { + std::lock_guard backendLock(m_backendLifetimeMutex); + if (m_backend) + { + m_backend->Flush(); + m_backend->Sync(GSSyncReason::Presentation); + frame = m_backend->Present(request); + } + } + + const bool hasFrame = static_cast(frame); + const uint32_t displayFbp = frame.displayFbp; + const uint32_t sourceFbp = frame.sourceFbp; + const uint32_t width = frame.width; + const uint32_t height = frame.height; + const bool usedPreferred = frame.usedPreferred; + { + std::lock_guard presentationLock(m_presentationMutex); + m_hostPresentationFrame = std::move(frame.pixels); + m_hostPresentationWidth = width; + m_hostPresentationHeight = height; + m_hostPresentationDisplayFbp = displayFbp; + m_hostPresentationSourceFbp = sourceFbp; + m_hostPresentationUsedPreferred = usedPreferred; + m_hasHostPresentationFrame = hasFrame; + } + + if (hasFrame) + { + std::lock_guard lock(m_stateMutex); + recordPresentDebugEventUnlocked(displayFbp, sourceFbp, width, height, usedPreferred); + } +} + +bool GS::copyLatchedHostPresentationFrame(std::vector &outPixels, + uint32_t &outWidth, + uint32_t &outHeight, + uint32_t *outDisplayFbp, + uint32_t *outSourceFbp, + bool *outUsedPreferred) const +{ + std::lock_guard lock(m_presentationMutex); + if (!m_hasHostPresentationFrame || m_hostPresentationFrame.empty()) + { + outPixels.clear(); + outWidth = 0u; + outHeight = 0u; + if (outDisplayFbp) + *outDisplayFbp = 0u; + if (outSourceFbp) + *outSourceFbp = 0u; + if (outUsedPreferred) + *outUsedPreferred = false; + return false; + } + + outWidth = m_hostPresentationWidth; + outHeight = m_hostPresentationHeight; + if (outDisplayFbp) + *outDisplayFbp = m_hostPresentationDisplayFbp; + if (outSourceFbp) + *outSourceFbp = m_hostPresentationSourceFbp; + if (outUsedPreferred) + *outUsedPreferred = m_hostPresentationUsedPreferred; + + const size_t packedRowBytes = static_cast(outWidth) * 4u; + outPixels.resize(packedRowBytes * static_cast(outHeight)); + if (outWidth != 0u && outHeight != 0u) + { + const size_t sourceRowBytes = static_cast(kHostFrameWidth) * 4u; + for (uint32_t y = 0; y < outHeight; ++y) + { + const size_t srcOffset = static_cast(y) * sourceRowBytes; + const size_t dstOffset = static_cast(y) * packedRowBytes; + if (srcOffset + packedRowBytes > m_hostPresentationFrame.size() || + dstOffset + packedRowBytes > outPixels.size()) + { + outPixels.clear(); + outWidth = 0u; + outHeight = 0u; + if (outDisplayFbp) + *outDisplayFbp = 0u; + if (outSourceFbp) + *outSourceFbp = 0u; + if (outUsedPreferred) + *outUsedPreferred = false; + return false; + } + + std::memcpy(outPixels.data() + dstOffset, + m_hostPresentationFrame.data() + srcOffset, + packedRowBytes); + } + } + return true; +} + +void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + if (!data || sizeBytes < 16 || !m_backend) + return; + + if (tryProcessNativeImageUploadPacket(data, sizeBytes)) + return; + + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed); + if (packetIndex < 48u) + { + const uint64_t tagLo = loadLE64(data); + const uint32_t nloop = static_cast(tagLo & 0x7FFFu); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); + if (nreg == 0u) + nreg = 16u; + RUNTIME_LOG("[gs:gif] idx=" << packetIndex + << " size=" << sizeBytes + << " nloop=" << nloop + << " flg=" << static_cast(flg) + << " nreg=" << nreg + << " ctx0fbp=" << m_ctx[0].frame.fbp + << " ctx1fbp=" << m_ctx[1].frame.fbp + << std::endl); + } + }); + + uint32_t offset = 0; + while (offset + 16 <= sizeBytes) + { + uint64_t tagLo = loadLE64(data + offset); + uint64_t tagHi = loadLE64(data + offset + 8); + offset += 16; + + m_curQ = 1.0f; + + uint32_t nloop = static_cast(tagLo & 0x7FFF); + uint8_t flg = static_cast((tagLo >> 58) & 0x3); + uint32_t nreg = static_cast((tagLo >> 60) & 0xF); + if (nreg == 0) + nreg = 16; + + recordGifTagDebugEventUnlocked(sizeBytes, nloop, flg, nreg); + + bool pre = ((tagLo >> 46) & 1) != 0; + if (pre) + { + writeRegisterUnlocked(GS_REG_PRIM, (tagLo >> 47) & 0x7FF); + } + + uint8_t regs[16]; + for (uint32_t i = 0; i < nreg; ++i) + regs[i] = static_cast((tagHi >> (i * 4)) & 0xF); + + if (flg == GIF_FMT_PACKED) + { + for (uint32_t loop = 0; loop < nloop; ++loop) + { + for (uint32_t r = 0; r < nreg; ++r) + { + if (offset + 16 > sizeBytes) + return; + uint64_t lo = loadLE64(data + offset); + uint64_t hi = loadLE64(data + offset + 8); + offset += 16; + writeRegisterPacked(regs[r], lo, hi); + } + } + } + else if (flg == GIF_FMT_REGLIST) + { + for (uint32_t loop = 0; loop < nloop; ++loop) + { + for (uint32_t r = 0; r < nreg; ++r) + { + if (offset + 8 > sizeBytes) + return; + writeRegisterUnlocked(regs[r], loadLE64(data + offset)); + offset += 8; + } + } + if ((nloop * nreg) & 1) + offset += 8; + } + else if (flg == GIF_FMT_IMAGE) + { + uint32_t imageBytes = nloop * 16; + if (offset + imageBytes > sizeBytes) + imageBytes = sizeBytes - offset; + processImageData(data + offset, imageBytes); + offset += imageBytes; + } + } +} + +bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + if (!data || sizeBytes < 16u || !m_backend) + return false; + + if (!validatePackedGifPacket(data, sizeBytes)) + return false; + + const bool processed = visitPackedGifPacket(data, sizeBytes, [&](const PackedGifPacketTag &tag) + { + m_curQ = 1.0f; + + recordGifTagDebugEventUnlocked(sizeBytes, tag.nloop, GIF_FMT_PACKED, tag.nreg); + + const bool pre = ((tag.lo >> 46u) & 1u) != 0u; + if (pre) + writeRegisterUnlocked(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu); + + uint32_t offset = tag.payloadOffset; + for (uint32_t loop = 0u; loop < tag.nloop; ++loop) + { + for (uint32_t r = 0u; r < tag.nreg; ++r) + { + const uint64_t lo = loadLE64(data + offset); + const uint64_t hi = loadLE64(data + offset + 8u); + offset += 16u; + writeRegisterPacked(tag.regs[r], lo, hi); + } + } + + return true; }); + + if (!processed) + return false; + + ++m_nativePackedGIFPacketCount; + return true; +} + +void GS::uploadImageNative(uint64_t bitbltbuf, + uint64_t trxpos, + uint64_t trxreg, + uint64_t trxdir, + const uint8_t *data, + uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + uploadImageNativeUnlocked(bitbltbuf, trxpos, trxreg, trxdir, data, sizeBytes); +} + +void GS::uploadImageNativeUnlocked(uint64_t bitbltbuf, + uint64_t trxpos, + uint64_t trxreg, + uint64_t trxdir, + const uint8_t *data, + uint32_t sizeBytes) +{ + if (!data || sizeBytes == 0 || !m_backend) + return; + + writeRegisterUnlocked(GS_REG_BITBLTBUF, bitbltbuf); + writeRegisterUnlocked(GS_REG_TRXPOS, trxpos); + writeRegisterUnlocked(GS_REG_TRXREG, trxreg); + writeRegisterUnlocked(GS_REG_TRXDIR, trxdir); + processImageData(data, sizeBytes); + ++m_nativeImageUploadCount; +} + +bool GS::tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes) +{ + constexpr uint32_t kSetupRegisters = 4u; + constexpr uint32_t kPackedAdPayloadBytes = kSetupRegisters * 16u; + constexpr uint64_t kPackedAdDescriptor = 0x0Eull; + + if (!data || sizeBytes < 16u + kPackedAdPayloadBytes + 16u) + return false; + + const uint64_t setupTagLo = loadLE64(data); + const uint64_t setupTagHi = loadLE64(data + 8u); + const uint32_t setupNloop = static_cast(setupTagLo & 0x7FFFu); + const uint8_t setupFlg = static_cast((setupTagLo >> 58u) & 0x3u); + uint32_t setupNreg = static_cast((setupTagLo >> 60u) & 0xFu); + if (setupNreg == 0u) + setupNreg = 16u; + + if (setupNloop != kSetupRegisters || + setupFlg != GIF_FMT_PACKED || + setupNreg != 1u || + (setupTagHi & 0xFull) != kPackedAdDescriptor) + { + return false; + } + + uint64_t regs[kSetupRegisters] = {}; + uint32_t offset = 16u; + constexpr uint8_t expectedRegs[kSetupRegisters] = { + GS_REG_BITBLTBUF, + GS_REG_TRXPOS, + GS_REG_TRXREG, + GS_REG_TRXDIR, + }; + + for (uint32_t i = 0; i < kSetupRegisters; ++i) + { + regs[i] = loadLE64(data + offset); + const uint64_t reg = loadLE64(data + offset + 8u); + if ((reg & 0xFFu) != expectedRegs[i]) + return false; + offset += 16u; + } + + const uint32_t trxdirMode = static_cast(regs[3] & 0x3ull); + const uint32_t rrw = static_cast(regs[2] & 0xFFFull); + const uint32_t rrh = static_cast((regs[2] >> 32u) & 0xFFFull); + if (trxdirMode != 0u || rrw == 0u || rrh == 0u) + return false; + + if (offset + 16u > sizeBytes) + return false; + + const uint64_t imageTagLo = loadLE64(data + offset); + const uint8_t imageFlg = static_cast((imageTagLo >> 58u) & 0x3u); + const uint32_t imageNloop = static_cast(imageTagLo & 0x7FFFu); + if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u) + return false; + + offset += 16u; + const uint64_t imageBytes64 = static_cast(imageNloop) * 16ull; + if (imageBytes64 > 0xFFFFFFFFull) + return false; + const uint32_t imageBytes = static_cast(imageBytes64); + if (offset + imageBytes != sizeBytes) + return false; + + uploadImageNativeUnlocked(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes); + return true; +} + +void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) +{ + switch (regDesc) + { + case 0x00: + writeRegisterUnlocked(GS_REG_PRIM, lo & 0x7FF); + break; + case 0x01: + m_curR = static_cast(lo & 0xFF); + m_curG = static_cast((lo >> 32) & 0xFF); + m_curB = static_cast(hi & 0xFF); + m_curA = static_cast((hi >> 32) & 0xFF); + break; + case 0x02: + { + uint32_t sBits = static_cast(lo & 0xFFFFFFFF); + uint32_t tBits = static_cast((lo >> 32) & 0xFFFFFFFF); + uint32_t qBits = static_cast(hi & 0xFFFFFFFF); + std::memcpy(&m_curS, &sBits, 4); + std::memcpy(&m_curT, &tBits, 4); + std::memcpy(&m_curQ, &qBits, 4); + if (m_curQ == 0.0f) + m_curQ = 1.0f; + break; + } + case 0x03: + m_curU = static_cast(lo & 0x3FFFu); + m_curV = static_cast((lo >> 32) & 0x3FFFu); + break; + case 0x04: + { + uint16_t x = static_cast(lo & 0xFFFF); + uint16_t y = static_cast((lo >> 32) & 0xFFFF); + uint32_t z = static_cast((hi >> 4) & 0xFFFFFF); + uint8_t f = static_cast((hi >> 36) & 0xFF); + bool adk = ((hi >> 47) & 1) != 0; + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyzf] idx=" << debugIndex + << " x=" << x + << " y=" << y + << " z=0x" << std::hex << z + << std::dec + << " fog=" << static_cast(f) + << " kick=" << static_cast(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(x) / 16.0f; + vtx.y = static_cast(y) / 16.0f; + vtx.z = static_cast(z); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vtx.fog = f; + vertexKick(!adk); + break; + } + case 0x05: + { + uint16_t x = static_cast(lo & 0xFFFF); + uint16_t y = static_cast((lo >> 32) & 0xFFFF); + uint32_t z = static_cast(hi & 0xFFFFFFFF); + bool adk = ((hi >> 47) & 1) != 0; + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyz] idx=" << debugIndex + << " x=" << x + << " y=" << y + << " z=0x" << std::hex << z + << std::dec + << " kick=" << static_cast(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(x) / 16.0f; + vtx.y = static_cast(y) / 16.0f; + vtx.z = static_cast(z); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vtx.fog = m_curFog; + vertexKick(!adk); + break; + } + case 0x0A: + m_curFog = static_cast((hi >> 36) & 0xFF); + break; + case 0x0C: + { + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyzf3] idx=" << debugIndex + << " x=" << static_cast(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(lo & 0xFFFF) / 16.0f; + vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; + vtx.z = static_cast((hi >> 4) & 0xFFFFFF); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vtx.fog = static_cast((hi >> 36) & 0xFF); + vertexKick(false); + break; + } + case 0x0D: + { + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyz3] idx=" << debugIndex + << " x=" << static_cast(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(lo & 0xFFFF) / 16.0f; + vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; + vtx.z = static_cast(hi & 0xFFFFFFFF); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vtx.fog = m_curFog; + vertexKick(false); + break; + } + case 0x0E: + { + uint8_t addr = static_cast(hi & 0xFF); + writeRegisterUnlocked(addr, lo); + break; + } + case 0x0F: + break; + default: + writeRegisterUnlocked(regDesc, lo); + break; + } +} + +void GS::writeRegister(uint8_t regAddr, uint64_t value) +{ + std::lock_guard lock(m_stateMutex); + writeRegisterUnlocked(regAddr, value); +} + +void GS::writeRegisterUnlocked(uint8_t regAddr, uint64_t value) +{ + const bool interestingReg = + regAddr == GS_REG_PRIM || + regAddr == GS_REG_RGBAQ || + regAddr == GS_REG_ST || + regAddr == GS_REG_UV || + regAddr == GS_REG_XYZ2 || + regAddr == GS_REG_XYZ3 || + regAddr == GS_REG_XYZF2 || + regAddr == GS_REG_XYZF3 || + regAddr == GS_REG_TEX0_1 || + regAddr == GS_REG_TEX0_2 || + regAddr == GS_REG_TEX2_1 || + regAddr == GS_REG_TEX2_2 || + regAddr == GS_REG_TEXCLUT || + regAddr == GS_REG_TEXA || + regAddr == GS_REG_XYOFFSET_1 || + regAddr == GS_REG_XYOFFSET_2 || + regAddr == GS_REG_SCISSOR_1 || + regAddr == GS_REG_SCISSOR_2 || + regAddr == GS_REG_FRAME_1 || + regAddr == GS_REG_FRAME_2 || + regAddr == GS_REG_ALPHA_1 || + regAddr == GS_REG_ALPHA_2 || + regAddr == GS_REG_TEST_1 || + regAddr == GS_REG_TEST_2 || + regAddr == GS_REG_BITBLTBUF || + regAddr == GS_REG_TRXPOS || + regAddr == GS_REG_TRXREG || + regAddr == GS_REG_TRXDIR; + + PS2_IF_AGRESSIVE_LOGS({ + if (interestingReg) + { + const uint32_t debugIndex = s_debugGsRegisterCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 128u) + { + RUNTIME_LOG("[gs:reg] idx=" << debugIndex + << " reg=0x" << std::hex << static_cast(regAddr) + << " value=0x" << value + << std::dec + << std::endl); + } + } + }); + + const bool isCopyRelevantReg = + regAddr == GS_REG_PRIM || + regAddr == GS_REG_TEX0_2 || + regAddr == GS_REG_TEX1_2 || + regAddr == GS_REG_ALPHA_2 || + regAddr == GS_REG_TEST_2 || + regAddr == GS_REG_PABE || + regAddr == GS_REG_FRAME_2 || + regAddr == GS_REG_XYOFFSET_2 || + regAddr == GS_REG_SCISSOR_2; + PS2_IF_AGRESSIVE_LOGS({ + if (isCopyRelevantReg && + s_debugCopyRegCount.fetch_add(1u, std::memory_order_relaxed) < 64u) + { + RUNTIME_LOG("[gs:copy-reg] reg=0x" + << std::hex << static_cast(regAddr) + << " value=0x" << value + << std::dec + << " primCtxt=" << static_cast(m_prim.ctxt) + << " ctx0fbp=" << m_ctx[0].frame.fbp + << " ctx1fbp=" << m_ctx[1].frame.fbp + << std::endl); + } + }); + + switch (regAddr) + { + case GS_REG_PRIM: + { + m_primRegister = decodePrimRegister(value); + if (m_prmodecont) + { + m_prim = m_primRegister; + } + else + { + // PRIM always selects the primitive topology. With AC=0, all + // rendering attributes remain sourced from PRMODE. + m_prim.type = m_primRegister.type; + } + m_vtxCount = 0; + m_vtxIndex = 0; + break; + } + case GS_REG_RGBAQ: + { + m_curR = static_cast(value & 0xFF); + m_curG = static_cast((value >> 8) & 0xFF); + m_curB = static_cast((value >> 16) & 0xFF); + m_curA = static_cast((value >> 24) & 0xFF); + uint32_t qBits = static_cast((value >> 32) & 0xFFFFFFFF); + std::memcpy(&m_curQ, &qBits, 4); + if (m_curQ == 0.0f) + m_curQ = 1.0f; + break; + } + case GS_REG_ST: + { + uint32_t sBits = static_cast(value & 0xFFFFFFFF); + uint32_t tBits = static_cast((value >> 32) & 0xFFFFFFFF); + std::memcpy(&m_curS, &sBits, 4); + std::memcpy(&m_curT, &tBits, 4); + break; + } + case GS_REG_UV: + { + m_curU = static_cast(value & 0x3FFFu); + m_curV = static_cast((value >> 16) & 0x3FFFu); + break; + } + case GS_REG_XYZF2: + case GS_REG_XYZF3: + { + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(value & 0xFFFF) / 16.0f; + vtx.y = static_cast((value >> 16) & 0xFFFF) / 16.0f; + vtx.z = static_cast((value >> 32) & 0xFFFFFF); + vtx.fog = static_cast((value >> 56) & 0xFF); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vertexKick(regAddr == GS_REG_XYZF2); + break; + } + case GS_REG_XYZ2: + case GS_REG_XYZ3: + { + GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; + vtx.x = static_cast(value & 0xFFFF) / 16.0f; + vtx.y = static_cast((value >> 16) & 0xFFFF) / 16.0f; + vtx.z = static_cast((value >> 32) & 0xFFFFFFFF); + vtx.r = m_curR; + vtx.g = m_curG; + vtx.b = m_curB; + vtx.a = m_curA; + vtx.q = m_curQ; + vtx.s = m_curS; + vtx.t = m_curT; + vtx.u = m_curU; + vtx.v = m_curV; + vtx.fog = m_curFog; + vertexKick(regAddr == GS_REG_XYZ2); + break; + } + case GS_REG_TEX0_1: + case GS_REG_TEX0_2: + { + int ci = (regAddr == GS_REG_TEX0_2) ? 1 : 0; + auto &t = m_ctx[ci].tex0; + t.tbp0 = static_cast(value & 0x3FFF); + t.tbw = static_cast((value >> 14) & 0x3F); + t.psm = static_cast((value >> 20) & 0x3F); + t.tw = static_cast((value >> 26) & 0xF); + t.th = static_cast((value >> 30) & 0xF); + t.tcc = static_cast((value >> 34) & 0x1); + t.tfx = static_cast((value >> 35) & 0x3); + t.cbp = static_cast((value >> 37) & 0x3FFF); + t.cpsm = static_cast((value >> 51) & 0xF); + t.csm = static_cast((value >> 55) & 0x1); + t.csa = static_cast((value >> 56) & 0x1F); + t.cld = static_cast((value >> 61) & 0x7); + break; + } + case GS_REG_CLAMP_1: + case GS_REG_CLAMP_2: + { + int ci = (regAddr == GS_REG_CLAMP_2) ? 1 : 0; + m_ctx[ci].clamp = value; + break; + } + case GS_REG_FOG: + m_curFog = static_cast((value >> 56) & 0xFF); + break; + case GS_REG_TEX1_1: + case GS_REG_TEX1_2: + { + int ci = (regAddr == GS_REG_TEX1_2) ? 1 : 0; + m_ctx[ci].tex1 = value; + break; + } + case GS_REG_TEX2_1: + case GS_REG_TEX2_2: + { + int ci = (regAddr == GS_REG_TEX2_2) ? 1 : 0; + auto &t = m_ctx[ci].tex0; + t.psm = static_cast((value >> 20) & 0x3F); + t.cbp = static_cast((value >> 37) & 0x3FFF); + t.cpsm = static_cast((value >> 51) & 0xF); + t.csm = static_cast((value >> 55) & 0x1); + t.csa = static_cast((value >> 56) & 0x1F); + t.cld = static_cast((value >> 61) & 0x7); + break; + } + case GS_REG_XYOFFSET_1: + case GS_REG_XYOFFSET_2: + { + int ci = (regAddr == GS_REG_XYOFFSET_2) ? 1 : 0; + m_ctx[ci].xyoffset.ofx = static_cast(value & 0xFFFF); + m_ctx[ci].xyoffset.ofy = static_cast((value >> 32) & 0xFFFF); + break; + } + case GS_REG_PRMODECONT: + { + m_prmodecont = (value & 1) != 0; + const GSPrimType type = m_primRegister.type; + m_prim = m_prmodecont ? m_primRegister : m_prmodeRegister; + m_prim.type = type; + break; + } + case GS_REG_PRMODE: + { + m_prmodeRegister = decodePrimRegister(value); + if (!m_prmodecont) + { + const GSPrimType type = m_primRegister.type; + m_prim = m_prmodeRegister; + m_prim.type = type; + } + break; + } + case GS_REG_TEXCLUT: + m_texclut.cbw = static_cast(value & 0x3Fu); + m_texclut.cou = static_cast((value >> 6) & 0x3Fu); + m_texclut.cov = static_cast((value >> 12) & 0x3FFu); + break; + case GS_REG_SCISSOR_1: + case GS_REG_SCISSOR_2: + { + int ci = (regAddr == GS_REG_SCISSOR_2) ? 1 : 0; + m_ctx[ci].scissor.x0 = static_cast(value & 0x7FF); + m_ctx[ci].scissor.x1 = static_cast((value >> 16) & 0x7FF); + m_ctx[ci].scissor.y0 = static_cast((value >> 32) & 0x7FF); + m_ctx[ci].scissor.y1 = static_cast((value >> 48) & 0x7FF); + break; + } + case GS_REG_ALPHA_1: + case GS_REG_ALPHA_2: + { + int ci = (regAddr == GS_REG_ALPHA_2) ? 1 : 0; + m_ctx[ci].alpha = value; + break; + } + case GS_REG_TEST_1: + case GS_REG_TEST_2: + { + int ci = (regAddr == GS_REG_TEST_2) ? 1 : 0; + m_ctx[ci].test = value; + break; + } + case GS_REG_FRAME_1: + case GS_REG_FRAME_2: + { + int ci = (regAddr == GS_REG_FRAME_2) ? 1 : 0; + m_ctx[ci].frame.fbp = static_cast(value & 0x1FF); + m_ctx[ci].frame.fbw = static_cast((value >> 16) & 0x3F); + m_ctx[ci].frame.psm = static_cast((value >> 24) & 0x3F); + m_ctx[ci].frame.fbmsk = static_cast((value >> 32) & 0xFFFFFFFF); + break; + } + case GS_REG_ZBUF_1: + case GS_REG_ZBUF_2: + { + int ci = (regAddr == GS_REG_ZBUF_2) ? 1 : 0; + m_ctx[ci].zbuf.zbp = value & 0x1FF; + m_ctx[ci].zbuf.psm = ((value >> 24) & 0xF) | 0x30; + m_ctx[ci].zbuf.zmask = (value >> 32) & 1; + break; + } + case GS_REG_FBA_1: + case GS_REG_FBA_2: + { + int ci = (regAddr == GS_REG_FBA_2) ? 1 : 0; + m_ctx[ci].fba = value; + break; + } + case GS_REG_BITBLTBUF: + { + m_bitbltbuf.sbp = static_cast(value & 0x3FFF); + m_bitbltbuf.sbw = static_cast((value >> 16) & 0x3F); + m_bitbltbuf.spsm = static_cast((value >> 24) & 0x3F); + m_bitbltbuf.dbp = static_cast((value >> 32) & 0x3FFF); + m_bitbltbuf.dbw = static_cast((value >> 48) & 0x3F); + m_bitbltbuf.dpsm = static_cast((value >> 56) & 0x3F); + break; + } + case GS_REG_TRXPOS: + { + m_trxpos.ssax = static_cast(value & 0x7FF); + m_trxpos.ssay = static_cast((value >> 16) & 0x7FF); + m_trxpos.dsax = static_cast((value >> 32) & 0x7FF); + m_trxpos.dsay = static_cast((value >> 48) & 0x7FF); + m_trxpos.dir = static_cast((value >> 59) & 0x3); + break; + } + case GS_REG_TRXREG: + { + m_trxreg.rrw = static_cast(value & 0xFFF); + m_trxreg.rrh = static_cast((value >> 32) & 0xFFF); + break; + } + case GS_REG_TRXDIR: + { + m_trxdir = static_cast(value & 0x3); + + if (m_backend) + { + GSTransferCommand command{}; + command.bitbltbuf = m_bitbltbuf; + command.trxpos = m_trxpos; + command.trxreg = m_trxreg; + command.direction = m_trxdir; + m_backend->BeginTransfer(command); + } + recordTransferDebugEventUnlocked(); + break; + } + case GS_REG_HWREG: + { + uint8_t buf[8]; + std::memcpy(buf, &value, 8); + processImageData(buf, 8); + break; + } + case GS_REG_PABE: + m_pabe = (value & 1u) != 0u; + break; + case GS_REG_FOGCOL: + m_fogR = static_cast(value & 0xFFu); + m_fogG = static_cast((value >> 8) & 0xFFu); + m_fogB = static_cast((value >> 16) & 0xFFu); + break; + case GS_REG_TEXFLUSH: + if (m_backend) + m_backend->TextureFlush(); + break; + case GS_REG_SCANMSK: + m_scanmsk = value; + break; + case GS_REG_DIMX: + m_dimx = value; + break; + case GS_REG_DTHE: + m_dthe = value; + break; + case GS_REG_COLCLAMP: + m_colclamp = value; + break; + case GS_REG_MIPTBP1_1: + case GS_REG_MIPTBP1_2: + { + const int ci = (regAddr == GS_REG_MIPTBP1_2) ? 1 : 0; + m_ctx[ci].miptbp1 = value; + break; + } + case GS_REG_MIPTBP2_1: + case GS_REG_MIPTBP2_2: + { + const int ci = (regAddr == GS_REG_MIPTBP2_2) ? 1 : 0; + m_ctx[ci].miptbp2 = value; + break; + } + case GS_REG_TEXA: + { + m_texa.ta0 = static_cast(value & 0xFFu); + m_texa.aem = ((value >> 15) & 0x1u) != 0u; + m_texa.ta1 = static_cast((value >> 32) & 0xFFu); + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t texaIndex = s_debugTexaWriteCount.fetch_add(1u, std::memory_order_relaxed); + if (texaIndex < 24u) + { + RUNTIME_LOG("[gs:texa] idx=" << texaIndex + << " value=0x" << std::hex << value + << " ta0=0x" << ((value >> 0) & 0xFFu) + << " aem=" << ((value >> 15) & 0x1u) + << " ta1=0x" << ((value >> 32) & 0xFFu) + << std::dec + << std::endl); + } + }); + break; + } + case GS_REG_SIGNAL: + { + if (m_privRegs) + { + uint32_t id = static_cast(value & 0xFFFFFFFF); + uint32_t mask = static_cast(value >> 32); + uint32_t lo = static_cast(m_privRegs->siglblid & 0xFFFFFFFF); + lo = (lo & ~mask) | (id & mask); + m_privRegs->siglblid = (m_privRegs->siglblid & 0xFFFFFFFF00000000ULL) | lo; + m_privRegs->csr.fetch_or(0x1); + } + break; + } + case GS_REG_FINISH: + { + if (m_backend) + { + m_backend->Flush(); + m_backend->Sync(GSSyncReason::Finish); + } + if (m_privRegs) + m_privRegs->csr.fetch_or(0x2); + break; + } + case GS_REG_LABEL: + { + if (m_privRegs) + { + uint32_t id = static_cast(value & 0xFFFFFFFF); + uint32_t mask = static_cast(value >> 32); + uint32_t hi = static_cast(m_privRegs->siglblid >> 32); + hi = (hi & ~mask) | (id & mask); + m_privRegs->siglblid = (static_cast(hi) << 32) | (m_privRegs->siglblid & 0xFFFFFFFF); + } + break; + } + case 0x59: + if (m_privRegs) + m_privRegs->dispfb1 = value; + break; + case 0x5a: + if (m_privRegs) + m_privRegs->display1 = value; + break; + case 0x5b: + if (m_privRegs) + m_privRegs->dispfb2 = value; + break; + case 0x5c: + if (m_privRegs) + m_privRegs->display2 = value; + break; + case 0x5f: + if (m_privRegs) + m_privRegs->bgcolor = value; + break; + default: + break; + } + + recordRegisterDebugEventUnlocked(regAddr, value); +} + +void GS::vertexKick(bool drawing) +{ + ++m_vtxCount; + ++m_vtxIndex; + + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsVertexKickCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 96u) + { + RUNTIME_LOG("[gs:kick] idx=" << debugIndex + << " drawing=" << static_cast(drawing ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << " vtxCount=" << m_vtxCount + << std::endl); + } + }); + + int needed = 0; + switch (m_prim.type) + { + case GS_PRIM_POINT: + needed = 1; + break; + case GS_PRIM_LINE: + needed = 2; + break; + case GS_PRIM_LINESTRIP: + needed = 2; + break; + case GS_PRIM_TRIANGLE: + needed = 3; + break; + case GS_PRIM_TRISTRIP: + needed = 3; + break; + case GS_PRIM_TRIFAN: + needed = 3; + break; + case GS_PRIM_SPRITE: + needed = 2; + break; + default: + return; + } + + if (m_vtxCount < needed) + return; + + if (drawing && m_backend) + { + GSPrimitiveBatch batch = buildDrawBatch(needed); + updatePreferredDisplaySourceForDraw(batch); + m_backend->Submit(batch); + recordDrawDebugEventUnlocked(needed); + } + + switch (m_prim.type) + { + case GS_PRIM_LINE: + case GS_PRIM_TRIANGLE: + case GS_PRIM_SPRITE: + case GS_PRIM_POINT: + m_vtxCount = 0; + break; + case GS_PRIM_LINESTRIP: + m_vtxQueue[0] = m_vtxQueue[1]; + m_vtxCount = 1; + break; + case GS_PRIM_TRISTRIP: + m_vtxQueue[0] = m_vtxQueue[1]; + m_vtxQueue[1] = m_vtxQueue[2]; + m_vtxCount = 2; + break; + case GS_PRIM_TRIFAN: + m_vtxQueue[1] = m_vtxQueue[2]; + m_vtxCount = 2; + break; + default: + m_vtxCount = 0; + break; + } +} + +void GS::processImageData(const uint8_t *data, uint32_t sizeBytes) +{ + if (m_backend) + m_backend->UploadImage(data, sizeBytes); +} + + +bool GS::clearFramebufferContext(uint32_t contextIndex, uint32_t rgba) +{ + std::lock_guard lock(m_stateMutex); + return m_backend && m_backend->ClearFramebuffer(m_ctx[(contextIndex != 0u) ? 1 : 0], rgba); +} + +bool GS::clearActiveFramebuffer(uint32_t rgba) +{ + std::lock_guard lock(m_stateMutex); + return m_backend && m_backend->ClearFramebuffer(activeContext(), rgba); +} + +uint32_t GS::consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) +{ + std::lock_guard lock(m_stateMutex); + return m_backend ? m_backend->ConsumeLocalToHostBytes(dst, maxBytes) : 0u; +} + +void GS::setRasterBackend(std::unique_ptr backend) +{ + if (!backend) + backend = std::make_unique(); + + std::lock_guard lock(m_stateMutex); + std::lock_guard backendLock(m_backendLifetimeMutex); + if (m_backend) + { + m_backend->Flush(); + m_backend->Sync(GSSyncReason::Reset); + + // The external 4 MiB GS allocation is the backend hand-off format. + // This keeps hot backend replacement deterministic even when a future + // GPU backend keeps a private/mirrored local-memory representation. + if (m_localMemoryStorage && m_localMemorySize != 0u) + { + std::vector localMemory; + m_backend->SnapshotVram(localMemory); + const size_t bytes = std::min(localMemory.size(), m_localMemorySize); + if (bytes != 0u) + std::memcpy(m_localMemoryStorage, localMemory.data(), bytes); + } + } + + m_backend = std::move(backend); + m_backend->Initialize(m_localMemoryStorage, m_localMemorySize); +} + +uint32_t GS::ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const +{ + std::lock_guard lock(m_stateMutex); + return m_backend ? m_backend->ReadVram(psm, base, bw, x, y) : 0u; +} + +void GS::WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) +{ + std::lock_guard lock(m_stateMutex); + if (m_backend) + m_backend->WriteVram(psm, base, bw, x, y, value); +} + +GSPrimitiveBatch GS::buildDrawBatch(int vertexCount) const +{ + GSPrimitiveBatch batch{}; + batch.vertexCount = static_cast(std::min(vertexCount, 3)); + for (int i = 0; i < batch.vertexCount; ++i) + batch.vertices[static_cast(i)] = m_vtxQueue[i]; + batch.state.context = m_ctx[m_prim.ctxt ? 1 : 0]; + batch.state.prim = m_prim; + batch.state.texa = m_texa; + batch.state.texclut = m_texclut; + batch.state.pabe = m_pabe; + batch.state.scanmsk = m_scanmsk; + batch.state.dimx = m_dimx; + batch.state.dthe = m_dthe; + batch.state.colclamp = m_colclamp; + batch.state.fogR = m_fogR; + batch.state.fogG = m_fogG; + batch.state.fogB = m_fogB; + batch.state.textureWidth = static_cast(1u << std::min(batch.state.context.tex0.tw, 10u)); + batch.state.textureHeight = static_cast(1u << std::min(batch.state.context.tex0.th, 10u)); + const uint64_t tex1 = batch.state.context.tex1; + const uint8_t mmag = static_cast((tex1 >> 5u) & 0x1u); + const uint8_t mmin = static_cast((tex1 >> 6u) & 0x7u); + batch.state.linearFilter = mmag != 0u || mmin == 1u || (mmin & 0x4u) != 0u; + return batch; +} + +void GS::updatePreferredDisplaySourceForDraw(const GSPrimitiveBatch &batch) +{ + const GSDrawState &state = batch.state; + const GSContext &ctx = state.context; + if (m_hasPreferredDisplaySource && ctx.frame.fbp == m_preferredDisplayDestFbp) + m_hasPreferredDisplaySource = false; + if (state.prim.type != GS_PRIM_SPRITE || batch.vertexCount < 2u) + return; + + const GSVertex &v0 = batch.vertices[0]; + const GSVertex &v1 = batch.vertices[1]; + int x0 = static_cast(v0.x) - (ctx.xyoffset.ofx >> 4); + int y0 = static_cast(v0.y) - (ctx.xyoffset.ofy >> 4); + int x1 = static_cast(v1.x) - (ctx.xyoffset.ofx >> 4); + int y1 = static_cast(v1.y) - (ctx.xyoffset.ofy >> 4); + if (x0 > x1) std::swap(x0, x1); + if (y0 > y1) std::swap(y0, y1); + const int xEnd = x0 + std::max(1, x1 - x0) - 1; + const int yEnd = y0 + std::max(1, y1 - y0) - 1; + const uint8_t alphaMode = static_cast(ctx.alpha & 0xFFu); + const uint8_t alphaFix = static_cast((ctx.alpha >> 32u) & 0xFFu); + const bool displayCopy = state.prim.tme && state.prim.abe && state.prim.fst && state.prim.ctxt && + ctx.frame.fbp != ctx.tex0.tbp0 && alphaMode == 0x64u && + (alphaFix == 0x60u || alphaFix == 0x80u) && + x0 <= 0 && y0 <= 0 && xEnd >= 639 && yEnd >= 447; + if (displayCopy) + { + m_preferredDisplaySourceFrame = {ctx.tex0.tbp0, ctx.tex0.tbw, ctx.tex0.psm, 0u}; + m_preferredDisplayDestFbp = ctx.frame.fbp; + m_hasPreferredDisplaySource = true; + } +} diff --git a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp b/ps2xRuntime/src/lib/gs/ps2_gif_arbiter.cpp similarity index 98% rename from ps2xRuntime/src/lib/ps2_gif_arbiter.cpp rename to ps2xRuntime/src/lib/gs/ps2_gif_arbiter.cpp index 1b83711..c289ba0 100644 --- a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp +++ b/ps2xRuntime/src/lib/gs/ps2_gif_arbiter.cpp @@ -1,4 +1,4 @@ -#include "runtime/ps2_gif_arbiter.h" +#include "runtime/gs/ps2_gif_arbiter.h" #include #include diff --git a/ps2xRuntime/src/lib/ps2_gs_memory.cpp b/ps2xRuntime/src/lib/gs/ps2_gs_memory.cpp similarity index 99% rename from ps2xRuntime/src/lib/ps2_gs_memory.cpp rename to ps2xRuntime/src/lib/gs/ps2_gs_memory.cpp index e061953..fecf9fd 100644 --- a/ps2xRuntime/src/lib/ps2_gs_memory.cpp +++ b/ps2xRuntime/src/lib/gs/ps2_gs_memory.cpp @@ -1,6 +1,6 @@ #include -#include "runtime/ps2_gs_memory.h" +#include "runtime/gs/ps2_gs_memory.h" namespace GSMem { diff --git a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp deleted file mode 100644 index d444ad8..0000000 --- a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp +++ /dev/null @@ -1,2961 +0,0 @@ -#include "runtime/ps2_gs_gpu.h" -#include "runtime/ps2_gs_common.h" -#include "runtime/ps2_gs_psmct16.h" -#include "runtime/ps2_gs_psmct32.h" -#include "runtime/ps2_gs_psmt4.h" -#include "runtime/ps2_gs_psmt8.h" -#include "ps2_log.h" -#include "ps2_syscalls.h" -#include "runtime/ps2_memory.h" -#include "runtime/ps2_gs_memory.h" -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - static constexpr uint32_t kDefaultDisplayWidth = 640u; - static constexpr uint32_t kDefaultDisplayHeight = 448u; - static constexpr uint32_t kHostFrameWidth = 640u; - static constexpr uint32_t kHostFrameHeight = 512u; - - GSPrimReg decodePrimRegister(uint64_t value) - { - GSPrimReg prim{}; - prim.type = static_cast(value & 0x7u); - prim.iip = ((value >> 3) & 1u) != 0u; - prim.tme = ((value >> 4) & 1u) != 0u; - prim.fge = ((value >> 5) & 1u) != 0u; - prim.abe = ((value >> 6) & 1u) != 0u; - prim.aa1 = ((value >> 7) & 1u) != 0u; - prim.fst = ((value >> 8) & 1u) != 0u; - prim.ctxt = ((value >> 9) & 1u) != 0u; - prim.fix = ((value >> 10) & 1u) != 0u; - return prim; - } - - uint16_t encodeFramePixelPSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a) - { - return static_cast(((r >> 3) & 0x1Fu) | - (((g >> 3) & 0x1Fu) << 5) | - (((b >> 3) & 0x1Fu) << 10) | - ((a >= 0x40u) ? 0x8000u : 0u)); - } - - uint32_t addrPSMCT16Family(uint32_t basePtr, uint32_t width, uint8_t psm, uint32_t x, uint32_t y) - { - switch (psm) - { - case GS_PSM_CT16: - return GSPSMCT16::addrPSMCT16(basePtr, width, x, y); - case GS_PSM_CT16S: - return GSPSMCT16::addrPSMCT16S(basePtr, width, x, y); - case GS_PSM_Z16: - return GSPSMCT16::addrPSMZ16(basePtr, width, x, y); - case GS_PSM_Z16S: - return GSPSMCT16::addrPSMZ16S(basePtr, width, x, y); - default: - return 0u; - } - } - - static inline uint64_t loadLE64(const uint8_t *p) - { - uint64_t v; - std::memcpy(&v, p, 8); - return v; - } - - struct PackedGifPacketTag - { - uint64_t lo = 0u; - uint64_t hi = 0u; - uint32_t payloadOffset = 0u; - uint32_t nloop = 0u; - uint32_t nreg = 0u; - uint8_t regs[16]{}; - }; - - template - bool visitPackedGifPacket(const uint8_t *data, uint32_t sizeBytes, Visitor &&visitor) - { - uint32_t offset = 0u; - while (offset + 16u <= sizeBytes) - { - PackedGifPacketTag tag{}; - tag.lo = loadLE64(data + offset); - tag.hi = loadLE64(data + offset + 8u); - - const uint8_t flg = static_cast((tag.lo >> 58u) & 0x3u); - if (flg != GIF_FMT_PACKED) - return false; - - tag.nloop = static_cast(tag.lo & 0x7FFFu); - tag.nreg = static_cast((tag.lo >> 60u) & 0xFu); - if (tag.nreg == 0u) - tag.nreg = 16u; - - const uint64_t payloadBytes64 = - static_cast(tag.nloop) * static_cast(tag.nreg) * 16ull; - if (payloadBytes64 > 0xFFFFFFFFull) - return false; - - offset += 16u; - const uint32_t payloadBytes = static_cast(payloadBytes64); - if (payloadBytes > sizeBytes - offset) - return false; - - tag.payloadOffset = offset; - for (uint32_t i = 0u; i < tag.nreg; ++i) - tag.regs[i] = static_cast((tag.hi >> (i * 4u)) & 0xFu); - - if (!visitor(tag)) - return false; - - offset += payloadBytes; - } - - return offset == sizeBytes; - } - - bool validatePackedGifPacket(const uint8_t *data, uint32_t sizeBytes) - { - return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &) - { return true; }); - } - - void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight) - { - const uint32_t dx = static_cast((display64 >> 0) & 0x0FFFu); - const uint32_t dy = static_cast((display64 >> 12) & 0x07FFu); - const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); - const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); - const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); - - outWidth = (dw + 1u) / (magh + 1u); - outHeight = dh + 1u; - - if (outWidth < 64u || outHeight < 64u) - { - outWidth = kDefaultDisplayWidth; - outHeight = kDefaultDisplayHeight; - } - - outWidth = std::min(outWidth, kHostFrameWidth); - outHeight = std::min(outHeight, kHostFrameHeight); - } - - GSFrameReg decodeDisplayFrame(uint64_t dispfb64) - { - GSFrameReg frame{}; - frame.fbp = static_cast(dispfb64 & 0x1FFu); - frame.fbw = static_cast((dispfb64 >> 9) & 0x3Fu); - frame.psm = static_cast((dispfb64 >> 15) & 0x1Fu); - return frame; - } - - struct GSDisplayReadOrigin - { - uint32_t x = 0u; - uint32_t y = 0u; - }; - - GSDisplayReadOrigin decodeDisplayReadOrigin(uint64_t dispfb64) - { - GSDisplayReadOrigin origin{}; - origin.x = static_cast((dispfb64 >> 32) & 0x7FFu); - origin.y = static_cast((dispfb64 >> 43) & 0x7FFu); - return origin; - } - - bool hasDisplaySetup(uint64_t display64, const GSFrameReg &frame) - { - const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); - const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); - const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); - return frame.fbw != 0u || dw != 0u || dh != 0u || magh != 0u; - } - - struct GSPmodeState - { - bool enableCrt1 = false; - bool enableCrt2 = false; - bool mmod = false; - bool amod = false; - bool slbg = false; - uint8_t alp = 0u; - }; - - GSPmodeState decodePmode(uint64_t pmode64) - { - GSPmodeState pmode{}; - pmode.enableCrt1 = (pmode64 & 0x1ull) != 0ull; - pmode.enableCrt2 = (pmode64 & 0x2ull) != 0ull; - pmode.mmod = ((pmode64 >> 5) & 0x1ull) != 0ull; - pmode.amod = ((pmode64 >> 6) & 0x1ull) != 0ull; - pmode.slbg = ((pmode64 >> 7) & 0x1ull) != 0ull; - pmode.alp = static_cast((pmode64 >> 8) & 0xFFu); - return pmode; - } - - struct GSSmode2State - { - bool interlaced = false; - bool frameMode = true; - }; - - GSSmode2State decodeSMode2(uint64_t smode264) - { - GSSmode2State smode2{}; - smode2.interlaced = (smode264 & 0x1ull) != 0ull; - smode2.frameMode = ((smode264 >> 1) & 0x1ull) != 0ull; - return smode2; - } - - void applyFieldPresentation(std::vector &pixels, uint32_t width, uint32_t height, bool oddField) - { - if (pixels.empty() || width == 0u || height < 2u) - { - return; - } - - const std::vector source = pixels; - for (uint32_t y = 0; y < height; ++y) - { - uint32_t sourceY = ((y >> 1u) << 1u) + (oddField ? 1u : 0u); - if (sourceY >= height) - { - sourceY = height - 1u; - } - - const uint8_t *srcRow = source.data() + (sourceY * kHostFrameWidth * 4u); - uint8_t *dstRow = pixels.data() + (y * kHostFrameWidth * 4u); - std::memcpy(dstRow, srcRow, width * 4u); - } - } - - void normalizePresentationAlpha(std::vector &pixels, uint32_t width, uint32_t height) - { - if (pixels.empty() || width == 0u || height == 0u) - { - return; - } - - for (uint32_t y = 0; y < height; ++y) - { - uint8_t *row = pixels.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width; ++x) - { - row[x * 4u + 3u] = 255u; - } - } - } - - uint8_t blendPresentationChannel(uint8_t src, uint8_t dst, uint32_t factor) - { - const int delta = static_cast(src) - static_cast(dst); - return GSInternal::clampU8(static_cast(dst) + ((delta * static_cast(factor)) / 255)); - } - - uint32_t countNonBlackPixels(const std::vector &pixels, uint32_t width, uint32_t height) - { - uint32_t count = 0u; - for (uint32_t y = 0; y < height; ++y) - { - const uint8_t *row = pixels.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width; ++x) - { - const uint8_t r = row[x * 4u + 0u]; - const uint8_t g = row[x * 4u + 1u]; - const uint8_t b = row[x * 4u + 2u]; - if (r != 0u || g != 0u || b != 0u) - { - ++count; - } - } - } - return count; - } - - bool clearFramebufferRect(GS *gs, const GSContext &ctx, uint32_t rgba) - { - if (ctx.frame.fbw == 0u) - { - return false; - } - - const uint32_t stride = GSInternal::fbStride(ctx.frame.fbw, ctx.frame.psm); - if (stride == 0u) - { - return false; - } - - const u32 x0 = static_cast(std::max(0, ctx.scissor.x0)); - const u32 x1 = static_cast(std::max(x0, ctx.scissor.x1)); - const u32 y0 = static_cast(std::max(0, ctx.scissor.y0)); - const u32 y1 = static_cast(std::max(y0, ctx.scissor.y1)); - - uint8_t r = static_cast(rgba & 0xFFu); - uint8_t g = static_cast((rgba >> 8) & 0xFFu); - uint8_t b = static_cast((rgba >> 16) & 0xFFu); - uint8_t a = static_cast((rgba >> 24) & 0xFFu); - - u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp); - u32 fbw = std::max(ctx.frame.fbw, 1u); - u32 fpsm = ctx.frame.psm; - - if ((ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24) - { - a = static_cast(a | 0x80u); - } - - if (ctx.frame.psm == GS_PSM_CT32 || ctx.frame.psm == GS_PSM_CT24) - { - const uint32_t srcPixel = - static_cast(r) | - (static_cast(g) << 8) | - (static_cast(b) << 16) | - (static_cast(a) << 24); - - for (int y = y0; y <= y1; ++y) - { - for (int x = x0; x <= x1; ++x) - { - uint32_t pixel = srcPixel; - if (ctx.frame.fbmsk != 0u) - { - const u32 c = gs->ReadVram(fpsm, fbp, fbw, x, y); - pixel = (pixel & ~ctx.frame.fbmsk) | (c & ctx.frame.fbmsk); - } - gs->WriteVram(fpsm, fbp, fbw, x, y, pixel); - } - } - return true; - } - - if (ctx.frame.psm == GS_PSM_CT16 || ctx.frame.psm == GS_PSM_CT16S) - { - const uint16_t srcPixel = encodeFramePixelPSMCT16(r, g, b, a); - const uint16_t mask = static_cast(ctx.frame.fbmsk & 0xFFFFu); - const uint32_t widthBlocks = (ctx.frame.fbw != 0u) ? ctx.frame.fbw : 1u; - const uint32_t basePtr = GSInternal::framePageBaseToBlock(ctx.frame.fbp); - - for (int y = y0; y <= y1; ++y) - { - for (int x = x0; x <= x1; ++x) - { - uint16_t pixel = srcPixel; - if (mask != 0u) - { - const u16 c = gs->ReadVram(fpsm, fbp, fbw, x, y); - pixel = static_cast((pixel & ~mask) | (c & mask)); - } - gs->WriteVram(fpsm, fbp, fbw, x, y, pixel); - } - } - return true; - } - - return false; - } - - std::atomic s_debugGifPacketCount{0}; - std::atomic s_debugGsRegisterCount{0}; - std::atomic s_debugGsPackedVertexCount{0}; - std::atomic s_debugGsVertexKickCount{0}; - std::atomic s_debugCopyRegCount{0}; - std::atomic s_debugTexaWriteCount{0}; - std::atomic s_debugCvFontUploadCount{0}; - std::atomic s_debugLocalCopyCount{0}; -} - -using namespace GSInternal; - -GS::GS() -{ - using namespace GSMem; - - InitLookupTables(); - - for (usz i = 0; i < m_read_vram_funcs.size(); ++i) - { - switch (i) - { - case GS_PSM_CT32: - m_read_vram_funcs[i] = ReadCT32; - m_write_vram_funcs[i] = WriteCT32; - break; - case GS_PSM_CT24: - m_read_vram_funcs[i] = ReadCT24; - m_write_vram_funcs[i] = WriteCT24; - break; - case GS_PSM_CT16: - m_read_vram_funcs[i] = ReadCT16; - m_write_vram_funcs[i] = WriteCT16; - break; - case GS_PSM_CT16S: - m_read_vram_funcs[i] = ReadCT16S; - m_write_vram_funcs[i] = WriteCT16S; - break; - case GS_PSM_T8: - m_read_vram_funcs[i] = ReadP8; - m_write_vram_funcs[i] = WriteP8; - break; - case GS_PSM_T8H: - m_read_vram_funcs[i] = ReadP8H; - m_write_vram_funcs[i] = WriteP8H; - break; - case GS_PSM_T4: - m_read_vram_funcs[i] = ReadP4; - m_write_vram_funcs[i] = WriteP4; - break; - case GS_PSM_T4HH: - m_read_vram_funcs[i] = ReadP4HH; - m_write_vram_funcs[i] = WriteP4HH; - break; - case GS_PSM_T4HL: - m_read_vram_funcs[i] = ReadP4HL; - m_write_vram_funcs[i] = WriteP4HL; - break; - case GS_PSM_Z32: - m_read_vram_funcs[i] = ReadZ32; - m_write_vram_funcs[i] = WriteZ32; - break; - case GS_PSM_Z24: - m_read_vram_funcs[i] = ReadZ24; - m_write_vram_funcs[i] = WriteZ24; - break; - case GS_PSM_Z16: - m_read_vram_funcs[i] = ReadZ16; - m_write_vram_funcs[i] = WriteZ16; - break; - case GS_PSM_Z16S: - m_read_vram_funcs[i] = ReadZ16S; - m_write_vram_funcs[i] = WriteZ16S; - break; - default: - m_read_vram_funcs[i] = ReadNull; - m_write_vram_funcs[i] = WriteNull; - break; - } - } - - reset(); -} - -void GS::init(uint8_t *vram, uint32_t vramSize, GSRegisters *privRegs) -{ - m_vram = vram; - m_vramSize = vramSize; - m_privRegs = privRegs; - reset(); -} - -void GS::reset() -{ - std::lock_guard lock(m_stateMutex); - std::memset(m_ctx, 0, sizeof(m_ctx)); - m_prim = {}; - m_primRegister = {}; - m_prmodeRegister = {}; - m_curR = 0x80; - m_curG = 0x80; - m_curB = 0x80; - m_curA = 0x80; - m_curQ = 1.0f; - m_curS = 0.0f; - m_curT = 0.0f; - m_curU = 0; - m_curV = 0; - m_curFog = 0; - m_fogR = 0; - m_fogG = 0; - m_fogB = 0; - m_prmodecont = true; - m_pabe = false; - m_texa = {0u, false, 0u}; - m_texclut = {0u, 0u, 0u}; - m_bitbltbuf = {}; - m_trxpos = {}; - m_trxreg = {}; - m_trxdir = 3; - m_vtxCount = 0; - m_vtxIndex = 0; - m_localToHostBuffer.clear(); - m_localToHostReadPos = 0; - m_preferredDisplaySourceFrame = {}; - m_preferredDisplayDestFbp = 0; - m_hasPreferredDisplaySource = false; - { - std::lock_guard presentationLock(m_presentationMutex); - m_hostPresentationFrame.clear(); - m_hostPresentationWidth = 0u; - m_hostPresentationHeight = 0u; - m_hostPresentationDisplayFbp = 0u; - m_hostPresentationSourceFbp = 0u; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = false; - } - - m_debugHistoryWrite = 0; - m_debugHistoryCount = 0; - m_debugNextSeq = 1; - m_debugFrameIndex = 0; - m_debugLastVsyncTick = UINT64_MAX; - - for (int i = 0; i < 2; ++i) - { - m_ctx[i].frame.fbw = 10; - m_ctx[i].scissor = {0, 639, 0, 447}; - m_ctx[i].xyoffset = {0, 0}; - } -} - -GSContext &GS::activeContext() -{ - return m_ctx[m_prim.ctxt ? 1 : 0]; -} - -void GS::snapshotVRAM() -{ - std::lock_guard stateLock(m_stateMutex); - if (!m_vram || m_vramSize == 0) - return; - std::lock_guard lock(m_snapshotMutex); - m_displaySnapshot.resize(m_vramSize); - std::memcpy(m_displaySnapshot.data(), m_vram, m_vramSize); -} - -const uint8_t *GS::lockDisplaySnapshot(uint32_t &outSize) -{ - m_snapshotMutex.lock(); - if (m_displaySnapshot.empty()) - { - outSize = 0; - return nullptr; - } - - outSize = static_cast(m_displaySnapshot.size()); - return m_displaySnapshot.data(); -} - -GSDebugSnapshot GS::getDebugSnapshot() const -{ - std::lock_guard lock(m_stateMutex); - - GSDebugSnapshot snapshot{}; - snapshot.ctx[0] = m_ctx[0]; - snapshot.ctx[1] = m_ctx[1]; - snapshot.prim = m_prim; - snapshot.texa = m_texa; - snapshot.texclut = m_texclut; - snapshot.bitbltbuf = m_bitbltbuf; - snapshot.trxpos = m_trxpos; - snapshot.trxreg = m_trxreg; - snapshot.trxdir = m_trxdir; - snapshot.transferX = m_transferState.x; - snapshot.transferY = m_transferState.y; - snapshot.transferTotalPixels = m_transferState.total_pixels; - snapshot.transferCopiedPixels = m_transferState.copied_pixels; - snapshot.lastDisplayBaseBytes = m_lastDisplayBaseBytes; - snapshot.preferredDisplaySourceFrame = m_preferredDisplaySourceFrame; - snapshot.preferredDisplayDestFbp = m_preferredDisplayDestFbp; - snapshot.hasPreferredDisplaySource = m_hasPreferredDisplaySource; - { - std::lock_guard presentationLock(m_presentationMutex); - snapshot.hostPresentationWidth = m_hostPresentationWidth; - snapshot.hostPresentationHeight = m_hostPresentationHeight; - snapshot.hostPresentationDisplayFbp = m_hostPresentationDisplayFbp; - snapshot.hostPresentationSourceFbp = m_hostPresentationSourceFbp; - snapshot.hostPresentationUsedPreferred = m_hostPresentationUsedPreferred; - snapshot.hasHostPresentationFrame = m_hasHostPresentationFrame; - } - snapshot.localToHostPendingBytes = (m_localToHostReadPos < m_localToHostBuffer.size()) - ? (m_localToHostBuffer.size() - m_localToHostReadPos) - : 0u; - return snapshot; -} - -std::vector GS::getDebugHistory() const -{ - std::lock_guard lock(m_stateMutex); - - std::vector out; - out.reserve(m_debugHistoryCount); - const size_t first = (m_debugHistoryWrite + kDebugHistoryCapacity - m_debugHistoryCount) % kDebugHistoryCapacity; - for (size_t i = 0; i < m_debugHistoryCount; ++i) - { - out.push_back(m_debugHistory[(first + i) % kDebugHistoryCapacity]); - } - return out; -} - -void GS::clearDebugHistory() -{ - std::lock_guard lock(m_stateMutex); - m_debugHistoryWrite = 0; - m_debugHistoryCount = 0; - m_debugNextSeq = 1; - m_debugFrameIndex = 0; - m_debugLastVsyncTick = UINT64_MAX; -} - -bool GS::isDebugHistoryPaused() const -{ - std::lock_guard lock(m_stateMutex); - return m_debugHistoryPaused; -} - -void GS::setDebugHistoryPaused(bool paused) -{ - std::lock_guard lock(m_stateMutex); - m_debugHistoryPaused = paused; -} - -GSDebugHistoryEntry GS::makeDebugEventUnlocked(GSDebugEventKind kind) const -{ - GSDebugHistoryEntry entry{}; - entry.kind = kind; - entry.prim = m_prim; - const uint32_t ci = m_prim.ctxt ? 1u : 0u; - entry.frame = m_ctx[ci].frame; - entry.zbuf = m_ctx[ci].zbuf; - entry.tex0 = m_ctx[ci].tex0; - entry.scissor = m_ctx[ci].scissor; - entry.test = m_ctx[ci].test; - entry.alpha = m_ctx[ci].alpha; - entry.bitbltbuf = m_bitbltbuf; - entry.trxpos = m_trxpos; - entry.trxreg = m_trxreg; - entry.trxdir = m_trxdir; - entry.transferPixels = m_transferState.total_pixels; - return entry; -} - -void GS::recordDebugEventUnlocked(GSDebugHistoryEntry entry) -{ - if (m_debugHistoryPaused) - { - return; - } - - const uint64_t tick = m_privRegs ? m_privRegs->vsyncTick.load(std::memory_order_acquire) : 0u; - if (m_debugLastVsyncTick == UINT64_MAX) - { - m_debugLastVsyncTick = tick; - } - else if (tick != m_debugLastVsyncTick) - { - ++m_debugFrameIndex; - m_debugLastVsyncTick = tick; - } - - entry.seq = m_debugNextSeq++; - entry.vsyncTick = tick; - entry.frameIndex = m_debugFrameIndex; - - m_debugHistory[m_debugHistoryWrite] = entry; - m_debugHistoryWrite = (m_debugHistoryWrite + 1u) % kDebugHistoryCapacity; - if (m_debugHistoryCount < kDebugHistoryCapacity) - { - ++m_debugHistoryCount; - } -} - -void GS::recordGifTagDebugEventUnlocked(uint32_t sizeBytes, uint32_t nloop, uint8_t flg, uint32_t nreg) -{ - if (m_debugHistoryPaused) - { - return; - } - - GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::GifTag); - entry.gifSizeBytes = sizeBytes; - entry.gifNloop = nloop; - entry.gifFlg = flg; - entry.gifNreg = static_cast(std::min(nreg, 16u)); - recordDebugEventUnlocked(entry); -} - -void GS::recordRegisterDebugEventUnlocked(uint8_t regAddr, uint64_t value) -{ - if (m_debugHistoryPaused) - { - return; - } - - switch (regAddr) - { - case GS_REG_PRIM: - case GS_REG_TEX0_1: - case GS_REG_TEX0_2: - case GS_REG_TEX2_1: - case GS_REG_TEX2_2: - case GS_REG_TEXA: - case GS_REG_TEXCLUT: - case GS_REG_FRAME_1: - case GS_REG_FRAME_2: - case GS_REG_ZBUF_1: - case GS_REG_ZBUF_2: - case GS_REG_ALPHA_1: - case GS_REG_ALPHA_2: - case GS_REG_TEST_1: - case GS_REG_TEST_2: - case GS_REG_SCISSOR_1: - case GS_REG_SCISSOR_2: - case GS_REG_XYOFFSET_1: - case GS_REG_XYOFFSET_2: - case GS_REG_BITBLTBUF: - case GS_REG_TRXPOS: - case GS_REG_TRXREG: - case GS_REG_TRXDIR: - break; - default: - return; - } - - GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Register); - entry.reg = regAddr; - entry.regValue = value; - recordDebugEventUnlocked(entry); -} - -void GS::recordDrawDebugEventUnlocked(int vertexCount) -{ - if (m_debugHistoryPaused) - { - return; - } - - if (vertexCount <= 0) - { - return; - } - - GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Draw); - entry.vertexCount = static_cast(vertexCount); - - const int count = std::min(vertexCount, kMaxVerts); - entry.xMin = entry.xMax = m_vtxQueue[0].x; - entry.yMin = entry.yMax = m_vtxQueue[0].y; - entry.zMin = entry.zMax = m_vtxQueue[0].z; - entry.aMin = entry.aMax = m_vtxQueue[0].a; - - for (int i = 1; i < count; ++i) - { - const GSVertex &v = m_vtxQueue[i]; - entry.xMin = std::min(entry.xMin, v.x); - entry.xMax = std::max(entry.xMax, v.x); - entry.yMin = std::min(entry.yMin, v.y); - entry.yMax = std::max(entry.yMax, v.y); - entry.zMin = std::min(entry.zMin, v.z); - entry.zMax = std::max(entry.zMax, v.z); - entry.aMin = std::min(entry.aMin, v.a); - entry.aMax = std::max(entry.aMax, v.a); - } - - recordDebugEventUnlocked(entry); -} - -void GS::recordTransferDebugEventUnlocked() -{ - if (m_debugHistoryPaused) - { - return; - } - - GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Transfer); - entry.transferPixels = m_transferState.total_pixels; - recordDebugEventUnlocked(entry); -} - -void GS::recordPresentDebugEventUnlocked(uint32_t displayFbp, uint32_t sourceFbp, uint32_t width, uint32_t height, bool usedPreferred) -{ - if (m_debugHistoryPaused) - { - return; - } - - GSDebugHistoryEntry entry = makeDebugEventUnlocked(GSDebugEventKind::Present); - entry.displayFbp = displayFbp; - entry.sourceFbp = sourceFbp; - entry.width = width; - entry.height = height; - entry.usedPreferred = usedPreferred; - recordDebugEventUnlocked(entry); -} - -bool GS::getPreferredDisplaySource(GSFrameReg &outSource, uint32_t &outDestFbp) const -{ - std::lock_guard lock(m_stateMutex); - if (!m_hasPreferredDisplaySource) - { - outSource = {}; - outDestFbp = 0u; - return false; - } - - outSource = m_preferredDisplaySourceFrame; - outDestFbp = m_preferredDisplayDestFbp; - return true; -} - -void GS::unlockDisplaySnapshot() -{ - m_snapshotMutex.unlock(); -} - -uint32_t GS::getLastDisplayBaseBytes() const -{ - return m_lastDisplayBaseBytes; -} - -void GS::refreshDisplaySnapshot() -{ - snapshotVRAM(); -} - -bool GS::copyFrameToHostRgbaUnlocked(const GSFrameReg &frame, - uint32_t width, - uint32_t height, - std::vector &outPixels, - bool preserveAlpha, - bool useLocalMemoryLayout, - bool frameBaseIsPages, - uint32_t sourceOriginX, - uint32_t sourceOriginY) const -{ - if (!m_vram || m_vramSize == 0u) - { - return false; - } - - outPixels.resize(kHostFrameWidth * kHostFrameHeight * 4u); - auto failCopy = [&outPixels]() -> bool - { - outPixels.clear(); - return false; - }; - - const uint32_t baseBytes = frameBaseIsPages ? (frame.fbp * 8192u) : (frame.fbp * 256u); - const uint32_t basePtr = frameBaseIsPages ? GSInternal::framePageBaseToBlock(frame.fbp) : frame.fbp; - const uint32_t fbwBlocks = frame.fbw ? frame.fbw : (kHostFrameWidth / 64u); - const uint32_t bytesPerPixel = (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) ? 2u : 4u; - const uint32_t strideBytes = fbwBlocks * 64u * bytesPerPixel; - - if (frame.psm == GS_PSM_CT32 || frame.psm == GS_PSM_CT24) - { - const uint32_t srcPixelBytes = (frame.psm == GS_PSM_CT24) ? 3u : 4u; - if (useLocalMemoryLayout) - { - for (uint32_t y = 0; y < height; ++y) - { - uint8_t *dstRow = outPixels.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width; ++x) - { - const uint32_t srcX = sourceOriginX + x; - const uint32_t srcY = sourceOriginY + y; - - const u32 c = ReadVram(frame.psm, basePtr, fbwBlocks, srcX, srcY); - - const u32 r = c & 0xFF; - const u32 g = (c >> 8) & 0xFF; - const u32 b = (c >> 16) & 0xFF; - - u32 a = 0xFF; - if (preserveAlpha && frame.psm != GS_PSM_CT24) - { - a = (c >> 24) & 0xFF; - } - - dstRow[x * 4u + 0u] = r; - dstRow[x * 4u + 1u] = g; - dstRow[x * 4u + 2u] = b; - dstRow[x * 4u + 3u] = a; - } - } - return true; - } - - for (uint32_t y = 0; y < height; ++y) - { - const uint32_t dstOff = y * kHostFrameWidth * 4u; - uint8_t *dstRow = outPixels.data() + dstOff; - for (uint32_t x = 0; x < width; ++x) - { - const uint32_t srcX = sourceOriginX + x; - const uint32_t srcY = sourceOriginY + y; - const uint32_t srcOff = baseBytes + (srcY * strideBytes) + (srcX * srcPixelBytes); - if (srcOff + srcPixelBytes > m_vramSize) - { - return failCopy(); - } - - dstRow[x * 4u + 0u] = m_vram[srcOff + 0u]; - dstRow[x * 4u + 1u] = m_vram[srcOff + 1u]; - dstRow[x * 4u + 2u] = m_vram[srcOff + 2u]; - dstRow[x * 4u + 3u] = - (preserveAlpha && frame.psm != GS_PSM_CT24) ? m_vram[srcOff + 3u] : 255u; - } - } - return true; - } - - if (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) - { - if (useLocalMemoryLayout) - { - for (uint32_t y = 0; y < height; ++y) - { - const uint32_t dstOff = y * kHostFrameWidth * 4u; - uint8_t *dst = outPixels.data() + dstOff; - for (uint32_t x = 0; x < width; ++x) - { - const uint32_t srcX = sourceOriginX + x; - const uint32_t srcY = sourceOriginY + y; - - const u16 c = ReadVram(frame.psm, basePtr, fbwBlocks, srcX, srcY); - - const uint32_t r = c & 31u; - const uint32_t g = (c >> 5) & 31u; - const uint32_t b = (c >> 10) & 31u; - dst[x * 4u + 0u] = static_cast((r << 3) | (r >> 2)); - dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); - dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); - dst[x * 4u + 3u] = preserveAlpha ? ((c & 0x8000u) ? 0x80u : 0x00u) : 255u; - } - } - return true; - } - - for (uint32_t y = 0; y < height; ++y) - { - const uint32_t dstOff = y * kHostFrameWidth * 4u; - uint8_t *dst = outPixels.data() + dstOff; - for (uint32_t x = 0; x < width; ++x) - { - const uint32_t srcX = sourceOriginX + x; - const uint32_t srcY = sourceOriginY + y; - const uint32_t srcOff = baseBytes + (srcY * strideBytes) + (srcX * 2u); - if (srcOff + sizeof(uint16_t) > m_vramSize) - { - return failCopy(); - } - - uint16_t pixel = 0u; - std::memcpy(&pixel, m_vram + srcOff, sizeof(pixel)); - const uint32_t r = pixel & 31u; - const uint32_t g = (pixel >> 5) & 31u; - const uint32_t b = (pixel >> 10) & 31u; - dst[x * 4u + 0u] = static_cast((r << 3) | (r >> 2)); - dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); - dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); - dst[x * 4u + 3u] = preserveAlpha ? ((pixel & 0x8000u) ? 0x80u : 0x00u) : 255u; - } - } - return true; - } - - return failCopy(); -} - -void GS::latchHostPresentationFrame() -{ - thread_local std::vector vramSnapshot; - thread_local GS presentationGs; - - thread_local GSRegisters privateRegisters{}; - GSFrameReg contextFrames[2]{}; - GSFrameReg preferredSource{}; - uint32_t preferredDestFbp = 0u; - bool hasPreferredSource = false; - uint32_t vramSize = 0u; - - { - std::lock_guard lock(m_stateMutex); - if (!m_privRegs || !m_vram || m_vramSize == 0u) - { - std::lock_guard presentationLock(m_presentationMutex); - m_hostPresentationFrame.clear(); - m_hostPresentationWidth = 0u; - m_hostPresentationHeight = 0u; - m_hostPresentationDisplayFbp = 0u; - m_hostPresentationSourceFbp = 0u; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = false; - return; - } - - vramSize = m_vramSize; - vramSnapshot.resize(vramSize); - std::memcpy(vramSnapshot.data(), m_vram, vramSize); - - privateRegisters.pmode = m_privRegs->pmode; - privateRegisters.smode1 = m_privRegs->smode1; - privateRegisters.smode2 = m_privRegs->smode2; - privateRegisters.srfsh = m_privRegs->srfsh; - privateRegisters.synch1 = m_privRegs->synch1; - privateRegisters.synch2 = m_privRegs->synch2; - privateRegisters.syncv = m_privRegs->syncv; - privateRegisters.dispfb1 = m_privRegs->dispfb1; - privateRegisters.display1 = m_privRegs->display1; - privateRegisters.dispfb2 = m_privRegs->dispfb2; - privateRegisters.display2 = m_privRegs->display2; - privateRegisters.extbuf = m_privRegs->extbuf; - privateRegisters.extdata = m_privRegs->extdata; - privateRegisters.extwrite = m_privRegs->extwrite; - privateRegisters.bgcolor = m_privRegs->bgcolor; - privateRegisters.csr.store(m_privRegs->csr.load(std::memory_order_acquire), std::memory_order_relaxed); - privateRegisters.vsyncTick.store(m_privRegs->vsyncTick.load(std::memory_order_acquire), std::memory_order_relaxed); - privateRegisters.imr = m_privRegs->imr; - privateRegisters.busdir = m_privRegs->busdir; - privateRegisters.siglblid = m_privRegs->siglblid; - - contextFrames[0] = m_ctx[0].frame; - contextFrames[1] = m_ctx[1].frame; - preferredSource = m_preferredDisplaySourceFrame; - preferredDestFbp = m_preferredDisplayDestFbp; - hasPreferredSource = m_hasPreferredDisplaySource; - } - - presentationGs.init(vramSnapshot.data(), vramSize, &privateRegisters); - presentationGs.m_ctx[0].frame = contextFrames[0]; - presentationGs.m_ctx[1].frame = contextFrames[1]; - presentationGs.m_preferredDisplaySourceFrame = preferredSource; - presentationGs.m_preferredDisplayDestFbp = preferredDestFbp; - presentationGs.m_hasPreferredDisplaySource = hasPreferredSource; - presentationGs.latchHostPresentationFrameUnlocked(); - - uint32_t displayFbp = 0u; - uint32_t sourceFbp = 0u; - uint32_t width = 0u; - uint32_t height = 0u; - bool usedPreferred = false; - bool hasFrame = false; - { - std::lock_guard presentationLock(m_presentationMutex); - m_hostPresentationFrame.swap(presentationGs.m_hostPresentationFrame); - m_hostPresentationWidth = presentationGs.m_hostPresentationWidth; - m_hostPresentationHeight = presentationGs.m_hostPresentationHeight; - m_hostPresentationDisplayFbp = presentationGs.m_hostPresentationDisplayFbp; - m_hostPresentationSourceFbp = presentationGs.m_hostPresentationSourceFbp; - m_hostPresentationUsedPreferred = presentationGs.m_hostPresentationUsedPreferred; - m_hasHostPresentationFrame = presentationGs.m_hasHostPresentationFrame; - - displayFbp = m_hostPresentationDisplayFbp; - sourceFbp = m_hostPresentationSourceFbp; - width = m_hostPresentationWidth; - height = m_hostPresentationHeight; - usedPreferred = m_hostPresentationUsedPreferred; - hasFrame = m_hasHostPresentationFrame; - } - - if (hasFrame) - { - std::lock_guard lock(m_stateMutex); - recordPresentDebugEventUnlocked(displayFbp, sourceFbp, width, height, usedPreferred); - } -} - -void GS::latchHostPresentationFrameUnlocked() -{ - if (!m_privRegs || !m_vram || m_vramSize == 0u) - { - m_hostPresentationFrame.clear(); - m_hostPresentationWidth = 0u; - m_hostPresentationHeight = 0u; - m_hostPresentationDisplayFbp = 0u; - m_hostPresentationSourceFbp = 0u; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = false; - return; - } - - const GSPmodeState pmode = decodePmode(m_privRegs->pmode); - const GSSmode2State smode2 = decodeSMode2(m_privRegs->smode2); - const bool applyFieldMode = smode2.interlaced && !smode2.frameMode; - const bool oddField = (m_privRegs->vsyncTick.load(std::memory_order_acquire) & 1ull) != 0ull; - const GSFrameReg displayFrame1 = decodeDisplayFrame(m_privRegs->dispfb1); - const GSFrameReg displayFrame2 = decodeDisplayFrame(m_privRegs->dispfb2); - const GSDisplayReadOrigin displayOrigin1 = decodeDisplayReadOrigin(m_privRegs->dispfb1); - const GSDisplayReadOrigin displayOrigin2 = decodeDisplayReadOrigin(m_privRegs->dispfb2); - - uint32_t width1 = 0u; - uint32_t height1 = 0u; - uint32_t width2 = 0u; - uint32_t height2 = 0u; - decodeDisplaySize(m_privRegs->display1, width1, height1); - decodeDisplaySize(m_privRegs->display2, width2, height2); - - const bool validCrt1 = pmode.enableCrt1 && hasDisplaySetup(m_privRegs->display1, displayFrame1); - const bool validCrt2 = pmode.enableCrt2 && hasDisplaySetup(m_privRegs->display2, displayFrame2); - - auto copyDisplaySource = [&](const GSFrameReg &displayFrame, - const GSDisplayReadOrigin &displayOrigin, - uint32_t width, - uint32_t height, - bool allowPreferred, - bool preserveAlpha, - GSFrameReg &selectedFrame, - std::vector &scratch, - bool &usedPreferred) -> bool - { - selectedFrame = displayFrame; - scratch.clear(); - usedPreferred = false; - - if (allowPreferred && - m_hasPreferredDisplaySource && - m_preferredDisplayDestFbp == displayFrame.fbp && - (m_preferredDisplaySourceFrame.fbw != 0u || m_preferredDisplaySourceFrame.fbp != displayFrame.fbp)) - { - if (copyFrameToHostRgbaUnlocked(m_preferredDisplaySourceFrame, - width, - height, - scratch, - preserveAlpha, - true, - false, - 0u, - 0u)) - { - selectedFrame = m_preferredDisplaySourceFrame; - usedPreferred = true; - } - } - - if (scratch.empty() && - !copyFrameToHostRgbaUnlocked(displayFrame, - width, - height, - scratch, - preserveAlpha, - true, - true, - displayOrigin.x, - displayOrigin.y)) - { - return false; - } - - if (!usedPreferred && displayFrame.fbp == 0u && countNonBlackPixels(scratch, width, height) == 0u) - { - for (int contextIndex = 0; contextIndex < 2; ++contextIndex) - { - const GSFrameReg &candidate = m_ctx[contextIndex].frame; - if (candidate.fbp == selectedFrame.fbp && - candidate.fbw == selectedFrame.fbw && - candidate.psm == selectedFrame.psm) - { - continue; - } - - std::vector candidatePixels; - if (!copyFrameToHostRgbaUnlocked(candidate, - width, - height, - candidatePixels, - preserveAlpha, - true, - true, - 0u, - 0u)) - { - continue; - } - - if (countNonBlackPixels(candidatePixels, width, height) == 0u) - { - continue; - } - - selectedFrame = candidate; - scratch.swap(candidatePixels); - break; - } - } - - return true; - }; - - if (!validCrt1 && !validCrt2) - { - m_hostPresentationFrame.clear(); - m_hostPresentationWidth = 0u; - m_hostPresentationHeight = 0u; - m_hostPresentationDisplayFbp = 0u; - m_hostPresentationSourceFbp = 0u; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = false; - return; - } - - if (validCrt1 && validCrt2) - { - GSFrameReg selectedFrame1{}; - GSFrameReg selectedFrame2{}; - std::vector rc1; - std::vector rc2; - bool usedPreferred1 = false; - bool usedPreferred2 = false; - - const bool copiedCrt1 = copyDisplaySource(displayFrame1, displayOrigin1, width1, height1, false, true, selectedFrame1, rc1, usedPreferred1); - const bool copiedCrt2 = copyDisplaySource(displayFrame2, displayOrigin2, width2, height2, false, true, selectedFrame2, rc2, usedPreferred2); - - if (copiedCrt1 && copiedCrt2) - { - const uint32_t width = std::max(width1, width2); - const uint32_t height = std::max(height1, height2); - const uint8_t bgR = static_cast(m_privRegs->bgcolor & 0xFFu); - const uint8_t bgG = static_cast((m_privRegs->bgcolor >> 8) & 0xFFu); - const uint8_t bgB = static_cast((m_privRegs->bgcolor >> 16) & 0xFFu); - const uint8_t bgA = pmode.alp; - - std::vector merged(kHostFrameWidth * kHostFrameHeight * 4u, 0u); - for (uint32_t y = 0; y < height; ++y) - { - uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width; ++x) - { - dstRow[x * 4u + 0u] = bgR; - dstRow[x * 4u + 1u] = bgG; - dstRow[x * 4u + 2u] = bgB; - dstRow[x * 4u + 3u] = bgA; - } - } - - if (!pmode.slbg) - { - for (uint32_t y = 0; y < height2; ++y) - { - const uint8_t *srcRow = rc2.data() + (y * kHostFrameWidth * 4u); - uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width2; ++x) - { - dstRow[x * 4u + 0u] = srcRow[x * 4u + 0u]; - dstRow[x * 4u + 1u] = srcRow[x * 4u + 1u]; - dstRow[x * 4u + 2u] = srcRow[x * 4u + 2u]; - dstRow[x * 4u + 3u] = srcRow[x * 4u + 3u]; - } - } - } - - for (uint32_t y = 0; y < height1; ++y) - { - const uint8_t *srcRow = rc1.data() + (y * kHostFrameWidth * 4u); - uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width1; ++x) - { - const uint8_t srcR = srcRow[x * 4u + 0u]; - const uint8_t srcG = srcRow[x * 4u + 1u]; - const uint8_t srcB = srcRow[x * 4u + 2u]; - const uint8_t srcA = srcRow[x * 4u + 3u]; - const uint8_t dstR = dstRow[x * 4u + 0u]; - const uint8_t dstG = dstRow[x * 4u + 1u]; - const uint8_t dstB = dstRow[x * 4u + 2u]; - const uint8_t dstA = dstRow[x * 4u + 3u]; - const uint32_t factor = pmode.mmod - ? static_cast(pmode.alp) - : std::min(255u, static_cast(srcA) * 2u); - - dstRow[x * 4u + 0u] = blendPresentationChannel(srcR, dstR, factor); - dstRow[x * 4u + 1u] = blendPresentationChannel(srcG, dstG, factor); - dstRow[x * 4u + 2u] = blendPresentationChannel(srcB, dstB, factor); - dstRow[x * 4u + 3u] = pmode.amod ? dstA : srcA; - } - } - - for (uint32_t y = 0; y < height; ++y) - { - uint8_t *row = merged.data() + (y * kHostFrameWidth * 4u); - for (uint32_t x = 0; x < width; ++x) - { - row[x * 4u + 3u] = 255u; - } - } - - if (applyFieldMode) - { - applyFieldPresentation(merged, width, height, oddField); - } - - m_hostPresentationFrame.swap(merged); - m_hostPresentationWidth = width; - m_hostPresentationHeight = height; - m_hostPresentationDisplayFbp = displayFrame1.fbp; - m_hostPresentationSourceFbp = selectedFrame1.fbp; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = true; - recordPresentDebugEventUnlocked(m_hostPresentationDisplayFbp, - m_hostPresentationSourceFbp, - m_hostPresentationWidth, - m_hostPresentationHeight, - m_hostPresentationUsedPreferred); - return; - } - } - - const GSFrameReg &displayFrame = validCrt1 ? displayFrame1 : displayFrame2; - const uint32_t width = validCrt1 ? width1 : width2; - const uint32_t height = validCrt1 ? height1 : height2; - - GSFrameReg selectedFrame = displayFrame; - std::vector scratch; - bool usedPreferred = false; - const GSDisplayReadOrigin &displayOrigin = validCrt1 ? displayOrigin1 : displayOrigin2; - if (!copyDisplaySource(displayFrame, displayOrigin, width, height, true, false, selectedFrame, scratch, usedPreferred)) - { - m_hostPresentationFrame.clear(); - m_hostPresentationWidth = 0u; - m_hostPresentationHeight = 0u; - m_hostPresentationDisplayFbp = displayFrame.fbp; - m_hostPresentationSourceFbp = 0u; - m_hostPresentationUsedPreferred = false; - m_hasHostPresentationFrame = false; - return; - } - - if (applyFieldMode) - { - applyFieldPresentation(scratch, width, height, oddField); - } - - normalizePresentationAlpha(scratch, width, height); - - m_hostPresentationFrame.swap(scratch); - m_hostPresentationWidth = width; - m_hostPresentationHeight = height; - m_hostPresentationDisplayFbp = displayFrame.fbp; - m_hostPresentationSourceFbp = selectedFrame.fbp; - m_hostPresentationUsedPreferred = usedPreferred; - m_hasHostPresentationFrame = true; - recordPresentDebugEventUnlocked(m_hostPresentationDisplayFbp, - m_hostPresentationSourceFbp, - m_hostPresentationWidth, - m_hostPresentationHeight, - m_hostPresentationUsedPreferred); -} - -bool GS::copyLatchedHostPresentationFrame(std::vector &outPixels, - uint32_t &outWidth, - uint32_t &outHeight, - uint32_t *outDisplayFbp, - uint32_t *outSourceFbp, - bool *outUsedPreferred) const -{ - std::lock_guard lock(m_presentationMutex); - if (!m_hasHostPresentationFrame || m_hostPresentationFrame.empty()) - { - outPixels.clear(); - outWidth = 0u; - outHeight = 0u; - if (outDisplayFbp) - *outDisplayFbp = 0u; - if (outSourceFbp) - *outSourceFbp = 0u; - if (outUsedPreferred) - *outUsedPreferred = false; - return false; - } - - outWidth = m_hostPresentationWidth; - outHeight = m_hostPresentationHeight; - if (outDisplayFbp) - *outDisplayFbp = m_hostPresentationDisplayFbp; - if (outSourceFbp) - *outSourceFbp = m_hostPresentationSourceFbp; - if (outUsedPreferred) - *outUsedPreferred = m_hostPresentationUsedPreferred; - - const size_t packedRowBytes = static_cast(outWidth) * 4u; - outPixels.resize(packedRowBytes * static_cast(outHeight)); - if (outWidth != 0u && outHeight != 0u) - { - const size_t sourceRowBytes = static_cast(kHostFrameWidth) * 4u; - for (uint32_t y = 0; y < outHeight; ++y) - { - const size_t srcOffset = static_cast(y) * sourceRowBytes; - const size_t dstOffset = static_cast(y) * packedRowBytes; - if (srcOffset + packedRowBytes > m_hostPresentationFrame.size() || - dstOffset + packedRowBytes > outPixels.size()) - { - outPixels.clear(); - outWidth = 0u; - outHeight = 0u; - if (outDisplayFbp) - *outDisplayFbp = 0u; - if (outSourceFbp) - *outSourceFbp = 0u; - if (outUsedPreferred) - *outUsedPreferred = false; - return false; - } - - std::memcpy(outPixels.data() + dstOffset, - m_hostPresentationFrame.data() + srcOffset, - packedRowBytes); - } - } - return true; -} - -void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) -{ - std::lock_guard lock(m_stateMutex); - if (!data || sizeBytes < 16 || !m_vram) - return; - - if (tryProcessNativeImageUploadPacket(data, sizeBytes)) - return; - - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed); - if (packetIndex < 48u) - { - const uint64_t tagLo = loadLE64(data); - const uint32_t nloop = static_cast(tagLo & 0x7FFFu); - const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); - uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); - if (nreg == 0u) - nreg = 16u; - RUNTIME_LOG("[gs:gif] idx=" << packetIndex - << " size=" << sizeBytes - << " nloop=" << nloop - << " flg=" << static_cast(flg) - << " nreg=" << nreg - << " ctx0fbp=" << m_ctx[0].frame.fbp - << " ctx1fbp=" << m_ctx[1].frame.fbp - << std::endl); - } - }); - - uint32_t offset = 0; - while (offset + 16 <= sizeBytes) - { - uint64_t tagLo = loadLE64(data + offset); - uint64_t tagHi = loadLE64(data + offset + 8); - offset += 16; - - m_curQ = 1.0f; - - uint32_t nloop = static_cast(tagLo & 0x7FFF); - uint8_t flg = static_cast((tagLo >> 58) & 0x3); - uint32_t nreg = static_cast((tagLo >> 60) & 0xF); - if (nreg == 0) - nreg = 16; - - recordGifTagDebugEventUnlocked(sizeBytes, nloop, flg, nreg); - - bool pre = ((tagLo >> 46) & 1) != 0; - if (pre) - { - writeRegisterUnlocked(GS_REG_PRIM, (tagLo >> 47) & 0x7FF); - } - - uint8_t regs[16]; - for (uint32_t i = 0; i < nreg; ++i) - regs[i] = static_cast((tagHi >> (i * 4)) & 0xF); - - if (flg == GIF_FMT_PACKED) - { - for (uint32_t loop = 0; loop < nloop; ++loop) - { - for (uint32_t r = 0; r < nreg; ++r) - { - if (offset + 16 > sizeBytes) - return; - uint64_t lo = loadLE64(data + offset); - uint64_t hi = loadLE64(data + offset + 8); - offset += 16; - writeRegisterPacked(regs[r], lo, hi); - } - } - } - else if (flg == GIF_FMT_REGLIST) - { - for (uint32_t loop = 0; loop < nloop; ++loop) - { - for (uint32_t r = 0; r < nreg; ++r) - { - if (offset + 8 > sizeBytes) - return; - writeRegisterUnlocked(regs[r], loadLE64(data + offset)); - offset += 8; - } - } - if ((nloop * nreg) & 1) - offset += 8; - } - else if (flg == GIF_FMT_IMAGE) - { - uint32_t imageBytes = nloop * 16; - if (offset + imageBytes > sizeBytes) - imageBytes = sizeBytes - offset; - processImageData(data + offset, imageBytes); - offset += imageBytes; - } - } -} - -bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes) -{ - std::lock_guard lock(m_stateMutex); - if (!data || sizeBytes < 16u || !m_vram) - return false; - - if (!validatePackedGifPacket(data, sizeBytes)) - return false; - - const bool processed = visitPackedGifPacket(data, sizeBytes, [&](const PackedGifPacketTag &tag) - { - m_curQ = 1.0f; - - recordGifTagDebugEventUnlocked(sizeBytes, tag.nloop, GIF_FMT_PACKED, tag.nreg); - - const bool pre = ((tag.lo >> 46u) & 1u) != 0u; - if (pre) - writeRegisterUnlocked(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu); - - uint32_t offset = tag.payloadOffset; - for (uint32_t loop = 0u; loop < tag.nloop; ++loop) - { - for (uint32_t r = 0u; r < tag.nreg; ++r) - { - const uint64_t lo = loadLE64(data + offset); - const uint64_t hi = loadLE64(data + offset + 8u); - offset += 16u; - writeRegisterPacked(tag.regs[r], lo, hi); - } - } - - return true; }); - - if (!processed) - return false; - - ++m_nativePackedGIFPacketCount; - return true; -} - -void GS::uploadImageNative(uint64_t bitbltbuf, - uint64_t trxpos, - uint64_t trxreg, - uint64_t trxdir, - const uint8_t *data, - uint32_t sizeBytes) -{ - std::lock_guard lock(m_stateMutex); - uploadImageNativeUnlocked(bitbltbuf, trxpos, trxreg, trxdir, data, sizeBytes); -} - -void GS::uploadImageNativeUnlocked(uint64_t bitbltbuf, - uint64_t trxpos, - uint64_t trxreg, - uint64_t trxdir, - const uint8_t *data, - uint32_t sizeBytes) -{ - if (!data || sizeBytes == 0 || !m_vram) - return; - - writeRegisterUnlocked(GS_REG_BITBLTBUF, bitbltbuf); - writeRegisterUnlocked(GS_REG_TRXPOS, trxpos); - writeRegisterUnlocked(GS_REG_TRXREG, trxreg); - writeRegisterUnlocked(GS_REG_TRXDIR, trxdir); - processImageData(data, sizeBytes); - ++m_nativeImageUploadCount; -} - -bool GS::tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes) -{ - constexpr uint32_t kSetupRegisters = 4u; - constexpr uint32_t kPackedAdPayloadBytes = kSetupRegisters * 16u; - constexpr uint64_t kPackedAdDescriptor = 0x0Eull; - - if (!data || sizeBytes < 16u + kPackedAdPayloadBytes + 16u) - return false; - - const uint64_t setupTagLo = loadLE64(data); - const uint64_t setupTagHi = loadLE64(data + 8u); - const uint32_t setupNloop = static_cast(setupTagLo & 0x7FFFu); - const uint8_t setupFlg = static_cast((setupTagLo >> 58u) & 0x3u); - uint32_t setupNreg = static_cast((setupTagLo >> 60u) & 0xFu); - if (setupNreg == 0u) - setupNreg = 16u; - - if (setupNloop != kSetupRegisters || - setupFlg != GIF_FMT_PACKED || - setupNreg != 1u || - (setupTagHi & 0xFull) != kPackedAdDescriptor) - { - return false; - } - - uint64_t regs[kSetupRegisters] = {}; - uint32_t offset = 16u; - constexpr uint8_t expectedRegs[kSetupRegisters] = { - GS_REG_BITBLTBUF, - GS_REG_TRXPOS, - GS_REG_TRXREG, - GS_REG_TRXDIR, - }; - - for (uint32_t i = 0; i < kSetupRegisters; ++i) - { - regs[i] = loadLE64(data + offset); - const uint64_t reg = loadLE64(data + offset + 8u); - if ((reg & 0xFFu) != expectedRegs[i]) - return false; - offset += 16u; - } - - const uint32_t trxdirMode = static_cast(regs[3] & 0x3ull); - const uint32_t rrw = static_cast(regs[2] & 0xFFFull); - const uint32_t rrh = static_cast((regs[2] >> 32u) & 0xFFFull); - if (trxdirMode != 0u || rrw == 0u || rrh == 0u) - return false; - - if (offset + 16u > sizeBytes) - return false; - - const uint64_t imageTagLo = loadLE64(data + offset); - const uint8_t imageFlg = static_cast((imageTagLo >> 58u) & 0x3u); - const uint32_t imageNloop = static_cast(imageTagLo & 0x7FFFu); - if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u) - return false; - - offset += 16u; - const uint64_t imageBytes64 = static_cast(imageNloop) * 16ull; - if (imageBytes64 > 0xFFFFFFFFull) - return false; - const uint32_t imageBytes = static_cast(imageBytes64); - if (offset + imageBytes != sizeBytes) - return false; - - uploadImageNativeUnlocked(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes); - return true; -} - -void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) -{ - switch (regDesc) - { - case 0x00: - writeRegisterUnlocked(GS_REG_PRIM, lo & 0x7FF); - break; - case 0x01: - m_curR = static_cast(lo & 0xFF); - m_curG = static_cast((lo >> 32) & 0xFF); - m_curB = static_cast(hi & 0xFF); - m_curA = static_cast((hi >> 32) & 0xFF); - break; - case 0x02: - { - uint32_t sBits = static_cast(lo & 0xFFFFFFFF); - uint32_t tBits = static_cast((lo >> 32) & 0xFFFFFFFF); - uint32_t qBits = static_cast(hi & 0xFFFFFFFF); - std::memcpy(&m_curS, &sBits, 4); - std::memcpy(&m_curT, &tBits, 4); - std::memcpy(&m_curQ, &qBits, 4); - if (m_curQ == 0.0f) - m_curQ = 1.0f; - break; - } - case 0x03: - m_curU = static_cast(lo & 0x3FFFu); - m_curV = static_cast((lo >> 32) & 0x3FFFu); - break; - case 0x04: - { - uint16_t x = static_cast(lo & 0xFFFF); - uint16_t y = static_cast((lo >> 32) & 0xFFFF); - uint32_t z = static_cast((hi >> 4) & 0xFFFFFF); - uint8_t f = static_cast((hi >> 36) & 0xFF); - bool adk = ((hi >> 47) & 1) != 0; - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - RUNTIME_LOG("[gs:packed-xyzf] idx=" << debugIndex - << " x=" << x - << " y=" << y - << " z=0x" << std::hex << z - << std::dec - << " fog=" << static_cast(f) - << " kick=" << static_cast(!adk ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << std::endl); - } - }); - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(x) / 16.0f; - vtx.y = static_cast(y) / 16.0f; - vtx.z = static_cast(z); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vtx.fog = f; - vertexKick(!adk); - break; - } - case 0x05: - { - uint16_t x = static_cast(lo & 0xFFFF); - uint16_t y = static_cast((lo >> 32) & 0xFFFF); - uint32_t z = static_cast(hi & 0xFFFFFFFF); - bool adk = ((hi >> 47) & 1) != 0; - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - RUNTIME_LOG("[gs:packed-xyz] idx=" << debugIndex - << " x=" << x - << " y=" << y - << " z=0x" << std::hex << z - << std::dec - << " kick=" << static_cast(!adk ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << std::endl); - } - }); - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(x) / 16.0f; - vtx.y = static_cast(y) / 16.0f; - vtx.z = static_cast(z); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vtx.fog = m_curFog; - vertexKick(!adk); - break; - } - case 0x0A: - m_curFog = static_cast((hi >> 36) & 0xFF); - break; - case 0x0C: - { - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - RUNTIME_LOG("[gs:packed-xyzf3] idx=" << debugIndex - << " x=" << static_cast(lo & 0xFFFFu) - << " y=" << static_cast((lo >> 32) & 0xFFFFu) - << " kick=0" - << " prim=" << static_cast(m_prim.type) - << std::endl); - } - }); - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(lo & 0xFFFF) / 16.0f; - vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; - vtx.z = static_cast((hi >> 4) & 0xFFFFFF); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vtx.fog = static_cast((hi >> 36) & 0xFF); - vertexKick(false); - break; - } - case 0x0D: - { - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - RUNTIME_LOG("[gs:packed-xyz3] idx=" << debugIndex - << " x=" << static_cast(lo & 0xFFFFu) - << " y=" << static_cast((lo >> 32) & 0xFFFFu) - << " kick=0" - << " prim=" << static_cast(m_prim.type) - << std::endl); - } - }); - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(lo & 0xFFFF) / 16.0f; - vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; - vtx.z = static_cast(hi & 0xFFFFFFFF); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vtx.fog = m_curFog; - vertexKick(false); - break; - } - case 0x0E: - { - uint8_t addr = static_cast(hi & 0xFF); - writeRegisterUnlocked(addr, lo); - break; - } - case 0x0F: - break; - default: - writeRegisterUnlocked(regDesc, lo); - break; - } -} - -void GS::writeRegister(uint8_t regAddr, uint64_t value) -{ - std::lock_guard lock(m_stateMutex); - writeRegisterUnlocked(regAddr, value); -} - -void GS::writeRegisterUnlocked(uint8_t regAddr, uint64_t value) -{ - const bool interestingReg = - regAddr == GS_REG_PRIM || - regAddr == GS_REG_RGBAQ || - regAddr == GS_REG_ST || - regAddr == GS_REG_UV || - regAddr == GS_REG_XYZ2 || - regAddr == GS_REG_XYZ3 || - regAddr == GS_REG_XYZF2 || - regAddr == GS_REG_XYZF3 || - regAddr == GS_REG_TEX0_1 || - regAddr == GS_REG_TEX0_2 || - regAddr == GS_REG_TEX2_1 || - regAddr == GS_REG_TEX2_2 || - regAddr == GS_REG_TEXCLUT || - regAddr == GS_REG_TEXA || - regAddr == GS_REG_XYOFFSET_1 || - regAddr == GS_REG_XYOFFSET_2 || - regAddr == GS_REG_SCISSOR_1 || - regAddr == GS_REG_SCISSOR_2 || - regAddr == GS_REG_FRAME_1 || - regAddr == GS_REG_FRAME_2 || - regAddr == GS_REG_ALPHA_1 || - regAddr == GS_REG_ALPHA_2 || - regAddr == GS_REG_TEST_1 || - regAddr == GS_REG_TEST_2 || - regAddr == GS_REG_BITBLTBUF || - regAddr == GS_REG_TRXPOS || - regAddr == GS_REG_TRXREG || - regAddr == GS_REG_TRXDIR; - - PS2_IF_AGRESSIVE_LOGS({ - if (interestingReg) - { - const uint32_t debugIndex = s_debugGsRegisterCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 128u) - { - RUNTIME_LOG("[gs:reg] idx=" << debugIndex - << " reg=0x" << std::hex << static_cast(regAddr) - << " value=0x" << value - << std::dec - << std::endl); - } - } - }); - - const bool isCopyRelevantReg = - regAddr == GS_REG_PRIM || - regAddr == GS_REG_TEX0_2 || - regAddr == GS_REG_TEX1_2 || - regAddr == GS_REG_ALPHA_2 || - regAddr == GS_REG_TEST_2 || - regAddr == GS_REG_PABE || - regAddr == GS_REG_FRAME_2 || - regAddr == GS_REG_XYOFFSET_2 || - regAddr == GS_REG_SCISSOR_2; - PS2_IF_AGRESSIVE_LOGS({ - if (isCopyRelevantReg && - s_debugCopyRegCount.fetch_add(1u, std::memory_order_relaxed) < 64u) - { - RUNTIME_LOG("[gs:copy-reg] reg=0x" - << std::hex << static_cast(regAddr) - << " value=0x" << value - << std::dec - << " primCtxt=" << static_cast(m_prim.ctxt) - << " ctx0fbp=" << m_ctx[0].frame.fbp - << " ctx1fbp=" << m_ctx[1].frame.fbp - << std::endl); - } - }); - - switch (regAddr) - { - case GS_REG_PRIM: - { - m_primRegister = decodePrimRegister(value); - if (m_prmodecont) - { - m_prim = m_primRegister; - } - else - { - // PRIM always selects the primitive topology. With AC=0, all - // rendering attributes remain sourced from PRMODE. - m_prim.type = m_primRegister.type; - } - m_vtxCount = 0; - m_vtxIndex = 0; - break; - } - case GS_REG_RGBAQ: - { - m_curR = static_cast(value & 0xFF); - m_curG = static_cast((value >> 8) & 0xFF); - m_curB = static_cast((value >> 16) & 0xFF); - m_curA = static_cast((value >> 24) & 0xFF); - uint32_t qBits = static_cast((value >> 32) & 0xFFFFFFFF); - std::memcpy(&m_curQ, &qBits, 4); - if (m_curQ == 0.0f) - m_curQ = 1.0f; - break; - } - case GS_REG_ST: - { - uint32_t sBits = static_cast(value & 0xFFFFFFFF); - uint32_t tBits = static_cast((value >> 32) & 0xFFFFFFFF); - std::memcpy(&m_curS, &sBits, 4); - std::memcpy(&m_curT, &tBits, 4); - break; - } - case GS_REG_UV: - { - m_curU = static_cast(value & 0x3FFFu); - m_curV = static_cast((value >> 16) & 0x3FFFu); - break; - } - case GS_REG_XYZF2: - case GS_REG_XYZF3: - { - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(value & 0xFFFF) / 16.0f; - vtx.y = static_cast((value >> 16) & 0xFFFF) / 16.0f; - vtx.z = static_cast((value >> 32) & 0xFFFFFF); - vtx.fog = static_cast((value >> 56) & 0xFF); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vertexKick(regAddr == GS_REG_XYZF2); - break; - } - case GS_REG_XYZ2: - case GS_REG_XYZ3: - { - GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; - vtx.x = static_cast(value & 0xFFFF) / 16.0f; - vtx.y = static_cast((value >> 16) & 0xFFFF) / 16.0f; - vtx.z = static_cast((value >> 32) & 0xFFFFFFFF); - vtx.r = m_curR; - vtx.g = m_curG; - vtx.b = m_curB; - vtx.a = m_curA; - vtx.q = m_curQ; - vtx.s = m_curS; - vtx.t = m_curT; - vtx.u = m_curU; - vtx.v = m_curV; - vtx.fog = m_curFog; - vertexKick(regAddr == GS_REG_XYZ2); - break; - } - case GS_REG_TEX0_1: - case GS_REG_TEX0_2: - { - int ci = (regAddr == GS_REG_TEX0_2) ? 1 : 0; - auto &t = m_ctx[ci].tex0; - t.tbp0 = static_cast(value & 0x3FFF); - t.tbw = static_cast((value >> 14) & 0x3F); - t.psm = static_cast((value >> 20) & 0x3F); - t.tw = static_cast((value >> 26) & 0xF); - t.th = static_cast((value >> 30) & 0xF); - t.tcc = static_cast((value >> 34) & 0x1); - t.tfx = static_cast((value >> 35) & 0x3); - t.cbp = static_cast((value >> 37) & 0x3FFF); - t.cpsm = static_cast((value >> 51) & 0xF); - t.csm = static_cast((value >> 55) & 0x1); - t.csa = static_cast((value >> 56) & 0x1F); - t.cld = static_cast((value >> 61) & 0x7); - break; - } - case GS_REG_CLAMP_1: - case GS_REG_CLAMP_2: - { - int ci = (regAddr == GS_REG_CLAMP_2) ? 1 : 0; - m_ctx[ci].clamp = value; - break; - } - case GS_REG_FOG: - m_curFog = static_cast((value >> 56) & 0xFF); - break; - case GS_REG_TEX1_1: - case GS_REG_TEX1_2: - { - int ci = (regAddr == GS_REG_TEX1_2) ? 1 : 0; - m_ctx[ci].tex1 = value; - break; - } - case GS_REG_TEX2_1: - case GS_REG_TEX2_2: - { - int ci = (regAddr == GS_REG_TEX2_2) ? 1 : 0; - auto &t = m_ctx[ci].tex0; - t.psm = static_cast((value >> 20) & 0x3F); - t.cbp = static_cast((value >> 37) & 0x3FFF); - t.cpsm = static_cast((value >> 51) & 0xF); - t.csm = static_cast((value >> 55) & 0x1); - t.csa = static_cast((value >> 56) & 0x1F); - t.cld = static_cast((value >> 61) & 0x7); - break; - } - case GS_REG_XYOFFSET_1: - case GS_REG_XYOFFSET_2: - { - int ci = (regAddr == GS_REG_XYOFFSET_2) ? 1 : 0; - m_ctx[ci].xyoffset.ofx = static_cast(value & 0xFFFF); - m_ctx[ci].xyoffset.ofy = static_cast((value >> 32) & 0xFFFF); - break; - } - case GS_REG_PRMODECONT: - { - m_prmodecont = (value & 1) != 0; - const GSPrimType type = m_primRegister.type; - m_prim = m_prmodecont ? m_primRegister : m_prmodeRegister; - m_prim.type = type; - break; - } - case GS_REG_PRMODE: - { - m_prmodeRegister = decodePrimRegister(value); - if (!m_prmodecont) - { - const GSPrimType type = m_primRegister.type; - m_prim = m_prmodeRegister; - m_prim.type = type; - } - break; - } - case GS_REG_TEXCLUT: - m_texclut.cbw = static_cast(value & 0x3Fu); - m_texclut.cou = static_cast((value >> 6) & 0x3Fu); - m_texclut.cov = static_cast((value >> 12) & 0x3FFu); - break; - case GS_REG_SCISSOR_1: - case GS_REG_SCISSOR_2: - { - int ci = (regAddr == GS_REG_SCISSOR_2) ? 1 : 0; - m_ctx[ci].scissor.x0 = static_cast(value & 0x7FF); - m_ctx[ci].scissor.x1 = static_cast((value >> 16) & 0x7FF); - m_ctx[ci].scissor.y0 = static_cast((value >> 32) & 0x7FF); - m_ctx[ci].scissor.y1 = static_cast((value >> 48) & 0x7FF); - break; - } - case GS_REG_ALPHA_1: - case GS_REG_ALPHA_2: - { - int ci = (regAddr == GS_REG_ALPHA_2) ? 1 : 0; - m_ctx[ci].alpha = value; - break; - } - case GS_REG_TEST_1: - case GS_REG_TEST_2: - { - int ci = (regAddr == GS_REG_TEST_2) ? 1 : 0; - m_ctx[ci].test = value; - break; - } - case GS_REG_FRAME_1: - case GS_REG_FRAME_2: - { - int ci = (regAddr == GS_REG_FRAME_2) ? 1 : 0; - m_ctx[ci].frame.fbp = static_cast(value & 0x1FF); - m_ctx[ci].frame.fbw = static_cast((value >> 16) & 0x3F); - m_ctx[ci].frame.psm = static_cast((value >> 24) & 0x3F); - m_ctx[ci].frame.fbmsk = static_cast((value >> 32) & 0xFFFFFFFF); - break; - } - case GS_REG_ZBUF_1: - case GS_REG_ZBUF_2: - { - int ci = (regAddr == GS_REG_ZBUF_2) ? 1 : 0; - m_ctx[ci].zbuf.zbp = value & 0x1FF; - m_ctx[ci].zbuf.psm = ((value >> 24) & 0xF) | 0x30; - m_ctx[ci].zbuf.zmask = (value >> 32) & 1; - break; - } - case GS_REG_FBA_1: - case GS_REG_FBA_2: - { - int ci = (regAddr == GS_REG_FBA_2) ? 1 : 0; - m_ctx[ci].fba = value; - break; - } - case GS_REG_BITBLTBUF: - { - m_bitbltbuf.sbp = static_cast(value & 0x3FFF); - m_bitbltbuf.sbw = static_cast((value >> 16) & 0x3F); - m_bitbltbuf.spsm = static_cast((value >> 24) & 0x3F); - m_bitbltbuf.dbp = static_cast((value >> 32) & 0x3FFF); - m_bitbltbuf.dbw = static_cast((value >> 48) & 0x3F); - m_bitbltbuf.dpsm = static_cast((value >> 56) & 0x3F); - break; - } - case GS_REG_TRXPOS: - { - m_trxpos.ssax = static_cast(value & 0x7FF); - m_trxpos.ssay = static_cast((value >> 16) & 0x7FF); - m_trxpos.dsax = static_cast((value >> 32) & 0x7FF); - m_trxpos.dsay = static_cast((value >> 48) & 0x7FF); - m_trxpos.dir = static_cast((value >> 59) & 0x3); - break; - } - case GS_REG_TRXREG: - { - m_trxreg.rrw = static_cast(value & 0xFFF); - m_trxreg.rrh = static_cast((value >> 32) & 0xFFF); - break; - } - case GS_REG_TRXDIR: - { - m_trxdir = static_cast(value & 0x3); - - // We need the transfer state to survive the call to performLocalTo*Transfer - // This is because transfers can be broken into multiple IMAGE tags and we - // don't want to start all over again from the initial state - // The transfer starts officially when TRXDIR is accessed - m_transferState.x = m_trxpos.dsax; - m_transferState.y = m_trxpos.dsay; - m_transferState.total_pixels = m_trxreg.rrw * m_trxreg.rrh; - m_transferState.copied_pixels = 0; - - if (m_trxdir == 2 && m_vram) - { - performLocalToLocalTransfer(); - } - else if (m_trxdir == 1 && m_vram) - { - performLocalToHostToBuffer(); - } - recordTransferDebugEventUnlocked(); - break; - } - case GS_REG_HWREG: - { - uint8_t buf[8]; - std::memcpy(buf, &value, 8); - processImageData(buf, 8); - break; - } - case GS_REG_PABE: - m_pabe = (value & 1u) != 0u; - break; - case GS_REG_FOGCOL: - m_fogR = static_cast(value & 0xFFu); - m_fogG = static_cast((value >> 8) & 0xFFu); - m_fogB = static_cast((value >> 16) & 0xFFu); - break; - case GS_REG_TEXFLUSH: - case GS_REG_SCANMSK: - case GS_REG_DIMX: - case GS_REG_DTHE: - case GS_REG_COLCLAMP: - case GS_REG_MIPTBP1_1: - case GS_REG_MIPTBP1_2: - case GS_REG_MIPTBP2_1: - case GS_REG_MIPTBP2_2: - break; - case GS_REG_TEXA: - { - m_texa.ta0 = static_cast(value & 0xFFu); - m_texa.aem = ((value >> 15) & 0x1u) != 0u; - m_texa.ta1 = static_cast((value >> 32) & 0xFFu); - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t texaIndex = s_debugTexaWriteCount.fetch_add(1u, std::memory_order_relaxed); - if (texaIndex < 24u) - { - RUNTIME_LOG("[gs:texa] idx=" << texaIndex - << " value=0x" << std::hex << value - << " ta0=0x" << ((value >> 0) & 0xFFu) - << " aem=" << ((value >> 15) & 0x1u) - << " ta1=0x" << ((value >> 32) & 0xFFu) - << std::dec - << std::endl); - } - }); - break; - } - case GS_REG_SIGNAL: - { - if (m_privRegs) - { - uint32_t id = static_cast(value & 0xFFFFFFFF); - uint32_t mask = static_cast(value >> 32); - uint32_t lo = static_cast(m_privRegs->siglblid & 0xFFFFFFFF); - lo = (lo & ~mask) | (id & mask); - m_privRegs->siglblid = (m_privRegs->siglblid & 0xFFFFFFFF00000000ULL) | lo; - m_privRegs->csr.fetch_or(0x1); - } - break; - } - case GS_REG_FINISH: - { - if (m_privRegs) - m_privRegs->csr.fetch_or(0x2); - break; - } - case GS_REG_LABEL: - { - if (m_privRegs) - { - uint32_t id = static_cast(value & 0xFFFFFFFF); - uint32_t mask = static_cast(value >> 32); - uint32_t hi = static_cast(m_privRegs->siglblid >> 32); - hi = (hi & ~mask) | (id & mask); - m_privRegs->siglblid = (static_cast(hi) << 32) | (m_privRegs->siglblid & 0xFFFFFFFF); - } - break; - } - case 0x59: - if (m_privRegs) - m_privRegs->dispfb1 = value; - break; - case 0x5a: - if (m_privRegs) - m_privRegs->display1 = value; - break; - case 0x5b: - if (m_privRegs) - m_privRegs->dispfb2 = value; - break; - case 0x5c: - if (m_privRegs) - m_privRegs->display2 = value; - break; - case 0x5f: - if (m_privRegs) - m_privRegs->bgcolor = value; - break; - default: - break; - } - - recordRegisterDebugEventUnlocked(regAddr, value); -} - -void GS::performLocalToLocalTransfer() -{ - if (!m_vram) - return; - - const u32 sbp = m_bitbltbuf.sbp; - const u8 sbw = m_bitbltbuf.sbw; - const u8 spsm = m_bitbltbuf.spsm; - const u32 dbp = m_bitbltbuf.dbp; - const u8 dbw = m_bitbltbuf.dbw; - const u8 dpsm = m_bitbltbuf.dpsm; - const u32 rrw = m_trxreg.rrw; - const u32 rrh = m_trxreg.rrh; - const u32 ssax = m_trxpos.ssax; - const u32 ssay = m_trxpos.ssay; - const u32 dsax = m_trxpos.dsax; - const u32 dsay = m_trxpos.dsay; - const u32 dir = m_trxpos.dir; - - const u32 total_pixels = rrw * rrh; - - if (total_pixels == 0) - { - m_trxdir = 3; - return; - } - - // TODO: clean this up / optimize - switch (dir) - { - case 0: // left -> right top -> bottom - { - u32 pixel_count = 0; - while (pixel_count < total_pixels) - { - const u32 x = pixel_count % rrw; - const u32 y = pixel_count / rrw; - - const u32 sx = x + ssax; - const u32 sy = y + ssay; - const u32 dx = x + dsax; - const u32 dy = y + dsay; - - WriteVram(dpsm, dbp, dbw, dx, dy, ReadVram(spsm, sbp, sbw, sx, sy)); - - pixel_count++; - } - } - break; - - // left -> right - // bottom -> top (invert y) - case 1: - { - u32 pixel_count = 0; - while (pixel_count < total_pixels) - { - const u32 x = pixel_count % rrw; - const u32 y = rrh - (pixel_count / rrw) - 1; - - const u32 sx = x + ssax; - const u32 sy = y + ssay; - const u32 dx = x + dsax; - const u32 dy = y + dsay; - - WriteVram(dpsm, dbp, dbw, dx, dy, ReadVram(spsm, sbp, sbw, sx, sy)); - - pixel_count++; - } - } - break; - - // right -> left (invert x) - // top -> bottom - case 2: - { - u32 pixel_count = 0; - while (pixel_count < total_pixels) - { - const u32 x = rrw - (pixel_count % rrw) - 1; - const u32 y = pixel_count / rrw; - - const u32 sx = x + ssax; - const u32 sy = y + ssay; - const u32 dx = x + dsax; - const u32 dy = y + dsay; - - WriteVram(dpsm, dbp, dbw, dx, dy, ReadVram(spsm, sbp, sbw, sx, sy)); - - pixel_count++; - } - } - break; - - // right to left (invert x) - // bottom to top (invert y) - case 3: - { - u32 pixel_count = 0; - while (pixel_count < total_pixels) - { - const u32 x = rrw - (pixel_count % rrw) - 1; - const u32 y = rrh - (pixel_count / rrw) - 1; - - const u32 sx = x + ssax; - const u32 sy = y + ssay; - const u32 dx = x + dsax; - const u32 dy = y + dsay; - - WriteVram(dpsm, dbp, dbw, dx, dy, ReadVram(spsm, sbp, sbw, sx, sy)); - - pixel_count++; - } - } - break; - - default: - break; - } - - m_trxdir = 3; -} - -void GS::vertexKick(bool drawing) -{ - ++m_vtxCount; - ++m_vtxIndex; - - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t debugIndex = s_debugGsVertexKickCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 96u) - { - RUNTIME_LOG("[gs:kick] idx=" << debugIndex - << " drawing=" << static_cast(drawing ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << " vtxCount=" << m_vtxCount - << std::endl); - } - }); - - int needed = 0; - switch (m_prim.type) - { - case GS_PRIM_POINT: - needed = 1; - break; - case GS_PRIM_LINE: - needed = 2; - break; - case GS_PRIM_LINESTRIP: - needed = 2; - break; - case GS_PRIM_TRIANGLE: - needed = 3; - break; - case GS_PRIM_TRISTRIP: - needed = 3; - break; - case GS_PRIM_TRIFAN: - needed = 3; - break; - case GS_PRIM_SPRITE: - needed = 2; - break; - default: - return; - } - - if (m_vtxCount < needed) - return; - - if (drawing) - { - m_rasterizer.drawPrimitive(this); - recordDrawDebugEventUnlocked(needed); - } - - switch (m_prim.type) - { - case GS_PRIM_LINE: - case GS_PRIM_TRIANGLE: - case GS_PRIM_SPRITE: - case GS_PRIM_POINT: - m_vtxCount = 0; - break; - case GS_PRIM_LINESTRIP: - m_vtxQueue[0] = m_vtxQueue[1]; - m_vtxCount = 1; - break; - case GS_PRIM_TRISTRIP: - m_vtxQueue[0] = m_vtxQueue[1]; - m_vtxQueue[1] = m_vtxQueue[2]; - m_vtxCount = 2; - break; - case GS_PRIM_TRIFAN: - m_vtxQueue[1] = m_vtxQueue[2]; - m_vtxCount = 2; - break; - default: - m_vtxCount = 0; - break; - } -} - -void GS::processImageData(const uint8_t *data, uint32_t sizeBytes) -{ - // wrong direction set - if (m_trxdir != 0 || !m_vram) - { - return; - } - - // no height and width means transfer is invalid - if (m_trxreg.rrw == 0 || m_trxreg.rrh == 0) - { - return; - } - - u32 dbp = m_bitbltbuf.dbp; - u8 dbw = std::max(m_bitbltbuf.dbw, 1u); - u8 dpsm = m_bitbltbuf.dpsm; - - u32 rrw = m_trxreg.rrw; - u32 rrh = m_trxreg.rrh; - u32 dsax = m_trxpos.dsax; - u32 dsay = m_trxpos.dsay; - - u32 data_offset = 0; - - // remove the format branching from the loops - // TODO: fixup copypasta - switch (dpsm) - { - case GS_PSM_CT32: - while (data_offset < sizeBytes) - { - u32 c; - std::memcpy(&c, &data[data_offset], sizeof(u32)); - - GSMem::WriteCT32(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 4; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_Z32: - while (data_offset < sizeBytes) - { - u32 c; - std::memcpy(&c, &data[data_offset], sizeof(u32)); - - GSMem::WriteZ32(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 4; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_CT24: - while (data_offset < sizeBytes) - { - u32 c; - std::memcpy(&c, &data[data_offset], sizeof(u32)); - - GSMem::WriteCT24(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 3; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_Z24: - while (data_offset < sizeBytes) - { - u32 c; - std::memcpy(&c, &data[data_offset], sizeof(u32)); - - GSMem::WriteZ24(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 3; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_CT16: - while (data_offset < sizeBytes) - { - u16 c; - std::memcpy(&c, &data[data_offset], sizeof(u16)); - - GSMem::WriteCT16(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 2; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_Z16: - while (data_offset < sizeBytes) - { - u16 c; - std::memcpy(&c, &data[data_offset], sizeof(u16)); - - GSMem::WriteZ16(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 2; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_CT16S: - while (data_offset < sizeBytes) - { - u16 c; - std::memcpy(&c, &data[data_offset], sizeof(u16)); - - GSMem::WriteCT16S(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 2; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_Z16S: - while (data_offset < sizeBytes) - { - u16 c; - std::memcpy(&c, &data[data_offset], sizeof(u16)); - - GSMem::WriteZ16S(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 2; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_T8: - while (data_offset < sizeBytes) - { - u8 c = data[data_offset]; - - GSMem::WriteP8(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 1; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - - case GS_PSM_T8H: - while (data_offset < sizeBytes) - { - u8 c = data[data_offset]; - - GSMem::WriteP8H(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c); - - m_transferState.x++; - m_transferState.copied_pixels++; - data_offset += 1; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - case GS_PSM_T4: - while (data_offset < sizeBytes) - { - u8 c0 = data[data_offset] & 0xF; - u8 c1 = (data[data_offset] >> 4) & 0xF; - - GSMem::WriteP4(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c0); - GSMem::WriteP4(m_vram, dbp, dbw, m_transferState.x + 1, m_transferState.y, c1); - - m_transferState.x += 2; - m_transferState.copied_pixels += 2; - data_offset += 1; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - case GS_PSM_T4HL: - while (data_offset < sizeBytes) - { - u8 c0 = data[data_offset] & 0xF; - u8 c1 = (data[data_offset] >> 4) & 0xF; - - GSMem::WriteP4HL(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c0); - GSMem::WriteP4HL(m_vram, dbp, dbw, m_transferState.x + 1, m_transferState.y, c1); - - m_transferState.x += 2; - m_transferState.copied_pixels += 2; - data_offset += 1; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - case GS_PSM_T4HH: - while (data_offset < sizeBytes) - { - u8 c0 = data[data_offset] & 0xF; - u8 c1 = (data[data_offset] >> 4) & 0xF; - - GSMem::WriteP4HH(m_vram, dbp, dbw, m_transferState.x, m_transferState.y, c0); - GSMem::WriteP4HH(m_vram, dbp, dbw, m_transferState.x + 1, m_transferState.y, c1); - - m_transferState.x += 2; - m_transferState.copied_pixels += 2; - data_offset += 1; - - if ((m_transferState.copied_pixels % rrw) == 0) - { - m_transferState.x = dsax; - m_transferState.y++; - } - - if (m_transferState.copied_pixels >= m_transferState.total_pixels) - { - // deactivate the transfer - m_trxdir = 3; - m_transferState.total_pixels = 0; - break; - } - } - break; - } -} - -void GS::performLocalToHostToBuffer() -{ - m_localToHostBuffer.clear(); - m_localToHostReadPos = 0; - - if (!m_vram) - return; - - uint32_t sbp = m_bitbltbuf.sbp; - uint8_t sbw = std::max(m_bitbltbuf.sbw, 1u); - uint8_t spsm = m_bitbltbuf.spsm; - uint32_t rrw = m_trxreg.rrw; - uint32_t rrh = m_trxreg.rrh; - uint32_t ssax = m_trxpos.ssax; - uint32_t ssay = m_trxpos.ssay; - - u32 bpp = GSMem::BitsPerPixel(static_cast(spsm)); - - u32 pixel_total = rrw * rrh; - u32 bytes_total = (pixel_total * bpp) / 8; - - m_localToHostBuffer.reserve(bytes_total); - - u32 pixel_count = 0; - while (pixel_count < pixel_total) - { - const u32 x = pixel_count % rrw; - const u32 y = pixel_count / rrw; - - const u32 v = ReadVram(spsm, sbp, sbw, x + ssax, y + ssay); - - switch (bpp) - { - case 32: - m_localToHostBuffer.push_back(v & 0xFF); - m_localToHostBuffer.push_back((v >> 8) & 0xFF); - m_localToHostBuffer.push_back((v >> 16) & 0xFF); - m_localToHostBuffer.push_back((v >> 24) & 0xFF); - break; - case 24: - m_localToHostBuffer.push_back(v & 0xFF); - m_localToHostBuffer.push_back((v >> 8) & 0xFF); - m_localToHostBuffer.push_back((v >> 16) & 0xFF); - break; - case 16: - m_localToHostBuffer.push_back(v & 0xFF); - m_localToHostBuffer.push_back((v >> 8) & 0xFF); - break; - case 8: - m_localToHostBuffer.push_back(v); - break; - case 4: - { - const u32 v2 = ReadVram(spsm, sbp, sbw, x + ssax + 1, y + ssay); - - m_localToHostBuffer.push_back(v | ((v2 & 0xF) << 4)); - pixel_count++; - break; - } - default: - break; - } - - pixel_count++; - } -} - -bool GS::clearFramebufferContext(uint32_t contextIndex, uint32_t rgba) -{ - std::lock_guard lock(m_stateMutex); - return clearFramebufferRect(this, m_ctx[(contextIndex != 0u) ? 1 : 0], rgba); -} - -bool GS::clearActiveFramebuffer(uint32_t rgba) -{ - std::lock_guard lock(m_stateMutex); - return clearFramebufferRect(this, activeContext(), rgba); -} - -uint32_t GS::consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) -{ - std::lock_guard lock(m_stateMutex); - if (!dst || maxBytes == 0) - return 0; - size_t avail = m_localToHostBuffer.size() - m_localToHostReadPos; - if (avail == 0) - return 0; - size_t toCopy = (avail < maxBytes) ? avail : static_cast(maxBytes); - std::memcpy(dst, m_localToHostBuffer.data() + m_localToHostReadPos, toCopy); - m_localToHostReadPos += toCopy; - return static_cast(toCopy); -} diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp deleted file mode 100644 index 96cff26..0000000 --- a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp +++ /dev/null @@ -1,1102 +0,0 @@ -#include "runtime/ps2_gs_rasterizer.h" -#include "runtime/ps2_gs_gpu.h" -#include "runtime/ps2_gs_common.h" -#include "runtime/ps2_gs_psmct16.h" -#include "runtime/ps2_gs_psmct32.h" -#include "runtime/ps2_gs_psmt4.h" -#include "runtime/ps2_gs_psmt8.h" -#include "runtime/ps2_gs_memory.h" -#include "ps2_log.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace GSInternal; - -namespace -{ - float fabsQ(float q) - { - return (std::fabs(q) > 1.0e-8f) ? q : 1.0f; - } - - u16 Rgba8888ToRgba5551(u32 c) - { - uint32_t r = ((c >> 0) & 0xFF) >> 3; - uint32_t g = ((c >> 8) & 0xFF) >> 3; - uint32_t b = ((c >> 16) & 0xFF) >> 3; - uint32_t a = ((c >> 24) & 0xFF) >> 7; - - return (r | (g << 5) | (b << 10) | (a << 15)); - } - - u32 Rgba5551ToRgba8888(u16 c) - { - u32 r = ((c >> 0) & 0x1F) << 3; - u32 g = ((c >> 5) & 0x1F) << 3; - u32 b = ((c >> 10) & 0x1F) << 3; - u32 a = ((c >> 15) & 0x01) << 7; - - return (r | (g << 8) | (b << 16) | (a << 24)); - } - - u32 pack32(u8 r, u8 g, u8 b, u8 a) - { - return static_cast(r) | (g << 8) | (b << 16) | (a << 24); - } - - uint32_t applyTexa(const GSTexaReg &texa, uint8_t psm, uint32_t texel) - { - if (psm == GS_PSM_CT32) - return texel; - - const uint8_t r = static_cast(texel & 0xFFu); - const uint8_t g = static_cast((texel >> 8) & 0xFFu); - const uint8_t b = static_cast((texel >> 16) & 0xFFu); - const bool rgbZero = r == 0u && g == 0u && b == 0u; - uint8_t a = static_cast((texel >> 24) & 0xFFu); - - switch (psm) - { - case GS_PSM_CT24: - a = (texa.aem && rgbZero) ? 0u : texa.ta0; - break; - case GS_PSM_CT16: - case GS_PSM_CT16S: - if ((a & 0x80u) != 0u) - a = texa.ta1; - else - a = (texa.aem && rgbZero) ? 0u : texa.ta0; - break; - default: - break; - } - - return (texel & 0x00FFFFFFu) | (static_cast(a) << 24); - } - - uint32_t addrPSMCT16Family(uint32_t basePtr, uint32_t width, uint8_t psm, uint32_t x, uint32_t y) - { - switch (psm) - { - case GS_PSM_CT16: - return GSPSMCT16::addrPSMCT16(basePtr, width, x, y); - case GS_PSM_CT16S: - return GSPSMCT16::addrPSMCT16S(basePtr, width, x, y); - case GS_PSM_Z16: - return GSPSMCT16::addrPSMZ16(basePtr, width, x, y); - case GS_PSM_Z16S: - return GSPSMCT16::addrPSMZ16S(basePtr, width, x, y); - default: - return 0u; - } - } - - std::atomic s_debugPrimitiveCount{0}; - std::atomic s_debugPixelCount{0}; - std::atomic s_debugContext1PrimitiveCount{0}; - std::atomic s_debugFbp150PixelCount{0}; - - int wrapTextureCoordinate(int coordinate, - int textureSize, - uint8_t mode, - uint16_t regionMin, - uint16_t regionMax) - { - switch (mode & 0x3u) - { - case 0: // REPEAT - return static_cast(static_cast(coordinate) & static_cast(textureSize - 1)); - case 1: // CLAMP - return clampInt(coordinate, 0, textureSize - 1); - case 2: // REGION_CLAMP - return std::min(std::max(coordinate, static_cast(regionMin)), static_cast(regionMax)); - case 3: // REGION_REPEAT - return static_cast((static_cast(coordinate) & static_cast(regionMin)) | static_cast(regionMax)); - default: - return coordinate; - } - } - - bool passesAlphaTest(uint64_t testReg, uint8_t alpha) - { - if ((testReg & 0x1u) == 0u) - return true; - - const uint8_t atst = static_cast((testReg >> 1) & 0x7u); - const uint8_t aref = static_cast((testReg >> 4) & 0xFFu); - - switch (atst) - { - case 0: - return false; - case 1: - return true; - case 2: - return alpha < aref; - case 3: - return alpha <= aref; - case 4: - return alpha == aref; - case 5: - return alpha >= aref; - case 6: - return alpha > aref; - case 7: - return alpha != aref; - default: - return true; - } - } - - struct PixelWriteMask - { - bool writeRgb = true; - bool writeAlpha = true; - bool writeDepth = true; - - bool writesFramebuffer() const - { - return writeRgb || writeAlpha; - } - - bool writesAnything() const - { - return writesFramebuffer() || writeDepth; - } - }; - - PixelWriteMask classifyAlphaTest(uint64_t testReg, uint8_t alpha, uint8_t framePsm) - { - const bool pass = passesAlphaTest(testReg, alpha); - if (pass) - return {}; - - // TEST.AFAIL controls what happens when the alpha comparison fails. - switch (static_cast((testReg >> 12) & 0x3u)) - { - case 1: // FB_ONLY - return {true, true, false}; - case 2: // ZB_ONLY - return {false, false, true}; - case 3: // RGB_ONLY - // RGB_ONLY is only distinct for RGBA32. The GS treats it as - // FB_ONLY for RGB24 and RGBA16 framebuffers. - if (framePsm == GS_PSM_CT32) - return {true, false, false}; - return {true, true, false}; - case 0: // KEEP - default: - return {false, false, false}; - } - } - - bool passesDestinationAlphaTest(uint64_t testReg, uint8_t framePsm, uint32_t rawFramebufferPixel) - { - const bool date = ((testReg >> 14) & 0x1u) != 0u; - if (!date) - return true; - - const bool datm = ((testReg >> 15) & 0x1u) != 0u; - switch (framePsm) - { - case GS_PSM_CT32: - return (((rawFramebufferPixel >> 31) & 0x1u) != 0u) == datm; - case GS_PSM_CT16: - case GS_PSM_CT16S: - return (((rawFramebufferPixel >> 15) & 0x1u) != 0u) == datm; - case GS_PSM_CT24: - // RGB24 has no destination alpha, so DATE always passes. - return true; - default: - return true; - } - } - - struct TextureCombineResult - { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - - TextureCombineResult combineTexture(const GSTex0Reg &tex, - uint8_t vr, - uint8_t vg, - uint8_t vb, - uint8_t va, - uint8_t tr, - uint8_t tg, - uint8_t tb, - uint8_t ta) - { - const bool textureHasAlpha = tex.tcc != 0u; - TextureCombineResult out{tr, tg, tb, textureHasAlpha ? ta : va}; - - switch (tex.tfx) - { - case 0: // MODULATE - out.r = clampU8((tr * vr) >> 7); - out.g = clampU8((tg * vg) >> 7); - out.b = clampU8((tb * vb) >> 7); - out.a = textureHasAlpha ? clampU8((ta * va) >> 7) : va; - break; - case 1: // DECAL - out.r = tr; - out.g = tg; - out.b = tb; - out.a = textureHasAlpha ? ta : va; - break; - case 2: // HIGHLIGHT - out.r = clampU8(((tr * vr) >> 7) + va); - out.g = clampU8(((tg * vg) >> 7) + va); - out.b = clampU8(((tb * vb) >> 7) + va); - out.a = textureHasAlpha ? clampU8(ta + va) : va; - break; - case 3: // HIGHLIGHT2 - out.r = clampU8(((tr * vr) >> 7) + va); - out.g = clampU8(((tg * vg) >> 7) + va); - out.b = clampU8(((tb * vb) >> 7) + va); - out.a = textureHasAlpha ? ta : va; - break; - default: - out.r = tr; - out.g = tg; - out.b = tb; - out.a = textureHasAlpha ? ta : va; - break; - } - - return out; - } - - uint32_t swizzleClutIndexCSM1(uint32_t index) - { - // CSM1 swaps address bits 3 and 4. Preserve the remaining bits: - // 16-bit CLUTs expose a ninth address bit through CSA[4]. - return (index & ~0x18u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u); - } - - // TODO: clut cache - uint32_t resolveClutIndex(uint8_t index, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm) - { - uint32_t clutIndex = static_cast(index); - - // CSM2 addresses the source directly through TEXCLUT. CSA is required - // to be zero there, so it must not offset the source coordinates. - if (csm != 0u) - return (sourcePsm == GS_PSM_T4 || - sourcePsm == GS_PSM_T4HH || - sourcePsm == GS_PSM_T4HL) - ? (clutIndex & 0x0Fu) - : clutIndex; - - const bool is16BitClut = cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S; - const uint32_t csaMask = is16BitClut ? 0x1Fu : 0x0Fu; - const uint32_t clutIndexMask = is16BitClut ? 0x1FFu : 0x0FFu; - const uint32_t clutBase = (static_cast(csa) & csaMask) << 4u; - - switch (sourcePsm) - { - case GS_PSM_T4: - case GS_PSM_T4HH: - case GS_PSM_T4HL: - clutIndex = clutBase + (clutIndex & 0x0Fu); - break; - case GS_PSM_T8: - case GS_PSM_T8H: - clutIndex = clutBase + clutIndex; - break; - default: - return clutIndex; - } - - return swizzleClutIndexCSM1(clutIndex & clutIndexMask); - } - - int textureDimension(uint8_t exponent) - { - // TEX0.TW/TH saturate at 1024 pixels on the GS. - return 1 << std::min(exponent, 10u); - } - - bool tex1UsesLinearFilter(uint64_t tex1) - { - const uint8_t mmag = static_cast((tex1 >> 5) & 0x1u); - const uint8_t mmin = static_cast((tex1 >> 6) & 0x7u); - return mmag != 0u || mmin == 1u || (mmin & 0x4u) != 0u; - } - - uint8_t lerpChannel(uint8_t c00, uint8_t c10, uint8_t c01, uint8_t c11, float fx, float fy) - { - const float top = static_cast(c00) + (static_cast(c10) - static_cast(c00)) * fx; - const float bottom = static_cast(c01) + (static_cast(c11) - static_cast(c01)) * fx; - return clampU8(static_cast(std::lround(top + (bottom - top) * fy))); - } -} - -void GSRasterizer::drawPrimitive(GS *gs) -{ - const auto &ctx = gs->activeContext(); - PS2_IF_AGRESSIVE_LOGS({ - const uint32_t primitiveIndex = s_debugPrimitiveCount.fetch_add(1u, std::memory_order_relaxed); - if (primitiveIndex < 64u) - { - std::cout << "[gs:prim] idx=" << primitiveIndex - << " type=" << static_cast(gs->m_prim.type) - << " tme=" << static_cast(gs->m_prim.tme) - << " abe=" << static_cast(gs->m_prim.abe) - << " fst=" << static_cast(gs->m_prim.fst) - << " ctxt=" << static_cast(gs->m_prim.ctxt) - << " fbp=" << ctx.frame.fbp - << " fbw=" << ctx.frame.fbw - << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec - << " tex0=(" - << "tbp0=" << ctx.tex0.tbp0 - << " tbw=" << static_cast(ctx.tex0.tbw) - << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec - << " tw=" << static_cast(ctx.tex0.tw) - << " th=" << static_cast(ctx.tex0.th) - << " tcc=" << static_cast(ctx.tex0.tcc) - << " tfx=" << static_cast(ctx.tex0.tfx) - << " cbp=" << ctx.tex0.cbp - << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec - << " csm=" << static_cast(ctx.tex0.csm) - << " csa=" << static_cast(ctx.tex0.csa) - << ")" - << " texclut=(" - << "cbw=" << static_cast(gs->m_texclut.cbw) - << " cou=" << static_cast(gs->m_texclut.cou) - << " cov=" << gs->m_texclut.cov - << ")" - << " ofx=" << (ctx.xyoffset.ofx >> 4) - << " ofy=" << (ctx.xyoffset.ofy >> 4) - << " scissor=(" << ctx.scissor.x0 - << "," << ctx.scissor.y0 - << ")-(" << ctx.scissor.x1 - << "," << ctx.scissor.y1 << ")" - << " test=0x" << std::hex << ctx.test - << " alpha=0x" << ctx.alpha - << std::dec - << " v0=(" << gs->m_vtxQueue[0].x << "," << gs->m_vtxQueue[0].y << ")" - << " uv0=(" << (gs->m_vtxQueue[0].u >> 4) << "," << (gs->m_vtxQueue[0].v >> 4) << ")" - << " stq0=(" << gs->m_vtxQueue[0].s << "," << gs->m_vtxQueue[0].t << "," << gs->m_vtxQueue[0].q << ")" - << " v1=(" << gs->m_vtxQueue[1].x << "," << gs->m_vtxQueue[1].y << ")" - << " uv1=(" << (gs->m_vtxQueue[1].u >> 4) << "," << (gs->m_vtxQueue[1].v >> 4) << ")" - << " stq1=(" << gs->m_vtxQueue[1].s << "," << gs->m_vtxQueue[1].t << "," << gs->m_vtxQueue[1].q << ")" - << " v2=(" << gs->m_vtxQueue[2].x << "," << gs->m_vtxQueue[2].y << ")" - << " uv2=(" << (gs->m_vtxQueue[2].u >> 4) << "," << (gs->m_vtxQueue[2].v >> 4) << ")" - << " stq2=(" << gs->m_vtxQueue[2].s << "," << gs->m_vtxQueue[2].t << "," << gs->m_vtxQueue[2].q << ")" - << " rgba0=(" << static_cast(gs->m_vtxQueue[0].r) << "," - << static_cast(gs->m_vtxQueue[0].g) << "," - << static_cast(gs->m_vtxQueue[0].b) << "," - << static_cast(gs->m_vtxQueue[0].a) << ")" - << " rgba1=(" << static_cast(gs->m_vtxQueue[1].r) << "," - << static_cast(gs->m_vtxQueue[1].g) << "," - << static_cast(gs->m_vtxQueue[1].b) << "," - << static_cast(gs->m_vtxQueue[1].a) << ")" - << " rgba2=(" << static_cast(gs->m_vtxQueue[2].r) << "," - << static_cast(gs->m_vtxQueue[2].g) << "," - << static_cast(gs->m_vtxQueue[2].b) << "," - << static_cast(gs->m_vtxQueue[2].a) << ")" - << std::endl; - } - }); - - PS2_IF_AGRESSIVE_LOGS({ - if ((gs->m_prim.ctxt != 0u || ctx.frame.fbp == 150u) && - s_debugContext1PrimitiveCount.fetch_add(1u, std::memory_order_relaxed) < 32u) - { - std::cout << "[gs:copy-prim]" - << " type=" << static_cast(gs->m_prim.type) - << " tme=" << static_cast(gs->m_prim.tme) - << " abe=" << static_cast(gs->m_prim.abe) - << " fst=" << static_cast(gs->m_prim.fst) - << " ctxt=" << static_cast(gs->m_prim.ctxt) - << " fbp=" << ctx.frame.fbp - << " fbw=" << ctx.frame.fbw - << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec - << " tex0=(" - << "tbp0=" << ctx.tex0.tbp0 - << " tbw=" << static_cast(ctx.tex0.tbw) - << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec - << " tcc=" << static_cast(ctx.tex0.tcc) - << " tfx=" << static_cast(ctx.tex0.tfx) - << " cbp=" << ctx.tex0.cbp - << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec - << " csm=" << static_cast(ctx.tex0.csm) - << " csa=" << static_cast(ctx.tex0.csa) - << ")" - << " texclut=(" - << "cbw=" << static_cast(gs->m_texclut.cbw) - << " cou=" << static_cast(gs->m_texclut.cou) - << " cov=" << gs->m_texclut.cov - << ")" - << " ofx=" << (ctx.xyoffset.ofx >> 4) - << " ofy=" << (ctx.xyoffset.ofy >> 4) - << " scissor=(" << ctx.scissor.x0 - << "," << ctx.scissor.y0 - << ")-(" << ctx.scissor.x1 - << "," << ctx.scissor.y1 << ")" - << " test=0x" << std::hex << ctx.test - << " alpha=0x" << ctx.alpha - << std::dec << std::endl; - } - }); - - if (gs->m_hasPreferredDisplaySource && ctx.frame.fbp == gs->m_preferredDisplayDestFbp) - { - gs->m_hasPreferredDisplaySource = false; - } - - switch (gs->m_prim.type) - { - case GS_PRIM_SPRITE: - drawSprite(gs); - break; - case GS_PRIM_TRIANGLE: - case GS_PRIM_TRISTRIP: - case GS_PRIM_TRIFAN: - drawTriangle(gs); - break; - case GS_PRIM_LINE: - case GS_PRIM_LINESTRIP: - drawLine(gs); - break; - case GS_PRIM_POINT: - { - const GSVertex &v = gs->m_vtxQueue[0]; - const auto &ctx = gs->activeContext(); - int px = static_cast(v.x) - (ctx.xyoffset.ofx >> 4); - int py = static_cast(v.y) - (ctx.xyoffset.ofy >> 4); - writePixel(gs, px, py, static_cast(v.z), v.r, v.g, v.b, v.a, v.fog); - break; - } - default: - break; - } -} - -void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog) -{ - const auto &ctx = gs->activeContext(); - if (x < ctx.scissor.x0 || x > ctx.scissor.x1 || y < ctx.scissor.y0 || y > ctx.scissor.y1) - return; - - if (gs->m_prim.fge) - { - const uint32_t inverseFog = 255u - fog; - auto applyFog = [&](uint8_t input, uint8_t fogColor) -> uint8_t - { - return static_cast(((static_cast(fog) * input) >> 8) + ((inverseFog * fogColor) >> 8)); - }; - - r = applyFog(r, gs->m_fogR); - g = applyFog(g, gs->m_fogG); - b = applyFog(b, gs->m_fogB); - } - - const u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp); - const u32 fbw = std::max(ctx.frame.fbw, 1u); - const u32 fpsm = ctx.frame.psm; - const u32 zbp = GSInternal::framePageBaseToBlock(ctx.zbuf.zbp); - const u32 zpsm = ctx.zbuf.psm; - - const PixelWriteMask writeMask = classifyAlphaTest(ctx.test, a, static_cast(fpsm)); - if (!writeMask.writesAnything()) - { - return; - } - - const uint32_t ztestMethod = static_cast((ctx.test >> 17) & 3u); - const bool alphaBlendEnabled = gs->m_prim.abe; - const bool preserveDestinationAlpha = writeMask.writeRgb && !writeMask.writeAlpha && fpsm == GS_PSM_CT32; - const bool destinationAlphaTestNeedsRead = ((ctx.test >> 14) & 0x1u) != 0u && (fpsm == GS_PSM_CT32 || fpsm == GS_PSM_CT16 || fpsm == GS_PSM_CT16S); - - // small optimization, avoid reading the framebuffer for simple draws - // TODO: only one address lookup for rmw - const bool frmw = destinationAlphaTestNeedsRead || (writeMask.writesFramebuffer() && ((ctx.frame.fbmsk != 0) || alphaBlendEnabled || preserveDestinationAlpha)); - - u32 rawFramebufferPixel = 0; - u32 fbrgba = 0; - if (frmw) - { - rawFramebufferPixel = gs->ReadVram(fpsm, fbp, fbw, x, y); - fbrgba = rawFramebufferPixel; - - if (bitsPerPixel(fpsm) == 16) - { - fbrgba = Rgba5551ToRgba8888(fbrgba); - } - else if (fpsm == GS_PSM_CT24) - { - // The GS supplies 0x80 as destination alpha for RGB24 blending. - fbrgba |= 0x80000000u; - } - } - - if (!passesDestinationAlphaTest(ctx.test, static_cast(fpsm), rawFramebufferPixel)) - { - return; - } - - bool zpass = false; - uint32_t storedZ = 0u; - switch (ztestMethod) - { - case 0: - zpass = false; - break; - case 1: - zpass = true; - break; - case 2: - storedZ = gs->ReadVram(zpsm, zbp, fbw, x, y); - zpass = static_cast(z) >= storedZ; - break; - case 3: - storedZ = gs->ReadVram(zpsm, zbp, fbw, x, y); - zpass = static_cast(z) > storedZ; - break; - } - - if (!zpass) - { - return; - } - - if (writeMask.writesFramebuffer()) - { - const u8 srcR = r; - const u8 srcG = g; - const u8 srcB = b; - - if (gs->m_prim.abe) - { - uint8_t dr = fbrgba & 0xFF; - uint8_t dg = (fbrgba >> 8) & 0xFF; - uint8_t db = (fbrgba >> 16) & 0xFF; - uint8_t da = (fbrgba >> 24) & 0xFF; - - // PABE disables alpha blending when the source alpha MSB is clear. - if (!(gs->m_pabe && (a & 0x80u) == 0u)) - { - uint64_t alphaReg = ctx.alpha; - uint8_t asel = alphaReg & 3; - uint8_t bsel = (alphaReg >> 2) & 3; - uint8_t csel = (alphaReg >> 4) & 3; - uint8_t dsel = (alphaReg >> 6) & 3; - uint8_t fix = static_cast((alphaReg >> 32) & 0xFF); - - auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int - { - if (sel == 0) - return cs; - if (sel == 1) - return cd; - return 0; - }; - int cAlpha = (csel == 0) ? a : (csel == 1) ? da - : fix; - - r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr)); - g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg)); - b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db)); - } - else - { - r = srcR; - g = srcG; - b = srcB; - } - } - - if (writeMask.writeAlpha && (ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24) - { - a = static_cast(a | 0x80u); - } - - u32 pixel = pack32(r, g, b, a); - - if (ctx.frame.fbmsk != 0) - { - pixel = (pixel & ~ctx.frame.fbmsk) | (fbrgba & ctx.frame.fbmsk); - } - - if (preserveDestinationAlpha) - { - pixel = (pixel & 0x00FFFFFFu) | (fbrgba & 0xFF000000u); - } - - // format conversion - if (bitsPerPixel(fpsm) == 16) - { - pixel = Rgba8888ToRgba5551(pixel); - } - - gs->WriteVram(fpsm, fbp, fbw, x, y, pixel); - } - - if (writeMask.writeDepth && !ctx.zbuf.zmask) - { - gs->WriteVram(zpsm, zbp, fbw, x, y, z); - } -} - -uint32_t GSRasterizer::lookupCLUT(GS *gs, - uint8_t index, - uint32_t cbp, - uint8_t cpsm, - uint8_t csm, - uint8_t csa, - uint8_t sourcePsm) -{ - const uint32_t clutIndex = resolveClutIndex(index, cpsm, csm, csa, sourcePsm); - const uint32_t clutWidth = (gs->m_texclut.cbw != 0u) ? static_cast(gs->m_texclut.cbw) : 1u; - const uint32_t clutX = static_cast(gs->m_texclut.cou) + (clutIndex & 0x0Fu); - const uint32_t clutY = static_cast(gs->m_texclut.cov) + (clutIndex >> 4); - - switch (cpsm) - { - case GS_PSM_CT32: - return applyTexa(gs->m_texa, cpsm, GSMem::ReadCT32(gs->m_vram, cbp, clutWidth, clutX, clutY)); - case GS_PSM_CT24: - return applyTexa(gs->m_texa, cpsm, GSMem::ReadCT24(gs->m_vram, cbp, clutWidth, clutX, clutY)); - case GS_PSM_CT16: - return applyTexa(gs->m_texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16(gs->m_vram, cbp, clutWidth, clutX, clutY))); - case GS_PSM_CT16S: - return applyTexa(gs->m_texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16S(gs->m_vram, cbp, clutWidth, clutX, clutY))); - default: - break; - } - - return 0xFFFF00FFu; -} - -uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, float q, uint16_t u, uint16_t v) -{ - const auto &ctx = gs->activeContext(); - const auto &tex = ctx.tex0; - - const int texW = textureDimension(tex.tw); - const int texH = textureDimension(tex.th); - const uint64_t clamp = ctx.clamp; - const uint8_t wrapU = static_cast(clamp & 0x3u); - const uint8_t wrapV = static_cast((clamp >> 2) & 0x3u); - const uint16_t minU = static_cast((clamp >> 4) & 0x3FFu); - const uint16_t maxU = static_cast((clamp >> 14) & 0x3FFu); - const uint16_t minV = static_cast((clamp >> 24) & 0x3FFu); - const uint16_t maxV = static_cast((clamp >> 34) & 0x3FFu); - - float texUf, texVf; - if (gs->m_prim.fst) - { - texUf = static_cast(u) / 16.0f; - texVf = static_cast(v) / 16.0f; - } - else - { - const float invQ = 1.0f / fabsQ(q); - texUf = s * invQ * static_cast(texW); - texVf = t * invQ * static_cast(texH); - } - - auto samplePoint = [&](int sampleU, int sampleV) -> uint32_t - { - sampleU = wrapTextureCoordinate(sampleU, texW, wrapU, minU, maxU); - sampleV = wrapTextureCoordinate(sampleV, texH, wrapV, minV, maxV); - - u32 out = gs->ReadVram(tex.psm, tex.tbp0, tex.tbw, sampleU, sampleV); - - switch (tex.psm) - { - case GS_PSM_CT32: - case GS_PSM_Z32: - case GS_PSM_CT24: - case GS_PSM_Z24: - return applyTexa(gs->m_texa, tex.psm, out); - case GS_PSM_CT16: - case GS_PSM_CT16S: - case GS_PSM_Z16: - case GS_PSM_Z16S: - return applyTexa(gs->m_texa, tex.psm, Rgba5551ToRgba8888(out)); - case GS_PSM_T8: - case GS_PSM_T8H: - case GS_PSM_T4: - case GS_PSM_T4HL: - case GS_PSM_T4HH: - return lookupCLUT(gs, static_cast(out), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); - } - - return 0xFFFF00FFu; - }; - - if (!tex1UsesLinearFilter(ctx.tex1)) - { - return samplePoint(static_cast(texUf), static_cast(texVf)); - } - - const float sampleU = texUf - 0.5f; - const float sampleV = texVf - 0.5f; - const int u0 = static_cast(std::floor(sampleU)); - const int v0 = static_cast(std::floor(sampleV)); - const int u1 = u0 + 1; - const int v1 = v0 + 1; - const float fx = sampleU - static_cast(u0); - const float fy = sampleV - static_cast(v0); - - const uint32_t c00 = samplePoint(u0, v0); - const uint32_t c10 = samplePoint(u1, v0); - const uint32_t c01 = samplePoint(u0, v1); - const uint32_t c11 = samplePoint(u1, v1); - - const uint8_t r = lerpChannel(static_cast(c00 & 0xFFu), - static_cast(c10 & 0xFFu), - static_cast(c01 & 0xFFu), - static_cast(c11 & 0xFFu), - fx, fy); - const uint8_t g = lerpChannel(static_cast((c00 >> 8) & 0xFFu), - static_cast((c10 >> 8) & 0xFFu), - static_cast((c01 >> 8) & 0xFFu), - static_cast((c11 >> 8) & 0xFFu), - fx, fy); - const uint8_t b = lerpChannel(static_cast((c00 >> 16) & 0xFFu), - static_cast((c10 >> 16) & 0xFFu), - static_cast((c01 >> 16) & 0xFFu), - static_cast((c11 >> 16) & 0xFFu), - fx, fy); - const uint8_t a = lerpChannel(static_cast((c00 >> 24) & 0xFFu), - static_cast((c10 >> 24) & 0xFFu), - static_cast((c01 >> 24) & 0xFFu), - static_cast((c11 >> 24) & 0xFFu), - fx, fy); - - return static_cast(r) | - (static_cast(g) << 8) | - (static_cast(b) << 16) | - (static_cast(a) << 24); -} - -void GSRasterizer::drawSprite(GS *gs) -{ - const GSVertex &v0 = gs->m_vtxQueue[0]; - const GSVertex &v1 = gs->m_vtxQueue[1]; - const auto &ctx = gs->activeContext(); - - int ofx = ctx.xyoffset.ofx >> 4; - int ofy = ctx.xyoffset.ofy >> 4; - - int x0 = static_cast(v0.x) - ofx; - int y0 = static_cast(v0.y) - ofy; - int x1 = static_cast(v1.x) - ofx; - int y1 = static_cast(v1.y) - ofy; - u32 z1 = static_cast(v1.z); - - if (x0 > x1) - std::swap(x0, x1); - if (y0 > y1) - std::swap(y0, y1); - - const int unclippedX0 = x0; - const int unclippedY0 = y0; - const int spanX = std::max(1, x1 - x0); - const int spanY = std::max(1, y1 - y0); - const int unclippedX1 = unclippedX0 + spanX - 1; - const int unclippedY1 = unclippedY0 + spanY - 1; - - // If the sprite rectangle is fully outside scissor, nothing should render. - if (unclippedX1 < ctx.scissor.x0 || unclippedX0 > ctx.scissor.x1 || - unclippedY1 < ctx.scissor.y0 || unclippedY0 > ctx.scissor.y1) - { - // maybe a log here idk ? - return; - } - - const int drawX0 = clampInt(unclippedX0, ctx.scissor.x0, ctx.scissor.x1); - const int drawY0 = clampInt(unclippedY0, ctx.scissor.y0, ctx.scissor.y1); - const int drawX1 = clampInt(unclippedX1, ctx.scissor.x0, ctx.scissor.x1); - const int drawY1 = clampInt(unclippedY1, ctx.scissor.y0, ctx.scissor.y1); - - const uint64_t alphaReg = ctx.alpha; - const uint8_t alphaMode = static_cast(alphaReg & 0xFFu); - const uint8_t alphaFix = static_cast((alphaReg >> 32) & 0xFFu); - const bool looksLikeDisplayCopy = - gs->m_prim.tme && - gs->m_prim.abe && - gs->m_prim.fst && - gs->m_prim.ctxt && - ctx.frame.fbp != ctx.tex0.tbp0 && - alphaMode == 0x64u && - (alphaFix == 0x60u || alphaFix == 0x80u) && - unclippedX0 <= 0 && - unclippedY0 <= 0 && - unclippedX1 >= 639 && - unclippedY1 >= 447; - if (looksLikeDisplayCopy) - { - gs->m_preferredDisplaySourceFrame = {ctx.tex0.tbp0, ctx.tex0.tbw, ctx.tex0.psm, 0u}; - gs->m_preferredDisplayDestFbp = ctx.frame.fbp; - gs->m_hasPreferredDisplaySource = true; - } - - uint8_t r = v1.r, g = v1.g, b = v1.b, a = v1.a; - - if (gs->m_prim.tme) - { - const auto &tex = ctx.tex0; - const int texW = textureDimension(tex.tw); - const int texH = textureDimension(tex.th); - - float u0f, v0f, u1f, v1f; - if (gs->m_prim.fst) - { - u0f = static_cast(v0.u >> 4); - v0f = static_cast(v0.v >> 4); - u1f = static_cast(v1.u >> 4); - v1f = static_cast(v1.v >> 4); - } - else - { - const float q0 = fabsQ(v0.q); - const float q1 = fabsQ(v1.q); - u0f = (v0.s / q0) * static_cast(texW); - v0f = (v0.t / q0) * static_cast(texH); - u1f = (v1.s / q1) * static_cast(texW); - v1f = (v1.t / q1) * static_cast(texH); - } - - float spriteW = static_cast(spanX); - float spriteH = static_cast(spanY); - if (spriteW < 1.0f) - spriteW = 1.0f; - if (spriteH < 1.0f) - spriteH = 1.0f; - - for (int y = drawY0; y <= drawY1; ++y) - { - float ty = (static_cast(y - unclippedY0) + 0.5f) / spriteH; - float texVf = v0f + (v1f - v0f) * ty; - - for (int x = drawX0; x <= drawX1; ++x) - { - float tx = (static_cast(x - unclippedX0) + 0.5f) / spriteW; - float texUf = u0f + (u1f - u0f) * tx; - uint32_t texel = 0xFFFF00FFu; - if (gs->m_prim.fst) - { - const int fixedU = static_cast((texUf * 16.0f) + 0.5f); - const int fixedV = static_cast((texVf * 16.0f) + 0.5f); - const uint16_t sampleU = static_cast(clampInt(fixedU, 0, 0xFFFF)); - const uint16_t sampleV = static_cast(clampInt(fixedV, 0, 0xFFFF)); - texel = sampleTexture(gs, 0.0f, 0.0f, 1.0f, sampleU, sampleV); - } - else - { - texel = sampleTexture(gs, texUf / static_cast(texW), texVf / static_cast(texH), 1.0f, 0u, 0u); - } - - uint8_t tr = static_cast(texel & 0xFF); - uint8_t tg = static_cast((texel >> 8) & 0xFF); - uint8_t tb = static_cast((texel >> 16) & 0xFF); - uint8_t ta = static_cast((texel >> 24) & 0xFF); - - const TextureCombineResult color = combineTexture(tex, r, g, b, a, tr, tg, tb, ta); - writePixel(gs, x, y, z1, color.r, color.g, color.b, color.a, v1.fog); - } - } - } - else - { - for (int y = drawY0; y <= drawY1; ++y) - for (int x = drawX0; x <= drawX1; ++x) - writePixel(gs, x, y, z1, r, g, b, a, v1.fog); - } -} - -void GSRasterizer::drawTriangle(GS *gs) -{ - const GSVertex &v0 = gs->m_vtxQueue[0]; - const GSVertex &v1 = gs->m_vtxQueue[1]; - const GSVertex &v2 = gs->m_vtxQueue[2]; - const auto &ctx = gs->activeContext(); - - int ofx = ctx.xyoffset.ofx >> 4; - int ofy = ctx.xyoffset.ofy >> 4; - - float fx0 = v0.x - static_cast(ofx); - float fy0 = v0.y - static_cast(ofy); - float fx1 = v1.x - static_cast(ofx); - float fy1 = v1.y - static_cast(ofy); - float fx2 = v2.x - static_cast(ofx); - float fy2 = v2.y - static_cast(ofy); - - int minX = static_cast(std::floor(std::min({fx0, fx1, fx2}))); - int maxX = static_cast(std::ceil(std::max({fx0, fx1, fx2}))); - int minY = static_cast(std::floor(std::min({fy0, fy1, fy2}))); - int maxY = static_cast(std::ceil(std::max({fy0, fy1, fy2}))); - - minX = clampInt(minX, ctx.scissor.x0, ctx.scissor.x1); - maxX = clampInt(maxX, ctx.scissor.x0, ctx.scissor.x1); - minY = clampInt(minY, ctx.scissor.y0, ctx.scissor.y1); - maxY = clampInt(maxY, ctx.scissor.y0, ctx.scissor.y1); - - float denom = (fy1 - fy2) * (fx0 - fx2) + (fx2 - fx1) * (fy0 - fy2); - if (std::fabs(denom) < 0.001f) - return; - - const float winding = (denom < 0.0f) ? -1.0f : 1.0f; - const float invAbsDenom = 1.0f / std::fabs(denom); - constexpr float kEdgeEpsilon = 1.0e-4f; - - for (int y = minY; y <= maxY; ++y) - { - float py = static_cast(y) + 0.5f; - for (int x = minX; x <= maxX; ++x) - { - float px = static_cast(x) + 0.5f; - - float w0 = (((fy1 - fy2) * (px - fx2) + (fx2 - fx1) * (py - fy2)) * winding) * invAbsDenom; - float w1 = (((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (py - fy2)) * winding) * invAbsDenom; - float w2 = 1.0f - w0 - w1; - - if (w0 < -kEdgeEpsilon || w1 < -kEdgeEpsilon || w2 < -kEdgeEpsilon) - continue; - - double z = v0.z * w0 + v1.z * w1 + v2.z * w2; - - uint8_t r, g, b, a; - if (gs->m_prim.iip) - { - r = clampU8(static_cast(v0.r * w0 + v1.r * w1 + v2.r * w2)); - g = clampU8(static_cast(v0.g * w0 + v1.g * w1 + v2.g * w2)); - b = clampU8(static_cast(v0.b * w0 + v1.b * w1 + v2.b * w2)); - a = clampU8(static_cast(v0.a * w0 + v1.a * w1 + v2.a * w2)); - } - else - { - r = v2.r; - g = v2.g; - b = v2.b; - a = v2.a; - } - - if (gs->m_prim.tme) - { - float is, it, iq; - uint16_t iu, iv; - if (gs->m_prim.fst) - { - iu = static_cast(v0.u * w0 + v1.u * w1 + v2.u * w2); - iv = static_cast(v0.v * w0 + v1.v * w1 + v2.v * w2); - is = 0.0f; - it = 0.0f; - iq = 1.0f; - } - else - { - // The GS DDA interpolates the homogeneous S, T and Q - // values. Texel coordinates are calculated from S/Q and - // T/Q only after interpolation. - is = v0.s * w0 + v1.s * w1 + v2.s * w2; - it = v0.t * w0 + v1.t * w1 + v2.t * w2; - iq = v0.q * w0 + v1.q * w1 + v2.q * w2; - iu = 0; - iv = 0; - } - - uint32_t texel = sampleTexture(gs, is, it, iq, iu, iv); - - uint8_t tr = static_cast(texel & 0xFF); - uint8_t tg = static_cast((texel >> 8) & 0xFF); - uint8_t tb = static_cast((texel >> 16) & 0xFF); - uint8_t ta = static_cast((texel >> 24) & 0xFF); - - const auto &tex = ctx.tex0; - const uint8_t shadeR = r; - const uint8_t shadeG = g; - const uint8_t shadeB = b; - const uint8_t shadeA = a; - const TextureCombineResult color = combineTexture(tex, shadeR, shadeG, shadeB, shadeA, tr, tg, tb, ta); - - r = color.r; - g = color.g; - b = color.b; - a = color.a; - } - - const uint8_t fog = clampU8(static_cast(v0.fog * w0 + v1.fog * w1 + v2.fog * w2)); - writePixel(gs, x, y, static_cast(z + 0.5), r, g, b, a, fog); - } - } -} - -void GSRasterizer::drawLine(GS *gs) -{ - const GSVertex &v0 = gs->m_vtxQueue[0]; - const GSVertex &v1 = gs->m_vtxQueue[1]; - const auto &ctx = gs->activeContext(); - - int ofx = ctx.xyoffset.ofx >> 4; - int ofy = ctx.xyoffset.ofy >> 4; - - int x0 = static_cast(v0.x) - ofx; - int y0 = static_cast(v0.y) - ofy; - int x1 = static_cast(v1.x) - ofx; - int y1 = static_cast(v1.y) - ofy; - - int dx = std::abs(x1 - x0); - int dy = -std::abs(y1 - y0); - int sx = (x0 < x1) ? 1 : -1; - int sy = (y0 < y1) ? 1 : -1; - int err = dx + dy; - - int totalSteps = std::max(std::abs(x1 - x0), std::abs(y1 - y0)); - if (totalSteps == 0) - totalSteps = 1; - int step = 0; - - for (;;) - { - float t = static_cast(step) / static_cast(totalSteps); - uint8_t r, g, b, a; - if (gs->m_prim.iip) - { - r = clampU8(static_cast(v0.r + (v1.r - v0.r) * t)); - g = clampU8(static_cast(v0.g + (v1.g - v0.g) * t)); - b = clampU8(static_cast(v0.b + (v1.b - v0.b) * t)); - a = clampU8(static_cast(v0.a + (v1.a - v0.a) * t)); - } - else - { - r = v1.r; - g = v1.g; - b = v1.b; - a = v1.a; - } - - double z = (v0.z + (v1.z - v0.z) * t); - const uint8_t fog = clampU8(static_cast(v0.fog + (v1.fog - v0.fog) * t)); - writePixel(gs, x0, y0, static_cast(z), r, g, b, a, fog); - - if (x0 == x1 && y0 == y1) - break; - - int e2 = 2 * err; - if (e2 >= dy) - { - err += dy; - x0 += sx; - } - if (e2 <= dx) - { - err += dx; - y0 += sy; - } - ++step; - } -} diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index 01bbcc2..7cb2ba4 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -1,6 +1,6 @@ #include "runtime/ps2_memory.h" #include "runtime/ps2_address.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/gs_frontend.h" #include "ps2_log.h" #include #include diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index a6ed05b..ddacb0c 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -4,7 +4,7 @@ #include "ps2_syscalls.h" #include "game_overrides.h" #include "ps2_runtime_macros.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ee_scheduler.h" #include "ThreadNaming.h" #include "Kernel/Stubs/Audio.h" diff --git a/ps2xRuntime/src/lib/ps2_vu1.cpp b/ps2xRuntime/src/lib/ps2_vu1.cpp deleted file mode 100644 index 2c22cc2..0000000 --- a/ps2xRuntime/src/lib/ps2_vu1.cpp +++ /dev/null @@ -1 +0,0 @@ -// VU1 interpreter implementation has been split into src/lib/vu/*.cpp. diff --git a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp index dbaead4..bd75bba 100644 --- a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp +++ b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp @@ -1,6 +1,6 @@ #include "runtime/ps2_vu1.h" -#include "runtime/ps2_gif_arbiter.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/ps2_gif_arbiter.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ps2_memory.h" #include "ps2_vu1_detail.h" diff --git a/ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp b/ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp index 7d235d7..94be789 100644 --- a/ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp +++ b/ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp @@ -1,6 +1,6 @@ #include "runtime/ps2_vu1.h" -#include "runtime/ps2_gif_arbiter.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/ps2_gif_arbiter.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ps2_memory.h" #include "ps2_vu1_detail.h" diff --git a/ps2xTest/src/ps2_gs_tests.cpp b/ps2xTest/src/ps2_gs_tests.cpp index 5f6915d..8343775 100644 --- a/ps2xTest/src/ps2_gs_tests.cpp +++ b/ps2xTest/src/ps2_gs_tests.cpp @@ -3,13 +3,12 @@ #include "ps2_runtime.h" #include "ps2_stubs.h" #include "ps2_syscalls.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ee_scheduler.h" -#include "runtime/ps2_gs_memory.h" -#include "runtime/ps2_gs_rasterizer.h" -#include "runtime/ps2_gs_psmct32.h" -#include "runtime/ps2_gs_psmt4.h" -#include "runtime/ps2_gs_psmt8.h" +#include "runtime/gs/ps2_gs_memory.h" +#include "runtime/gs/ps2_gs_psmct32.h" +#include "runtime/gs/ps2_gs_psmt4.h" +#include "runtime/gs/ps2_gs_psmt8.h" #include "Stubs/Helpers/Support.h" #include "Stubs/GS.h" @@ -2861,9 +2860,13 @@ void register_ps2_gs_tests() std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - GSRasterizer rasterizer; constexpr uint32_t kTexTbp = 64u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kZbuf = (1ull << 32); constexpr uint64_t kTex0 = (static_cast(kTexTbp) << 0) | (16ull << 14) | @@ -2873,22 +2876,57 @@ void register_ps2_gs_tests() (1ull << 34) | (1ull << 35); constexpr uint64_t kPrim = - static_cast(GS_PRIM_TRIANGLE) | + static_cast(GS_PRIM_SPRITE) | (1ull << 4); constexpr uint32_t kExpectedColor = 0xFF3366CCu; constexpr uint32_t kUnsaturatedColor = 0xFF00FF00u; + auto packFloat = [](float value) -> uint32_t + { + uint32_t bits = 0u; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; + }; + auto packSt = [&](float s, float tValue) -> uint64_t + { + return static_cast(packFloat(s)) | + (static_cast(packFloat(tValue)) << 32u); + }; + gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 1u, 0u, kExpectedColor); gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 32u, 0u, kUnsaturatedColor); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_ZBUF_1, kZbuf); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0x30000ull); gs.writeRegister(GS_REG_TEX0_1, kTex0); gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x3F80000080808080ull); + gs.writeRegister(GS_REG_ST, packSt(1.0f / 1024.0f, 0.0f)); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_ST, packSt(1.0f / 1024.0f, 0.0f)); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); - const uint32_t sampled = - rasterizer.sampleTexture(&gs, 1.0f / 1024.0f, 0.0f, 1.0f, 0u, 0u); + const uint32_t sampled = gs.ReadVram(GS_PSM_CT32, 0u, 1u, 0u, 0u); t.Equals(sampled, kExpectedColor, "TW/TH values above 10 should address a 1024-pixel texture instead of growing beyond GS limits"); }); + tc.Run("GS backend replacement preserves canonical local memory", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kColor = 0xA55A33CCu; + gs.WriteVram(GS_PSM_CT32, 64u, 1u, 3u, 2u, kColor); + gs.setRasterBackend(nullptr); + + t.Equals(gs.ReadVram(GS_PSM_CT32, 64u, 1u, 3u, 2u), kColor, + "switching raster backends must retain the logical 4 MiB GS local memory"); + }); + tc.Run("GS TEX2 updates CLUT state independently from TEX0", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); diff --git a/ps2xTest/src/ps2_memory_tests.cpp b/ps2xTest/src/ps2_memory_tests.cpp index aa8013c..7c3e8ea 100644 --- a/ps2xTest/src/ps2_memory_tests.cpp +++ b/ps2xTest/src/ps2_memory_tests.cpp @@ -1,7 +1,7 @@ #include "MiniTest.h" #include "runtime/ps2_memory.h" -#include "runtime/ps2_gs_gpu.h" -#include "runtime/ps2_gs_psmct32.h" +#include "runtime/gs/gs_frontend.h" +#include "runtime/gs/ps2_gs_psmct32.h" #include "ps2_runtime.h" #include "ps2_runtime_macros.h" #include "Stubs/DMA.h" diff --git a/ps2xTest/src/ps2_runtime_expansion_tests.cpp b/ps2xTest/src/ps2_runtime_expansion_tests.cpp index cbf6434..ad7df46 100644 --- a/ps2xTest/src/ps2_runtime_expansion_tests.cpp +++ b/ps2xTest/src/ps2_runtime_expansion_tests.cpp @@ -7,9 +7,9 @@ #include "runtime/ps2_memory.h" #include "ps2_syscalls.h" #include "ps2_stubs.h" -#include "runtime/ps2_gs_gpu.h" +#include "runtime/gs/gs_frontend.h" #include "runtime/ee_scheduler.h" -#include "runtime/ps2_gs_psmct32.h" +#include "runtime/gs/ps2_gs_psmct32.h" #include "ps2_runtime_macros.h" #include "Stubs/MPEG.h" #include "Stubs/CD.h" diff --git a/ps2xTest/src/ps2_vu1_tests.cpp b/ps2xTest/src/ps2_vu1_tests.cpp index 8c0ff0d..0754b69 100644 --- a/ps2xTest/src/ps2_vu1_tests.cpp +++ b/ps2xTest/src/ps2_vu1_tests.cpp @@ -1,7 +1,7 @@ #include "MiniTest.h" -#include "runtime/ps2_gif_arbiter.h" -#include "runtime/ps2_gs_gpu.h" -#include "runtime/ps2_gs_psmct32.h" +#include "runtime/gs/ps2_gif_arbiter.h" +#include "runtime/gs/gs_frontend.h" +#include "runtime/gs/ps2_gs_psmct32.h" #include "runtime/ps2_memory.h" #include "runtime/ps2_vu1.h" From d9ea4fb63d77e24bba92aeab91fd29f63155afc9 Mon Sep 17 00:00:00 2001 From: Sinan Date: Wed, 19 Aug 2026 00:49:38 +0200 Subject: [PATCH 2/3] Let a thread resume at the instruction after a syscall (#210) A syscall can hand control back to the scheduler before the instruction after it runs. SetSyscall lets the guest install its own handler for a syscall number; dispatchSyscallOverride then suspends the calling thread and queues that handler as a GuestInvocation. When the invocation finishes, EeScheduler resumes the parent thread at the address the generated code stored just before calling handleSyscall -- the instruction right after the syscall. The analyzer never marked that address as an entry point. It queues resume entries for JAL and JALR only, so no generated function could be re-entered there, EeScheduler's hasFunction() check failed, and the thread was made dormant instead of resumed. The thread simply stops; because the scheduler then drains normally and run() returns, it looks like a clean shutdown rather than a fault, which makes it awkward to recognise. This is reachable during early boot on a real title. Dragon Quest VIII hits it in crt0: the Metrowerks startup code installs a handler for syscall 0x83 and immediately issues it, and execution ends there, roughly ten functions into the binary. Note the offset is +4, not the +8 used for JAL and JALR -- syscall has no delay slot. (cherry picked from commit becb2be5bd0dc3deebeec8454df21ea7d7062b45) --- ps2xRecomp/src/lib/control_flow_analyzer.cpp | 8 ++++ ps2xTest/src/code_generator_tests.cpp | 40 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/ps2xRecomp/src/lib/control_flow_analyzer.cpp b/ps2xRecomp/src/lib/control_flow_analyzer.cpp index a099f5c..ed7578d 100644 --- a/ps2xRecomp/src/lib/control_flow_analyzer.cpp +++ b/ps2xRecomp/src/lib/control_flow_analyzer.cpp @@ -135,6 +135,14 @@ namespace ps2recomp for (const auto &inst : instructions) { + // A guest-installed syscall handler runs as a separate invocation, + // so the scheduler resumes this thread at syscall+4 and needs an + // entry point there. +4, not +8: syscall has no delay slot. + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYSCALL) + { + queueResumeEntryTarget(inst.address + 4u); + } + bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL); if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL) { diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 2dd9c53..1a1f370 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -144,6 +144,17 @@ static Instruction makeJr(uint32_t address, uint8_t rs) return inst; } +static Instruction makeSyscall(uint32_t address) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = OPCODE_SPECIAL; + inst.function = SPECIAL_SYSCALL; + inst.hasDelaySlot = false; + inst.raw = (OPCODE_SPECIAL << 26) | SPECIAL_SYSCALL; + return inst; +} + static void printGeneratedCode(const std::string& name, const std::string& code) { #ifdef PRINT_GENERATED_CODE @@ -556,6 +567,35 @@ void register_code_generator_tests() "unresolved JALR should not pretend it has a resolved local jump table"); }); + tc.Run("syscall marks the following instruction as a resume entry", [](TestCase &t) { + // Shape of a real SDK syscall wrapper: + // addiu $v1, $zero, ; syscall ; jr $ra ; + Function func; + func.name = "syscall_wrapper"; + func.start = 0x4000; + func.end = 0x4010; + func.isRecompiled = true; + func.isStub = false; + + std::vector instructions{ + makeAddiu(0x4000, 3, 0, 0x83), + makeSyscall(0x4004), + makeJr(0x4008, 31), + makeNop(0x400C), + }; + + CodeGenerator gen({}, {}); + CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets(func, instructions); + + // +4, not +8: syscall has no delay slot. + t.IsTrue(analysis.resumeEntryPoints.contains(0x4008u), + "syscall should mark the next instruction as resumable"); + t.IsTrue(analysis.entryPoints.contains(0x4008u), + "syscall resume pc should emit a label in the owner"); + t.IsFalse(analysis.resumeEntryPoints.contains(0x400Cu), + "syscall must not claim a delay slot it does not have"); + }); + tc.Run("resume entry targets emit a top-level pc switch in the owner wrapper", [](TestCase &t) { Function func; func.name = "resume_owner"; From 14b1e5cb39b4af7e6fc12f9a29fdc751efde49d7 Mon Sep 17 00:00:00 2001 From: Sinan Date: Wed, 19 Aug 2026 00:50:13 +0200 Subject: [PATCH 3/3] Start the main thread with COP0 Status.IE set (#214) Guest code reads COP0 Status to decide whether interrupts are enabled, and two different bits are involved: IE (bit 0) the architectural MIPS interrupt enable, set once by the kernel during boot and normally left set. EIE (bit 16) the EE-specific enable that `ei` and `di` toggle. We never execute the boot ROM, so nothing was setting either one, and R5900Context started with Status at zero. That is not cosmetic. libkernel's StartThread opens with `mfc0 Status; xori 1; andi 1` and bails out with -1 when IE is clear -- its "you must call iStartThread from an interrupt handler" guard. With Status at zero that guard fired every time, so every StartThread failed. Dragon Quest VIII hits this during boot: it creates its CD streaming thread, gets -1, prints "Can't start thread for streaming." and then deadlocks with every thread blocked and none runnable. Nothing in the runtime logs anything, because from its point of view the guest simply asked a question and got an answer. EIE matters for the matching reason: DIntr reports whether it was set so the caller knows whether to pair it with an EIntr. Starting at zero makes DIntr always answer "already disabled", so the re-enable never happens. Two changes, both needed: - R5900Context's constructor now sets Status to EIE | IE rather than 0. BEV is deliberately left clear -- that selects the boot exception vectors, which is the pre-handoff state, not this one. - PS2Runtime's constructor no longer memsets m_cpuContext. R5900Context already zeroes itself before applying its reset values, so the memset only threw those values away. Threads created later were unaffected because EeScheduler::startThread assigns `R5900Context{}`, which is why this presented as "the main thread cannot start threads" rather than something more obviously global. (cherry picked from commit 2ac2ce632082ff8b7683f0380361ddcbc410bdbc) --- ps2xRuntime/include/ps2_runtime.h | 8 ++++---- ps2xRuntime/src/lib/ps2_runtime.cpp | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index a899408..d365cb0 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -150,10 +150,10 @@ struct alignas(16) R5900Context // Reset COP0 registers cop0_random = 47; // Start at maximum value - // cop0_status = 0x400000; // BEV set, ERL clear, kernel mode - // 0x00400000 = BEV (Boot Exception Vectors). - // 0x00000000 = Normal mode (after BIOS handoff). - cop0_status = 0x00000000; + // Status as the EE kernel leaves it at handoff. IE (bit 0) and EIE + // (bit 16) are separate enables and guest code reads both; libkernel's + // StartThread refuses to run while IE is clear. + cop0_status = 0x00010001; // EIE | IE cop0_prid = 0x00002e20; // CPU ID for R5900 in_delay_slot = false; diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index ddacb0c..0e8471e 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -491,7 +491,9 @@ PS2Runtime::PS2Runtime() } #endif - std::memset(&m_cpuContext, 0, sizeof(m_cpuContext)); + // Assign rather than memset: R5900Context's constructor zeroes itself and + // then applies the COP0 reset values, which a memset here would discard. + m_cpuContext = R5900Context{}; // R0 is always zero in MIPS m_cpuContext.r[0] = _mm_set1_epi32(0);