#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); }