#include #include #include #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(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; m_transferWriteHandlers[PSMCT24] = &CGSHandler::TransferWriteHandlerPSMCT24; m_transferWriteHandlers[PSMCT16] = &CGSHandler::TransferWriteHandlerGeneric; m_transferWriteHandlers[PSMCT16S] = &CGSHandler::TransferWriteHandlerGeneric; m_transferWriteHandlers[PSMT8] = &CGSHandler::TransferWriteHandlerGeneric; 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; m_transferReadHandlers[PSMCT24] = &CGSHandler::TransferReadHandler24; m_transferReadHandlers[PSMCT16] = &CGSHandler::TransferReadHandlerGeneric; m_transferReadHandlers[PSMT8] = &CGSHandler::TransferReadHandlerGeneric; m_transferReadHandlers[PSMT8H] = &CGSHandler::TransferReadHandlerPSMT8H; m_transferReadHandlers[PSMZ32] = &CGSHandler::TransferReadHandlerGeneric; m_transferReadHandlers[PSMZ24] = &CGSHandler::TransferReadHandler24; m_transferReadHandlers[PSMZ16S] = &CGSHandler::TransferReadHandlerGeneric; 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(m_presentationParams.windowWidth)) || (viewportHeight[0] > static_cast(m_presentationParams.windowHeight))) { selectedViewport = 1; assert( viewportWidth[1] <= static_cast(m_presentationParams.windowWidth) && viewportHeight[1] <= static_cast(m_presentationParams.windowHeight)); } int offsetX = static_cast(m_presentationParams.windowWidth - viewportWidth[selectedViewport]) / 2; int offsetY = static_cast(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(m_presentationParams.windowWidth - sourceSize.first) / 2; int offsetY = static_cast(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(STATE_RAM, GetRam(), RAMSIZE)); archive.InsertFile(std::make_unique(STATE_REGS, m_nReg, sizeof(uint64) * CGSHandler::REGISTER_MAX)); archive.InsertFile(std::make_unique(STATE_TRXCTX, &m_trxCtx, sizeof(TRXCONTEXT))); { auto registerFile = std::make_unique(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(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(); //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; } #if LOGGING_ENABLED 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(); #if LOGGING_ENABLED 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) uint8* imageData = new uint8[length + 0x10]; memcpy(imageData, data, length); memset(imageData + length, 0, 0x10); SendGSCall( [this, imageData, length]() { #ifdef DEBUGGER_INCLUDED if(m_frameDump) { m_frameDump->AddImagePacket(imageData, length); } #endif FeedImageDataImpl(imageData, length); delete[] imageData; }); } 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(write.second); auto siglblid = make_convertible(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