mirror of
https://github.com/jpd002/Play-.git
synced 2025-04-28 21:57:57 +03:00
2205 lines
58 KiB
C++
2205 lines
58 KiB
C++
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <functional>
|
|
#include "../AppConfig.h"
|
|
#include "../Log.h"
|
|
#include "../states/MemoryStateFile.h"
|
|
#include "../states/RegisterStateFile.h"
|
|
#include "../FrameDump.h"
|
|
#include "../ee/INTC.h"
|
|
#include "GSHandler.h"
|
|
#include "GsPixelFormats.h"
|
|
#include "string_format.h"
|
|
#include "ThreadUtils.h"
|
|
|
|
//Shadow Hearts 2 looks for this specific value
|
|
#define GS_REVISION (7)
|
|
|
|
#define R_REG(a, v, r) \
|
|
if((a)&0x4) \
|
|
{ \
|
|
v = (uint32)(r >> 32); \
|
|
} \
|
|
else \
|
|
{ \
|
|
v = (uint32)(r & 0xFFFFFFFF); \
|
|
}
|
|
|
|
#define W_REG(a, v, r) \
|
|
if((a)&0x4) \
|
|
{ \
|
|
(r) &= 0x00000000FFFFFFFFULL; \
|
|
(r) |= (uint64)(v) << 32; \
|
|
} \
|
|
else \
|
|
{ \
|
|
(r) &= 0xFFFFFFFF00000000ULL; \
|
|
(r) |= (v); \
|
|
}
|
|
|
|
#define STATE_RAM ("gs/ram")
|
|
#define STATE_REGS ("gs/regs")
|
|
#define STATE_TRXCTX ("gs/trxcontext")
|
|
#define STATE_PRIVREGS ("gs/privregs.xml")
|
|
|
|
#define STATE_PRIVREGS_PMODE ("PMODE")
|
|
#define STATE_PRIVREGS_SMODE2 ("SMODE2")
|
|
#define STATE_PRIVREGS_DISPFB1 ("DISPFB1")
|
|
#define STATE_PRIVREGS_DISPLAY1 ("DISPLAY1")
|
|
#define STATE_PRIVREGS_DISPFB2 ("DISPFB2")
|
|
#define STATE_PRIVREGS_DISPLAY2 ("DISPLAY2")
|
|
#define STATE_PRIVREGS_CSR ("CSR")
|
|
#define STATE_PRIVREGS_IMR ("IMR")
|
|
#define STATE_PRIVREGS_BUSDIR ("BUSDIR")
|
|
#define STATE_PRIVREGS_SIGLBLID ("SIGLBLID")
|
|
#define STATE_PRIVREGS_CRTMODE ("CrtMode")
|
|
|
|
#define STATE_REG_CBP0 ("cbp0")
|
|
#define STATE_REG_CBP1 ("cbp1")
|
|
|
|
#define LOG_NAME ("gs")
|
|
|
|
CGSHandler::CGSHandler(bool gsThreaded)
|
|
: m_gsThreaded(gsThreaded)
|
|
{
|
|
RegisterPreferences();
|
|
|
|
m_presentationParams.mode = static_cast<PRESENTATION_MODE>(CAppConfig::GetInstance().GetPreferenceInteger(PREF_CGSHANDLER_PRESENTATION_MODE));
|
|
m_presentationParams.windowWidth = 512;
|
|
m_presentationParams.windowHeight = 384;
|
|
|
|
m_pRAM = new uint8[RAMSIZE];
|
|
m_pCLUT = new uint16[CLUTENTRYCOUNT];
|
|
for(int i = 0; i < MAX_INFLIGHT_FRAMES; i++)
|
|
{
|
|
m_writeBuffers[i] = new RegisterWrite[REGISTERWRITEBUFFER_SIZE];
|
|
}
|
|
|
|
for(int i = 0; i < PSM_MAX; i++)
|
|
{
|
|
m_transferWriteHandlers[i] = &CGSHandler::TransferWriteHandlerInvalid;
|
|
m_transferReadHandlers[i] = &CGSHandler::TransferReadHandlerInvalid;
|
|
}
|
|
|
|
m_transferWriteHandlers[PSMCT32] = &CGSHandler::TransferWriteHandlerGeneric<CGsPixelFormats::STORAGEPSMCT32>;
|
|
m_transferWriteHandlers[PSMCT24] = &CGSHandler::TransferWriteHandlerPSMCT24;
|
|
m_transferWriteHandlers[PSMCT16] = &CGSHandler::TransferWriteHandlerGeneric<CGsPixelFormats::STORAGEPSMCT16>;
|
|
m_transferWriteHandlers[PSMCT16S] = &CGSHandler::TransferWriteHandlerGeneric<CGsPixelFormats::STORAGEPSMCT16S>;
|
|
m_transferWriteHandlers[PSMT8] = &CGSHandler::TransferWriteHandlerGeneric<CGsPixelFormats::STORAGEPSMT8>;
|
|
m_transferWriteHandlers[PSMT4] = &CGSHandler::TransferWriteHandlerPSMT4;
|
|
m_transferWriteHandlers[PSMT8H] = &CGSHandler::TransferWriteHandlerPSMT8H;
|
|
m_transferWriteHandlers[PSMT4HL] = &CGSHandler::TransferWriteHandlerPSMT4H<24, 0x0F000000>;
|
|
m_transferWriteHandlers[PSMT4HH] = &CGSHandler::TransferWriteHandlerPSMT4H<28, 0xF0000000>;
|
|
|
|
m_transferReadHandlers[PSMCT32] = &CGSHandler::TransferReadHandlerGeneric<CGsPixelFormats::STORAGEPSMCT32>;
|
|
m_transferReadHandlers[PSMCT24] = &CGSHandler::TransferReadHandler24<CGsPixelFormats::STORAGEPSMCT32>;
|
|
m_transferReadHandlers[PSMCT16] = &CGSHandler::TransferReadHandlerGeneric<CGsPixelFormats::STORAGEPSMCT16>;
|
|
m_transferReadHandlers[PSMT8] = &CGSHandler::TransferReadHandlerGeneric<CGsPixelFormats::STORAGEPSMT8>;
|
|
m_transferReadHandlers[PSMT8H] = &CGSHandler::TransferReadHandlerPSMT8H;
|
|
m_transferReadHandlers[PSMZ32] = &CGSHandler::TransferReadHandlerGeneric<CGsPixelFormats::STORAGEPSMZ32>;
|
|
m_transferReadHandlers[PSMZ24] = &CGSHandler::TransferReadHandler24<CGsPixelFormats::STORAGEPSMZ32>;
|
|
m_transferReadHandlers[PSMZ16S] = &CGSHandler::TransferReadHandlerGeneric<CGsPixelFormats::STORAGEPSMZ16S>;
|
|
|
|
ResetBase();
|
|
|
|
if(m_gsThreaded)
|
|
{
|
|
m_thread = std::thread([&]() { ThreadProc(); });
|
|
Framework::ThreadUtils::SetThreadName(m_thread, "GS Thread");
|
|
}
|
|
}
|
|
|
|
CGSHandler::~CGSHandler()
|
|
{
|
|
if(m_gsThreaded)
|
|
{
|
|
SendGSCall([this]() { m_threadDone = true; });
|
|
m_thread.join();
|
|
}
|
|
delete[] m_pRAM;
|
|
delete[] m_pCLUT;
|
|
for(int i = 0; i < MAX_INFLIGHT_FRAMES; i++)
|
|
{
|
|
delete[] m_writeBuffers[i];
|
|
}
|
|
}
|
|
|
|
void CGSHandler::RegisterPreferences()
|
|
{
|
|
CAppConfig::GetInstance().RegisterPreferenceInteger(PREF_CGSHANDLER_PRESENTATION_MODE, CGSHandler::PRESENTATION_MODE_FIT);
|
|
CAppConfig::GetInstance().RegisterPreferenceBoolean(PREF_CGSHANDLER_GS_RAM_READS_ENABLED, true);
|
|
CAppConfig::GetInstance().RegisterPreferenceBoolean(PREF_CGSHANDLER_WIDESCREEN, false);
|
|
}
|
|
|
|
void CGSHandler::NotifyPreferencesChanged()
|
|
{
|
|
SendGSCall([this]() { NotifyPreferencesChangedImpl(); });
|
|
}
|
|
|
|
void CGSHandler::SetIntc(CINTC* intc)
|
|
{
|
|
m_intc = intc;
|
|
}
|
|
|
|
void CGSHandler::Reset()
|
|
{
|
|
ResetBase();
|
|
SendGSCall(std::bind(&CGSHandler::ResetImpl, this), true);
|
|
}
|
|
|
|
void CGSHandler::ResetBase()
|
|
{
|
|
memset(m_nReg, 0, sizeof(uint64) * 0x80);
|
|
memset(m_pRAM, 0, RAMSIZE);
|
|
memset(m_pCLUT, 0, CLUTSIZE);
|
|
memset(&m_trxCtx, 0, sizeof(m_trxCtx));
|
|
m_nPMODE = 0;
|
|
m_nSMODE2 = 0x3; // Interlacing with fullframe
|
|
m_nDISPFB1.heldValue = 0;
|
|
m_nDISPFB1.value.q = 0;
|
|
m_nDISPLAY1.heldValue = 0;
|
|
m_nDISPLAY1.value.q = 0x1BF9FF72617467;
|
|
m_nDISPFB2.heldValue = 0;
|
|
m_nDISPFB2.value.q = 0;
|
|
m_nDISPLAY2.heldValue = 0;
|
|
m_nDISPLAY2.value.q = 0x1BF9FF72617467;
|
|
m_nCSR = CSR_FIFO_EMPTY | (GS_REVISION << 16);
|
|
m_nIMR = ~0;
|
|
m_nBUSDIR = 0;
|
|
m_nSIGLBLID = 0;
|
|
m_crtMode = CRT_MODE_NTSC;
|
|
m_nCBP0 = 0;
|
|
m_nCBP1 = 0;
|
|
#ifdef _DEBUG
|
|
m_transferCount = 0;
|
|
#endif
|
|
m_framesInFlight = 0;
|
|
m_writeBufferSize = 0;
|
|
m_writeBufferProcessIndex = 0;
|
|
m_writeBufferSubmitIndex = 0;
|
|
m_writeBufferIndex = 0;
|
|
m_currentWriteBuffer = m_writeBuffers[m_writeBufferIndex];
|
|
}
|
|
|
|
void CGSHandler::ResetImpl()
|
|
{
|
|
}
|
|
|
|
void CGSHandler::NotifyPreferencesChangedImpl()
|
|
{
|
|
}
|
|
|
|
void CGSHandler::SetPresentationParams(const PRESENTATION_PARAMS& presentationParams)
|
|
{
|
|
m_presentationParams = presentationParams;
|
|
}
|
|
|
|
CGSHandler::PRESENTATION_VIEWPORT CGSHandler::GetPresentationViewport() const
|
|
{
|
|
PRESENTATION_VIEWPORT viewport;
|
|
auto sourceSize = std::make_pair(GetCrtWidth(), GetCrtHeight());
|
|
if(CAppConfig::GetInstance().GetPreferenceBoolean(PREF_CGSHANDLER_WIDESCREEN))
|
|
{
|
|
sourceSize = std::make_pair(1920, 1080);
|
|
}
|
|
switch(m_presentationParams.mode)
|
|
{
|
|
case PRESENTATION_MODE_FILL:
|
|
viewport.offsetX = 0;
|
|
viewport.offsetY = 0;
|
|
viewport.width = m_presentationParams.windowWidth;
|
|
viewport.height = m_presentationParams.windowHeight;
|
|
break;
|
|
case PRESENTATION_MODE_FIT:
|
|
{
|
|
int viewportWidth[2];
|
|
int viewportHeight[2];
|
|
{
|
|
viewportWidth[0] = m_presentationParams.windowWidth;
|
|
viewportHeight[0] = (sourceSize.first != 0) ? (m_presentationParams.windowWidth * sourceSize.second) / sourceSize.first : 0;
|
|
}
|
|
{
|
|
viewportWidth[1] = (sourceSize.second != 0) ? (m_presentationParams.windowHeight * sourceSize.first) / sourceSize.second : 0;
|
|
viewportHeight[1] = m_presentationParams.windowHeight;
|
|
}
|
|
int selectedViewport = 0;
|
|
if(
|
|
(viewportWidth[0] > static_cast<int>(m_presentationParams.windowWidth)) ||
|
|
(viewportHeight[0] > static_cast<int>(m_presentationParams.windowHeight)))
|
|
{
|
|
selectedViewport = 1;
|
|
assert(
|
|
viewportWidth[1] <= static_cast<int>(m_presentationParams.windowWidth) &&
|
|
viewportHeight[1] <= static_cast<int>(m_presentationParams.windowHeight));
|
|
}
|
|
int offsetX = static_cast<int>(m_presentationParams.windowWidth - viewportWidth[selectedViewport]) / 2;
|
|
int offsetY = static_cast<int>(m_presentationParams.windowHeight - viewportHeight[selectedViewport]) / 2;
|
|
viewport.offsetX = offsetX;
|
|
viewport.offsetY = offsetY;
|
|
viewport.width = viewportWidth[selectedViewport];
|
|
viewport.height = viewportHeight[selectedViewport];
|
|
}
|
|
break;
|
|
case PRESENTATION_MODE_ORIGINAL:
|
|
{
|
|
int offsetX = static_cast<int>(m_presentationParams.windowWidth - sourceSize.first) / 2;
|
|
int offsetY = static_cast<int>(m_presentationParams.windowHeight - sourceSize.second) / 2;
|
|
viewport.offsetX = offsetX;
|
|
viewport.offsetY = offsetY;
|
|
viewport.width = sourceSize.first;
|
|
viewport.height = sourceSize.second;
|
|
}
|
|
break;
|
|
}
|
|
return viewport;
|
|
}
|
|
|
|
void CGSHandler::SaveState(Framework::CZipArchiveWriter& archive)
|
|
{
|
|
SendGSCall([&]() { SyncMemoryCache(); }, true);
|
|
|
|
archive.InsertFile(std::make_unique<CMemoryStateFile>(STATE_RAM, GetRam(), RAMSIZE));
|
|
archive.InsertFile(std::make_unique<CMemoryStateFile>(STATE_REGS, m_nReg, sizeof(uint64) * CGSHandler::REGISTER_MAX));
|
|
archive.InsertFile(std::make_unique<CMemoryStateFile>(STATE_TRXCTX, &m_trxCtx, sizeof(TRXCONTEXT)));
|
|
|
|
{
|
|
auto registerFile = std::make_unique<CRegisterStateFile>(STATE_PRIVREGS);
|
|
|
|
registerFile->SetRegister64(STATE_PRIVREGS_PMODE, m_nPMODE);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_SMODE2, m_nSMODE2);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_DISPFB1, m_nDISPFB1.value.q);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_DISPLAY1, m_nDISPLAY1.value.q);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_DISPFB2, m_nDISPFB2.value.q);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_DISPLAY2, m_nDISPLAY2.value.q);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_CSR, m_nCSR);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_IMR, m_nIMR);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_BUSDIR, m_nBUSDIR);
|
|
registerFile->SetRegister64(STATE_PRIVREGS_SIGLBLID, m_nSIGLBLID);
|
|
registerFile->SetRegister32(STATE_PRIVREGS_CRTMODE, m_crtMode);
|
|
registerFile->SetRegister32(STATE_REG_CBP0, m_nCBP0);
|
|
registerFile->SetRegister32(STATE_REG_CBP1, m_nCBP1);
|
|
|
|
archive.InsertFile(std::move(registerFile));
|
|
}
|
|
}
|
|
|
|
void CGSHandler::LoadState(Framework::CZipArchiveReader& archive)
|
|
{
|
|
archive.BeginReadFile(STATE_RAM)->Read(GetRam(), RAMSIZE);
|
|
archive.BeginReadFile(STATE_REGS)->Read(m_nReg, sizeof(uint64) * CGSHandler::REGISTER_MAX);
|
|
archive.BeginReadFile(STATE_TRXCTX)->Read(&m_trxCtx, sizeof(TRXCONTEXT));
|
|
|
|
{
|
|
CRegisterStateFile registerFile(*archive.BeginReadFile(STATE_PRIVREGS));
|
|
m_nPMODE = registerFile.GetRegister64(STATE_PRIVREGS_PMODE);
|
|
m_nSMODE2 = registerFile.GetRegister64(STATE_PRIVREGS_SMODE2);
|
|
m_nDISPFB1.value.q = registerFile.GetRegister64(STATE_PRIVREGS_DISPFB1);
|
|
m_nDISPLAY1.value.q = registerFile.GetRegister64(STATE_PRIVREGS_DISPLAY1);
|
|
m_nDISPFB2.value.q = registerFile.GetRegister64(STATE_PRIVREGS_DISPFB2);
|
|
m_nDISPLAY2.value.q = registerFile.GetRegister64(STATE_PRIVREGS_DISPLAY2);
|
|
m_nCSR = registerFile.GetRegister64(STATE_PRIVREGS_CSR);
|
|
m_nIMR = registerFile.GetRegister64(STATE_PRIVREGS_IMR);
|
|
m_nBUSDIR = registerFile.GetRegister64(STATE_PRIVREGS_BUSDIR);
|
|
m_nSIGLBLID = registerFile.GetRegister64(STATE_PRIVREGS_SIGLBLID);
|
|
m_crtMode = static_cast<CRT_MODE>(registerFile.GetRegister32(STATE_PRIVREGS_CRTMODE));
|
|
m_nCBP0 = registerFile.GetRegister32(STATE_REG_CBP0);
|
|
m_nCBP1 = registerFile.GetRegister32(STATE_REG_CBP1);
|
|
}
|
|
|
|
SendGSCall([&]() { WriteBackMemoryCache(); });
|
|
}
|
|
|
|
void CGSHandler::Copy(CGSHandler* source)
|
|
{
|
|
source->SendGSCall([source]() { source->SyncMemoryCache(); }, true);
|
|
|
|
memcpy(GetRam(), source->GetRam(), RAMSIZE);
|
|
memcpy(m_nReg, source->m_nReg, sizeof(uint64) * CGSHandler::REGISTER_MAX);
|
|
m_trxCtx = source->m_trxCtx;
|
|
|
|
{
|
|
m_nPMODE = source->m_nPMODE;
|
|
m_nSMODE2 = source->m_nSMODE2;
|
|
m_nDISPFB1.value.q = source->m_nDISPFB1.value.q;
|
|
m_nDISPLAY1.value.q = source->m_nDISPLAY1.value.q;
|
|
m_nDISPFB2.value.q = source->m_nDISPFB2.value.q;
|
|
m_nDISPLAY2.value.q = source->m_nDISPLAY2.value.q;
|
|
m_nCSR = source->m_nCSR;
|
|
m_nIMR = source->m_nIMR;
|
|
m_nBUSDIR = source->m_nBUSDIR;
|
|
m_nSIGLBLID = source->m_nSIGLBLID;
|
|
m_crtMode = source->m_crtMode;
|
|
m_nCBP0 = source->m_nCBP0;
|
|
m_nCBP1 = source->m_nCBP1;
|
|
}
|
|
|
|
SendGSCall([&]() { WriteBackMemoryCache(); });
|
|
}
|
|
|
|
void CGSHandler::TriggerFrameDump(const FrameDumpCallback& frameDumpCallback)
|
|
{
|
|
#ifdef DEBUGGER_INCLUDED
|
|
m_mailBox.SendCall(
|
|
[=]() {
|
|
if(m_frameDumpCallback) return;
|
|
m_frameDumpCallback = frameDumpCallback;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::UpdateFrameDumpState()
|
|
{
|
|
#ifdef DEBUGGER_INCLUDED
|
|
if(m_frameDump && !m_frameDump->GetPackets().empty())
|
|
{
|
|
m_frameDumpCallback(*m_frameDump.get());
|
|
m_frameDumpCallback = FrameDumpCallback();
|
|
m_frameDump.reset();
|
|
}
|
|
else if(m_frameDumpCallback)
|
|
{
|
|
m_frameDump = std::make_unique<CFrameDump>();
|
|
|
|
//This is expected to be called from the GS thread
|
|
SyncMemoryCache();
|
|
|
|
memcpy(m_frameDump->GetInitialGsRam(), GetRam(), RAMSIZE);
|
|
memcpy(m_frameDump->GetInitialGsRegisters(), GetRegisters(), CGSHandler::REGISTER_MAX * sizeof(uint64));
|
|
m_frameDump->SetInitialSMODE2(GetSMODE2());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::InitFromFrameDump(CFrameDump* frameDump)
|
|
{
|
|
//This is expected to be called from outside the GS thread
|
|
|
|
memcpy(GetRam(), frameDump->GetInitialGsRam(), RAMSIZE);
|
|
memcpy(GetRegisters(), frameDump->GetInitialGsRegisters(), CGSHandler::REGISTER_MAX * sizeof(uint64));
|
|
SetSMODE2(frameDump->GetInitialSMODE2());
|
|
|
|
SendGSCall([&]() { WriteBackMemoryCache(); });
|
|
}
|
|
|
|
bool CGSHandler::GetDrawEnabled() const
|
|
{
|
|
return m_drawEnabled;
|
|
}
|
|
|
|
void CGSHandler::SetDrawEnabled(bool drawEnabled)
|
|
{
|
|
m_drawEnabled = drawEnabled;
|
|
}
|
|
|
|
void CGSHandler::SetHBlank()
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
m_nCSR |= CSR_HSYNC_INT;
|
|
NotifyEvent(CSR_HSYNC_INT);
|
|
}
|
|
|
|
void CGSHandler::SetVBlank()
|
|
{
|
|
{
|
|
Finish();
|
|
Flip();
|
|
}
|
|
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
m_nCSR |= CSR_VSYNC_INT;
|
|
NotifyEvent(CSR_VSYNC_INT);
|
|
}
|
|
|
|
void CGSHandler::ResetVBlank()
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
|
|
//Alternate current field
|
|
m_nCSR ^= CSR_FIELD;
|
|
}
|
|
|
|
void CGSHandler::NotifyEvent(uint32 eventBit)
|
|
{
|
|
uint32 mask = (~m_nIMR >> 8) & 0x1F;
|
|
bool hasPendingInterrupt = (eventBit & mask) != 0;
|
|
if(m_intc && hasPendingInterrupt)
|
|
{
|
|
m_intc->AssertLine(CINTC::INTC_LINE_GS);
|
|
}
|
|
}
|
|
|
|
uint32 CGSHandler::ReadPrivRegister(uint32 nAddress)
|
|
{
|
|
uint32 nData = 0;
|
|
switch(nAddress & ~0x0F)
|
|
{
|
|
case GS_CSR:
|
|
case GS_CSR_ALT:
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
R_REG(nAddress, nData, m_nCSR);
|
|
}
|
|
break;
|
|
case GS_IMR:
|
|
R_REG(nAddress, nData, m_nIMR);
|
|
break;
|
|
case GS_SIGLBLID:
|
|
R_REG(nAddress, nData, m_nSIGLBLID);
|
|
break;
|
|
default:
|
|
CLog::GetInstance().Warn(LOG_NAME, "Read an unhandled priviledged register (0x%08X).\r\n", nAddress);
|
|
nData = 0xCCCCCCCC;
|
|
break;
|
|
}
|
|
return nData;
|
|
}
|
|
|
|
void CGSHandler::WritePrivRegister(uint32 nAddress, uint32 nData)
|
|
{
|
|
switch(nAddress & ~0x0F)
|
|
{
|
|
case GS_PMODE:
|
|
W_REG(nAddress, nData, m_nPMODE);
|
|
break;
|
|
case GS_SMODE2:
|
|
W_REG(nAddress, nData, m_nSMODE2);
|
|
break;
|
|
case GS_DISPFB1:
|
|
WriteToDelayedRegister(nAddress, nData, m_nDISPFB1);
|
|
break;
|
|
case GS_DISPLAY1:
|
|
WriteToDelayedRegister(nAddress, nData, m_nDISPLAY1);
|
|
break;
|
|
case GS_DISPFB2:
|
|
WriteToDelayedRegister(nAddress, nData, m_nDISPFB2);
|
|
break;
|
|
case GS_DISPLAY2:
|
|
WriteToDelayedRegister(nAddress, nData, m_nDISPLAY2);
|
|
break;
|
|
case GS_CSR:
|
|
case GS_CSR_ALT:
|
|
{
|
|
if(!(nAddress & 0x04))
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
if(nData & CSR_SIGNAL_EVENT)
|
|
{
|
|
m_nCSR &= ~CSR_SIGNAL_EVENT;
|
|
}
|
|
if(nData & CSR_FINISH_EVENT)
|
|
{
|
|
m_nCSR &= ~CSR_FINISH_EVENT;
|
|
}
|
|
if(nData & CSR_HSYNC_INT)
|
|
{
|
|
m_nCSR &= ~CSR_HSYNC_INT;
|
|
}
|
|
if(nData & CSR_VSYNC_INT)
|
|
{
|
|
m_nCSR &= ~CSR_VSYNC_INT;
|
|
}
|
|
if(nData & CSR_RESET)
|
|
{
|
|
//TODO: Reset other registers
|
|
m_nPMODE = 0;
|
|
m_nDISPFB1.heldValue = m_nDISPFB1.value.q = 0;
|
|
m_nDISPFB2.heldValue = m_nDISPFB2.value.q = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case GS_IMR:
|
|
W_REG(nAddress, nData, m_nIMR);
|
|
if(!(nAddress & 0x04))
|
|
{
|
|
//Some games (Soul Calibur 2, Crash Nitro Kart) will unmask interrupts
|
|
//right after starting a transfer that sets a SIGNAL... Let the
|
|
//interrupt go through even though it's not supposed to work that way
|
|
NotifyEvent(m_nCSR & 0x1F);
|
|
}
|
|
break;
|
|
case GS_BUSDIR:
|
|
W_REG(nAddress, nData, m_nBUSDIR);
|
|
break;
|
|
case GS_SIGLBLID:
|
|
W_REG(nAddress, nData, m_nSIGLBLID);
|
|
break;
|
|
default:
|
|
CLog::GetInstance().Warn(LOG_NAME, "Wrote to an unhandled priviledged register (0x%08X, 0x%08X).\r\n", nAddress, nData);
|
|
break;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if(nAddress & 0x04)
|
|
{
|
|
LogPrivateWrite(nAddress);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::Initialize()
|
|
{
|
|
SendGSCall(std::bind(&CGSHandler::InitializeImpl, this), true);
|
|
}
|
|
|
|
void CGSHandler::Release()
|
|
{
|
|
SendGSCall(std::bind(&CGSHandler::ReleaseImpl, this), true);
|
|
}
|
|
|
|
void CGSHandler::Finish(bool forceWait)
|
|
{
|
|
FlushWriteBuffer();
|
|
SendGSCall(std::bind(&CGSHandler::MarkNewFrame, this));
|
|
bool wait = (++m_framesInFlight == MAX_INFLIGHT_FRAMES);
|
|
wait |= forceWait;
|
|
SendGSCall(
|
|
[this]() {
|
|
assert(m_framesInFlight != 0);
|
|
m_framesInFlight--;
|
|
},
|
|
wait, wait);
|
|
}
|
|
|
|
void CGSHandler::Flip(uint32 flags)
|
|
{
|
|
bool waitForCompletion = (flags & FLIP_FLAG_WAIT) != 0;
|
|
bool force = (flags & FLIP_FLAG_FORCE) != 0;
|
|
SendGSCall(
|
|
[this, displayInfo = GetCurrentDisplayInfo(), force]() {
|
|
if(force || m_regsDirty)
|
|
{
|
|
FlipImpl(displayInfo);
|
|
}
|
|
m_regsDirty = false;
|
|
},
|
|
waitForCompletion, waitForCompletion);
|
|
}
|
|
|
|
void CGSHandler::FlipImpl(const DISPLAY_INFO&)
|
|
{
|
|
OnFlipComplete();
|
|
m_flipped = true;
|
|
}
|
|
|
|
void CGSHandler::MarkNewFrame()
|
|
{
|
|
OnNewFrame(m_drawCallCount);
|
|
m_drawCallCount = 0;
|
|
UpdateFrameDumpState();
|
|
#ifdef _DEBUG
|
|
CLog::GetInstance().Print(LOG_NAME, "Frame Done.\r\n---------------------------------------------------------------------------------\r\n");
|
|
#endif
|
|
}
|
|
|
|
uint8* CGSHandler::GetRam() const
|
|
{
|
|
return m_pRAM;
|
|
}
|
|
|
|
uint64* CGSHandler::GetRegisters()
|
|
{
|
|
return m_nReg;
|
|
}
|
|
|
|
uint64 CGSHandler::GetSMODE2() const
|
|
{
|
|
return m_nSMODE2;
|
|
}
|
|
|
|
void CGSHandler::SetSMODE2(uint64 value)
|
|
{
|
|
m_nSMODE2 = value;
|
|
}
|
|
|
|
uint64 CGSHandler::GetBUSDIR() const
|
|
{
|
|
return m_nBUSDIR;
|
|
}
|
|
|
|
void CGSHandler::FeedImageData(const void* data, uint32 length)
|
|
{
|
|
assert(m_writeBufferProcessIndex == m_writeBufferSize);
|
|
SubmitWriteBuffer();
|
|
|
|
#ifdef _DEBUG
|
|
m_transferCount++;
|
|
#endif
|
|
|
|
//Allocate 0x10 more bytes to allow transfer handlers
|
|
//to read beyond the actual length of the buffer (ie.: PSMCT24)
|
|
|
|
std::vector<uint8> imageData(length + 0x10);
|
|
memcpy(imageData.data(), data, length);
|
|
SendGSCall(
|
|
[this, imageData = std::move(imageData), length]() {
|
|
#ifdef DEBUGGER_INCLUDED
|
|
if(m_frameDump)
|
|
{
|
|
m_frameDump->AddImagePacket(imageData.data(), length);
|
|
}
|
|
#endif
|
|
FeedImageDataImpl(imageData.data(), length);
|
|
});
|
|
}
|
|
|
|
void CGSHandler::ReadImageData(void* data, uint32 length)
|
|
{
|
|
assert(m_writeBufferProcessIndex == m_writeBufferSize);
|
|
SubmitWriteBuffer();
|
|
SendGSCall([this, data, length]() { ReadImageDataImpl(data, length); }, true);
|
|
}
|
|
|
|
void CGSHandler::ProcessWriteBuffer(const CGsPacketMetadata* metadata)
|
|
{
|
|
assert(m_writeBufferProcessIndex <= m_writeBufferSize);
|
|
assert(m_writeBufferSubmitIndex <= m_writeBufferProcessIndex);
|
|
#ifdef DEBUGGER_INCLUDED
|
|
if(uint32 packetSize = m_writeBufferSize - m_writeBufferProcessIndex; packetSize != 0)
|
|
{
|
|
SendGSCall(
|
|
[this,
|
|
packet = m_currentWriteBuffer + m_writeBufferProcessIndex,
|
|
packetSize,
|
|
metadata = metadata ? *metadata : CGsPacketMetadata()]() {
|
|
if(m_frameDump)
|
|
{
|
|
m_frameDump->AddRegisterPacket(packet, packetSize, &metadata);
|
|
}
|
|
});
|
|
}
|
|
#endif
|
|
for(uint32 writeIndex = m_writeBufferProcessIndex; writeIndex < m_writeBufferSize; writeIndex++)
|
|
{
|
|
const auto& write = m_currentWriteBuffer[writeIndex];
|
|
switch(write.first)
|
|
{
|
|
case GS_REG_SIGNAL:
|
|
{
|
|
auto signal = make_convertible<SIGNAL>(write.second);
|
|
auto siglblid = make_convertible<SIGLBLID>(m_nSIGLBLID);
|
|
siglblid.sigid &= ~signal.idmsk;
|
|
siglblid.sigid |= signal.id;
|
|
m_nSIGLBLID = siglblid;
|
|
assert((m_nCSR & CSR_SIGNAL_EVENT) == 0);
|
|
m_nCSR |= CSR_SIGNAL_EVENT;
|
|
NotifyEvent(CSR_SIGNAL_EVENT);
|
|
}
|
|
break;
|
|
case GS_REG_FINISH:
|
|
m_nCSR |= CSR_FINISH_EVENT;
|
|
NotifyEvent(CSR_FINISH_EVENT);
|
|
break;
|
|
case GS_REG_LABEL:
|
|
{
|
|
auto label = make_convertible<LABEL>(write.second);
|
|
auto siglblid = make_convertible<SIGLBLID>(m_nSIGLBLID);
|
|
siglblid.lblid &= ~label.idmsk;
|
|
siglblid.lblid |= label.id;
|
|
m_nSIGLBLID = siglblid;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
m_writeBufferProcessIndex = m_writeBufferSize;
|
|
uint32 submitPending = m_writeBufferProcessIndex - m_writeBufferSubmitIndex;
|
|
if(submitPending >= REGISTERWRITEBUFFER_SUBMIT_THRESHOLD)
|
|
{
|
|
SubmitWriteBuffer();
|
|
}
|
|
}
|
|
|
|
void CGSHandler::SubmitWriteBuffer()
|
|
{
|
|
assert(m_writeBufferSubmitIndex <= m_writeBufferSize);
|
|
if(m_writeBufferSubmitIndex == m_writeBufferSize) return;
|
|
|
|
#ifdef _DEBUG
|
|
m_transferCount++;
|
|
#endif
|
|
|
|
auto bufferStart = m_currentWriteBuffer + m_writeBufferSubmitIndex;
|
|
auto bufferEnd = m_currentWriteBuffer + m_writeBufferSize;
|
|
|
|
SendGSCall(
|
|
[this, bufferStart, bufferEnd]() {
|
|
SubmitWriteBufferImpl(bufferStart, bufferEnd);
|
|
});
|
|
|
|
m_writeBufferSubmitIndex = m_writeBufferSize;
|
|
}
|
|
|
|
void CGSHandler::FlushWriteBuffer()
|
|
{
|
|
//Everything should be processed at this point
|
|
assert(m_writeBufferProcessIndex == m_writeBufferSize);
|
|
//Make sure everything is submitted
|
|
SubmitWriteBuffer();
|
|
m_writeBufferSize = 0;
|
|
m_writeBufferProcessIndex = 0;
|
|
m_writeBufferSubmitIndex = 0;
|
|
m_writeBufferIndex++;
|
|
m_writeBufferIndex %= MAX_INFLIGHT_FRAMES;
|
|
m_currentWriteBuffer = m_writeBuffers[m_writeBufferIndex];
|
|
//Nothing should be written to the buffer after that
|
|
}
|
|
|
|
void CGSHandler::WriteRegisterImpl(uint8 nRegister, uint64 nData)
|
|
{
|
|
nRegister &= REGISTER_MAX - 1;
|
|
m_nReg[nRegister] = nData;
|
|
m_regsDirty = true;
|
|
|
|
switch(nRegister)
|
|
{
|
|
case GS_REG_TEX0_1:
|
|
case GS_REG_TEX0_2:
|
|
{
|
|
unsigned int nContext = nRegister - GS_REG_TEX0_1;
|
|
assert(nContext == 0 || nContext == 1);
|
|
auto tex0 = make_convertible<TEX0>(m_nReg[nRegister]);
|
|
SyncCLUT(tex0);
|
|
|
|
auto tex1 = make_convertible<TEX1>(m_nReg[GS_REG_TEX1_1 + nContext]);
|
|
if(tex1.nMipBaseAddr)
|
|
{
|
|
uint32 bufAddr = tex0.GetBufPtr();
|
|
uint32 bufWidth = tex0.nBufWidth;
|
|
uint32 width = tex0.GetWidth();
|
|
uint32 height = tex0.GetHeight();
|
|
uint32 pixelSize = CGsPixelFormats::GetPsmPixelSize(tex0.nPsm);
|
|
uint64 miptbp[3] = {};
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
uint32 levelSize = (width * height * pixelSize) / 8;
|
|
bufAddr = bufAddr + levelSize;
|
|
bufWidth = std::max(bufWidth >> 1, 1U);
|
|
width = std::max(width >> 1, 1U);
|
|
height = std::max(width >> 1, 1U);
|
|
uint32 tbp = (bufAddr / 256) & 0x3FFF;
|
|
uint32 tbw = bufWidth & 0x3F;
|
|
miptbp[i] = tbp | (tbw << 14);
|
|
}
|
|
m_nReg[GS_REG_MIPTBP1_1 + nContext] = miptbp[0] | (miptbp[1] << 20) | (miptbp[2] << 40);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GS_REG_TEX2_1:
|
|
case GS_REG_TEX2_2:
|
|
{
|
|
//Update TEX0
|
|
unsigned int nContext = nRegister - GS_REG_TEX2_1;
|
|
assert(nContext == 0 || nContext == 1);
|
|
|
|
const uint64 nMask = 0xFFFFFFE003F00000ULL;
|
|
m_nReg[GS_REG_TEX0_1 + nContext] &= ~nMask;
|
|
m_nReg[GS_REG_TEX0_1 + nContext] |= nData & nMask;
|
|
|
|
auto tex0 = make_convertible<TEX0>(m_nReg[GS_REG_TEX0_1 + nContext]);
|
|
SyncCLUT(tex0);
|
|
}
|
|
break;
|
|
|
|
case GS_REG_TRXDIR:
|
|
BeginTransfer();
|
|
break;
|
|
|
|
case GS_REG_HWREG:
|
|
#ifdef _DEBUG
|
|
m_transferCount++;
|
|
#endif
|
|
FeedImageDataImpl(reinterpret_cast<const uint8*>(&nData), 8);
|
|
break;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
LogWrite(nRegister, nData);
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::FeedImageDataImpl(const uint8* imageData, uint32 length)
|
|
{
|
|
if(m_trxCtx.nSize == 0)
|
|
{
|
|
#ifdef _DEBUG
|
|
CLog::GetInstance().Warn(LOG_NAME, "Warning. Received image data when no transfer was expected.\r\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if(m_trxCtx.nSize < length)
|
|
{
|
|
length = m_trxCtx.nSize;
|
|
}
|
|
|
|
TransferWrite(imageData, length);
|
|
m_trxCtx.nSize -= length;
|
|
|
|
if(m_trxCtx.nSize == 0)
|
|
{
|
|
ProcessHostToLocalTransfer();
|
|
|
|
#ifdef _DEBUG
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
CLog::GetInstance().Print(LOG_NAME, "Completed image transfer at 0x%08X (dirty = %d).\r\n", bltBuf.GetDstPtr(), m_trxCtx.nDirty);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
assert(m_transferCount != 0);
|
|
m_transferCount--;
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::ReadImageDataImpl(void* ptr, uint32 size)
|
|
{
|
|
assert(m_trxCtx.nSize != 0);
|
|
assert(m_trxCtx.nSize == size);
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
FRAMEWORK_MAYBE_UNUSED auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
|
|
assert(trxPos.nDIR == 0);
|
|
((this)->*(m_transferReadHandlers[bltBuf.nSrcPsm]))(ptr, size);
|
|
}
|
|
|
|
void CGSHandler::SubmitWriteBufferImpl(const RegisterWrite* writeStart, const RegisterWrite* writeEnd)
|
|
{
|
|
for(auto write = writeStart; write != writeEnd; write++)
|
|
{
|
|
WriteRegisterImpl(write->first, write->second);
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
assert(m_transferCount != 0);
|
|
m_transferCount--;
|
|
#endif
|
|
}
|
|
|
|
void CGSHandler::BeginTransfer()
|
|
{
|
|
uint32 trxDir = m_nReg[GS_REG_TRXDIR] & 0x03;
|
|
if(trxDir == 0 || trxDir == 1)
|
|
{
|
|
//"Host to Local" or "Local to Host"
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto psm = (trxDir == 0) ? bltBuf.nDstPsm : bltBuf.nSrcPsm;
|
|
unsigned int nPixelSize = 0;
|
|
|
|
//We need to figure out the pixel size of the stream
|
|
switch(psm)
|
|
{
|
|
case PSMCT32:
|
|
case PSMZ32:
|
|
nPixelSize = 32;
|
|
break;
|
|
case PSMCT24:
|
|
case PSMZ24:
|
|
nPixelSize = 24;
|
|
break;
|
|
case PSMCT16:
|
|
case PSMCT16S:
|
|
case PSMZ16S:
|
|
nPixelSize = 16;
|
|
break;
|
|
case PSMT8:
|
|
case PSMT8H:
|
|
nPixelSize = 8;
|
|
break;
|
|
case PSMT4:
|
|
case PSMT4HH:
|
|
case PSMT4HL:
|
|
nPixelSize = 4;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
//Make sure transfer size is a multiple of 16. Some games (ex.: Gregory Horror Show)
|
|
//specify transfer width/height that will give a transfer size that is not a multiple of 16
|
|
//and will prevent proper handling of image transfer (texture cache will not be invalidated).
|
|
//We also need to make sure transfer size is rounded up to the next quadword boundary
|
|
uint32 trxBitCount = ((trxReg.nRRW * trxReg.nRRH * nPixelSize) + 0x7F) & ~0x7F;
|
|
m_trxCtx.nSize = trxBitCount / 8;
|
|
m_trxCtx.nRealSize = m_trxCtx.nSize;
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY = 0;
|
|
|
|
if(trxDir == 0)
|
|
{
|
|
BeginTransferWrite();
|
|
CLog::GetInstance().Print(LOG_NAME, "Starting transfer to 0x%08X, buffer size %d, psm: %d, size (%dx%d)\r\n",
|
|
bltBuf.GetDstPtr(), bltBuf.GetDstWidth(), bltBuf.nDstPsm, trxReg.nRRW, trxReg.nRRH);
|
|
}
|
|
else if(trxDir == 1)
|
|
{
|
|
ProcessLocalToHostTransfer();
|
|
CLog::GetInstance().Print(LOG_NAME, "Starting transfer from 0x%08X, buffer size %d, psm: %d, size (%dx%d)\r\n",
|
|
bltBuf.GetSrcPtr(), bltBuf.GetSrcWidth(), bltBuf.nSrcPsm, trxReg.nRRW, trxReg.nRRH);
|
|
}
|
|
}
|
|
else if(trxDir == 2)
|
|
{
|
|
//Local to Local
|
|
ProcessLocalToLocalTransfer();
|
|
}
|
|
}
|
|
|
|
void CGSHandler::BeginTransferWrite()
|
|
{
|
|
m_trxCtx.nDirty = false;
|
|
}
|
|
|
|
void CGSHandler::TransferWrite(const uint8* imageData, uint32 length)
|
|
{
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
m_trxCtx.nDirty |= ((this)->*(m_transferWriteHandlers[bltBuf.nDstPsm]))(imageData, length);
|
|
}
|
|
|
|
bool CGSHandler::TransferWriteHandlerInvalid(const void* pData, uint32 nLength)
|
|
{
|
|
assert(0);
|
|
return false;
|
|
}
|
|
|
|
template <typename Storage>
|
|
bool CGSHandler::TransferWriteHandlerGeneric(const void* pData, uint32 nLength)
|
|
{
|
|
bool nDirty = false;
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
nLength /= sizeof(typename Storage::Unit);
|
|
|
|
CGsPixelFormats::CPixelIndexor<Storage> Indexor(m_pRAM, trxBuf.GetDstPtr(), trxBuf.nDstWidth);
|
|
|
|
auto pSrc = reinterpret_cast<const typename Storage::Unit*>(pData);
|
|
|
|
for(unsigned int i = 0; i < nLength; i++)
|
|
{
|
|
uint32 nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
uint32 nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
auto pPixel = Indexor.GetPixelAddress(nX, nY);
|
|
|
|
if((*pPixel) != pSrc[i])
|
|
{
|
|
(*pPixel) = pSrc[i];
|
|
nDirty = true;
|
|
}
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
|
|
return nDirty;
|
|
}
|
|
|
|
bool CGSHandler::TransferWriteHandlerPSMCT24(const void* pData, uint32 nLength)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 Indexor(m_pRAM, trxBuf.GetDstPtr(), trxBuf.nDstWidth);
|
|
|
|
auto pSrc = reinterpret_cast<const uint8*>(pData);
|
|
|
|
for(unsigned int i = 0; i < nLength; i += 3)
|
|
{
|
|
uint32 nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
uint32 nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
uint32* pDstPixel = Indexor.GetPixelAddress(nX, nY);
|
|
uint32 nSrcPixel = *reinterpret_cast<const uint32*>(&pSrc[i]) & 0x00FFFFFF;
|
|
(*pDstPixel) &= 0xFF000000;
|
|
(*pDstPixel) |= nSrcPixel;
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGSHandler::TransferWriteHandlerPSMT4(const void* pData, uint32 nLength)
|
|
{
|
|
bool dirty = false;
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMT4 Indexor(m_pRAM, trxBuf.GetDstPtr(), trxBuf.nDstWidth);
|
|
|
|
auto pSrc = reinterpret_cast<const uint8*>(pData);
|
|
|
|
for(unsigned int i = 0; i < nLength; i++)
|
|
{
|
|
uint8 nPixel[2];
|
|
|
|
nPixel[0] = (pSrc[i] >> 0) & 0x0F;
|
|
nPixel[1] = (pSrc[i] >> 4) & 0x0F;
|
|
|
|
for(unsigned int j = 0; j < 2; j++)
|
|
{
|
|
uint32 nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
uint32 nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
uint8 currentPixel = Indexor.GetPixel(nX, nY);
|
|
if(currentPixel != nPixel[j])
|
|
{
|
|
Indexor.SetPixel(nX, nY, nPixel[j]);
|
|
dirty = true;
|
|
}
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return dirty;
|
|
}
|
|
|
|
template <uint32 nShift, uint32 nMask>
|
|
bool CGSHandler::TransferWriteHandlerPSMT4H(const void* pData, uint32 nLength)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 Indexor(m_pRAM, trxBuf.GetDstPtr(), trxBuf.nDstWidth);
|
|
|
|
auto pSrc = reinterpret_cast<const uint8*>(pData);
|
|
|
|
for(unsigned int i = 0; i < nLength; i++)
|
|
{
|
|
//Pixel 1
|
|
uint32 nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
uint32 nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
uint8 nSrcPixel = pSrc[i] & 0x0F;
|
|
|
|
uint32* pDstPixel = Indexor.GetPixelAddress(nX, nY);
|
|
(*pDstPixel) &= ~nMask;
|
|
(*pDstPixel) |= (nSrcPixel << nShift);
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
|
|
//Pixel 2
|
|
nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
nSrcPixel = (pSrc[i] & 0xF0);
|
|
|
|
pDstPixel = Indexor.GetPixelAddress(nX, nY);
|
|
(*pDstPixel) &= ~nMask;
|
|
(*pDstPixel) |= (nSrcPixel << (nShift - 4));
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGSHandler::TransferWriteHandlerPSMT8H(const void* pData, uint32 nLength)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 Indexor(m_pRAM, trxBuf.GetDstPtr(), trxBuf.nDstWidth);
|
|
|
|
auto pSrc = reinterpret_cast<const uint8*>(pData);
|
|
|
|
for(unsigned int i = 0; i < nLength; i++)
|
|
{
|
|
uint32 nX = (m_trxCtx.nRRX + trxPos.nDSAX) % 2048;
|
|
uint32 nY = (m_trxCtx.nRRY + trxPos.nDSAY) % 2048;
|
|
|
|
uint8 nSrcPixel = pSrc[i];
|
|
|
|
uint32* pDstPixel = Indexor.GetPixelAddress(nX, nY);
|
|
(*pDstPixel) &= ~0xFF000000;
|
|
(*pDstPixel) |= (nSrcPixel << 24);
|
|
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CGSHandler::TransferReadHandlerInvalid(void*, uint32)
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
template <typename Storage>
|
|
void CGSHandler::TransferReadHandlerGeneric(void* buffer, uint32 length)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
uint32 typedLength = length / sizeof(typename Storage::Unit);
|
|
auto typedBuffer = reinterpret_cast<typename Storage::Unit*>(buffer);
|
|
|
|
CGsPixelFormats::CPixelIndexor<Storage> indexor(GetRam(), trxBuf.GetSrcPtr(), trxBuf.nSrcWidth);
|
|
for(uint32 i = 0; i < typedLength; i++)
|
|
{
|
|
uint32 x = (m_trxCtx.nRRX + trxPos.nSSAX) % 2048;
|
|
uint32 y = (m_trxCtx.nRRY + trxPos.nSSAY) % 2048;
|
|
auto pixel = indexor.GetPixel(x, y);
|
|
typedBuffer[i] = pixel;
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Storage>
|
|
void CGSHandler::TransferReadHandler24(void* buffer, uint32 length)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
auto dst = reinterpret_cast<uint8*>(buffer);
|
|
|
|
CGsPixelFormats::CPixelIndexor<Storage> indexor(GetRam(), trxBuf.GetSrcPtr(), trxBuf.nSrcWidth);
|
|
for(uint32 i = 0; i < length; i += 3)
|
|
{
|
|
uint32 x = (m_trxCtx.nRRX + trxPos.nSSAX) % 2048;
|
|
uint32 y = (m_trxCtx.nRRY + trxPos.nSSAY) % 2048;
|
|
auto pixel = indexor.GetPixel(x, y);
|
|
dst[i + 0] = (pixel >> 0) & 0xFF;
|
|
dst[i + 1] = (pixel >> 8) & 0xFF;
|
|
dst[i + 2] = (pixel >> 16) & 0xFF;
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGSHandler::TransferReadHandlerPSMT8H(void* buffer, uint32 length)
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
|
|
auto dst = reinterpret_cast<uint8*>(buffer);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 indexor(GetRam(), trxBuf.GetSrcPtr(), trxBuf.nSrcWidth);
|
|
for(uint32 i = 0; i < length; i++)
|
|
{
|
|
uint32 x = (m_trxCtx.nRRX + trxPos.nSSAX) % 2048;
|
|
uint32 y = (m_trxCtx.nRRY + trxPos.nSSAY) % 2048;
|
|
auto pixel = indexor.GetPixel(x, y);
|
|
dst[i] = static_cast<uint8>(pixel >> 24);
|
|
m_trxCtx.nRRX++;
|
|
if(m_trxCtx.nRRX == trxReg.nRRW)
|
|
{
|
|
m_trxCtx.nRRX = 0;
|
|
m_trxCtx.nRRY++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGSHandler::SetCrt(bool nIsInterlaced, unsigned int nMode, bool nIsFrameMode)
|
|
{
|
|
m_crtMode = static_cast<CRT_MODE>(nMode);
|
|
|
|
SMODE2 smode2;
|
|
smode2 <<= 0;
|
|
smode2.interlaced = nIsInterlaced ? 1 : 0;
|
|
smode2.ffmd = nIsFrameMode ? 1 : 0;
|
|
m_nSMODE2 = smode2;
|
|
}
|
|
|
|
unsigned int CGSHandler::GetCrtWidth() const
|
|
{
|
|
switch(m_crtMode)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case CRT_MODE_NTSC:
|
|
case CRT_MODE_PAL:
|
|
case CRT_MODE_VGA_640_75:
|
|
return 640;
|
|
}
|
|
}
|
|
|
|
unsigned int CGSHandler::GetCrtHeight() const
|
|
{
|
|
switch(m_crtMode)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case CRT_MODE_NTSC:
|
|
return 448;
|
|
case CRT_MODE_PAL:
|
|
return 512;
|
|
case CRT_MODE_VGA_640_75:
|
|
return 480;
|
|
}
|
|
}
|
|
|
|
uint32 CGSHandler::GetCrtFrameRate() const
|
|
{
|
|
switch(m_crtMode)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case CRT_MODE_NTSC:
|
|
return 60;
|
|
case CRT_MODE_PAL:
|
|
return 50;
|
|
}
|
|
}
|
|
|
|
uint32 CGSHandler::GetCrtHSyncFrequency() const
|
|
{
|
|
switch(m_crtMode)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case CRT_MODE_NTSC:
|
|
return PS2::GS_NTSC_HSYNC_FREQ;
|
|
case CRT_MODE_PAL:
|
|
return PS2::GS_PAL_HSYNC_FREQ;
|
|
}
|
|
}
|
|
|
|
bool CGSHandler::GetCrtIsInterlaced() const
|
|
{
|
|
SMODE2 smode2;
|
|
smode2 <<= m_nSMODE2;
|
|
return smode2.interlaced;
|
|
}
|
|
|
|
bool CGSHandler::GetCrtIsFrameMode() const
|
|
{
|
|
SMODE2 smode2;
|
|
smode2 <<= m_nSMODE2;
|
|
return smode2.ffmd;
|
|
}
|
|
|
|
CGSHandler::DISPLAY_RECT CGSHandler::GetDisplayRect(uint64 displayReg) const
|
|
{
|
|
auto d = make_convertible<DISPLAY>(displayReg);
|
|
|
|
uint32 dispX = d.nX / (d.nMagX + 1);
|
|
uint32 dispY = d.nY / (d.nMagY + 1);
|
|
uint32 dispWidth = (d.nW + 1) / (d.nMagX + 1);
|
|
uint32 dispHeight = (d.nH + 1) / (d.nMagY + 1);
|
|
|
|
//Some games provide a huge height but seem to only need a fraction
|
|
//of what they need:
|
|
//- Silent Hill 3
|
|
//- Metal Slug 4
|
|
//- Shooting Love: Trizeal
|
|
//- And many others...
|
|
//Does the PCRTC clamp the values?
|
|
if(dispHeight > 640)
|
|
{
|
|
dispHeight /= 2;
|
|
}
|
|
|
|
bool halfHeight = GetCrtIsInterlaced() && GetCrtIsFrameMode();
|
|
if(halfHeight)
|
|
{
|
|
dispY /= 2;
|
|
dispHeight /= 2;
|
|
}
|
|
|
|
return DISPLAY_RECT{dispX, dispY, dispWidth, dispHeight};
|
|
}
|
|
|
|
CGSHandler::DISPLAY_INFO CGSHandler::GetCurrentDisplayInfo()
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
auto makeInfoFromRc = [this](unsigned int readCircuit) {
|
|
assert(readCircuit < 2);
|
|
auto dispFb = make_convertible<DISPFB>((readCircuit == 0) ? m_nDISPFB1.value.q : m_nDISPFB2.value.q);
|
|
auto dispRect = GetDisplayRect((readCircuit == 0) ? m_nDISPLAY1.value.q : m_nDISPLAY2.value.q);
|
|
|
|
DISPLAY_INFO info;
|
|
info.width = dispRect.width;
|
|
info.height = dispRect.height;
|
|
|
|
{
|
|
auto& layer = info.layers[0];
|
|
layer.enabled = true;
|
|
layer.width = dispRect.width;
|
|
layer.height = dispRect.height;
|
|
layer.bufPtr = dispFb.GetBufPtr();
|
|
layer.bufWidth = dispFb.GetBufWidth();
|
|
layer.psm = dispFb.nPSM;
|
|
}
|
|
|
|
return info;
|
|
};
|
|
uint32 rcMode = m_nPMODE & 0x03;
|
|
switch(rcMode)
|
|
{
|
|
default:
|
|
case 0:
|
|
//No read circuit enabled
|
|
return DISPLAY_INFO{};
|
|
case 1:
|
|
case 2:
|
|
return makeInfoFromRc((rcMode == 1) ? 0 : 1);
|
|
case 3:
|
|
{
|
|
//Both read circuits are enabled... See if we can find if there's only a single one being actually used.
|
|
//This happens in Capcom Classics Collection Vol. 2
|
|
{
|
|
bool fb1Null = (m_nDISPFB1.value.q == 0);
|
|
bool fb2Null = (m_nDISPFB2.value.q == 0);
|
|
if(!fb1Null && fb2Null)
|
|
{
|
|
return makeInfoFromRc(0);
|
|
}
|
|
if(fb1Null && !fb2Null)
|
|
{
|
|
return makeInfoFromRc(1);
|
|
}
|
|
}
|
|
|
|
auto pmode = make_convertible<PMODE>(m_nPMODE);
|
|
assert((pmode.mmod == 0) || (pmode.alp >= 0x7F));
|
|
assert(pmode.slbg == 0);
|
|
|
|
auto dispFb1 = make_convertible<DISPFB>(m_nDISPFB1.value.q);
|
|
auto dispFb2 = make_convertible<DISPFB>(m_nDISPFB2.value.q);
|
|
auto dispRect1 = GetDisplayRect(m_nDISPLAY1.value.q);
|
|
auto dispRect2 = GetDisplayRect(m_nDISPLAY2.value.q);
|
|
|
|
//Some games display the same thing but with a 1 pixel difference in their Y offsets.
|
|
//Avoid having to render the same thing twice.
|
|
//Example games:
|
|
//- Ys: The Ark of Napishtim
|
|
//- Dragon Quest 8
|
|
if(
|
|
(dispFb1.GetBufPtr() == dispFb2.GetBufPtr()) &&
|
|
(abs(static_cast<int>(dispRect1.offsetY - dispRect2.offsetY)) <= 1))
|
|
{
|
|
return makeInfoFromRc(0);
|
|
}
|
|
|
|
uint32 dispBaseX = std::min<uint32>(dispRect1.offsetX, dispRect2.offsetX);
|
|
uint32 dispBaseY = std::min<uint32>(dispRect1.offsetY, dispRect2.offsetY);
|
|
|
|
DISPLAY_INFO info;
|
|
info.width = std::max<uint32>(dispRect1.width, dispRect2.width);
|
|
info.height = std::max<uint32>(dispRect1.height, dispRect2.height);
|
|
|
|
{
|
|
auto& layer = info.layers[0];
|
|
layer.enabled = true;
|
|
layer.width = dispRect2.width;
|
|
layer.height = dispRect2.height;
|
|
layer.offsetX = dispRect2.offsetX - dispBaseX;
|
|
layer.offsetY = dispRect2.offsetY - dispBaseY;
|
|
layer.bufPtr = dispFb2.GetBufPtr();
|
|
layer.bufWidth = dispFb2.GetBufWidth();
|
|
layer.psm = dispFb2.nPSM;
|
|
}
|
|
|
|
{
|
|
auto& layer = info.layers[1];
|
|
layer.enabled = true;
|
|
layer.width = dispRect1.width;
|
|
layer.height = dispRect1.height;
|
|
layer.offsetX = dispRect1.offsetX - dispBaseX;
|
|
layer.offsetY = dispRect1.offsetY - dispBaseY;
|
|
layer.bufPtr = dispFb1.GetBufPtr();
|
|
layer.bufWidth = dispFb1.GetBufWidth();
|
|
layer.psm = dispFb1.nPSM;
|
|
layer.useConstantAlpha = (pmode.mmod != 0);
|
|
layer.constantAlpha = pmode.alp;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGSHandler::SyncCLUT(const TEX0& tex0)
|
|
{
|
|
if(!ProcessCLD(tex0)) return;
|
|
|
|
//assert(IsPsmIDTEX(tex0.nPsm));
|
|
switch(tex0.nPsm)
|
|
{
|
|
case PSMT8:
|
|
case PSMT8H:
|
|
ReadCLUT8(tex0);
|
|
break;
|
|
case PSMT4:
|
|
case PSMT4HH:
|
|
case PSMT4HL:
|
|
ReadCLUT4(tex0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CGSHandler::ProcessCLD(const TEX0& tex0)
|
|
{
|
|
switch(tex0.nCLD)
|
|
{
|
|
case 0:
|
|
//No changes to CLUT
|
|
return false;
|
|
default:
|
|
assert(false);
|
|
case 1:
|
|
return true;
|
|
case 2:
|
|
m_nCBP0 = tex0.nCBP;
|
|
return true;
|
|
case 3:
|
|
m_nCBP1 = tex0.nCBP;
|
|
return true;
|
|
case 4:
|
|
if(m_nCBP0 == tex0.nCBP) return false;
|
|
m_nCBP0 = tex0.nCBP;
|
|
return true;
|
|
case 5:
|
|
if(m_nCBP1 == tex0.nCBP) return false;
|
|
m_nCBP1 = tex0.nCBP;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
template <typename Indexor>
|
|
bool CGSHandler::ReadCLUT4_16(const TEX0& tex0)
|
|
{
|
|
bool changed = false;
|
|
|
|
assert(tex0.nCSA < 32);
|
|
|
|
Indexor indexor(m_pRAM, tex0.GetCLUTPtr(), 1);
|
|
uint32 clutOffset = tex0.nCSA * 16;
|
|
uint16* pDst = m_pCLUT + clutOffset;
|
|
|
|
for(unsigned int j = 0; j < 2; j++)
|
|
{
|
|
for(unsigned int i = 0; i < 8; i++)
|
|
{
|
|
uint16 color = indexor.GetPixel(i, j);
|
|
|
|
if(*pDst != color)
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
(*pDst++) = color;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
template <typename Indexor>
|
|
bool CGSHandler::ReadCLUT8_16(const TEX0& tex0)
|
|
{
|
|
bool changed = false;
|
|
|
|
Indexor indexor(m_pRAM, tex0.GetCLUTPtr(), 1);
|
|
|
|
for(unsigned int j = 0; j < 16; j++)
|
|
{
|
|
for(unsigned int i = 0; i < 16; i++)
|
|
{
|
|
uint16 color = indexor.GetPixel(i, j);
|
|
|
|
uint8 index = i + (j * 16);
|
|
index = (index & ~0x18) | ((index & 0x08) << 1) | ((index & 0x10) >> 1);
|
|
|
|
if(m_pCLUT[index] != color)
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
m_pCLUT[index] = color;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void CGSHandler::ReadCLUT4(const TEX0& tex0)
|
|
{
|
|
bool changed = false;
|
|
|
|
if(tex0.nCSM == 0)
|
|
{
|
|
//CSM1 mode
|
|
if(tex0.nCPSM == PSMCT32 || tex0.nCPSM == PSMCT24)
|
|
{
|
|
assert(tex0.nCSA < 16);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 Indexor(m_pRAM, tex0.GetCLUTPtr(), 1);
|
|
uint32 clutOffset = (tex0.nCSA & 0x0F) * 16;
|
|
uint16* pDst = m_pCLUT + clutOffset;
|
|
|
|
for(unsigned int j = 0; j < 2; j++)
|
|
{
|
|
for(unsigned int i = 0; i < 8; i++)
|
|
{
|
|
uint32 color = Indexor.GetPixel(i, j);
|
|
uint16 colorLo = static_cast<uint16>(color & 0xFFFF);
|
|
uint16 colorHi = static_cast<uint16>(color >> 16);
|
|
|
|
if(
|
|
(pDst[0x000] != colorLo) ||
|
|
(pDst[0x100] != colorHi))
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
pDst[0x000] = colorLo;
|
|
pDst[0x100] = colorHi;
|
|
pDst++;
|
|
}
|
|
}
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16)
|
|
{
|
|
changed = ReadCLUT4_16<CGsPixelFormats::CPixelIndexorPSMCT16>(tex0);
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16S)
|
|
{
|
|
changed = ReadCLUT4_16<CGsPixelFormats::CPixelIndexorPSMCT16S>(tex0);
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CSM2 mode
|
|
assert(tex0.nCPSM == PSMCT16);
|
|
assert(tex0.nCSA == 0);
|
|
|
|
auto texClut = make_convertible<TEXCLUT>(m_nReg[GS_REG_TEXCLUT]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT16 Indexor(m_pRAM, tex0.GetCLUTPtr(), texClut.nCBW);
|
|
unsigned int nOffsetX = texClut.GetOffsetU();
|
|
unsigned int nOffsetY = texClut.GetOffsetV();
|
|
uint16* pDst = m_pCLUT;
|
|
|
|
for(unsigned int i = 0; i < 0x10; i++)
|
|
{
|
|
uint16 color = Indexor.GetPixel(nOffsetX + i, nOffsetY);
|
|
|
|
if(*pDst != color)
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
(*pDst++) = color;
|
|
}
|
|
}
|
|
|
|
if(changed)
|
|
{
|
|
ProcessClutTransfer(tex0.nCSA, 0);
|
|
}
|
|
}
|
|
|
|
void CGSHandler::ReadCLUT8(const TEX0& tex0)
|
|
{
|
|
assert(tex0.nCSA == 0);
|
|
|
|
bool changed = false;
|
|
|
|
if(tex0.nCSM == 0)
|
|
{
|
|
if(tex0.nCPSM == PSMCT32 || tex0.nCPSM == PSMCT24)
|
|
{
|
|
CGsPixelFormats::CPixelIndexorPSMCT32 Indexor(m_pRAM, tex0.GetCLUTPtr(), 1);
|
|
|
|
for(unsigned int j = 0; j < 16; j++)
|
|
{
|
|
for(unsigned int i = 0; i < 16; i++)
|
|
{
|
|
uint32 color = Indexor.GetPixel(i, j);
|
|
uint16 colorLo = static_cast<uint16>(color & 0xFFFF);
|
|
uint16 colorHi = static_cast<uint16>(color >> 16);
|
|
|
|
uint8 index = i + (j * 16);
|
|
index = (index & ~0x18) | ((index & 0x08) << 1) | ((index & 0x10) >> 1);
|
|
|
|
if(
|
|
(m_pCLUT[index + 0x000] != colorLo) ||
|
|
(m_pCLUT[index + 0x100] != colorHi))
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
m_pCLUT[index + 0x000] = colorLo;
|
|
m_pCLUT[index + 0x100] = colorHi;
|
|
}
|
|
}
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16)
|
|
{
|
|
changed = ReadCLUT8_16<CGsPixelFormats::CPixelIndexorPSMCT16>(tex0);
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16S)
|
|
{
|
|
changed = ReadCLUT8_16<CGsPixelFormats::CPixelIndexorPSMCT16S>(tex0);
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CSM2 mode
|
|
assert(tex0.nCPSM == PSMCT16);
|
|
|
|
auto texClut = make_convertible<TEXCLUT>(m_nReg[GS_REG_TEXCLUT]);
|
|
|
|
CGsPixelFormats::CPixelIndexorPSMCT16 indexor(m_pRAM, tex0.GetCLUTPtr(), texClut.nCBW);
|
|
unsigned int offsetX = texClut.GetOffsetU();
|
|
unsigned int offsetY = texClut.GetOffsetV();
|
|
uint16* dst = m_pCLUT;
|
|
|
|
for(unsigned int i = 0; i < 0x100; i++)
|
|
{
|
|
uint16 color = indexor.GetPixel(offsetX + i, offsetY);
|
|
|
|
if(*dst != color)
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
(*dst++) = color;
|
|
}
|
|
}
|
|
|
|
if(changed)
|
|
{
|
|
ProcessClutTransfer(tex0.nCSA, 0);
|
|
}
|
|
}
|
|
|
|
bool CGSHandler::IsCompatibleFramebufferPSM(unsigned int psmFb, unsigned int psmTex)
|
|
{
|
|
if((psmTex == CGSHandler::PSMCT32) || (psmTex == CGSHandler::PSMCT24))
|
|
{
|
|
return (psmFb == CGSHandler::PSMCT32) || (psmFb == CGSHandler::PSMCT24);
|
|
}
|
|
else
|
|
{
|
|
return (psmFb == psmTex);
|
|
}
|
|
}
|
|
|
|
void CGSHandler::MakeLinearCLUT(const TEX0& tex0, std::array<uint32, 256>& clut) const
|
|
{
|
|
static const auto RGBA16ToRGBA32 =
|
|
[](uint16 color) {
|
|
return ((color & 0x8000) ? 0xFF000000 : 0) | ((color & 0x7C00) << 9) | ((color & 0x03E0) << 6) | ((color & 0x001F) << 3);
|
|
};
|
|
|
|
if(CGsPixelFormats::IsPsmIDTEX4(tex0.nPsm))
|
|
{
|
|
if(tex0.nCPSM == PSMCT32 || tex0.nCPSM == PSMCT24)
|
|
{
|
|
assert(tex0.nCSA < 16);
|
|
uint32 clutOffset = (tex0.nCSA & 0xF) * 16;
|
|
|
|
for(unsigned int i = 0; i < 16; i++)
|
|
{
|
|
uint32 color =
|
|
(static_cast<uint16>(m_pCLUT[i + clutOffset + 0x000])) |
|
|
(static_cast<uint16>(m_pCLUT[i + clutOffset + 0x100]) << 16);
|
|
clut[i] = color;
|
|
}
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16 || tex0.nCPSM == PSMCT16S)
|
|
{
|
|
//CSA is 5-bit, shouldn't go over 31
|
|
assert(tex0.nCSA < 32);
|
|
uint32 clutOffset = tex0.nCSA * 16;
|
|
|
|
for(unsigned int i = 0; i < 16; i++)
|
|
{
|
|
clut[i] = RGBA16ToRGBA32(m_pCLUT[i + clutOffset]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
else if(CGsPixelFormats::IsPsmIDTEX8(tex0.nPsm))
|
|
{
|
|
if(tex0.nCPSM == PSMCT32 || tex0.nCPSM == PSMCT24)
|
|
{
|
|
for(unsigned int i = 0; i < 256; i++)
|
|
{
|
|
uint32 offset = ((tex0.nCSA * 16) + i) & 0xFF;
|
|
uint32 color =
|
|
(static_cast<uint16>(m_pCLUT[offset + 0x000])) |
|
|
(static_cast<uint16>(m_pCLUT[offset + 0x100]) << 16);
|
|
clut[i] = color;
|
|
}
|
|
}
|
|
else if(tex0.nCPSM == PSMCT16 || tex0.nCPSM == PSMCT16S)
|
|
{
|
|
assert(tex0.nCSA == 0);
|
|
for(unsigned int i = 0; i < 256; i++)
|
|
{
|
|
clut[i] = RGBA16ToRGBA32(m_pCLUT[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGSHandler::SetLoggingEnabled(bool loggingEnabled)
|
|
{
|
|
m_loggingEnabled = loggingEnabled;
|
|
}
|
|
|
|
std::string CGSHandler::DisassembleWrite(uint8 registerId, uint64 data)
|
|
{
|
|
std::string result;
|
|
|
|
switch(registerId)
|
|
{
|
|
case GS_REG_PRIM:
|
|
{
|
|
auto pr = make_convertible<PRIM>(data);
|
|
result = string_format("PRIM(PRI: %i, IIP: %i, TME: %i, FGE: %i, ABE: %i, AA1: %i, FST: %i, CTXT: %i, FIX: %i)",
|
|
pr.nType, pr.nShading, pr.nTexture, pr.nFog, pr.nAlpha, pr.nAntiAliasing, pr.nUseUV, pr.nContext, pr.nUseFloat);
|
|
}
|
|
break;
|
|
case GS_REG_RGBAQ:
|
|
{
|
|
auto rgbaq = make_convertible<RGBAQ>(data);
|
|
result = string_format("RGBAQ(R: 0x%02X, G: 0x%02X, B: 0x%02X, A: 0x%02X, Q: %f)",
|
|
rgbaq.nR, rgbaq.nG, rgbaq.nB, rgbaq.nA, rgbaq.nQ);
|
|
}
|
|
break;
|
|
case GS_REG_ST:
|
|
{
|
|
auto st = make_convertible<ST>(data);
|
|
result = string_format("ST(S: %f, T: %f)",
|
|
st.nS, st.nT);
|
|
}
|
|
break;
|
|
case GS_REG_UV:
|
|
{
|
|
auto uv = make_convertible<UV>(data);
|
|
result = string_format("UV(U: %f, V: %f)",
|
|
uv.GetU(), uv.GetV());
|
|
}
|
|
break;
|
|
case GS_REG_XYZ2:
|
|
case GS_REG_XYZ3:
|
|
{
|
|
auto xyz = make_convertible<XYZ>(data);
|
|
result = string_format("%s(%f, %f, %u)",
|
|
(registerId == GS_REG_XYZ2) ? "XYZ2" : "XYZ3", xyz.GetX(), xyz.GetY(), xyz.nZ);
|
|
}
|
|
break;
|
|
case GS_REG_XYZF2:
|
|
case GS_REG_XYZF3:
|
|
{
|
|
auto xyzf = make_convertible<XYZF>(data);
|
|
result = string_format("%s(%f, %f, %i, %i)",
|
|
(registerId == GS_REG_XYZF2) ? "XYZF2" : "XYZF3", xyzf.GetX(), xyzf.GetY(), xyzf.nZ, xyzf.nF);
|
|
}
|
|
break;
|
|
case GS_REG_FOG:
|
|
{
|
|
auto fog = static_cast<uint8>(data >> 56);
|
|
result = string_format("FOG(F: %d)", fog);
|
|
}
|
|
break;
|
|
case GS_REG_TEX0_1:
|
|
case GS_REG_TEX0_2:
|
|
{
|
|
auto tex0 = make_convertible<TEX0>(data);
|
|
result = string_format("TEX0_%i(TBP: 0x%08X, TBW: %i, PSM: %i, TW: %i, TH: %i, TCC: %i, TFX: %i, CBP: 0x%08X, CPSM: %i, CSM: %i, CSA: %i, CLD: %i)",
|
|
(registerId == GS_REG_TEX0_1) ? 1 : 2, tex0.GetBufPtr(), tex0.GetBufWidth(), tex0.nPsm, tex0.GetWidth(), tex0.GetHeight(), tex0.nColorComp,
|
|
tex0.nFunction, tex0.GetCLUTPtr(), tex0.nCPSM, tex0.nCSM, tex0.nCSA, tex0.nCLD);
|
|
}
|
|
break;
|
|
case GS_REG_CLAMP_1:
|
|
case GS_REG_CLAMP_2:
|
|
{
|
|
auto clamp = make_convertible<CLAMP>(data);
|
|
result = string_format("CLAMP_%i(WMS: %i, WMT: %i, MINU: %i, MAXU: %i, MINV: %i, MAXV: %i)",
|
|
(registerId == GS_REG_CLAMP_1 ? 1 : 2), clamp.nWMS, clamp.nWMT, clamp.GetMinU(), clamp.GetMaxU(), clamp.GetMinV(), clamp.GetMaxV());
|
|
}
|
|
break;
|
|
case GS_REG_TEX1_1:
|
|
case GS_REG_TEX1_2:
|
|
{
|
|
auto tex1 = make_convertible<TEX1>(data);
|
|
result = string_format("TEX1_%i(LCM: %i, MXL: %i, MMAG: %i, MMIN: %i, MTBA: %i, L: %i, K: %i)",
|
|
(registerId == GS_REG_TEX1_1) ? 1 : 2, tex1.nLODMethod, tex1.nMaxMip, tex1.nMagFilter, tex1.nMinFilter, tex1.nMipBaseAddr, tex1.nLODL, tex1.nLODK);
|
|
}
|
|
break;
|
|
case GS_REG_TEX2_1:
|
|
case GS_REG_TEX2_2:
|
|
{
|
|
auto tex2 = make_convertible<TEX2>(data);
|
|
result = string_format("TEX2_%i(PSM: %i, CBP: 0x%08X, CPSM: %i, CSM: %i, CSA: %i, CLD: %i)",
|
|
(registerId == GS_REG_TEX2_1) ? 1 : 2, tex2.nPsm, tex2.GetCLUTPtr(), tex2.nCPSM, tex2.nCSM, tex2.nCSA, tex2.nCLD);
|
|
}
|
|
break;
|
|
case GS_REG_XYOFFSET_1:
|
|
case GS_REG_XYOFFSET_2:
|
|
result = string_format("XYOFFSET_%i(%i, %i)",
|
|
(registerId == GS_REG_XYOFFSET_1) ? 1 : 2, static_cast<uint32>(data >> 0), static_cast<uint32>(data >> 32));
|
|
break;
|
|
case GS_REG_PRMODECONT:
|
|
result = string_format("PRMODECONT(AC: %i)", data & 1);
|
|
break;
|
|
case GS_REG_PRMODE:
|
|
{
|
|
auto prm = make_convertible<PRMODE>(data);
|
|
result = string_format("PRMODE(IIP: %i, TME: %i, FGE: %i, ABE: %i, AA1: %i, FST: %i, CTXT: %i, FIX: %i)",
|
|
prm.nShading, prm.nTexture, prm.nFog, prm.nAlpha, prm.nAntiAliasing, prm.nUseUV, prm.nContext, prm.nUseFloat);
|
|
}
|
|
break;
|
|
case GS_REG_TEXCLUT:
|
|
{
|
|
auto clut = make_convertible<TEXCLUT>(data);
|
|
result = string_format("TEXCLUT(CBW: %i, COU: %i, COV: %i)",
|
|
clut.nCBW, clut.nCOU, clut.nCOV);
|
|
}
|
|
break;
|
|
case GS_REG_SCANMSK:
|
|
{
|
|
result = string_format("SCANMSK(MSK: %d)", data & 0x03);
|
|
}
|
|
break;
|
|
case GS_REG_MIPTBP1_1:
|
|
case GS_REG_MIPTBP1_2:
|
|
{
|
|
auto miptbp1 = make_convertible<MIPTBP1>(data);
|
|
result = string_format("MIPTBP1_%d(TBP1: 0x%08X, TBW1: %d, TBP2: 0x%08X, TBW2: %d, TBP3: 0x%08X, TBW3: %d)",
|
|
(registerId == GS_REG_MIPTBP1_1) ? 1 : 2,
|
|
miptbp1.GetTbp1(), miptbp1.GetTbw1(),
|
|
miptbp1.GetTbp2(), miptbp1.GetTbw2(),
|
|
miptbp1.GetTbp3(), miptbp1.GetTbw3());
|
|
}
|
|
break;
|
|
case GS_REG_MIPTBP2_1:
|
|
case GS_REG_MIPTBP2_2:
|
|
{
|
|
auto miptbp2 = make_convertible<MIPTBP2>(data);
|
|
result = string_format("MIPTBP2_%d(TBP4: 0x%08X, TBW4: %d, TBP5: 0x%08X, TBW5: %d, TBP6: 0x%08X, TBW6: %d)",
|
|
(registerId == GS_REG_MIPTBP2_1) ? 1 : 2,
|
|
miptbp2.GetTbp4(), miptbp2.GetTbw4(),
|
|
miptbp2.GetTbp5(), miptbp2.GetTbw5(),
|
|
miptbp2.GetTbp6(), miptbp2.GetTbw6());
|
|
}
|
|
break;
|
|
case GS_REG_TEXA:
|
|
{
|
|
auto texa = make_convertible<TEXA>(data);
|
|
result = string_format("TEXA(TA0: 0x%02X, AEM: %i, TA1: 0x%02X)",
|
|
texa.nTA0, texa.nAEM, texa.nTA1);
|
|
}
|
|
break;
|
|
case GS_REG_FOGCOL:
|
|
{
|
|
auto fogcol = make_convertible<FOGCOL>(data);
|
|
result = string_format("FOGCOL(R: 0x%02X, G: 0x%02X, B: 0x%02X)",
|
|
fogcol.nFCR, fogcol.nFCG, fogcol.nFCB);
|
|
}
|
|
break;
|
|
case GS_REG_TEXFLUSH:
|
|
result = "TEXFLUSH()";
|
|
break;
|
|
case GS_REG_ALPHA_1:
|
|
case GS_REG_ALPHA_2:
|
|
{
|
|
auto alpha = make_convertible<ALPHA>(data);
|
|
result = string_format("ALPHA_%i(A: %i, B: %i, C: %i, D: %i, FIX: 0x%02X)",
|
|
(registerId == GS_REG_ALPHA_1) ? 1 : 2, alpha.nA, alpha.nB, alpha.nC, alpha.nD, alpha.nFix);
|
|
}
|
|
break;
|
|
case GS_REG_SCISSOR_1:
|
|
case GS_REG_SCISSOR_2:
|
|
{
|
|
auto scissor = make_convertible<SCISSOR>(data);
|
|
result = string_format("SCISSOR_%i(SCAX0: %i, SCAX1: %i, SCAY0: %i, SCAY1: %i)",
|
|
(registerId == GS_REG_SCISSOR_1) ? 1 : 2, scissor.scax0, scissor.scax1, scissor.scay0, scissor.scay1);
|
|
}
|
|
break;
|
|
case GS_REG_COLCLAMP:
|
|
result = string_format("COLCLAMP(CLAMP: %d)", data & 1);
|
|
break;
|
|
case GS_REG_TEST_1:
|
|
case GS_REG_TEST_2:
|
|
{
|
|
auto tst = make_convertible<TEST>(data);
|
|
result = string_format("TEST_%i(ATE: %i, ATST: %i, AREF: 0x%02X, AFAIL: %i, DATE: %i, DATM: %i, ZTE: %i, ZTST: %i)",
|
|
(registerId == GS_REG_TEST_1) ? 1 : 2, tst.nAlphaEnabled, tst.nAlphaMethod, tst.nAlphaRef, tst.nAlphaFail,
|
|
tst.nDestAlphaEnabled, tst.nDestAlphaMode, tst.nDepthEnabled, tst.nDepthMethod);
|
|
}
|
|
break;
|
|
case GS_REG_PABE:
|
|
{
|
|
auto value = static_cast<uint8>(data & 1);
|
|
result = string_format("PABE(PABE: %d)", value);
|
|
}
|
|
break;
|
|
case GS_REG_FBA_1:
|
|
case GS_REG_FBA_2:
|
|
{
|
|
auto value = static_cast<uint8>(data & 1);
|
|
result = string_format("FBA_%d(FBA: %d)", (registerId == GS_REG_FBA_1) ? 1 : 2, value);
|
|
}
|
|
break;
|
|
case GS_REG_FRAME_1:
|
|
case GS_REG_FRAME_2:
|
|
{
|
|
auto fr = make_convertible<FRAME>(data);
|
|
result = string_format("FRAME_%i(FBP: 0x%08X, FBW: %d, PSM: %d, FBMSK: 0x%08X)",
|
|
(registerId == GS_REG_FRAME_1) ? 1 : 2, fr.GetBasePtr(), fr.GetWidth(), fr.nPsm, fr.nMask);
|
|
}
|
|
break;
|
|
case GS_REG_ZBUF_1:
|
|
case GS_REG_ZBUF_2:
|
|
{
|
|
auto zbuf = make_convertible<ZBUF>(data);
|
|
result = string_format("ZBUF_%i(ZBP: 0x%08X, PSM: %i, ZMSK: %i)",
|
|
(registerId == GS_REG_ZBUF_1) ? 1 : 2, zbuf.GetBasePtr(), zbuf.nPsm, zbuf.nMask);
|
|
}
|
|
break;
|
|
case GS_REG_BITBLTBUF:
|
|
{
|
|
auto buf = make_convertible<BITBLTBUF>(data);
|
|
result = string_format("BITBLTBUF(SBP: 0x%08X, SBW: %i, SPSM: %i, DBP: 0x%08X, DBW: %i, DPSM: %i)",
|
|
buf.GetSrcPtr(), buf.GetSrcWidth(), buf.nSrcPsm, buf.GetDstPtr(), buf.GetDstWidth(), buf.nDstPsm);
|
|
}
|
|
break;
|
|
case GS_REG_TRXPOS:
|
|
{
|
|
auto trxPos = make_convertible<TRXPOS>(data);
|
|
result = string_format("TRXPOS(SSAX: %i, SSAY: %i, DSAX: %i, DSAY: %i, DIR: %i)",
|
|
trxPos.nSSAX, trxPos.nSSAY, trxPos.nDSAX, trxPos.nDSAY, trxPos.nDIR);
|
|
}
|
|
break;
|
|
case GS_REG_TRXREG:
|
|
{
|
|
auto trxReg = make_convertible<TRXREG>(data);
|
|
result = string_format("TRXREG(RRW: %i, RRH: %i)",
|
|
trxReg.nRRW, trxReg.nRRH);
|
|
}
|
|
break;
|
|
case GS_REG_TRXDIR:
|
|
result = string_format("TRXDIR(XDIR: %i)", data & 0x03);
|
|
break;
|
|
case GS_REG_HWREG:
|
|
result = string_format("HWREG(DATA: 0x%016llX)", data);
|
|
break;
|
|
case GS_REG_SIGNAL:
|
|
{
|
|
auto signal = make_convertible<SIGNAL>(data);
|
|
result = string_format("SIGNAL(IDMSK: 0x%08X, ID: 0x%08X)",
|
|
signal.idmsk, signal.id);
|
|
}
|
|
break;
|
|
case GS_REG_FINISH:
|
|
result = "FINISH()";
|
|
break;
|
|
case GS_REG_LABEL:
|
|
{
|
|
auto label = make_convertible<LABEL>(data);
|
|
result = string_format("LABEL(IDMSK: 0x%08X, ID: 0x%08X)",
|
|
label.idmsk, label.id);
|
|
}
|
|
break;
|
|
default:
|
|
result = string_format("(Unknown register: 0x%02X)", registerId);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CGSHandler::LogWrite(uint8 registerId, uint64 data)
|
|
{
|
|
if(!m_loggingEnabled) return;
|
|
auto disassembledWrite = DisassembleWrite(registerId, data);
|
|
CLog::GetInstance().Print(LOG_NAME, "%s\r\n", disassembledWrite.c_str());
|
|
}
|
|
|
|
void CGSHandler::LogPrivateWrite(uint32 address)
|
|
{
|
|
assert((address & 0x04) != 0);
|
|
|
|
uint32 regAddress = address & ~0x0F;
|
|
switch(regAddress)
|
|
{
|
|
case GS_PMODE:
|
|
CLog::GetInstance().Print(LOG_NAME, "PMODE(0x%08X);\r\n", m_nPMODE);
|
|
break;
|
|
case GS_SMODE2:
|
|
{
|
|
SMODE2 smode2;
|
|
smode2 <<= m_nSMODE2;
|
|
CLog::GetInstance().Print(LOG_NAME, "SMODE2(inter = %d, ffmd = %d, dpms = %d);\r\n",
|
|
smode2.interlaced,
|
|
smode2.ffmd,
|
|
smode2.dpms);
|
|
}
|
|
break;
|
|
case GS_DISPFB1:
|
|
case GS_DISPFB2:
|
|
{
|
|
DISPFB dispfb;
|
|
dispfb <<= (regAddress == GS_DISPFB1) ? m_nDISPFB1.value.q : m_nDISPFB2.value.q;
|
|
CLog::GetInstance().Print(LOG_NAME, "DISPFB%d(FBP: 0x%08X, FBW: %d, PSM: %d, DBX: %d, DBY: %d);\r\n",
|
|
(regAddress == GS_DISPFB1) ? 1 : 2,
|
|
dispfb.GetBufPtr(),
|
|
dispfb.GetBufWidth(),
|
|
dispfb.nPSM,
|
|
dispfb.nX,
|
|
dispfb.nY);
|
|
}
|
|
break;
|
|
case GS_DISPLAY1:
|
|
case GS_DISPLAY2:
|
|
{
|
|
DISPLAY display;
|
|
display <<= (regAddress == GS_DISPLAY1) ? m_nDISPLAY1.value.q : m_nDISPLAY2.value.q;
|
|
CLog::GetInstance().Print(LOG_NAME, "DISPLAY%d(DX: %d, DY: %d, MAGH: %d, MAGV: %d, DW: %d, DH: %d);\r\n",
|
|
(regAddress == GS_DISPLAY1) ? 1 : 2,
|
|
display.nX,
|
|
display.nY,
|
|
display.nMagX,
|
|
display.nMagY,
|
|
display.nW,
|
|
display.nH);
|
|
}
|
|
break;
|
|
case GS_CSR:
|
|
case GS_CSR_ALT:
|
|
//CSR
|
|
break;
|
|
case GS_IMR:
|
|
CLog::GetInstance().Print(LOG_NAME, "IMR(0x%08X);\r\n", m_nIMR);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGSHandler::WriteToDelayedRegister(uint32 address, uint32 value, DELAYED_REGISTER& delayedRegister)
|
|
{
|
|
if(address & 0x04)
|
|
{
|
|
std::lock_guard registerMutexLock(m_registerMutex);
|
|
delayedRegister.value.d0 = delayedRegister.heldValue;
|
|
delayedRegister.value.d1 = value;
|
|
}
|
|
else
|
|
{
|
|
delayedRegister.heldValue = value;
|
|
}
|
|
}
|
|
|
|
void CGSHandler::ThreadProc()
|
|
{
|
|
while(!m_threadDone)
|
|
{
|
|
m_mailBox.WaitForCall();
|
|
while(m_mailBox.IsPending())
|
|
{
|
|
m_mailBox.ReceiveCall();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGSHandler::SendGSCall(const CMailBox::FunctionType& function, bool waitForCompletion, bool forceWaitForCompletion)
|
|
{
|
|
if(!m_gsThreaded)
|
|
{
|
|
waitForCompletion = false;
|
|
}
|
|
waitForCompletion |= forceWaitForCompletion;
|
|
m_mailBox.SendCall(function, waitForCompletion);
|
|
}
|
|
|
|
void CGSHandler::SendGSCall(CMailBox::FunctionType&& function)
|
|
{
|
|
m_mailBox.SendCall(std::move(function));
|
|
}
|
|
|
|
void CGSHandler::ProcessSingleFrame()
|
|
{
|
|
assert(!m_gsThreaded);
|
|
assert(!m_flipped);
|
|
while(!m_flipped)
|
|
{
|
|
m_mailBox.WaitForCall();
|
|
while(m_mailBox.IsPending() && !m_flipped)
|
|
{
|
|
m_mailBox.ReceiveCall();
|
|
}
|
|
}
|
|
m_flipped = false;
|
|
}
|
|
|
|
Framework::CBitmap CGSHandler::GetScreenshot()
|
|
{
|
|
throw std::runtime_error("Screenshot feature is not implemented in current backend.");
|
|
}
|