diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 0a80a984b..e4dc1956d 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 4259e12d2..a899408aa 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 000000000..9419cc23a --- /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 000000000..71e80324f --- /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 d968326f8..4c3f6410a 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 @@ class GS 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 @@ class GS 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 @@ class GS 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 @@ class GS 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 @@ class GS 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 @@ class GS 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 @@ class GS 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 000000000..836a3ff49 --- /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 3dfa8eb17..03306d3b0 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 17547ded8..000000000 --- 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 9972e4221..cea5b98a8 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 b29df1bcd..9c5491990 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 000000000..9c39ae2e7 --- /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 000000000..fd5a90d28 --- /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 1b83711ae..c289ba0f2 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 e061953ba..fecf9fd2c 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 d444ad84c..000000000 --- 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 96cff26da..000000000 --- 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 01bbcc25f..7cb2ba463 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 a6ed05b27..ddacb0c7d 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 2c22cc27e..000000000 --- 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 dbaead44b..bd75bba31 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 7d235d7b4..94be78914 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 5f6915d70..8343775cb 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 aa8013c4b..7c3e8ea5d 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 cbf64342e..ad7df4660 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 8c0ff0d3e..0754b69fc 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"