#include <mutex>
#include <condition_variable>
#include "Common/Log.h"
#include "Common/Thread/ThreadUtil.h"
#include "Core/Core.h"
#include "Core/HW/Display.h"
#include "GPU/Common/GPUDebugInterface.h"
#include "GPU/Debugger/Stepping.h"
#include "GPU/GPUState.h"
namespace GPUStepping {
enum PauseAction {
PAUSE_CONTINUE,
PAUSE_BREAK,
PAUSE_GETOUTPUTBUF,
PAUSE_GETFRAMEBUF,
PAUSE_GETDEPTHBUF,
PAUSE_GETSTENCILBUF,
PAUSE_GETTEX,
PAUSE_GETCLUT,
PAUSE_SETCMDVALUE,
PAUSE_FLUSHDRAW,
};
static bool isStepping;
static int stepCounter = 0;
static std::mutex pauseLock;
static PauseAction pauseAction = PAUSE_CONTINUE;
static std::mutex actionLock;
static std::condition_variable actionWait;
static volatile bool actionComplete;
static bool bufferResult;
static GPUDebugFramebufferType bufferType = GPU_DBG_FRAMEBUF_RENDER;
static GPUDebugBuffer bufferFrame;
static GPUDebugBuffer bufferDepth;
static GPUDebugBuffer bufferStencil;
static GPUDebugBuffer bufferTex;
static GPUDebugBuffer bufferClut;
static int bufferLevel;
static bool lastWasFramebuffer;
static u32 pauseSetCmdValue;
static GPUgstate lastGState;
const char *PauseActionToString(PauseAction action) {
switch (action) {
case PAUSE_CONTINUE: return "CONTINUE";
case PAUSE_BREAK: return "BREAK";
case PAUSE_GETOUTPUTBUF: return "GETOUTPUTBUF";
case PAUSE_GETFRAMEBUF: return "GETFRAMEBUF";
case PAUSE_GETDEPTHBUF: return "GETDEPTHBUF";
case PAUSE_GETSTENCILBUF: return "GETSTENCILBUF";
case PAUSE_GETTEX: return "GETTEX";
case PAUSE_GETCLUT: return "GETCLUT";
case PAUSE_SETCMDVALUE: return "SETCMDVALUE";
case PAUSE_FLUSHDRAW: return "FLUSHDRAW";
default: return "N/A";
}
}
static void SetPauseAction(PauseAction act, bool waitComplete = true) {
pauseLock.lock();
std::unique_lock<std::mutex> guard(actionLock);
pauseAction = act;
pauseLock.unlock();
actionComplete = false;
}
static void RunPauseAction() {
std::lock_guard<std::mutex> guard(actionLock);
if (pauseAction == PAUSE_BREAK) {
return;
}
DEBUG_LOG(Log::GeDebugger, "RunPauseAction: %s", PauseActionToString(pauseAction));
switch (pauseAction) {
case PAUSE_BREAK:
break;
case PAUSE_GETOUTPUTBUF:
bufferResult = gpuDebug->GetOutputFramebuffer(bufferFrame);
break;
case PAUSE_GETFRAMEBUF:
bufferResult = gpuDebug->GetCurrentFramebuffer(bufferFrame, bufferType);
break;
case PAUSE_GETDEPTHBUF:
bufferResult = gpuDebug->GetCurrentDepthbuffer(bufferDepth);
break;
case PAUSE_GETSTENCILBUF:
bufferResult = gpuDebug->GetCurrentStencilbuffer(bufferStencil);
break;
case PAUSE_GETTEX:
bufferResult = gpuDebug->GetCurrentTexture(bufferTex, bufferLevel, &lastWasFramebuffer);
break;
case PAUSE_GETCLUT:
bufferResult = gpuDebug->GetCurrentClut(bufferClut);
break;
case PAUSE_SETCMDVALUE:
gpuDebug->SetCmdValue(pauseSetCmdValue);
break;
case PAUSE_FLUSHDRAW:
gpuDebug->Flush();
break;
default:
ERROR_LOG(Log::GeDebugger, "Unsupported pause action, forgot to add it to the switch.");
break;
}
actionComplete = true;
actionWait.notify_all();
pauseAction = PAUSE_BREAK;
}
void WaitForPauseAction() {
std::unique_lock<std::mutex> guard(actionLock);
actionWait.wait(guard);
}
bool ProcessStepping() {
_dbg_assert_(gpuDebug);
std::unique_lock<std::mutex> guard(pauseLock);
if (coreState != CORE_STEPPING_GE) {
actionComplete = true;
actionWait.notify_all();
return false;
}
if (pauseAction == PAUSE_CONTINUE) {
DEBUG_LOG(Log::GeDebugger, "Continuing...");
actionComplete = true;
actionWait.notify_all();
coreState = CORE_RUNNING_GE;
return false;
}
RunPauseAction();
return true;
}
bool EnterStepping(CoreState coreState) {
_dbg_assert_(gpuDebug);
std::unique_lock<std::mutex> guard(pauseLock);
if (coreState == CORE_STEPPING_GE) {
return true;
}
if (coreState != CORE_RUNNING_CPU && coreState != CORE_RUNNING_GE) {
actionComplete = true;
actionWait.notify_all();
return false;
}
if (lastGState.cmdmem[1] == 0) {
lastGState = gstate;
lastGState.cmdmem[1] |= 0x01000000;
}
isStepping = true;
stepCounter++;
if (pauseAction == PAUSE_CONTINUE) {
pauseAction = PAUSE_BREAK;
}
::coreState = CORE_STEPPING_GE;
return true;
}
void ResumeFromStepping() {
lastGState = gstate;
isStepping = false;
SetPauseAction(PAUSE_CONTINUE, false);
}
bool IsStepping() {
return isStepping;
}
int GetSteppingCounter() {
return stepCounter;
}
static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPUDebugBuffer &resultBuffer) {
if (!isStepping && coreState != CORE_STEPPING_CPU) {
return false;
}
_dbg_assert_(strcmp(GetCurrentThreadName(), "EmuThread") != 0);
SetPauseAction(type);
WaitForPauseAction();
buffer = &resultBuffer;
return bufferResult;
}
bool GPU_GetOutputFramebuffer(const GPUDebugBuffer *&buffer) {
return GetBuffer(buffer, PAUSE_GETOUTPUTBUF, bufferFrame);
}
bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type) {
bufferType = type;
return GetBuffer(buffer, PAUSE_GETFRAMEBUF, bufferFrame);
}
bool GPU_GetCurrentDepthbuffer(const GPUDebugBuffer *&buffer) {
return GetBuffer(buffer, PAUSE_GETDEPTHBUF, bufferDepth);
}
bool GPU_GetCurrentStencilbuffer(const GPUDebugBuffer *&buffer) {
return GetBuffer(buffer, PAUSE_GETSTENCILBUF, bufferStencil);
}
bool GPU_GetCurrentTexture(const GPUDebugBuffer *&buffer, int level, bool *isFramebuffer) {
bufferLevel = level;
bool result = GetBuffer(buffer, PAUSE_GETTEX, bufferTex);
*isFramebuffer = lastWasFramebuffer;
return result;
}
bool GPU_GetCurrentClut(const GPUDebugBuffer *&buffer) {
return GetBuffer(buffer, PAUSE_GETCLUT, bufferClut);
}
bool GPU_SetCmdValue(u32 op) {
if (!isStepping && coreState != CORE_STEPPING_CPU) {
return false;
}
pauseSetCmdValue = op;
SetPauseAction(PAUSE_SETCMDVALUE);
return true;
}
bool GPU_FlushDrawing() {
if (!isStepping && coreState != CORE_STEPPING_CPU) {
return false;
}
SetPauseAction(PAUSE_FLUSHDRAW);
return true;
}
const GPUgstate &LastState() {
return lastGState;
}
}