Files
PS2Recomp/ps2xRuntime/src/lib/ps2_gs_gpu.cpp
T
Ran-j 3e5fb2b3b4 feat: cheap copy from host
feat: small perf o vsync tick
2026-08-05 19:03:03 -03:00

2962 lines
97 KiB
C++

#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 <atomic>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
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<GSPrimType>(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<uint16_t>(((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 <typename Visitor>
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<uint8_t>((tag.lo >> 58u) & 0x3u);
if (flg != GIF_FMT_PACKED)
return false;
tag.nloop = static_cast<uint32_t>(tag.lo & 0x7FFFu);
tag.nreg = static_cast<uint32_t>((tag.lo >> 60u) & 0xFu);
if (tag.nreg == 0u)
tag.nreg = 16u;
const uint64_t payloadBytes64 =
static_cast<uint64_t>(tag.nloop) * static_cast<uint64_t>(tag.nreg) * 16ull;
if (payloadBytes64 > 0xFFFFFFFFull)
return false;
offset += 16u;
const uint32_t payloadBytes = static_cast<uint32_t>(payloadBytes64);
if (payloadBytes > sizeBytes - offset)
return false;
tag.payloadOffset = offset;
for (uint32_t i = 0u; i < tag.nreg; ++i)
tag.regs[i] = static_cast<uint8_t>((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<uint32_t>((display64 >> 0) & 0x0FFFu);
const uint32_t dy = static_cast<uint32_t>((display64 >> 12) & 0x07FFu);
const uint32_t dw = static_cast<uint32_t>((display64 >> 32) & 0x0FFFu);
const uint32_t dh = static_cast<uint32_t>((display64 >> 44) & 0x07FFu);
const uint32_t magh = static_cast<uint32_t>((display64 >> 23) & 0x0Fu);
outWidth = (dw + 1u) / (magh + 1u);
outHeight = dh + 1u;
if (outWidth < 64u || outHeight < 64u)
{
outWidth = kDefaultDisplayWidth;
outHeight = kDefaultDisplayHeight;
}
outWidth = std::min<uint32_t>(outWidth, kHostFrameWidth);
outHeight = std::min<uint32_t>(outHeight, kHostFrameHeight);
}
GSFrameReg decodeDisplayFrame(uint64_t dispfb64)
{
GSFrameReg frame{};
frame.fbp = static_cast<uint32_t>(dispfb64 & 0x1FFu);
frame.fbw = static_cast<uint32_t>((dispfb64 >> 9) & 0x3Fu);
frame.psm = static_cast<uint8_t>((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<uint32_t>((dispfb64 >> 32) & 0x7FFu);
origin.y = static_cast<uint32_t>((dispfb64 >> 43) & 0x7FFu);
return origin;
}
bool hasDisplaySetup(uint64_t display64, const GSFrameReg &frame)
{
const uint32_t dw = static_cast<uint32_t>((display64 >> 32) & 0x0FFFu);
const uint32_t dh = static_cast<uint32_t>((display64 >> 44) & 0x07FFu);
const uint32_t magh = static_cast<uint32_t>((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<uint8_t>((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<uint8_t> &pixels, uint32_t width, uint32_t height, bool oddField)
{
if (pixels.empty() || width == 0u || height < 2u)
{
return;
}
const std::vector<uint8_t> 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<uint8_t> &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<int>(src) - static_cast<int>(dst);
return GSInternal::clampU8(static_cast<int>(dst) + ((delta * static_cast<int>(factor)) / 255));
}
uint32_t countNonBlackPixels(const std::vector<uint8_t> &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<u32>(std::max<int>(0, ctx.scissor.x0));
const u32 x1 = static_cast<u32>(std::max<int>(x0, ctx.scissor.x1));
const u32 y0 = static_cast<u32>(std::max<int>(0, ctx.scissor.y0));
const u32 y1 = static_cast<u32>(std::max<int>(y0, ctx.scissor.y1));
uint8_t r = static_cast<uint8_t>(rgba & 0xFFu);
uint8_t g = static_cast<uint8_t>((rgba >> 8) & 0xFFu);
uint8_t b = static_cast<uint8_t>((rgba >> 16) & 0xFFu);
uint8_t a = static_cast<uint8_t>((rgba >> 24) & 0xFFu);
u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp);
u32 fbw = std::max<u32>(ctx.frame.fbw, 1u);
u32 fpsm = ctx.frame.psm;
if ((ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24)
{
a = static_cast<uint8_t>(a | 0x80u);
}
if (ctx.frame.psm == GS_PSM_CT32 || ctx.frame.psm == GS_PSM_CT24)
{
const uint32_t srcPixel =
static_cast<uint32_t>(r) |
(static_cast<uint32_t>(g) << 8) |
(static_cast<uint32_t>(b) << 16) |
(static_cast<uint32_t>(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<uint16_t>(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<uint16_t>((pixel & ~mask) | (c & mask));
}
gs->WriteVram(fpsm, fbp, fbw, x, y, pixel);
}
}
return true;
}
return false;
}
std::atomic<uint32_t> s_debugGifPacketCount{0};
std::atomic<uint32_t> s_debugGsRegisterCount{0};
std::atomic<uint32_t> s_debugGsPackedVertexCount{0};
std::atomic<uint32_t> s_debugGsVertexKickCount{0};
std::atomic<uint32_t> s_debugCopyRegCount{0};
std::atomic<uint32_t> s_debugTexaWriteCount{0};
std::atomic<uint32_t> s_debugCvFontUploadCount{0};
std::atomic<uint32_t> 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<std::recursive_mutex> 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<std::mutex> 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<std::recursive_mutex> stateLock(m_stateMutex);
if (!m_vram || m_vramSize == 0)
return;
std::lock_guard<std::mutex> 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<uint32_t>(m_displaySnapshot.size());
return m_displaySnapshot.data();
}
GSDebugSnapshot GS::getDebugSnapshot() const
{
std::lock_guard<std::recursive_mutex> 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<std::mutex> 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<GSDebugHistoryEntry> GS::getDebugHistory() const
{
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
std::vector<GSDebugHistoryEntry> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(m_stateMutex);
return m_debugHistoryPaused;
}
void GS::setDebugHistoryPaused(bool paused)
{
std::lock_guard<std::recursive_mutex> 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<uint8_t>(std::min<uint32_t>(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<uint32_t>(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<std::recursive_mutex> 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<uint8_t> &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<uint8_t>((r << 3) | (r >> 2));
dst[x * 4u + 1u] = static_cast<uint8_t>((g << 3) | (g >> 2));
dst[x * 4u + 2u] = static_cast<uint8_t>((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<uint8_t>((r << 3) | (r >> 2));
dst[x * 4u + 1u] = static_cast<uint8_t>((g << 3) | (g >> 2));
dst[x * 4u + 2u] = static_cast<uint8_t>((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<uint8_t> 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<std::recursive_mutex> lock(m_stateMutex);
if (!m_privRegs || !m_vram || m_vramSize == 0u)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::recursive_mutex> 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<uint8_t> &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<uint8_t> 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<uint8_t> rc1;
std::vector<uint8_t> 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<uint8_t>(m_privRegs->bgcolor & 0xFFu);
const uint8_t bgG = static_cast<uint8_t>((m_privRegs->bgcolor >> 8) & 0xFFu);
const uint8_t bgB = static_cast<uint8_t>((m_privRegs->bgcolor >> 16) & 0xFFu);
const uint8_t bgA = pmode.alp;
std::vector<uint8_t> 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<uint32_t>(pmode.alp)
: std::min<uint32_t>(255u, static_cast<uint32_t>(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<uint8_t> 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<uint8_t> &outPixels,
uint32_t &outWidth,
uint32_t &outHeight,
uint32_t *outDisplayFbp,
uint32_t *outSourceFbp,
bool *outUsedPreferred) const
{
std::lock_guard<std::mutex> 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<size_t>(outWidth) * 4u;
outPixels.resize(packedRowBytes * static_cast<size_t>(outHeight));
if (outWidth != 0u && outHeight != 0u)
{
const size_t sourceRowBytes = static_cast<size_t>(kHostFrameWidth) * 4u;
for (uint32_t y = 0; y < outHeight; ++y)
{
const size_t srcOffset = static_cast<size_t>(y) * sourceRowBytes;
const size_t dstOffset = static_cast<size_t>(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<std::recursive_mutex> 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<uint32_t>(tagLo & 0x7FFFu);
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xFu);
if (nreg == 0u)
nreg = 16u;
RUNTIME_LOG("[gs:gif] idx=" << packetIndex
<< " size=" << sizeBytes
<< " nloop=" << nloop
<< " flg=" << static_cast<uint32_t>(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<uint32_t>(tagLo & 0x7FFF);
uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3);
uint32_t nreg = static_cast<uint32_t>((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<uint8_t>((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<std::recursive_mutex> 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<std::recursive_mutex> 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<uint32_t>(setupTagLo & 0x7FFFu);
const uint8_t setupFlg = static_cast<uint8_t>((setupTagLo >> 58u) & 0x3u);
uint32_t setupNreg = static_cast<uint32_t>((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<uint32_t>(regs[3] & 0x3ull);
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
const uint32_t rrh = static_cast<uint32_t>((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<uint8_t>((imageTagLo >> 58u) & 0x3u);
const uint32_t imageNloop = static_cast<uint32_t>(imageTagLo & 0x7FFFu);
if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u)
return false;
offset += 16u;
const uint64_t imageBytes64 = static_cast<uint64_t>(imageNloop) * 16ull;
if (imageBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t imageBytes = static_cast<uint32_t>(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<uint8_t>(lo & 0xFF);
m_curG = static_cast<uint8_t>((lo >> 32) & 0xFF);
m_curB = static_cast<uint8_t>(hi & 0xFF);
m_curA = static_cast<uint8_t>((hi >> 32) & 0xFF);
break;
case 0x02:
{
uint32_t sBits = static_cast<uint32_t>(lo & 0xFFFFFFFF);
uint32_t tBits = static_cast<uint32_t>((lo >> 32) & 0xFFFFFFFF);
uint32_t qBits = static_cast<uint32_t>(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<uint16_t>(lo & 0x3FFFu);
m_curV = static_cast<uint16_t>((lo >> 32) & 0x3FFFu);
break;
case 0x04:
{
uint16_t x = static_cast<uint16_t>(lo & 0xFFFF);
uint16_t y = static_cast<uint16_t>((lo >> 32) & 0xFFFF);
uint32_t z = static_cast<uint32_t>((hi >> 4) & 0xFFFFFF);
uint8_t f = static_cast<uint8_t>((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<uint32_t>(f)
<< " kick=" << static_cast<uint32_t>(!adk ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl);
}
});
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(x) / 16.0f;
vtx.y = static_cast<float>(y) / 16.0f;
vtx.z = static_cast<float>(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<uint16_t>(lo & 0xFFFF);
uint16_t y = static_cast<uint16_t>((lo >> 32) & 0xFFFF);
uint32_t z = static_cast<uint32_t>(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<uint32_t>(!adk ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl);
}
});
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(x) / 16.0f;
vtx.y = static_cast<float>(y) / 16.0f;
vtx.z = static_cast<float>(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<uint8_t>((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<uint32_t>(lo & 0xFFFFu)
<< " y=" << static_cast<uint32_t>((lo >> 32) & 0xFFFFu)
<< " kick=0"
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl);
}
});
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>((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<uint8_t>((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<uint32_t>(lo & 0xFFFFu)
<< " y=" << static_cast<uint32_t>((lo >> 32) & 0xFFFFu)
<< " kick=0"
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl);
}
});
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>(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<uint8_t>(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<std::recursive_mutex> 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<uint32_t>(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<uint32_t>(regAddr)
<< " value=0x" << value
<< std::dec
<< " primCtxt=" << static_cast<uint32_t>(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<uint8_t>(value & 0xFF);
m_curG = static_cast<uint8_t>((value >> 8) & 0xFF);
m_curB = static_cast<uint8_t>((value >> 16) & 0xFF);
m_curA = static_cast<uint8_t>((value >> 24) & 0xFF);
uint32_t qBits = static_cast<uint32_t>((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<uint32_t>(value & 0xFFFFFFFF);
uint32_t tBits = static_cast<uint32_t>((value >> 32) & 0xFFFFFFFF);
std::memcpy(&m_curS, &sBits, 4);
std::memcpy(&m_curT, &tBits, 4);
break;
}
case GS_REG_UV:
{
m_curU = static_cast<uint16_t>(value & 0x3FFFu);
m_curV = static_cast<uint16_t>((value >> 16) & 0x3FFFu);
break;
}
case GS_REG_XYZF2:
case GS_REG_XYZF3:
{
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(value & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f;
vtx.z = static_cast<double>((value >> 32) & 0xFFFFFF);
vtx.fog = static_cast<uint8_t>((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<float>(value & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f;
vtx.z = static_cast<double>((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<uint32_t>(value & 0x3FFF);
t.tbw = static_cast<uint8_t>((value >> 14) & 0x3F);
t.psm = static_cast<uint8_t>((value >> 20) & 0x3F);
t.tw = static_cast<uint8_t>((value >> 26) & 0xF);
t.th = static_cast<uint8_t>((value >> 30) & 0xF);
t.tcc = static_cast<uint8_t>((value >> 34) & 0x1);
t.tfx = static_cast<uint8_t>((value >> 35) & 0x3);
t.cbp = static_cast<uint32_t>((value >> 37) & 0x3FFF);
t.cpsm = static_cast<uint8_t>((value >> 51) & 0xF);
t.csm = static_cast<uint8_t>((value >> 55) & 0x1);
t.csa = static_cast<uint8_t>((value >> 56) & 0x1F);
t.cld = static_cast<uint8_t>((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<uint8_t>((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<uint8_t>((value >> 20) & 0x3F);
t.cbp = static_cast<uint32_t>((value >> 37) & 0x3FFF);
t.cpsm = static_cast<uint8_t>((value >> 51) & 0xF);
t.csm = static_cast<uint8_t>((value >> 55) & 0x1);
t.csa = static_cast<uint8_t>((value >> 56) & 0x1F);
t.cld = static_cast<uint8_t>((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<uint16_t>(value & 0xFFFF);
m_ctx[ci].xyoffset.ofy = static_cast<uint16_t>((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<uint8_t>(value & 0x3Fu);
m_texclut.cou = static_cast<uint8_t>((value >> 6) & 0x3Fu);
m_texclut.cov = static_cast<uint16_t>((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<uint16_t>(value & 0x7FF);
m_ctx[ci].scissor.x1 = static_cast<uint16_t>((value >> 16) & 0x7FF);
m_ctx[ci].scissor.y0 = static_cast<uint16_t>((value >> 32) & 0x7FF);
m_ctx[ci].scissor.y1 = static_cast<uint16_t>((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<uint32_t>(value & 0x1FF);
m_ctx[ci].frame.fbw = static_cast<uint32_t>((value >> 16) & 0x3F);
m_ctx[ci].frame.psm = static_cast<uint8_t>((value >> 24) & 0x3F);
m_ctx[ci].frame.fbmsk = static_cast<uint32_t>((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<uint32_t>(value & 0x3FFF);
m_bitbltbuf.sbw = static_cast<uint8_t>((value >> 16) & 0x3F);
m_bitbltbuf.spsm = static_cast<uint8_t>((value >> 24) & 0x3F);
m_bitbltbuf.dbp = static_cast<uint32_t>((value >> 32) & 0x3FFF);
m_bitbltbuf.dbw = static_cast<uint8_t>((value >> 48) & 0x3F);
m_bitbltbuf.dpsm = static_cast<uint8_t>((value >> 56) & 0x3F);
break;
}
case GS_REG_TRXPOS:
{
m_trxpos.ssax = static_cast<uint16_t>(value & 0x7FF);
m_trxpos.ssay = static_cast<uint16_t>((value >> 16) & 0x7FF);
m_trxpos.dsax = static_cast<uint16_t>((value >> 32) & 0x7FF);
m_trxpos.dsay = static_cast<uint16_t>((value >> 48) & 0x7FF);
m_trxpos.dir = static_cast<uint8_t>((value >> 59) & 0x3);
break;
}
case GS_REG_TRXREG:
{
m_trxreg.rrw = static_cast<uint16_t>(value & 0xFFF);
m_trxreg.rrh = static_cast<uint16_t>((value >> 32) & 0xFFF);
break;
}
case GS_REG_TRXDIR:
{
m_trxdir = static_cast<uint32_t>(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<uint8_t>(value & 0xFFu);
m_fogG = static_cast<uint8_t>((value >> 8) & 0xFFu);
m_fogB = static_cast<uint8_t>((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<uint8_t>(value & 0xFFu);
m_texa.aem = ((value >> 15) & 0x1u) != 0u;
m_texa.ta1 = static_cast<uint8_t>((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<uint32_t>(value & 0xFFFFFFFF);
uint32_t mask = static_cast<uint32_t>(value >> 32);
uint32_t lo = static_cast<uint32_t>(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<uint32_t>(value & 0xFFFFFFFF);
uint32_t mask = static_cast<uint32_t>(value >> 32);
uint32_t hi = static_cast<uint32_t>(m_privRegs->siglblid >> 32);
hi = (hi & ~mask) | (id & mask);
m_privRegs->siglblid = (static_cast<uint64_t>(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<uint32_t>(drawing ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(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<u8>(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<u8>(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<GSMem::PixelStorageMode>(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<std::recursive_mutex> lock(m_stateMutex);
return clearFramebufferRect(this, m_ctx[(contextIndex != 0u) ? 1 : 0], rgba);
}
bool GS::clearActiveFramebuffer(uint32_t rgba)
{
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
return clearFramebufferRect(this, activeContext(), rgba);
}
uint32_t GS::consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes)
{
std::lock_guard<std::recursive_mutex> 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<size_t>(maxBytes);
std::memcpy(dst, m_localToHostBuffer.data() + m_localToHostReadPos, toCopy);
m_localToHostReadPos += toCopy;
return static_cast<uint32_t>(toCopy);
}