mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-07-09 17:14:47 -06:00
Merge branch 'cemu-project:main' into main
This commit is contained in:
commit
6c012d42ed
@ -133,6 +133,7 @@ add_library(CemuCafe
|
||||
HW/Latte/Core/LatteSoftware.h
|
||||
HW/Latte/Core/LatteStreamoutGPU.cpp
|
||||
HW/Latte/Core/LatteSurfaceCopy.cpp
|
||||
HW/Latte/Core/LatteSurfaceCopy.h
|
||||
HW/Latte/Core/LatteTextureCache.cpp
|
||||
HW/Latte/Core/LatteTexture.cpp
|
||||
HW/Latte/Core/LatteTexture.h
|
||||
|
||||
@ -59,6 +59,8 @@ public:
|
||||
PatchEntry() {};
|
||||
virtual ~PatchEntry() {};
|
||||
|
||||
virtual sint32 getLineNumber() { return -1; }
|
||||
|
||||
// apply relocation or evaluate any expressions for this entry
|
||||
virtual PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) = 0;
|
||||
};
|
||||
@ -74,7 +76,7 @@ public:
|
||||
m_expressionString.assign(expressionStr, expressionLen);
|
||||
}
|
||||
|
||||
sint32 getLineNumber() { return m_lineNumber; }
|
||||
sint32 getLineNumber() override { return m_lineNumber; }
|
||||
|
||||
PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override;
|
||||
|
||||
@ -109,7 +111,7 @@ public:
|
||||
m_expressionString.assign(expressionStr, expressionLen);
|
||||
}
|
||||
|
||||
sint32 getLineNumber() { return m_lineNumber; }
|
||||
sint32 getLineNumber() override { return m_lineNumber; }
|
||||
|
||||
PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override;
|
||||
|
||||
@ -134,7 +136,7 @@ public:
|
||||
m_symbolName.assign(symbolName, symbolNameLen);
|
||||
}
|
||||
|
||||
sint32 getLineNumber() { return m_lineNumber; }
|
||||
sint32 getLineNumber() override { return m_lineNumber; }
|
||||
|
||||
PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override;
|
||||
|
||||
@ -179,6 +181,8 @@ public:
|
||||
delete[] m_dataBackup;
|
||||
}
|
||||
|
||||
sint32 getLineNumber() override { return m_lineNumber; }
|
||||
|
||||
uint32 getAddr() const
|
||||
{
|
||||
return m_addr;
|
||||
|
||||
@ -49,30 +49,6 @@ bool GraphicPack2::ResolvePresetConstant(const std::string& varname, double& val
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T _expressionFuncHA(T input)
|
||||
{
|
||||
uint32 u32 = (uint32)input;
|
||||
u32 = (((u32 >> 16) + ((u32 & 0x8000) ? 1 : 0)) & 0xffff);
|
||||
return (T)u32;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T _expressionFuncHI(T input)
|
||||
{
|
||||
uint32 u32 = (uint32)input;
|
||||
u32 = (u32 >> 16) & 0xffff;
|
||||
return (T)u32;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T _expressionFuncLO(T input)
|
||||
{
|
||||
uint32 u32 = (uint32)input;
|
||||
u32 &= 0xffff;
|
||||
return (T)u32;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T _expressionFuncReloc(T input)
|
||||
{
|
||||
@ -89,18 +65,7 @@ T _expressionFuncReloc(T input)
|
||||
double _cbResolveConstant(std::string_view varname)
|
||||
{
|
||||
std::string varnameOnly;
|
||||
std::string tokenOnly;
|
||||
// detect suffix
|
||||
bool hasSuffix = false;
|
||||
const auto idx = varname.find('@');
|
||||
if (idx != std::string_view::npos)
|
||||
{
|
||||
hasSuffix = true;
|
||||
varnameOnly = varname.substr(0, idx);
|
||||
tokenOnly = varname.substr(idx + 1);
|
||||
}
|
||||
else
|
||||
varnameOnly = varname;
|
||||
varnameOnly = varname;
|
||||
|
||||
double value;
|
||||
if (varnameOnly.length() >= 1 && varnameOnly[0] == '$')
|
||||
@ -172,37 +137,6 @@ double _cbResolveConstant(std::string_view varname)
|
||||
}
|
||||
value = v->second;
|
||||
}
|
||||
if (hasSuffix)
|
||||
{
|
||||
std::transform(tokenOnly.cbegin(), tokenOnly.cend(), tokenOnly.begin(), tolower);
|
||||
if (tokenOnly == "ha")
|
||||
{
|
||||
value = _expressionFuncHA<double>(value);
|
||||
}
|
||||
else if (tokenOnly == "h" || tokenOnly == "hi")
|
||||
{
|
||||
value = _expressionFuncHI<double>(value);
|
||||
}
|
||||
else if (tokenOnly == "l" || tokenOnly == "lo")
|
||||
{
|
||||
value = _expressionFuncLO<double>(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we treat unknown suffixes as unresolveable symbols
|
||||
resolverState.hasUnknownVariable = true;
|
||||
if (resolverState.captureUnresolvedSymbols)
|
||||
{
|
||||
std::string detailedSymbolName;
|
||||
detailedSymbolName.assign(varnameOnly);
|
||||
detailedSymbolName.append("@");
|
||||
detailedSymbolName.append(tokenOnly);
|
||||
detailedSymbolName.append(" (invalid suffix)");
|
||||
resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -212,11 +146,11 @@ double _cbResolveFunction(std::string_view funcname, double input)
|
||||
std::transform(funcnameLC.cbegin(), funcnameLC.cend(), funcnameLC.begin(), tolower);
|
||||
double value = input;
|
||||
if (funcnameLC == "ha" || funcnameLC == "ha16")
|
||||
value = _expressionFuncHA<double>(value);
|
||||
value = TExpressionParser<double>::ExpressionFuncHA(value);
|
||||
else if (funcnameLC == "hi" || funcnameLC == "hi16")
|
||||
value = _expressionFuncHI<double>(value);
|
||||
value = TExpressionParser<double>::ExpressionFuncHI(value);
|
||||
else if (funcnameLC == "lo" || funcnameLC == "lo16")
|
||||
value = _expressionFuncLO<double>(value);
|
||||
value = TExpressionParser<double>::ExpressionFuncLO(value);
|
||||
else if (funcnameLC == "reloc")
|
||||
value = _expressionFuncReloc<double>(value);
|
||||
else
|
||||
@ -606,7 +540,8 @@ bool _resolverPass(PatchContext_t& patchContext, std::vector<UnresolvedPatches_t
|
||||
else
|
||||
{
|
||||
// unknown error
|
||||
patchContext.errorHandler.printError(resolverState.currentGroup, -1, "Internal error");
|
||||
sint32 lineNumber = (*it)->getLineNumber();
|
||||
patchContext.errorHandler.printError(resolverState.currentGroup, lineNumber, "Unable to parse expression or other internal error");
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,13 +13,50 @@
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false);
|
||||
|
||||
DebuggerDispatcher g_debuggerDispatcher;
|
||||
|
||||
debuggerState_t debuggerState{ };
|
||||
struct DebuggerState
|
||||
{
|
||||
bool breakOnEntry{false};
|
||||
bool logOnlyMemoryBreakpoints{false};
|
||||
// breakpoints
|
||||
std::recursive_mutex breakpointsMtx;
|
||||
std::vector<DebuggerBreakpoint*> breakpoints;
|
||||
// patches
|
||||
std::vector<DebuggerPatch*> patches;
|
||||
// active memory breakpoint (only one for now)
|
||||
DebuggerBreakpoint* activeMemoryBreakpoint{};
|
||||
// debugging session
|
||||
struct
|
||||
{
|
||||
std::mutex debugSessionMtx;
|
||||
std::atomic_bool shouldBreak; // request any thread to break asap
|
||||
std::atomic_bool isTrapped{false};
|
||||
uint32 debuggedThreadMPTR{};
|
||||
PPCInterpreter_t* hCPU{};
|
||||
// step control
|
||||
std::atomic<DebuggerStepCommand> stepCommand{DebuggerStepCommand::None};
|
||||
}debugSession;
|
||||
};
|
||||
|
||||
DebuggerState s_debuggerState{};
|
||||
|
||||
std::vector<DebuggerBreakpoint*>& debugger_lockBreakpoints()
|
||||
{
|
||||
s_debuggerState.breakpointsMtx.lock();
|
||||
return s_debuggerState.breakpoints;
|
||||
}
|
||||
|
||||
void debugger_unlockBreakpoints()
|
||||
{
|
||||
s_debuggerState.breakpointsMtx.unlock();
|
||||
}
|
||||
|
||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address)
|
||||
{
|
||||
for (auto& it : debuggerState.breakpoints)
|
||||
for (auto& it : s_debuggerState.breakpoints)
|
||||
{
|
||||
if (it->address == address)
|
||||
return it;
|
||||
@ -29,7 +66,7 @@ DebuggerBreakpoint* debugger_getFirstBP(uint32 address)
|
||||
|
||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType)
|
||||
{
|
||||
for (auto& it : debuggerState.breakpoints)
|
||||
for (auto& it : s_debuggerState.breakpoints)
|
||||
{
|
||||
if (it->address == address)
|
||||
{
|
||||
@ -46,6 +83,21 @@ DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DebuggerBreakpoint* debugger_getBreakpointById(BreakpointId bpId)
|
||||
{
|
||||
for (auto& it : s_debuggerState.breakpoints)
|
||||
{
|
||||
DebuggerBreakpoint* chain = it;
|
||||
while (chain)
|
||||
{
|
||||
if (chain->id == bpId)
|
||||
return chain;
|
||||
chain = chain->next;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool debuggerBPChain_hasType(DebuggerBreakpoint* bp, uint8 bpType)
|
||||
{
|
||||
while (bp)
|
||||
@ -69,7 +121,7 @@ void debuggerBPChain_add(uint32 address, DebuggerBreakpoint* bp)
|
||||
return;
|
||||
}
|
||||
// no breakpoint chain exists for this address
|
||||
debuggerState.breakpoints.push_back(bp);
|
||||
s_debuggerState.breakpoints.push_back(bp);
|
||||
}
|
||||
|
||||
uint32 debugger_getAddressOriginalOpcode(uint32 address)
|
||||
@ -148,7 +200,7 @@ void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp)
|
||||
std::vector<std::thread::native_handle_type> schedulerThreadHandles = coreinit::OSGetSchedulerThreads();
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
debuggerState.activeMemoryBreakpoint = bp;
|
||||
s_debuggerState.activeMemoryBreakpoint = bp;
|
||||
for (auto& hThreadNH : schedulerThreadHandles)
|
||||
{
|
||||
HANDLE hThread = (HANDLE)hThreadNH;
|
||||
@ -156,7 +208,7 @@ void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp)
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
SuspendThread(hThread);
|
||||
GetThreadContext(hThread, &ctx);
|
||||
if (debuggerState.activeMemoryBreakpoint)
|
||||
if (s_debuggerState.activeMemoryBreakpoint)
|
||||
{
|
||||
ctx.Dr0 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address);
|
||||
ctx.Dr1 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address);
|
||||
@ -196,24 +248,24 @@ void debugger_handleSingleStepException(uint64 dr6)
|
||||
if (triggeredDR0 && triggeredDR1)
|
||||
{
|
||||
// write (and read) access
|
||||
if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
if (s_debuggerState.activeMemoryBreakpoint && s_debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
catchBP = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// read access
|
||||
if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ)
|
||||
if (s_debuggerState.activeMemoryBreakpoint && s_debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ)
|
||||
catchBP = true;
|
||||
}
|
||||
if (catchBP)
|
||||
{
|
||||
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||
if (debuggerState.logOnlyMemoryBreakpoints)
|
||||
if (s_debuggerState.logOnlyMemoryBreakpoints)
|
||||
{
|
||||
float memValueF = memory_readFloat(debuggerState.activeMemoryBreakpoint->address);
|
||||
uint32 memValue = memory_readU32(debuggerState.activeMemoryBreakpoint->address);
|
||||
float memValueF = memory_readFloat(s_debuggerState.activeMemoryBreakpoint->address);
|
||||
uint32 memValue = memory_readU32(s_debuggerState.activeMemoryBreakpoint->address);
|
||||
cemuLog_log(LogType::Force, "[Debugger] 0x{:08X} was read/written! New Value: 0x{:08X} (float {}) IP: {:08X} LR: {:08X}",
|
||||
debuggerState.activeMemoryBreakpoint->address,
|
||||
s_debuggerState.activeMemoryBreakpoint->address,
|
||||
memValue,
|
||||
memValueF,
|
||||
hCPU->instructionPointer,
|
||||
@ -243,10 +295,10 @@ void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite)
|
||||
DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, 0xFFFFFFFF, bpType, true);
|
||||
debuggerBPChain_add(address, bp);
|
||||
// disable any already existing memory breakpoint
|
||||
if (debuggerState.activeMemoryBreakpoint)
|
||||
if (s_debuggerState.activeMemoryBreakpoint)
|
||||
{
|
||||
debuggerState.activeMemoryBreakpoint->enabled = false;
|
||||
debuggerState.activeMemoryBreakpoint = nullptr;
|
||||
s_debuggerState.activeMemoryBreakpoint->enabled = false;
|
||||
s_debuggerState.activeMemoryBreakpoint = nullptr;
|
||||
}
|
||||
// activate new breakpoint
|
||||
debugger_updateMemoryBreakpoint(bp);
|
||||
@ -254,42 +306,44 @@ void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite)
|
||||
|
||||
void debugger_handleEntryBreakpoint(uint32 address)
|
||||
{
|
||||
if (!debuggerState.breakOnEntry)
|
||||
if (!s_debuggerState.breakOnEntry)
|
||||
return;
|
||||
|
||||
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
||||
}
|
||||
|
||||
void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
|
||||
void debugger_deleteBreakpoint(BreakpointId bpId)
|
||||
{
|
||||
for (auto& it : debuggerState.breakpoints)
|
||||
std::unique_lock _l(s_debuggerState.breakpointsMtx);
|
||||
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
|
||||
if (!bp)
|
||||
return;
|
||||
for (auto& it : s_debuggerState.breakpoints)
|
||||
{
|
||||
if (it->address == bp->address)
|
||||
{
|
||||
// for execution breakpoints make sure the instruction is restored
|
||||
if (bp->isExecuteBP())
|
||||
if (it->isExecuteBP())
|
||||
{
|
||||
bp->enabled = false;
|
||||
debugger_updateExecutionBreakpoint(bp->address);
|
||||
it->enabled = false;
|
||||
debugger_updateExecutionBreakpoint(it->address);
|
||||
}
|
||||
// remove
|
||||
if (it == bp)
|
||||
{
|
||||
// remove first in list
|
||||
debuggerState.breakpoints.erase(std::remove(debuggerState.breakpoints.begin(), debuggerState.breakpoints.end(), bp), debuggerState.breakpoints.end());
|
||||
s_debuggerState.breakpoints.erase(std::remove(s_debuggerState.breakpoints.begin(), s_debuggerState.breakpoints.end(), bp), s_debuggerState.breakpoints.end());
|
||||
DebuggerBreakpoint* nextBP = bp->next;
|
||||
if (nextBP)
|
||||
debuggerState.breakpoints.push_back(nextBP);
|
||||
s_debuggerState.breakpoints.push_back(nextBP);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove from list
|
||||
DebuggerBreakpoint* bpItr = it;
|
||||
while (bpItr->next != bp)
|
||||
{
|
||||
bpItr = bpItr->next;
|
||||
}
|
||||
cemu_assert_debug(bpItr->next != bp);
|
||||
cemu_assert_debug(bpItr->next == bp);
|
||||
bpItr->next = bp->next;
|
||||
}
|
||||
delete bp;
|
||||
@ -300,11 +354,12 @@ void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
|
||||
|
||||
void debugger_toggleExecuteBreakpoint(uint32 address)
|
||||
{
|
||||
std::unique_lock _l(s_debuggerState.breakpointsMtx);
|
||||
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_NORMAL);
|
||||
if (existingBP)
|
||||
{
|
||||
// delete existing breakpoint
|
||||
debugger_deleteBreakpoint(existingBP);
|
||||
debugger_deleteBreakpoint(existingBP->id);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -315,11 +370,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
|
||||
|
||||
void debugger_toggleLoggingBreakpoint(uint32 address)
|
||||
{
|
||||
std::unique_lock _l(s_debuggerState.breakpointsMtx);
|
||||
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
|
||||
if (existingBP)
|
||||
{
|
||||
// delete existing breakpoint
|
||||
debugger_deleteBreakpoint(existingBP);
|
||||
debugger_deleteBreakpoint(existingBP->id);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -328,20 +384,55 @@ void debugger_toggleLoggingBreakpoint(uint32 address)
|
||||
}
|
||||
}
|
||||
|
||||
void debugger_forceBreak()
|
||||
{
|
||||
debuggerState.debugSession.shouldBreak = true;
|
||||
}
|
||||
|
||||
bool debugger_isTrapped()
|
||||
{
|
||||
return debuggerState.debugSession.isTrapped;
|
||||
return s_debuggerState.debugSession.isTrapped;
|
||||
}
|
||||
|
||||
PPCInterpreter_t* debugger_lockDebugSession()
|
||||
{
|
||||
s_debuggerState.debugSession.debugSessionMtx.lock();
|
||||
if (!s_debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
s_debuggerState.debugSession.debugSessionMtx.unlock();
|
||||
return nullptr;
|
||||
}
|
||||
return s_debuggerState.debugSession.hCPU;
|
||||
}
|
||||
|
||||
void debugger_unlockDebugSession(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
cemu_assert_debug(s_debuggerState.debugSession.isTrapped);
|
||||
cemu_assert_debug(s_debuggerState.debugSession.hCPU == hCPU);
|
||||
s_debuggerState.debugSession.debugSessionMtx.unlock();
|
||||
}
|
||||
|
||||
PPCSnapshot debugger_getSnapshotFromSession(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
cemu_assert_debug(s_debuggerState.debugSession.isTrapped);
|
||||
cemu_assert_debug(s_debuggerState.debugSession.hCPU == hCPU);
|
||||
PPCSnapshot snapshot{};
|
||||
memcpy(snapshot.gpr, hCPU->gpr, sizeof(uint32) * 32);
|
||||
memcpy(snapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32);
|
||||
snapshot.spr_lr = hCPU->spr.LR;
|
||||
for (uint32 i = 0; i < 32; i++)
|
||||
snapshot.cr[i] = hCPU->cr[i];
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void debugger_resume()
|
||||
{
|
||||
// if there is a breakpoint on the current instruction then do a single 'step into' to skip it
|
||||
debuggerState.debugSession.run = true;
|
||||
s_debuggerState.debugSession.stepCommand = DebuggerStepCommand::Run;
|
||||
}
|
||||
|
||||
void debugger_stepCommand(DebuggerStepCommand stepCommand)
|
||||
{
|
||||
s_debuggerState.debugSession.stepCommand = stepCommand;
|
||||
}
|
||||
|
||||
void debugger_requestBreak()
|
||||
{
|
||||
s_debuggerState.debugSession.shouldBreak = true;
|
||||
}
|
||||
|
||||
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp)
|
||||
@ -351,6 +442,8 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
|
||||
{
|
||||
if (bpItr == bp)
|
||||
{
|
||||
if (bp->enabled == state)
|
||||
return;
|
||||
if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING)
|
||||
{
|
||||
bp->enabled = state;
|
||||
@ -360,7 +453,7 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
|
||||
else if (bpItr->isMemBP())
|
||||
{
|
||||
// disable other memory breakpoints
|
||||
for (auto& it : debuggerState.breakpoints)
|
||||
for (auto& it : s_debuggerState.breakpoints)
|
||||
{
|
||||
DebuggerBreakpoint* bpItr2 = it;
|
||||
while (bpItr2)
|
||||
@ -410,9 +503,9 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
|
||||
}
|
||||
}
|
||||
// merge with existing patches if the ranges touch
|
||||
for(sint32 i=0; i<debuggerState.patches.size(); i++)
|
||||
for(sint32 i=0; i<s_debuggerState.patches.size(); i++)
|
||||
{
|
||||
auto& patchItr = debuggerState.patches[i];
|
||||
auto& patchItr = s_debuggerState.patches[i];
|
||||
if (address + patchData.size() >= patchItr->address && address <= patchItr->address + patchItr->length)
|
||||
{
|
||||
uint32 newAddress = std::min(patch->address, patchItr->address);
|
||||
@ -434,11 +527,11 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
|
||||
patch = newPatch;
|
||||
delete patchItr;
|
||||
// remove currently iterated patch
|
||||
debuggerState.patches.erase(debuggerState.patches.begin()+i);
|
||||
s_debuggerState.patches.erase(s_debuggerState.patches.begin()+i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
debuggerState.patches.push_back(patch);
|
||||
s_debuggerState.patches.push_back(patch);
|
||||
// apply patch (if breakpoints exist then update those instead of actual data)
|
||||
if ((address & 3) != 0)
|
||||
cemu_assert_debug(false);
|
||||
@ -468,7 +561,7 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
|
||||
|
||||
bool debugger_hasPatch(uint32 address)
|
||||
{
|
||||
for (auto& patch : debuggerState.patches)
|
||||
for (auto& patch : s_debuggerState.patches)
|
||||
{
|
||||
if (address + 4 > patch->address && address < patch->address + patch->length)
|
||||
return true;
|
||||
@ -478,15 +571,15 @@ bool debugger_hasPatch(uint32 address)
|
||||
|
||||
void debugger_removePatch(uint32 address)
|
||||
{
|
||||
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
|
||||
for (sint32 i = 0; i < s_debuggerState.patches.size(); i++)
|
||||
{
|
||||
auto& patch = debuggerState.patches[i];
|
||||
auto& patch = s_debuggerState.patches[i];
|
||||
if (address < patch->address || address >= (patch->address + patch->length))
|
||||
continue;
|
||||
MPTR startAddress = patch->address;
|
||||
MPTR endAddress = patch->address + patch->length;
|
||||
// remove any breakpoints overlapping with the patch
|
||||
for (auto& bp : debuggerState.breakpoints)
|
||||
for (auto& bp : s_debuggerState.breakpoints)
|
||||
{
|
||||
if (bp->address + 4 > startAddress && bp->address < endAddress)
|
||||
{
|
||||
@ -499,228 +592,204 @@ void debugger_removePatch(uint32 address)
|
||||
PPCRecompiler_invalidateRange(startAddress, endAddress);
|
||||
// remove patch
|
||||
delete patch;
|
||||
debuggerState.patches.erase(debuggerState.patches.begin() + i);
|
||||
s_debuggerState.patches.erase(s_debuggerState.patches.begin() + i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
||||
bool debugger_CanStepOverInstruction(MPTR address)
|
||||
{
|
||||
bool isRecEnabled = ppcRecompilerEnabled;
|
||||
ppcRecompilerEnabled = false;
|
||||
uint32 initialIP = debuggerState.debugSession.instructionPointer;
|
||||
debugger_updateExecutionBreakpoint(initialIP, true);
|
||||
PPCInterpreterSlim_executeInstruction(hCPU);
|
||||
debugger_updateExecutionBreakpoint(initialIP);
|
||||
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
||||
if(updateDebuggerWindow)
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
ppcRecompilerEnabled = isRecEnabled;
|
||||
}
|
||||
|
||||
bool debugger_stepOver(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
bool isRecEnabled = ppcRecompilerEnabled;
|
||||
ppcRecompilerEnabled = false;
|
||||
// disassemble current instruction
|
||||
PPCDisassembledInstruction disasmInstr = { 0 };
|
||||
uint32 initialIP = debuggerState.debugSession.instructionPointer;
|
||||
debugger_updateExecutionBreakpoint(initialIP, true);
|
||||
ppcAssembler_disassemble(initialIP, memory_readU32(initialIP), &disasmInstr);
|
||||
if (disasmInstr.ppcAsmCode != PPCASM_OP_BL &&
|
||||
disasmInstr.ppcAsmCode != PPCASM_OP_BCTRL)
|
||||
{
|
||||
// nothing to skip, use step-into
|
||||
debugger_stepInto(hCPU);
|
||||
debugger_updateExecutionBreakpoint(initialIP);
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
ppcRecompilerEnabled = isRecEnabled;
|
||||
return false;
|
||||
}
|
||||
// create one-shot breakpoint at next instruction
|
||||
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
|
||||
// step over current instruction (to avoid breakpoint)
|
||||
debugger_stepInto(hCPU);
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
// restore breakpoints
|
||||
debugger_updateExecutionBreakpoint(initialIP);
|
||||
// run
|
||||
ppcRecompilerEnabled = isRecEnabled;
|
||||
return true;
|
||||
ppcAssembler_disassemble(address, debugger_getAddressOriginalOpcode(address), &disasmInstr);
|
||||
return disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA || disasmInstr.ppcAsmCode == PPCASM_OP_BCTRL;
|
||||
}
|
||||
|
||||
void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
|
||||
void debugger_handleLoggingBreakpoint(PPCInterpreter_t* hCPU, DebuggerBreakpoint* bp)
|
||||
{
|
||||
memcpy(debuggerState.debugSession.ppcSnapshot.gpr, hCPU->gpr, sizeof(uint32) * 32);
|
||||
memcpy(debuggerState.debugSession.ppcSnapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32);
|
||||
debuggerState.debugSession.ppcSnapshot.spr_lr = hCPU->spr.LR;
|
||||
for (uint32 i = 0; i < 32; i++)
|
||||
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
|
||||
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
||||
|
||||
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while ((pos = comment.find(prefix, pos)) != std::string::npos)
|
||||
{
|
||||
size_t endPos = comment.find('}', pos);
|
||||
if (endPos == std::string::npos)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
|
||||
{
|
||||
std::string replacement = formatFunc(regNum);
|
||||
comment.replace(pos, endPos - pos + 1, replacement);
|
||||
pos += replacement.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = endPos + 1;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pos = endPos + 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Replace integer register placeholders {rX}
|
||||
replacePlaceholders("{r", [&](int regNum) {
|
||||
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
|
||||
});
|
||||
|
||||
// Replace floating point register placeholders {fX}
|
||||
replacePlaceholders("{f", [&](int regNum) {
|
||||
return fmt::format("{}", hCPU->fpr[regNum].fpr);
|
||||
});
|
||||
|
||||
std::string logName = "Breakpoint '" + comment + "'";
|
||||
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
||||
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
||||
if (cemuLog_advancedPPCLoggingEnabled())
|
||||
DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]);
|
||||
}
|
||||
|
||||
void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||
// used to escape the breakpoint on the current instruction
|
||||
void debugger_stepOverCurrentBreakpoint(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
// Currently, we don't support multiple threads inside the debugger. Spin loop a thread if we already paused for another breakpoint hit.
|
||||
while (debuggerState.debugSession.isTrapped)
|
||||
std::unique_lock _l(s_debuggerState.breakpointsMtx);
|
||||
PPCRecompiler_Enable();
|
||||
MPTR bpAddress = hCPU->instructionPointer;
|
||||
debugger_updateExecutionBreakpoint(bpAddress, true);
|
||||
PPCInterpreterSlim_executeInstruction(hCPU);
|
||||
debugger_updateExecutionBreakpoint(bpAddress);
|
||||
PPCRecompiler_Disable();
|
||||
}
|
||||
|
||||
void debugger_enterTW(PPCInterpreter_t* hCPU, bool isSingleStep)
|
||||
{
|
||||
s_debuggerState.debugSession.debugSessionMtx.lock();
|
||||
while ( true )
|
||||
{
|
||||
bool trappedExpectedVal = false;
|
||||
if ( s_debuggerState.debugSession.isTrapped.compare_exchange_strong(trappedExpectedVal, true) )
|
||||
break;
|
||||
s_debuggerState.debugSession.debugSessionMtx.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
s_debuggerState.debugSession.debugSessionMtx.lock();
|
||||
}
|
||||
|
||||
// init debug state
|
||||
s_debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
|
||||
s_debuggerState.debugSession.hCPU = hCPU;
|
||||
s_debuggerState.debugSession.stepCommand = DebuggerStepCommand::None;
|
||||
s_debuggerState.debugSession.debugSessionMtx.unlock();
|
||||
// handle logging points
|
||||
DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer);
|
||||
bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT);
|
||||
while (bp)
|
||||
{
|
||||
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
||||
{
|
||||
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
||||
|
||||
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while ((pos = comment.find(prefix, pos)) != std::string::npos)
|
||||
{
|
||||
size_t endPos = comment.find('}', pos);
|
||||
if (endPos == std::string::npos)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
|
||||
{
|
||||
std::string replacement = formatFunc(regNum);
|
||||
comment.replace(pos, endPos - pos + 1, replacement);
|
||||
pos += replacement.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = endPos + 1;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pos = endPos + 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Replace integer register placeholders {rX}
|
||||
replacePlaceholders("{r", [&](int regNum) {
|
||||
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
|
||||
});
|
||||
|
||||
// Replace floating point register placeholders {fX}
|
||||
replacePlaceholders("{f", [&](int regNum) {
|
||||
return fmt::format("{}", hCPU->fpr[regNum].fpr);
|
||||
});
|
||||
|
||||
std::string logName = "Breakpoint '" + comment + "'";
|
||||
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
||||
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
||||
if (cemuLog_advancedPPCLoggingEnabled())
|
||||
DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]);
|
||||
break;
|
||||
}
|
||||
debugger_handleLoggingBreakpoint(hCPU, bp);
|
||||
bp = bp->next;
|
||||
}
|
||||
|
||||
// return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates
|
||||
if (!shouldBreak)
|
||||
// for logging breakpoints skip the waiting and notifying of the UI
|
||||
DebuggerStepCommand stepCommand = DebuggerStepCommand::Run;
|
||||
if (shouldBreak || isSingleStep)
|
||||
{
|
||||
uint32 backupIP = debuggerState.debugSession.instructionPointer;
|
||||
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
||||
debugger_stepInto(hCPU, false);
|
||||
PPCInterpreterSlim_executeInstruction(hCPU);
|
||||
debuggerState.debugSession.instructionPointer = backupIP;
|
||||
return;
|
||||
}
|
||||
|
||||
// handle breakpoints
|
||||
debuggerState.debugSession.isTrapped = true;
|
||||
debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
|
||||
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
||||
debuggerState.debugSession.hCPU = hCPU;
|
||||
debugger_createPPCStateSnapshot(hCPU);
|
||||
// remove one-shot breakpoint if it exists
|
||||
DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(debuggerState.debugSession.instructionPointer, DEBUGGER_BP_T_ONE_SHOT);
|
||||
if (singleshotBP)
|
||||
debugger_deleteBreakpoint(singleshotBP);
|
||||
g_debuggerDispatcher.NotifyDebugBreakpointHit();
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
// reset step control
|
||||
debuggerState.debugSession.stepInto = false;
|
||||
debuggerState.debugSession.stepOver = false;
|
||||
debuggerState.debugSession.run = false;
|
||||
while (debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// check for step commands
|
||||
if (debuggerState.debugSession.stepOver)
|
||||
// remove one-shot breakpoint if it exists
|
||||
DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(hCPU->instructionPointer, DEBUGGER_BP_T_ONE_SHOT);
|
||||
if (singleshotBP)
|
||||
debugger_deleteBreakpoint(singleshotBP->id);
|
||||
g_debuggerDispatcher.NotifyDebugBreakpointHit();
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
while (s_debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
if (debugger_stepOver(hCPU))
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// check for step commands from UI
|
||||
stepCommand = s_debuggerState.debugSession.stepCommand.exchange(DebuggerStepCommand::None);
|
||||
if (stepCommand != DebuggerStepCommand::None)
|
||||
break;
|
||||
if (!coreinit::OSIsSchedulerActive())
|
||||
{
|
||||
debugger_createPPCStateSnapshot(hCPU);
|
||||
break; // if true is returned, continue with execution
|
||||
// quit if scheduler is being shutdown
|
||||
stepCommand = DebuggerStepCommand::Run;
|
||||
break;
|
||||
}
|
||||
debugger_createPPCStateSnapshot(hCPU);
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
debuggerState.debugSession.stepOver = false;
|
||||
}
|
||||
if (debuggerState.debugSession.stepInto)
|
||||
if (stepCommand == DebuggerStepCommand::StepOver && !debugger_CanStepOverInstruction(hCPU->instructionPointer))
|
||||
{
|
||||
debugger_stepInto(hCPU);
|
||||
debugger_createPPCStateSnapshot(hCPU);
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
debuggerState.debugSession.stepInto = false;
|
||||
continue;
|
||||
}
|
||||
if (debuggerState.debugSession.run)
|
||||
{
|
||||
debugger_createPPCStateSnapshot(hCPU);
|
||||
debugger_stepInto(hCPU, false);
|
||||
PPCInterpreterSlim_executeInstruction(hCPU);
|
||||
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
||||
debuggerState.debugSession.run = false;
|
||||
break;
|
||||
stepCommand = DebuggerStepCommand::StepInto;
|
||||
}
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
g_debuggerDispatcher.NotifyRun();
|
||||
}
|
||||
s_debuggerState.debugSession.debugSessionMtx.lock();
|
||||
s_debuggerState.debugSession.hCPU = nullptr;
|
||||
s_debuggerState.debugSession.isTrapped = false;
|
||||
s_debuggerState.debugSession.debugSessionMtx.unlock();
|
||||
// leave the breakpointed instruction
|
||||
MPTR initialIP = hCPU->instructionPointer;
|
||||
debugger_stepOverCurrentBreakpoint(hCPU);
|
||||
|
||||
debuggerState.debugSession.isTrapped = false;
|
||||
debuggerState.debugSession.hCPU = nullptr;
|
||||
g_debuggerDispatcher.UpdateViewThreadsafe();
|
||||
g_debuggerDispatcher.NotifyRun();
|
||||
}
|
||||
// todo - if the current instruction is a HLE gateway, then we need to handle it with special logic (instead of generic debugger_stepOverCurrentBreakpoint):
|
||||
// First grab the HLE index from the original opcode and look up the function pointer
|
||||
// And then call the HLE func (must happen last, since it may not return immediately or ever)
|
||||
|
||||
void debugger_shouldBreak(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
if(debuggerState.debugSession.shouldBreak
|
||||
// exclude emulator trampoline area
|
||||
&& (hCPU->instructionPointer < MEMORY_CODE_TRAMPOLINE_AREA_ADDR || hCPU->instructionPointer > MEMORY_CODE_TRAMPOLINE_AREA_ADDR + MEMORY_CODE_TRAMPOLINE_AREA_SIZE))
|
||||
if (stepCommand == DebuggerStepCommand::StepInto)
|
||||
{
|
||||
debuggerState.debugSession.shouldBreak = false;
|
||||
|
||||
const uint32 address = (uint32)hCPU->instructionPointer;
|
||||
assert_dbg();
|
||||
//debugger_createBreakpoint(address, DEBUGGER_BP_TYPE_ONE_SHOT);
|
||||
debugger_enterTW(hCPU, true);
|
||||
}
|
||||
else if (stepCommand == DebuggerStepCommand::StepOver)
|
||||
{
|
||||
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
|
||||
void debugger_addParserSymbols(class ExpressionParser& ep)
|
||||
{
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
|
||||
std::vector<double> module_tmp(module_count);
|
||||
for (int i = 0; i < module_count; i++)
|
||||
const auto moduleCount = RPLLoader_GetModuleCount();
|
||||
const auto moduleList = RPLLoader_GetModuleList();
|
||||
std::vector<double> moduleTmp(moduleCount);
|
||||
for (sint32 i = 0; i < moduleCount; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
const auto module = moduleList[i];
|
||||
if (module)
|
||||
{
|
||||
module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
|
||||
ep.AddConstant(module->moduleName2, module_tmp[i]);
|
||||
moduleTmp[i] = (double)module->regionMappingBase_text.GetMPTR();
|
||||
ep.AddConstant(module->moduleName2, moduleTmp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (sint32 i = 0; i < 32; i++)
|
||||
ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]);
|
||||
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
|
||||
if (hCPU)
|
||||
{
|
||||
PPCSnapshot snapshot = debugger_getSnapshotFromSession(hCPU);
|
||||
for (sint32 i = 0; i < 32; i++)
|
||||
ep.AddConstant(fmt::format("r{}", i), snapshot.gpr[i]);
|
||||
debugger_unlockDebugSession(hCPU);
|
||||
}
|
||||
}
|
||||
|
||||
void debugger_jumpToAddressInDisasm(MPTR address)
|
||||
{
|
||||
g_debuggerDispatcher.MoveToAddressInDisassembly(address);
|
||||
}
|
||||
|
||||
void debugger_setOptionBreakOnEntry(bool isEnabled)
|
||||
{
|
||||
s_debuggerState.breakOnEntry = isEnabled;
|
||||
}
|
||||
|
||||
void debugger_setOptionLogOnlyMemoryBreakpoints(bool isEnabled)
|
||||
{
|
||||
s_debuggerState.logOnlyMemoryBreakpoints = isEnabled;
|
||||
}
|
||||
|
||||
bool debugger_getOptionBreakOnEntry()
|
||||
{
|
||||
return s_debuggerState.breakOnEntry;
|
||||
}
|
||||
|
||||
bool debugger_getOptionLogOnlyMemoryBreakpoints()
|
||||
{
|
||||
return s_debuggerState.logOnlyMemoryBreakpoints;
|
||||
}
|
||||
@ -21,9 +21,9 @@ class DebuggerCallbacks
|
||||
virtual void UpdateViewThreadsafe() {}
|
||||
virtual void NotifyDebugBreakpointHit() {}
|
||||
virtual void NotifyRun() {}
|
||||
virtual void MoveIP() {}
|
||||
virtual void NotifyModuleLoaded(void* module) {}
|
||||
virtual void NotifyModuleUnloaded(void* module) {}
|
||||
virtual void MoveToAddressInDisassembly(MPTR address) {}
|
||||
virtual void NotifyModuleLoaded(struct RPLModule* module) {}
|
||||
virtual void NotifyModuleUnloaded(struct RPLModule* module) {}
|
||||
virtual void NotifyGraphicPacksModified() {}
|
||||
virtual ~DebuggerCallbacks() = default;
|
||||
};
|
||||
@ -64,17 +64,17 @@ class DebuggerDispatcher
|
||||
m_callbacks->NotifyRun();
|
||||
}
|
||||
|
||||
void MoveIP()
|
||||
void MoveToAddressInDisassembly(MPTR address)
|
||||
{
|
||||
m_callbacks->MoveIP();
|
||||
m_callbacks->MoveToAddressInDisassembly(address);
|
||||
}
|
||||
|
||||
void NotifyModuleLoaded(void* module)
|
||||
void NotifyModuleLoaded(struct RPLModule* module)
|
||||
{
|
||||
m_callbacks->NotifyModuleLoaded(module);
|
||||
}
|
||||
|
||||
void NotifyModuleUnloaded(void* module)
|
||||
void NotifyModuleUnloaded(struct RPLModule* module)
|
||||
{
|
||||
m_callbacks->NotifyModuleUnloaded(module);
|
||||
}
|
||||
@ -85,8 +85,11 @@ class DebuggerDispatcher
|
||||
}
|
||||
} extern g_debuggerDispatcher;
|
||||
|
||||
using BreakpointId = uint64;
|
||||
|
||||
struct DebuggerBreakpoint
|
||||
{
|
||||
BreakpointId id;
|
||||
uint32 address;
|
||||
uint32 originalOpcodeValue;
|
||||
mutable uint8 bpType;
|
||||
@ -97,10 +100,11 @@ struct DebuggerBreakpoint
|
||||
DebuggerBreakpoint(uint32 address, uint32 originalOpcode, uint8 bpType = 0, bool enabled = true, std::wstring comment = std::wstring())
|
||||
:address(address), originalOpcodeValue(originalOpcode), bpType(bpType), enabled(enabled), comment(std::move(comment))
|
||||
{
|
||||
static std::atomic<uint64_t> bpIdGen{1};
|
||||
id = bpIdGen.fetch_add(1);
|
||||
next = nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool operator<(const DebuggerBreakpoint& rhs) const
|
||||
{
|
||||
return address < rhs.address;
|
||||
@ -133,64 +137,53 @@ struct DebuggerPatch
|
||||
|
||||
struct PPCSnapshot
|
||||
{
|
||||
uint32 gpr[32];
|
||||
FPR_t fpr[32];
|
||||
uint8 cr[32];
|
||||
uint32 spr_lr;
|
||||
uint32 gpr[32]{};
|
||||
FPR_t fpr[32]{};
|
||||
uint8 cr[32]{};
|
||||
uint32 spr_lr{};
|
||||
};
|
||||
|
||||
typedef struct
|
||||
enum class DebuggerStepCommand : uint8
|
||||
{
|
||||
bool breakOnEntry;
|
||||
bool logOnlyMemoryBreakpoints;
|
||||
// breakpoints
|
||||
std::vector<DebuggerBreakpoint*> breakpoints;
|
||||
std::vector<DebuggerPatch*> patches;
|
||||
DebuggerBreakpoint* activeMemoryBreakpoint;
|
||||
// debugging state
|
||||
struct
|
||||
{
|
||||
volatile bool shouldBreak; // debugger window requests a break asap
|
||||
volatile bool isTrapped; // if set, breakpoint is active and stepping through the code is possible
|
||||
uint32 debuggedThreadMPTR;
|
||||
volatile uint32 instructionPointer;
|
||||
PPCInterpreter_t* hCPU;
|
||||
// step control
|
||||
volatile bool stepOver;
|
||||
volatile bool stepInto;
|
||||
volatile bool run;
|
||||
// snapshot of PPC state
|
||||
PPCSnapshot ppcSnapshot;
|
||||
}debugSession;
|
||||
None,
|
||||
StepInto,
|
||||
StepOver,
|
||||
Run
|
||||
};
|
||||
|
||||
}debuggerState_t;
|
||||
|
||||
extern debuggerState_t debuggerState;
|
||||
|
||||
// new API
|
||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
||||
// breakpoint API
|
||||
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
||||
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
||||
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
||||
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
|
||||
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
||||
void debugger_deleteBreakpoint(BreakpointId bpId);
|
||||
std::vector<DebuggerBreakpoint*>& debugger_lockBreakpoints();
|
||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
||||
DebuggerBreakpoint* debugger_getBreakpointById(BreakpointId bpId);
|
||||
void debugger_unlockBreakpoints();
|
||||
|
||||
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
||||
|
||||
void debugger_handleEntryBreakpoint(uint32 address);
|
||||
|
||||
void debugger_deleteBreakpoint(DebuggerBreakpoint* bp);
|
||||
|
||||
void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false);
|
||||
|
||||
// patch API
|
||||
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
||||
bool debugger_hasPatch(uint32 address);
|
||||
void debugger_removePatch(uint32 address);
|
||||
|
||||
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
||||
// debug session
|
||||
PPCInterpreter_t* debugger_lockDebugSession();
|
||||
void debugger_unlockDebugSession(PPCInterpreter_t* hCPU);
|
||||
PPCSnapshot debugger_getSnapshotFromSession(PPCInterpreter_t* hCPU);
|
||||
void debugger_stepCommand(DebuggerStepCommand stepCommand);
|
||||
|
||||
// misc
|
||||
void debugger_requestBreak(); // break at the next possible instruction (any thread)
|
||||
bool debugger_isTrapped();
|
||||
void debugger_resume();
|
||||
|
||||
void debugger_enterTW(PPCInterpreter_t* hCPU);
|
||||
void debugger_shouldBreak(PPCInterpreter_t* hCPU);
|
||||
|
||||
void debugger_handleEntryBreakpoint(uint32 address);
|
||||
void debugger_enterTW(PPCInterpreter_t* hCPU, bool isSingleStep = false);
|
||||
void debugger_jumpToAddressInDisasm(MPTR address);
|
||||
void debugger_addParserSymbols(class ExpressionParser& ep);
|
||||
|
||||
// options
|
||||
void debugger_setOptionBreakOnEntry(bool isEnabled);
|
||||
bool debugger_getOptionBreakOnEntry();
|
||||
void debugger_setOptionLogOnlyMemoryBreakpoints(bool isEnabled);
|
||||
bool debugger_getOptionLogOnlyMemoryBreakpoints();
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#include "PPCFunctionBoundaryTracker.h"
|
||||
#include "PPCRecompiler.h"
|
||||
#include "PPCRecompilerIml.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "util/containers/RangeStore.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
@ -24,22 +23,35 @@
|
||||
#define PPCREC_FORCE_SYNCHRONOUS_COMPILATION 0 // if 1, then function recompilation will block and execute on the thread that called PPCRecompiler_visitAddressNoBlock
|
||||
#define PPCREC_LOG_RECOMPILATION_RESULTS 0
|
||||
|
||||
struct PPCInvalidationRange
|
||||
struct ppcInvalidationRange
|
||||
{
|
||||
MPTR startAddress;
|
||||
uint32 size;
|
||||
|
||||
PPCInvalidationRange(MPTR _startAddress, uint32 _size) : startAddress(_startAddress), size(_size) {};
|
||||
ppcInvalidationRange(MPTR _startAddress, uint32 _size) : startAddress(_startAddress), size(_size) {};
|
||||
};
|
||||
|
||||
struct ppcRecompilerFuncRange
|
||||
{
|
||||
MPTR ppcStart;
|
||||
uint32 ppcSize;
|
||||
void* x86Start;
|
||||
size_t x86Size;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
std::atomic_bool initialized{false};
|
||||
FSpinlock recompilerSpinlock;
|
||||
std::queue<MPTR> targetQueue;
|
||||
std::vector<PPCInvalidationRange> invalidationRanges;
|
||||
}PPCRecompilerState;
|
||||
|
||||
RangeStore<PPCRecFunction_t*, uint32, 7703, 0x2000> rangeStore_ppcRanges;
|
||||
std::vector<ppcInvalidationRange> invalidationRanges;
|
||||
std::atomic_int_fast32_t recompilerEnableCount{0};
|
||||
// recompiler thread
|
||||
std::thread workerThread;
|
||||
std::atomic_bool workerThreadStopSignal{false};
|
||||
// function storage
|
||||
RangeStore<PPCRecFunction_t*, uint32, 7703, 0x2000> functionStorage;
|
||||
}s_ppcRecompilerState;
|
||||
|
||||
void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uint64 ppcInterpreterInstance);
|
||||
void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)();
|
||||
@ -51,8 +63,6 @@ PPCRecompilerInstanceData_t* ppcRecompilerInstanceData;
|
||||
static std::mutex s_singleRecompilationMutex;
|
||||
#endif
|
||||
|
||||
bool ppcRecompilerEnabled = false;
|
||||
|
||||
void PPCRecompiler_recompileAtAddress(uint32 address);
|
||||
|
||||
// this function does never block and can fail if the recompiler lock cannot be acquired immediately
|
||||
@ -61,14 +71,14 @@ void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress)
|
||||
#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited)
|
||||
return;
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited)
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
return;
|
||||
}
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited;
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
s_singleRecompilationMutex.lock();
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] == PPCRecompiler_leaveRecompilerCode_visited)
|
||||
{
|
||||
@ -81,25 +91,25 @@ void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress)
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited)
|
||||
return;
|
||||
// try to acquire lock
|
||||
if (!PPCRecompilerState.recompilerSpinlock.try_lock())
|
||||
if (!s_ppcRecompilerState.recompilerSpinlock.try_lock())
|
||||
return;
|
||||
auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4];
|
||||
if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited)
|
||||
{
|
||||
// was visited since previous check
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
return;
|
||||
}
|
||||
// add to recompilation queue and flag as visited
|
||||
PPCRecompilerState.targetQueue.emplace(enterAddress);
|
||||
s_ppcRecompilerState.targetQueue.emplace(enterAddress);
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited;
|
||||
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
}
|
||||
|
||||
void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress)
|
||||
{
|
||||
if (ppcRecompilerEnabled == false)
|
||||
if (s_ppcRecompilerState.recompilerEnableCount <= 0)
|
||||
return;
|
||||
PPCRecompiler_visitAddressNoBlock(enterAddress);
|
||||
}
|
||||
@ -133,7 +143,7 @@ void PPCRecompiler_enter(PPCInterpreter_t* hCPU, PPCREC_JUMP_ENTRY funcPtr)
|
||||
void PPCRecompiler_attemptEnterWithoutRecompile(PPCInterpreter_t* hCPU, uint32 enterAddress)
|
||||
{
|
||||
cemu_assert_debug(hCPU->instructionPointer == enterAddress);
|
||||
if (ppcRecompilerEnabled == false)
|
||||
if (s_ppcRecompilerState.recompilerEnableCount <= 0)
|
||||
return;
|
||||
auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4];
|
||||
if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited && funcPtr != PPCRecompiler_leaveRecompilerCode_visited)
|
||||
@ -146,7 +156,7 @@ void PPCRecompiler_attemptEnterWithoutRecompile(PPCInterpreter_t* hCPU, uint32 e
|
||||
void PPCRecompiler_attemptEnter(PPCInterpreter_t* hCPU, uint32 enterAddress)
|
||||
{
|
||||
cemu_assert_debug(hCPU->instructionPointer == enterAddress);
|
||||
if (ppcRecompilerEnabled == false)
|
||||
if (s_ppcRecompilerState.recompilerEnableCount <= 0)
|
||||
return;
|
||||
if (hCPU->remainingCycles <= 0)
|
||||
return;
|
||||
@ -351,51 +361,51 @@ bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext)
|
||||
bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFunctionBoundaryTracker::PPCRange_t& range, PPCRecFunction_t* ppcRecFunc, std::vector<std::pair<MPTR, uint32>>& entryPoints)
|
||||
{
|
||||
// update jump table
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
|
||||
// check if the initial entrypoint is still flagged for recompilation
|
||||
// its possible that the range has been invalidated during the time it took to translate the function
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[initialEntryPoint / 4] != PPCRecompiler_leaveRecompilerCode_visited)
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the current range got invalidated during the time it took to recompile it
|
||||
bool isInvalidated = false;
|
||||
for (auto& invRange : PPCRecompilerState.invalidationRanges)
|
||||
for (auto& invRange : s_ppcRecompilerState.invalidationRanges)
|
||||
{
|
||||
MPTR rStartAddr = invRange.startAddress;
|
||||
MPTR rEndAddr = rStartAddr + invRange.size;
|
||||
for (auto& recFuncRange : ppcRecFunc->list_ranges)
|
||||
{
|
||||
if (recFuncRange.ppcAddress < (rEndAddr) && (recFuncRange.ppcAddress + recFuncRange.ppcSize) >= rStartAddr)
|
||||
if (recFuncRange.ppcAddress < (rEndAddr) && (recFuncRange.ppcAddress + recFuncRange.ppcSize) > rStartAddr)
|
||||
{
|
||||
isInvalidated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
PPCRecompilerState.invalidationRanges.clear();
|
||||
s_ppcRecompilerState.invalidationRanges.clear();
|
||||
if (isInvalidated)
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// update jump table
|
||||
// update jump table and remember which entries we updated
|
||||
cemu_assert_debug(ppcRecFunc->jumpTableEntries.empty());
|
||||
for (auto& itr : entryPoints)
|
||||
{
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[itr.first / 4] = (PPCREC_JUMP_ENTRY)((uint8*)ppcRecFunc->x86Code + itr.second);
|
||||
ppcRecFunc->jumpTableEntries.emplace_back(itr.first, ((uint8*)ppcRecFunc->x86Code + itr.second));
|
||||
}
|
||||
|
||||
|
||||
// due to inlining, some entrypoints can get optimized away
|
||||
// therefore we reset all addresses that are still marked as visited (but not recompiled)
|
||||
// we dont remove the points from the queue but any address thats not marked as visited won't get recompiled
|
||||
// if they are reachable, the interpreter will queue them again
|
||||
for (uint32 v = range.startAddress; v <= (range.startAddress + range.length); v += 4)
|
||||
for (uint32 v = range.startAddress; v < (range.startAddress + range.length); v += 4)
|
||||
{
|
||||
auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[v / 4];
|
||||
if (funcPtr == PPCRecompiler_leaveRecompilerCode_visited)
|
||||
@ -405,11 +415,9 @@ bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFun
|
||||
// register ranges
|
||||
for (auto& r : ppcRecFunc->list_ranges)
|
||||
{
|
||||
r.storedRange = rangeStore_ppcRanges.storeRange(ppcRecFunc, r.ppcAddress, r.ppcAddress + r.ppcSize);
|
||||
r.storedRange = s_ppcRecompilerState.functionStorage.storeRange(ppcRecFunc, r.ppcAddress, r.ppcAddress + r.ppcSize);
|
||||
}
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
|
||||
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -430,27 +438,18 @@ void PPCRecompiler_recompileAtAddress(uint32 address)
|
||||
// todo - use info from previously compiled ranges to determine full size of this function (and merge all the entryAddresses)
|
||||
|
||||
// collect all currently known entry points for this range
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
std::set<uint32> entryAddresses;
|
||||
|
||||
entryAddresses.emplace(address);
|
||||
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
|
||||
std::vector<std::pair<MPTR, uint32>> functionEntryPoints;
|
||||
auto func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints, funcBoundaries);
|
||||
|
||||
PPCRecFunction_t* func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints, funcBoundaries);
|
||||
if (!func)
|
||||
{
|
||||
return; // recompilation failed
|
||||
}
|
||||
bool r = PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints);
|
||||
PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints);
|
||||
}
|
||||
|
||||
std::thread s_threadRecompiler;
|
||||
std::atomic_bool s_recompilerThreadStopSignal{false};
|
||||
|
||||
void PPCRecompiler_thread()
|
||||
{
|
||||
SetThreadName("PPCRecompiler");
|
||||
@ -460,7 +459,7 @@ void PPCRecompiler_thread()
|
||||
|
||||
while (true)
|
||||
{
|
||||
if(s_recompilerThreadStopSignal)
|
||||
if(s_ppcRecompilerState.workerThreadStopSignal)
|
||||
return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// asynchronous recompilation:
|
||||
@ -469,26 +468,26 @@ void PPCRecompiler_thread()
|
||||
// 3) if yes -> calculate size, gather all entry points, recompile and update jump table
|
||||
while (true)
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
if (PPCRecompilerState.targetQueue.empty())
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
if (s_ppcRecompilerState.targetQueue.empty())
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
break;
|
||||
}
|
||||
auto enterAddress = PPCRecompilerState.targetQueue.front();
|
||||
PPCRecompilerState.targetQueue.pop();
|
||||
auto enterAddress = s_ppcRecompilerState.targetQueue.front();
|
||||
s_ppcRecompilerState.targetQueue.pop();
|
||||
|
||||
auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4];
|
||||
if (funcPtr != PPCRecompiler_leaveRecompilerCode_visited)
|
||||
{
|
||||
// only recompile functions if marked as visited
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
continue;
|
||||
}
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
|
||||
PPCRecompiler_recompileAtAddress(enterAddress);
|
||||
if(s_recompilerThreadStopSignal)
|
||||
if(s_ppcRecompilerState.workerThreadStopSignal)
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -512,9 +511,8 @@ void PPCRecompiler_reserveLookupTableBlock(uint32 offset)
|
||||
return;
|
||||
ppcRecompiler_reservedBlockMask[blockIndex] = true;
|
||||
|
||||
void* p1 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true);
|
||||
void* p3 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true);
|
||||
if( !p1 || !p3 )
|
||||
void* p = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true);
|
||||
if( !p )
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to allocate memory for recompiler (0x{:08x})", offset);
|
||||
cemu_assert(false);
|
||||
@ -540,21 +538,13 @@ void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size)
|
||||
}
|
||||
}
|
||||
|
||||
struct ppcRecompilerFuncRange_t
|
||||
bool PPCRecompiler_findFuncRanges(uint32 addr, ppcRecompilerFuncRange* rangesOut, size_t* countInOut)
|
||||
{
|
||||
MPTR ppcStart;
|
||||
uint32 ppcSize;
|
||||
void* x86Start;
|
||||
size_t x86Size;
|
||||
};
|
||||
|
||||
bool PPCRecompiler_findFuncRanges(uint32 addr, ppcRecompilerFuncRange_t* rangesOut, size_t* countInOut)
|
||||
{
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
size_t countIn = *countInOut;
|
||||
size_t countOut = 0;
|
||||
|
||||
rangeStore_ppcRanges.findRanges(addr, addr + 4, [rangesOut, countIn, &countOut](uint32 start, uint32 end, PPCRecFunction_t* func)
|
||||
s_ppcRecompilerState.functionStorage.findRanges(addr, addr + 4, [rangesOut, countIn, &countOut](uint32 start, uint32 end, PPCRecFunction_t* func)
|
||||
{
|
||||
if (countOut < countIn)
|
||||
{
|
||||
@ -566,40 +556,34 @@ bool PPCRecompiler_findFuncRanges(uint32 addr, ppcRecompilerFuncRange_t* rangesO
|
||||
countOut++;
|
||||
}
|
||||
);
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
*countInOut = countOut;
|
||||
if (countOut > countIn)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT uintptr_t * PPCRecompiler_getJumpTableBase()
|
||||
extern "C" DLLEXPORT uintptr_t* PPCRecompiler_getJumpTableBase()
|
||||
{
|
||||
if (ppcRecompilerInstanceData == nullptr)
|
||||
return nullptr;
|
||||
return (uintptr_t*)ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable;
|
||||
}
|
||||
|
||||
void PPCRecompiler_invalidateTableRange(uint32 offset, uint32 size)
|
||||
{
|
||||
if (ppcRecompilerInstanceData == nullptr)
|
||||
return;
|
||||
for (uint32 i = 0; i < size / 4; i++)
|
||||
{
|
||||
ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset / 4 + i] = nullptr;
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset / 4 + i] = PPCRecompiler_leaveRecompilerCode_unvisited;
|
||||
}
|
||||
}
|
||||
|
||||
void PPCRecompiler_deleteFunction(PPCRecFunction_t* func)
|
||||
{
|
||||
// assumes PPCRecompilerState.recompilerSpinlock is already held
|
||||
cemu_assert_debug(PPCRecompilerState.recompilerSpinlock.is_locked());
|
||||
cemu_assert_debug(s_ppcRecompilerState.recompilerSpinlock.is_locked());
|
||||
// unlink entrypoints from JumpTable
|
||||
for (auto& entrypoint : func->jumpTableEntries)
|
||||
{
|
||||
if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[entrypoint.ppcAddr / 4] == entrypoint.hostEntrypoint)
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[entrypoint.ppcAddr / 4] = PPCRecompiler_leaveRecompilerCode_unvisited;
|
||||
}
|
||||
// delete from storage
|
||||
for (auto& r : func->list_ranges)
|
||||
{
|
||||
PPCRecompiler_invalidateTableRange(r.ppcAddress, r.ppcSize);
|
||||
if(r.storedRange)
|
||||
rangeStore_ppcRanges.deleteRange(r.storedRange);
|
||||
s_ppcRecompilerState.functionStorage.deleteRange(r.storedRange);
|
||||
r.storedRange = nullptr;
|
||||
}
|
||||
// todo - free x86 code
|
||||
@ -607,32 +591,24 @@ void PPCRecompiler_deleteFunction(PPCRecFunction_t* func)
|
||||
|
||||
void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr)
|
||||
{
|
||||
if (ppcRecompilerEnabled == false)
|
||||
if (!s_ppcRecompilerState.initialized)
|
||||
return;
|
||||
if (startAddr >= PPC_REC_CODE_AREA_SIZE)
|
||||
return;
|
||||
cemu_assert_debug(endAddr >= startAddr);
|
||||
|
||||
PPCRecompilerState.recompilerSpinlock.lock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.lock();
|
||||
|
||||
uint32 rStart;
|
||||
uint32 rEnd;
|
||||
PPCRecFunction_t* rFunc;
|
||||
|
||||
// mark range as unvisited
|
||||
for (uint64 currentAddr = (uint64)startAddr&~3; currentAddr < (uint64)(endAddr&~3); currentAddr += 4)
|
||||
ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[currentAddr / 4] = PPCRecompiler_leaveRecompilerCode_unvisited;
|
||||
|
||||
// add entry to invalidation queue
|
||||
PPCRecompilerState.invalidationRanges.emplace_back(startAddr, endAddr-startAddr);
|
||||
|
||||
|
||||
while (rangeStore_ppcRanges.findFirstRange(startAddr, endAddr, rStart, rEnd, rFunc) )
|
||||
{
|
||||
// delete functions which intersect the invalidated range
|
||||
while (s_ppcRecompilerState.functionStorage.findFirstRange(startAddr, endAddr, rStart, rEnd, rFunc))
|
||||
PPCRecompiler_deleteFunction(rFunc);
|
||||
}
|
||||
// add entry to invalidation queue, this is used to invalidate functions for which recompilation has already started
|
||||
s_ppcRecompilerState.invalidationRanges.emplace_back(startAddr, endAddr-startAddr);
|
||||
|
||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||
s_ppcRecompilerState.recompilerSpinlock.unlock();
|
||||
}
|
||||
|
||||
#if defined(ARCH_X86_64)
|
||||
@ -686,9 +662,10 @@ void PPCRecompiler_initPlatform()
|
||||
|
||||
void PPCRecompiler_init()
|
||||
{
|
||||
s_ppcRecompilerState.recompilerEnableCount = 0;
|
||||
if (ActiveSettings::GetCPUMode() == CPUMode::SinglecoreInterpreter)
|
||||
{
|
||||
ppcRecompilerEnabled = false;
|
||||
cemuLog_log(LogType::Force, "Using singlecore interpreter");
|
||||
return;
|
||||
}
|
||||
if (LaunchSettings::ForceInterpreter() || LaunchSettings::ForceMultiCoreInterpreter())
|
||||
@ -701,7 +678,7 @@ void PPCRecompiler_init()
|
||||
MemMapper::FreeReservation(ppcRecompilerInstanceData, sizeof(PPCRecompilerInstanceData_t));
|
||||
ppcRecompilerInstanceData = nullptr;
|
||||
}
|
||||
debug_printf("Allocating %dMB for recompiler instance data...\n", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024));
|
||||
cemuLog_logDebug(LogType::Force, "Reserving {}MB for recompiler instance data", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024));
|
||||
ppcRecompilerInstanceData = (PPCRecompilerInstanceData_t*)MemMapper::ReserveMemory(nullptr, sizeof(PPCRecompilerInstanceData_t), MemMapper::PAGE_PERMISSION::P_RW);
|
||||
MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true);
|
||||
#ifdef ARCH_X86_64
|
||||
@ -717,25 +694,26 @@ void PPCRecompiler_init()
|
||||
|
||||
cemuLog_log(LogType::Force, "Recompiler initialized");
|
||||
|
||||
ppcRecompilerEnabled = true;
|
||||
s_ppcRecompilerState.initialized = true;
|
||||
s_ppcRecompilerState.recompilerEnableCount = 1; // enabled
|
||||
|
||||
// launch recompilation thread
|
||||
s_recompilerThreadStopSignal = false;
|
||||
s_threadRecompiler = std::thread(PPCRecompiler_thread);
|
||||
s_ppcRecompilerState.workerThreadStopSignal = false;
|
||||
s_ppcRecompilerState.workerThread = std::thread(PPCRecompiler_thread);
|
||||
}
|
||||
|
||||
void PPCRecompiler_Shutdown()
|
||||
{
|
||||
// shut down recompiler thread
|
||||
s_recompilerThreadStopSignal = true;
|
||||
if(s_threadRecompiler.joinable())
|
||||
s_threadRecompiler.join();
|
||||
s_ppcRecompilerState.workerThreadStopSignal = true;
|
||||
if(s_ppcRecompilerState.workerThread.joinable())
|
||||
s_ppcRecompilerState.workerThread.join();
|
||||
// clean up queues
|
||||
while(!PPCRecompilerState.targetQueue.empty())
|
||||
PPCRecompilerState.targetQueue.pop();
|
||||
PPCRecompilerState.invalidationRanges.clear();
|
||||
while(!s_ppcRecompilerState.targetQueue.empty())
|
||||
s_ppcRecompilerState.targetQueue.pop();
|
||||
s_ppcRecompilerState.invalidationRanges.clear();
|
||||
// clean range store
|
||||
rangeStore_ppcRanges.clear();
|
||||
s_ppcRecompilerState.functionStorage.clear();
|
||||
// clean up memory
|
||||
uint32 numBlocks = PPCRecompiler_GetNumAddressSpaceBlocks();
|
||||
for(uint32 i=0; i<numBlocks; i++)
|
||||
@ -744,9 +722,23 @@ void PPCRecompiler_Shutdown()
|
||||
continue;
|
||||
// deallocate
|
||||
uint64 offset = i * PPC_REC_ALLOC_BLOCK_SIZE;
|
||||
MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true);
|
||||
MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true);
|
||||
// mark as unmapped
|
||||
ppcRecompiler_reservedBlockMask[i] = false;
|
||||
}
|
||||
s_ppcRecompilerState.recompilerEnableCount = 0;
|
||||
s_ppcRecompilerState.initialized = false;
|
||||
}
|
||||
|
||||
// For each Enable call, Disable needs to be called once and vice versa
|
||||
void PPCRecompiler_Enable()
|
||||
{
|
||||
if (s_ppcRecompilerState.initialized)
|
||||
s_ppcRecompilerState.recompilerEnableCount++;
|
||||
}
|
||||
|
||||
void PPCRecompiler_Disable()
|
||||
{
|
||||
if (s_ppcRecompilerState.initialized)
|
||||
s_ppcRecompilerState.recompilerEnableCount--;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#define PPC_REC_CODE_AREA_START (0x00000000) // lower bound of executable memory area. Recompiler expects this address to be 0
|
||||
#define PPC_REC_CODE_AREA_END (0x10000000) // upper bound of executable memory area
|
||||
@ -17,11 +18,20 @@ struct ppcRecRange_t
|
||||
|
||||
struct PPCRecFunction_t
|
||||
{
|
||||
struct JumpTableEntry
|
||||
{
|
||||
MPTR ppcAddr;
|
||||
void* hostEntrypoint;
|
||||
|
||||
JumpTableEntry(MPTR ppcAddr, void* hostEntrypoint) : ppcAddr(ppcAddr), hostEntrypoint(hostEntrypoint) {};
|
||||
};
|
||||
|
||||
uint32 ppcAddress;
|
||||
uint32 ppcSize; // ppc code size of function
|
||||
void* x86Code; // pointer to x86 code
|
||||
size_t x86Size;
|
||||
std::vector<ppcRecRange_t> list_ranges;
|
||||
boost::container::small_vector<JumpTableEntry, 2> jumpTableEntries;
|
||||
};
|
||||
|
||||
#include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h"
|
||||
@ -119,7 +129,6 @@ typedef void ATTR_MS_ABI (*PPCREC_JUMP_ENTRY)();
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PPCRecFunction_t* ppcRecompilerFuncTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // one virtual-function pointer for each potential ppc instruction
|
||||
PPCREC_JUMP_ENTRY ppcRecompilerDirectJumpTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // lookup table for ppc offset to native code function
|
||||
// x64 data
|
||||
alignas(16) uint64 _x64XMM_xorNegateMaskBottom[2];
|
||||
@ -142,11 +151,13 @@ typedef struct
|
||||
}PPCRecompilerInstanceData_t;
|
||||
|
||||
extern PPCRecompilerInstanceData_t* ppcRecompilerInstanceData;
|
||||
extern bool ppcRecompilerEnabled;
|
||||
|
||||
void PPCRecompiler_init();
|
||||
void PPCRecompiler_Shutdown();
|
||||
|
||||
void PPCRecompiler_Enable();
|
||||
void PPCRecompiler_Disable();
|
||||
|
||||
void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size);
|
||||
|
||||
void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr);
|
||||
@ -155,8 +166,6 @@ extern void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uin
|
||||
extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)();
|
||||
extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)();
|
||||
|
||||
#define PPC_REC_INVALID_FUNCTION ((PPCRecFunction_t*)-1)
|
||||
|
||||
// recompiler interface
|
||||
|
||||
void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress);
|
||||
|
||||
@ -98,10 +98,6 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
|
||||
|
||||
void LatteRenderTarget_unloadAll();
|
||||
|
||||
// surface copy
|
||||
|
||||
void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel);
|
||||
|
||||
// texture cache
|
||||
|
||||
void LatteTC_Init();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteShader.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteSurfaceCopy.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
||||
|
||||
void LatteThread_Exit();
|
||||
@ -35,11 +36,19 @@ typedef struct
|
||||
uint64 shaderAuxHash;
|
||||
LatteConst::ShaderType shaderType;
|
||||
}deleteShader;
|
||||
|
||||
struct
|
||||
{
|
||||
LatteSurfaceCopyParam src;
|
||||
LatteSurfaceCopyParam dst;
|
||||
LatteSurfaceCopyRect rect;
|
||||
}textureCopy;
|
||||
};
|
||||
}LatteAsyncCommand_t;
|
||||
|
||||
#define ASYNC_CMD_FORCE_TEXTURE_READBACK 1
|
||||
#define ASYNC_CMD_DELETE_SHADER 2
|
||||
#define ASYNC_CMD_TEXTURE_COPY 3
|
||||
|
||||
std::queue<LatteAsyncCommand_t> LatteAsyncCommandQueue;
|
||||
|
||||
@ -82,6 +91,20 @@ void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAu
|
||||
swl_gpuAsyncCommands.UnlockWrite();
|
||||
}
|
||||
|
||||
void LatteAsyncCommand_queueTextureCopy(const LatteSurfaceCopyParam& src, const LatteSurfaceCopyParam& dst, const LatteSurfaceCopyRect& rect)
|
||||
{
|
||||
LatteAsyncCommand_t asyncCommand = {};
|
||||
// setup command
|
||||
asyncCommand.type = ASYNC_CMD_TEXTURE_COPY;
|
||||
asyncCommand.textureCopy.src = src;
|
||||
asyncCommand.textureCopy.dst = dst;
|
||||
asyncCommand.textureCopy.rect = rect;
|
||||
|
||||
swl_gpuAsyncCommands.LockWrite();
|
||||
LatteAsyncCommandQueue.push(asyncCommand);
|
||||
swl_gpuAsyncCommands.UnlockWrite();
|
||||
}
|
||||
|
||||
void LatteAsyncCommands_waitUntilAllProcessed()
|
||||
{
|
||||
while (LatteAsyncCommandQueue.empty() == false)
|
||||
@ -127,6 +150,10 @@ void LatteAsyncCommands_checkAndExecute()
|
||||
{
|
||||
LatteSHRC_RemoveFromCacheByHash(asyncCommand.deleteShader.shaderBaseHash, asyncCommand.deleteShader.shaderAuxHash, asyncCommand.deleteShader.shaderType);
|
||||
}
|
||||
else if (asyncCommand.type == ASYNC_CMD_TEXTURE_COPY)
|
||||
{
|
||||
LatteSurfaceCopy_copySurfaceNew(asyncCommand.textureCopy.src, asyncCommand.textureCopy.dst, asyncCommand.textureCopy.rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
struct LatteSurfaceCopyParam;
|
||||
struct LatteSurfaceCopyRect;
|
||||
|
||||
void LatteAsyncCommands_queueForceTextureReadback(MPTR physAddr, MPTR mipAddr, uint32 swizzle, sint32 format, sint32 width, sint32 height, sint32 depth, uint32 pitch, uint32 slice, sint32 dim, Latte::E_HWTILEMODE tilemode, sint32 aa, sint32 level);
|
||||
void LatteAsyncCommand_queueTextureCopy(const LatteSurfaceCopyParam& src, const LatteSurfaceCopyParam& dst, const LatteSurfaceCopyRect& rect);
|
||||
void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAuxHash, LatteConst::ShaderType shaderType);
|
||||
void LatteAsyncCommands_waitUntilAllProcessed();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
||||
#include "Cafe/HW/Latte/Core/LatteIndices.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
|
||||
#include "Cafe/HW/Latte/Core/LattePM4.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteSurfaceCopy.h"
|
||||
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||
#include "Cafe/OS/libs/TCL/TCL.h" // TCL currently handles the GPU command ringbuffer
|
||||
@ -805,37 +806,37 @@ LatteCMDPtr LatteCP_itHLEBottomOfPipeCB(LatteCMDPtr cmd, uint32 nWords)
|
||||
// GPU-side handler for GX2CopySurface/GX2CopySurfaceEx and similar
|
||||
LatteCMDPtr LatteCP_itHLECopySurfaceNew(LatteCMDPtr cmd, uint32 nWords)
|
||||
{
|
||||
cemu_assert_debug(nWords == 26);
|
||||
cemu_assert_debug(nWords == 4+9*2);
|
||||
// copy rect
|
||||
LatteSurfaceCopyRect copyRect;
|
||||
copyRect.x = LatteReadCMD();
|
||||
copyRect.y = LatteReadCMD();
|
||||
copyRect.width = LatteReadCMD();
|
||||
copyRect.height = LatteReadCMD();
|
||||
// src
|
||||
MPTR srcPhysAddr = LatteReadCMD();
|
||||
MPTR srcMipAddr = LatteReadCMD();
|
||||
uint32 srcSwizzle = LatteReadCMD();
|
||||
Latte::E_GX2SURFFMT srcSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
sint32 srcWidth = LatteReadCMD();
|
||||
sint32 srcHeight = LatteReadCMD();
|
||||
sint32 srcDepth = LatteReadCMD();
|
||||
uint32 srcPitch = LatteReadCMD();
|
||||
uint32 srcSlice = LatteReadCMD();
|
||||
Latte::E_DIM srcDim = (Latte::E_DIM)LatteReadCMD();
|
||||
Latte::E_HWTILEMODE srcTilemode = (Latte::E_HWTILEMODE)LatteReadCMD();
|
||||
sint32 srcAA = LatteReadCMD();
|
||||
sint32 srcLevel = LatteReadCMD();
|
||||
LatteSurfaceCopyParam src{};
|
||||
src.physDataAddr = LatteReadCMD();
|
||||
src.swizzle = LatteReadCMD();
|
||||
src.surfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
src.pitch = LatteReadCMD();
|
||||
src.heightInTexels = LatteReadCMD();
|
||||
src.sliceIndex = LatteReadCMD();
|
||||
src.dim = (Latte::E_DIM)LatteReadCMD();
|
||||
src.tilemode = (Latte::E_GX2TILEMODE)LatteReadCMD();
|
||||
src.aa = LatteReadCMD();
|
||||
// dst
|
||||
MPTR dstPhysAddr = LatteReadCMD();
|
||||
MPTR dstMipAddr = LatteReadCMD();
|
||||
uint32 dstSwizzle = LatteReadCMD();
|
||||
Latte::E_GX2SURFFMT dstSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
sint32 dstWidth = LatteReadCMD();
|
||||
sint32 dstHeight = LatteReadCMD();
|
||||
sint32 dstDepth = LatteReadCMD();
|
||||
uint32 dstPitch = LatteReadCMD();
|
||||
uint32 dstSlice = LatteReadCMD();
|
||||
Latte::E_DIM dstDim = (Latte::E_DIM)LatteReadCMD();
|
||||
Latte::E_HWTILEMODE dstTilemode = (Latte::E_HWTILEMODE)LatteReadCMD();
|
||||
sint32 dstAA = LatteReadCMD();
|
||||
sint32 dstLevel = LatteReadCMD();
|
||||
LatteSurfaceCopyParam dst{};
|
||||
dst.physDataAddr = LatteReadCMD();
|
||||
dst.swizzle = LatteReadCMD();
|
||||
dst.surfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
dst.pitch = LatteReadCMD();
|
||||
dst.heightInTexels = LatteReadCMD();
|
||||
dst.sliceIndex = LatteReadCMD();
|
||||
dst.dim = (Latte::E_DIM)LatteReadCMD();
|
||||
dst.tilemode = (Latte::E_GX2TILEMODE)LatteReadCMD();
|
||||
dst.aa = LatteReadCMD();
|
||||
|
||||
LatteSurfaceCopy_copySurfaceNew(srcPhysAddr, srcMipAddr, srcSwizzle, srcSurfaceFormat, srcWidth, srcHeight, srcDepth, srcPitch, srcSlice, srcDim, srcTilemode, srcAA, srcLevel, dstPhysAddr, dstMipAddr, dstSwizzle, dstSurfaceFormat, dstWidth, dstHeight, dstDepth, dstPitch, dstSlice, dstDim, dstTilemode, dstAA, dstLevel);
|
||||
LatteSurfaceCopy_copySurfaceNew(src, dst, copyRect);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
||||
@ -3,22 +3,65 @@
|
||||
#include "Cafe/HW/Latte/Core/LatteShader.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteSurfaceCopy.h"
|
||||
|
||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||
|
||||
void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel)
|
||||
/* Surface copies are tricky to handle because we simulate unified memory on top of two separate memory systems: RAM and VRAM
|
||||
* Cemu may only have texture data in either RAM or VRAM, or both. And doing transfers to or from CPU can quickly become prohibitively expensive
|
||||
* So we need to make best guesses on where the data needs to come from and where it needs to go
|
||||
* A complicating factor is that texture copies are more like memcpy, in the sense that they don't care about the actual underlying pixel format
|
||||
* For example a R32F texture can be copied as RGBA8. Similiarly, different tile modes can sometimes be identical under specific circumstances
|
||||
*/
|
||||
|
||||
void gx2SurfaceCopySoftware(
|
||||
uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint32 srcHwTileMode,
|
||||
uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode,
|
||||
uint32 copyWidth, uint32 copyHeight, uint32 copyBpp);
|
||||
|
||||
void LatteSurfaceCopy_CopyInRAM(const LatteSurfaceCopyParam& src, const LatteSurfaceCopyParam& dst, const LatteSurfaceCopyRect& rect)
|
||||
{
|
||||
// check if source is within valid mip range
|
||||
if (srcDim == Latte::E_DIM::DIM_3D && (srcDepth >> srcLevel) == 0 && (srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0)
|
||||
return;
|
||||
else if ((srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0)
|
||||
Latte::E_HWSURFFMT dstHwFormat = Latte::GetHWFormat(dst.surfaceFormat);
|
||||
|
||||
sint32 copyWidth = rect.width;
|
||||
sint32 copyHeight = rect.height;
|
||||
if (Latte::IsCompressedFormat(dstHwFormat))
|
||||
{
|
||||
copyWidth = (copyWidth + 3) / 4;
|
||||
copyHeight = (copyHeight + 3) / 4;
|
||||
}
|
||||
|
||||
uint32 dstBpp = Latte::GetFormatBits(dstHwFormat);
|
||||
|
||||
gx2SurfaceCopySoftware((uint8*)MEMPTR<void>(src.physDataAddr).GetPtr(), src.heightInTexels, src.pitch, 1, src.sliceIndex, src.swizzle, (uint32)src.tilemode,
|
||||
(uint8*)MEMPTR<void>(dst.physDataAddr).GetPtr(), dst.heightInTexels, dst.pitch, 1, dst.sliceIndex, dst.swizzle, (uint32)dst.tilemode,
|
||||
copyWidth, copyHeight, dstBpp);
|
||||
}
|
||||
|
||||
void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const LatteSurfaceCopyParam& dst, const LatteSurfaceCopyRect& rect)
|
||||
{
|
||||
cemu_assert_debug(rect.x == 0 && rect.y == 0); // origin offset not yet supported
|
||||
|
||||
cemu_assert_debug((rect.x + rect.width) <= dst.pitch * (Latte::IsCompressedFormat(dst.surfaceFormat)?4:1));
|
||||
cemu_assert_debug((rect.x + rect.width) <= src.pitch * (Latte::IsCompressedFormat(src.surfaceFormat)?4:1));
|
||||
|
||||
if (src.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL || dst.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
|
||||
{
|
||||
// todo - it's technically possible for a matching linear texture to be in the texture cache already
|
||||
// there is also a case of tiled to linear_special where we should trigger a readback without an actual destination texture
|
||||
LatteSurfaceCopy_CopyInRAM(src, dst, rect);
|
||||
return;
|
||||
}
|
||||
|
||||
// look up source texture
|
||||
// todo - for non-zero slices heightInTexels matters (used to calculate the slice size). We should take this into account during lookup
|
||||
LatteTexture* sourceTexture = nullptr;
|
||||
LatteTextureView* sourceView = LatteTC_GetTextureSliceViewOrTryCreate(srcPhysAddr, srcMipAddr, srcSurfaceFormat, srcTilemode, srcWidth, srcHeight, srcDepth, srcPitch, srcSwizzle, srcSlice, srcLevel);
|
||||
LatteTextureView* sourceView = LatteTC_GetTextureSliceViewOrTryCreate(src.physDataAddr, MPTR_NULL, src.surfaceFormat, Latte::MakeHWTileMode(src.tilemode), rect.x + rect.width, rect.y + rect.height, 1, src.pitch, src.swizzle, src.sliceIndex, 0);
|
||||
if (sourceView == nullptr)
|
||||
{
|
||||
debug_printf("HLECopySurface(): Source texture is not in list of dynamic textures\n");
|
||||
// source texture doesn't exist (yet) in texture cache
|
||||
// operate on RAM instead
|
||||
LatteSurfaceCopy_CopyInRAM(src, dst, rect);
|
||||
return;
|
||||
}
|
||||
sourceTexture = sourceView->baseTexture;
|
||||
@ -29,15 +72,14 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s
|
||||
}
|
||||
// look up destination texture
|
||||
LatteTexture* destinationTexture = nullptr;
|
||||
LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSlice(dstPhysAddr, dstWidth, dstHeight, dstPitch, dstLevel, dstSlice, dstSurfaceFormat);
|
||||
LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSliceMinSize(dst.physDataAddr, rect.x + rect.width, rect.y + rect.height, dst.pitch, 0, dst.sliceIndex, dst.surfaceFormat);
|
||||
// todo - Instead of lookupSliceMinSize lookup the base texture by data range instead and return mip/slice index
|
||||
if (destinationView)
|
||||
destinationTexture = destinationView->baseTexture;
|
||||
|
||||
// create destination texture if it doesnt exist
|
||||
if (!destinationTexture)
|
||||
{
|
||||
LatteTexture* renderTargetConf = nullptr;
|
||||
destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::IsMSAA(dstDim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false);
|
||||
destinationView = LatteTexture_CreateMapping(dst.physDataAddr, MPTR_NULL, rect.x + rect.width, rect.y + rect.height, 1, dst.pitch, Latte::MakeHWTileMode(dst.tilemode), dst.swizzle, 0, 1, dst.sliceIndex, 1, dst.surfaceFormat, dst.dim, Latte::IsMSAA(dst.dim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false);
|
||||
destinationTexture = destinationView->baseTexture;
|
||||
}
|
||||
// copy texture
|
||||
@ -46,15 +88,12 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s
|
||||
// mark source and destination texture as still in use
|
||||
LatteTC_MarkTextureStillInUse(destinationTexture);
|
||||
LatteTC_MarkTextureStillInUse(sourceTexture);
|
||||
sint32 realSrcSlice = srcSlice;
|
||||
sint32 realSrcSlice = src.sliceIndex;
|
||||
if (LatteTexture_doesEffectiveRescaleRatioMatch(sourceTexture, sourceView->firstMip, destinationTexture, destinationView->firstMip))
|
||||
{
|
||||
// adjust copy size
|
||||
sint32 copyWidth = std::max(srcWidth >> srcLevel, 1);
|
||||
sint32 copyHeight = std::max(srcHeight >> srcLevel, 1);
|
||||
// use the smaller width/height as copy size
|
||||
copyWidth = std::min(copyWidth, std::max(dstWidth >> dstLevel, 1));
|
||||
copyHeight = std::min(copyHeight, std::max(dstHeight >> dstLevel, 1));
|
||||
cemu_assert_debug(rect.x == 0 && rect.y == 0);
|
||||
sint32 copyWidth = rect.width;
|
||||
sint32 copyHeight = rect.height;
|
||||
sint32 effectiveCopyWidth = copyWidth;
|
||||
sint32 effectiveCopyHeight = copyHeight;
|
||||
LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0);
|
||||
@ -77,11 +116,19 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s
|
||||
}
|
||||
else
|
||||
debug_printf("Source or destination texture does not exist\n");
|
||||
|
||||
// download destination texture if it matches known accessed formats
|
||||
// if the texture is updated from a tiled to a linear format it's a strong indicator for CPU reads
|
||||
// in which case we should sync the texture back to CPU RAM
|
||||
const bool sourceIsLinear = sourceTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || sourceTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_GENERAL;
|
||||
const bool destinationIsLinear = destinationTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || destinationTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_GENERAL;
|
||||
bool shouldReadback = !sourceIsLinear && destinationIsLinear;
|
||||
// special case for Bayonetta 2
|
||||
if (destinationTexture->width == 8 && destinationTexture->height == 8 && destinationTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "Texture readback after copy for Bayonetta 2 (phys: 0x{:08x})", destinationTexture->physAddress);
|
||||
shouldReadback = true;
|
||||
}
|
||||
if (shouldReadback)
|
||||
{
|
||||
LatteTextureReadback_Initate(destinationView);
|
||||
}
|
||||
}
|
||||
|
||||
25
src/Cafe/HW/Latte/Core/LatteSurfaceCopy.h
Normal file
25
src/Cafe/HW/Latte/Core/LatteSurfaceCopy.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
struct LatteSurfaceCopyParam
|
||||
{
|
||||
// effective parameters (with mip index baked into them)
|
||||
MPTR physDataAddr; // points to actual mip
|
||||
uint32 swizzle;
|
||||
Latte::E_GX2SURFFMT surfaceFormat;
|
||||
sint32 heightInTexels;
|
||||
uint32 pitch;
|
||||
Latte::E_DIM dim;
|
||||
Latte::E_GX2TILEMODE tilemode;
|
||||
sint32 aa;
|
||||
sint32 sliceIndex;
|
||||
};
|
||||
|
||||
struct LatteSurfaceCopyRect
|
||||
{
|
||||
uint32 x;
|
||||
uint32 y;
|
||||
uint32 width; // in pixels
|
||||
uint32 height;
|
||||
};
|
||||
|
||||
void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const LatteSurfaceCopyParam& dst, const LatteSurfaceCopyRect& rect);
|
||||
@ -1138,6 +1138,7 @@ void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>&
|
||||
}
|
||||
}
|
||||
|
||||
// return or create a view, requires existing base texture. Returns nullptr if it doesn't exist yet
|
||||
LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution)
|
||||
{
|
||||
LatteTextureView* sourceView;
|
||||
|
||||
@ -94,7 +94,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di
|
||||
objName.objectType = VK_OBJECT_TYPE_IMAGE;
|
||||
objName.pNext = nullptr;
|
||||
objName.objectHandle = (uint64_t)vkObjTex->m_image;
|
||||
auto objNameStr = fmt::format("tex_{:08x}_fmt{:04x}", physAddress, (uint32)format);
|
||||
auto objNameStr = fmt::format("tex_{:08x}_fmt{:04x}_tm{:x}", physAddress, (uint32)format, (uint32)tileMode);
|
||||
objName.pObjectName = objNameStr.c_str();
|
||||
vkSetDebugUtilsObjectNameEXT(m_vkr->GetLogicalDevice(), &objName);
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp)
|
||||
return score;
|
||||
}
|
||||
|
||||
void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols)
|
||||
void DebugLogStackTrace(OSThread_t* thread, MPTR sp)
|
||||
{
|
||||
// sp might not point to a valid stackframe
|
||||
// scan stack and evaluate which sp is most likely the beginning of the stackframe
|
||||
@ -88,10 +88,7 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols)
|
||||
}
|
||||
}
|
||||
|
||||
if (highestScoreSP != sp)
|
||||
cemuLog_log(LogType::Force, fmt::format("Trace starting at SP {0:08x} r1 = {1:08x}", highestScoreSP, sp));
|
||||
else
|
||||
cemuLog_log(LogType::Force, fmt::format("Trace starting at SP/r1 {0:08x}", highestScoreSP));
|
||||
cemuLog_log(LogType::Force, fmt::format("Trace starting at SP {:08x} r1={:08x}", highestScoreSP, sp));
|
||||
|
||||
// print stack trace
|
||||
uint32 currentStackPtr = highestScoreSP;
|
||||
@ -108,9 +105,7 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols)
|
||||
uint32 returnAddress = 0;
|
||||
returnAddress = memory_readU32(nextStackPtr + 4);
|
||||
|
||||
RPLStoredSymbol* symbol = nullptr;
|
||||
if(printSymbols)
|
||||
symbol = rplSymbolStorage_getByClosestAddress(returnAddress);
|
||||
RPLStoredSymbol* symbol = rplSymbolStorage_getByClosestAddress(returnAddress);
|
||||
|
||||
if(symbol)
|
||||
cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x} ({}.{}+0x{:x})", nextStackPtr, returnAddress, (const char*)symbol->libName, (const char*)symbol->symbolName, returnAddress - symbol->address));
|
||||
|
||||
@ -59,7 +59,7 @@ namespace coreinit
|
||||
PPCCoreCallback(_osDynLoadFuncFree, _mem);
|
||||
}
|
||||
|
||||
uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut)
|
||||
uint32 OSDynLoad_AcquireInternal(const char* libName, uint32be* moduleHandleOut, bool checkOnly)
|
||||
{
|
||||
// truncate path
|
||||
sint32 fileNameStartIndex = 0;
|
||||
@ -88,6 +88,11 @@ namespace coreinit
|
||||
uint32 rplHandle = RPLLoader_GetHandleByModuleName(libName);
|
||||
if (rplHandle == RPL_INVALID_HANDLE && !RPLLoader_HasDependency(libName))
|
||||
{
|
||||
if (checkOnly)
|
||||
{
|
||||
*moduleHandleOut = 0;
|
||||
return 0;
|
||||
}
|
||||
RPLLoader_AddDependency(libName);
|
||||
RPLLoader_UpdateDependencies();
|
||||
RPLLoader_Link();
|
||||
@ -106,6 +111,11 @@ namespace coreinit
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut)
|
||||
{
|
||||
return OSDynLoad_AcquireInternal(libName, moduleHandleOut, false);
|
||||
}
|
||||
|
||||
void OSDynLoad_Release(uint32 moduleHandle)
|
||||
{
|
||||
if (moduleHandle == RPL_INVALID_HANDLE)
|
||||
@ -114,6 +124,11 @@ namespace coreinit
|
||||
RPLLoader_UpdateDependencies();
|
||||
}
|
||||
|
||||
uint32 OSDynLoad_IsModuleLoaded(const char* libName, uint32be* moduleHandleOut)
|
||||
{
|
||||
return OSDynLoad_AcquireInternal(libName, moduleHandleOut, true);
|
||||
}
|
||||
|
||||
uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut)
|
||||
{
|
||||
if (moduleHandle == 0xFFFFFFFF)
|
||||
@ -140,6 +155,7 @@ namespace coreinit
|
||||
|
||||
cafeExportRegister("coreinit", OSDynLoad_Acquire, LogType::Placeholder);
|
||||
cafeExportRegister("coreinit", OSDynLoad_Release, LogType::Placeholder);
|
||||
cafeExportRegister("coreinit", OSDynLoad_IsModuleLoaded, LogType::Placeholder);
|
||||
cafeExportRegister("coreinit", OSDynLoad_FindExport, LogType::Placeholder);
|
||||
}
|
||||
}
|
||||
@ -1483,6 +1483,11 @@ namespace coreinit
|
||||
s_threadToFiber.clear();
|
||||
}
|
||||
|
||||
bool OSIsSchedulerActive()
|
||||
{
|
||||
return sSchedulerActive;
|
||||
}
|
||||
|
||||
SysAllocator<OSThread_t, PPC_CORE_COUNT> s_defaultThreads;
|
||||
SysAllocator<uint8, PPC_CORE_COUNT * 1024 * 1024> s_stack;
|
||||
|
||||
|
||||
@ -610,6 +610,7 @@ namespace coreinit
|
||||
// scheduler
|
||||
void OSSchedulerBegin(sint32 numCPUEmulationThreads);
|
||||
void OSSchedulerEnd();
|
||||
bool OSIsSchedulerActive();
|
||||
|
||||
// internal
|
||||
void __OSAddReadyThreadToRunQueue(OSThread_t* thread);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteDraw.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteSurfaceCopy.h"
|
||||
#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h"
|
||||
#include "util/highresolutiontimer/HighResolutionTimer.h"
|
||||
#include "GX2.h"
|
||||
@ -127,6 +128,11 @@ void gx2SurfaceCopySoftware(
|
||||
uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode,
|
||||
uint32 copyWidth, uint32 copyHeight, uint32 copyBpp)
|
||||
{
|
||||
if (srcHwTileMode == 16)
|
||||
srcHwTileMode = 0;
|
||||
if (dstHwTileMode == 16)
|
||||
dstHwTileMode = 0;
|
||||
|
||||
if (srcHwTileMode == 4 && dstHwTileMode == 4 && (copyWidth & 7) == 0 && (copyHeight & 7) == 0 && copyBpp <= 32) // todo - check sample == 1
|
||||
{
|
||||
gx2SurfaceCopySoftware_fastPath_tm4Copy(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, copyWidth, copyHeight, copyBpp);
|
||||
@ -147,7 +153,19 @@ void gx2SurfaceCopySoftware(
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
|
||||
void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSlice, GX2Surface* dstSurface, uint32 dstMip, uint32 dstSlice)
|
||||
// Surface copy handling is complicated because of having to simulate unified memory
|
||||
// GX2CopySurface supports two modes:
|
||||
// - When source or destination surface have a tilemode of LINEAR_SPECIAL (16) -> Copy is done synchronously on the CPU
|
||||
// - In all other cases -> Copy is done on the GPU via draw commands
|
||||
|
||||
// But in Cemu things are more complicated, we generally can't do pure CPU copies because a surface's texture data
|
||||
// may only exist in VRAM right now. So we always need to delegate copying to the renderer thread (which has access to the texture cache)
|
||||
|
||||
// In Cemu we thus handle it like this:
|
||||
// For GX2 CPU copies -> Submit as async command to the renderer (will be processed asap) and stall until completed
|
||||
// For GX2 GPU copies -> Submit as HLE command to Latte's command queue to be executed in order
|
||||
|
||||
void GX2CopySurfaceInternal(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSlice, GX2Surface* dstSurface, uint32 dstMip, uint32 dstSlice)
|
||||
{
|
||||
sint32 dstWidth = dstSurface->width;
|
||||
sint32 dstHeight = dstSurface->height;
|
||||
@ -167,8 +185,6 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src
|
||||
// handle format
|
||||
Latte::E_GX2SURFFMT srcFormat = srcSurface->format;
|
||||
Latte::E_GX2SURFFMT dstFormat = dstSurface->format;
|
||||
uint32 srcBPP = Latte::GetFormatBits(srcFormat);
|
||||
uint32 dstBPP = Latte::GetFormatBits(dstFormat);
|
||||
auto srcHwFormat = Latte::GetHWFormat(srcFormat);
|
||||
auto dstHwFormat = Latte::GetHWFormat(dstFormat);
|
||||
// get texture info
|
||||
@ -182,173 +198,130 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src
|
||||
debug_printf("GX2CopySurface(): mip count is 0\n");
|
||||
return;
|
||||
}
|
||||
// get input pointer
|
||||
uint8* inputData = NULL;
|
||||
cemu_assert(srcMip < srcSurface->numLevels);
|
||||
if( srcMip == 0 )
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr);
|
||||
else if( srcMip == 1 )
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr);
|
||||
else
|
||||
{
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr + srcSurface->mipOffset[srcMip - 1]);
|
||||
}
|
||||
// get output pointer
|
||||
uint8* outputData = NULL;
|
||||
cemu_assert(dstMip < dstSurface->numLevels);
|
||||
if( dstMip == 0 )
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr);
|
||||
else if( dstMip == 1 )
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr);
|
||||
else
|
||||
{
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr + dstSurface->mipOffset[dstMip - 1]);
|
||||
}
|
||||
|
||||
// make sure formats are compatible
|
||||
if( srcHwFormat != dstHwFormat )
|
||||
{
|
||||
// mismatching format
|
||||
cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format mismatch");
|
||||
cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format mismatch (src=0x{:04x} dst=0x{:04x})", (sint32)srcFormat, (sint32)dstFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
// note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible.
|
||||
// src
|
||||
// get input pointer
|
||||
cemu_assert(srcMip < srcSurface->numLevels);
|
||||
uint8* srcDataPtr = nullptr;
|
||||
if( srcMip == 0 )
|
||||
srcDataPtr = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr);
|
||||
else if( srcMip == 1 )
|
||||
srcDataPtr = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr);
|
||||
else
|
||||
srcDataPtr = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr + srcSurface->mipOffset[srcMip - 1]);
|
||||
// get output pointer
|
||||
cemu_assert(dstMip < dstSurface->numLevels);
|
||||
uint8* dstDataPtr = nullptr;
|
||||
if( dstMip == 0 )
|
||||
dstDataPtr = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr);
|
||||
else if( dstMip == 1 )
|
||||
dstDataPtr = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr);
|
||||
else
|
||||
dstDataPtr = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr + dstSurface->mipOffset[dstMip - 1]);
|
||||
// note: pitch is taken from surfOutSrc/surfOutDst, which may different from the pitch stored in the input surface structs
|
||||
uint32 srcPitch = surfOutSrc.pitch;
|
||||
uint32 srcSwizzle = srcSurface->swizzle;
|
||||
uint32 srcHwTileMode = (uint32)surfOutSrc.hwTileMode;
|
||||
uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1);
|
||||
if (srcHwTileMode == 0) // linear
|
||||
Latte::E_GX2TILEMODE srcTilemode = (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) ? srcSurface->tileMode.value() : (Latte::E_GX2TILEMODE)surfOutSrc.hwTileMode;
|
||||
Latte::E_GX2TILEMODE dstTilemode = (dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) ? dstSurface->tileMode.value() : (Latte::E_GX2TILEMODE)surfOutDst.hwTileMode;
|
||||
|
||||
if (srcTilemode == Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL)
|
||||
{
|
||||
// todo - why is this necessary? GX2CalculateSurfaceInfo should already handle this
|
||||
srcPitch = srcSurface->pitch >> srcMip;
|
||||
srcPitch = std::max<uint32>(srcPitch, 1);
|
||||
}
|
||||
// dst
|
||||
uint32 dstPitch = surfOutDst.pitch;
|
||||
uint32 dstSwizzle = dstSurface->swizzle;
|
||||
uint32 dstHwTileMode = (uint32)surfOutDst.hwTileMode;
|
||||
uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1);
|
||||
uint32 dstBpp = surfOutDst.bpp;
|
||||
uint32 copyWidth = std::max<uint32>(dstWidth>>dstMip, 1);
|
||||
uint32 copyHeight = std::max<uint32>(dstHeight>>dstMip, 1);
|
||||
|
||||
//debug_printf("Src Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(srcSurface->imagePtr), _swapEndianU32(srcSurface->width), _swapEndianU32(srcSurface->height), _swapEndianU32(srcSurface->swizzle), _swapEndianU32(srcSurface->tileMode), _swapEndianU32(srcSurface->format), (uint32)srcSurface->resFlag);
|
||||
//debug_printf("Dst Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(dstSurface->imagePtr), _swapEndianU32(dstSurface->width), _swapEndianU32(dstSurface->height), _swapEndianU32(dstSurface->swizzle), _swapEndianU32(dstSurface->tileMode), _swapEndianU32(dstSurface->format), (uint32)dstSurface->resFlag);
|
||||
cemu_assert(copyWidth <= std::max<uint32>(srcWidth>>srcMip, 1));
|
||||
cemu_assert(copyHeight <= std::max<uint32>(srcHeight>>srcMip, 1));
|
||||
|
||||
bool requestGPURAMCopy = false;
|
||||
bool debugTestForceCPUCopy = false;
|
||||
cemuLog_log(LogType::GX2, "GX2CopySurface:");
|
||||
cemuLog_log(LogType::GX2,"srcSurface: imagePtr=0x{:08x} mipPtr=0x{:08x} swizzle=0x{:06x} width={} height={} depth={} pitch=0x{:x} tilemode=0x{:x} format=0x{:x} mip={} slice={}",
|
||||
(uint32)srcSurface->imagePtr, (uint32)srcSurface->mipPtr, (uint32)srcSurface->swizzle, (uint32)srcSurface->width, (uint32)srcSurface->height, (uint32)srcSurface->depth, (uint32)srcSurface->pitch,
|
||||
(uint32)srcSurface->tileMode.value(), (uint32)srcSurface->format.value(), srcMip, srcSlice);
|
||||
cemuLog_log(LogType::GX2, "dstSurface: imagePtr=0x{:08x} mipPtr=0x{:08x} swizzle=0x{:06x} width={} height={} depth={} pitch=0x{:x} tilemode=0x{:x} format=0x{:x} mip={} slice={}",
|
||||
(uint32)dstSurface->imagePtr, (uint32)dstSurface->mipPtr, (uint32)dstSurface->swizzle, (uint32)dstSurface->width, (uint32)dstSurface->height, (uint32)dstSurface->depth, (uint32)dstSurface->pitch,
|
||||
(uint32)dstSurface->tileMode.value(), (uint32)dstSurface->format.value(), dstMip, dstSlice);
|
||||
|
||||
if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1)
|
||||
debugTestForceCPUCopy = true;
|
||||
|
||||
if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1 && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL )
|
||||
bool isGX2CPUCopy = srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL || dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL;
|
||||
if (isGX2CPUCopy)
|
||||
{
|
||||
LatteAsyncCommands_queueForceTextureReadback(
|
||||
srcSurface->imagePtr,
|
||||
srcSurface->mipPtr,
|
||||
srcSurface->swizzle,
|
||||
(uint32)srcSurface->format.value(),
|
||||
srcSurface->width,
|
||||
srcSurface->height,
|
||||
srcSurface->depth,
|
||||
srcSurface->pitch,
|
||||
srcSlice,
|
||||
(uint32)srcSurface->dim.value(),
|
||||
Latte::MakeHWTileMode(srcSurface->tileMode),
|
||||
srcSurface->aa,
|
||||
srcMip);
|
||||
LatteSurfaceCopyParam srcCopy{};
|
||||
srcCopy.physDataAddr = MEMPTR<void>(srcDataPtr).GetMPTR();
|
||||
srcCopy.swizzle = srcSurface->swizzle;
|
||||
srcCopy.surfaceFormat = srcSurface->format;
|
||||
srcCopy.heightInTexels = surfOutSrc.height;
|
||||
srcCopy.pitch = surfOutSrc.pitch;
|
||||
srcCopy.dim = srcSurface->dim;
|
||||
srcCopy.tilemode = srcTilemode;
|
||||
srcCopy.aa = srcSurface->aa;
|
||||
srcCopy.sliceIndex = (sint32)srcSlice;
|
||||
|
||||
LatteSurfaceCopyParam dstCopy{};
|
||||
dstCopy.physDataAddr = MEMPTR<void>(dstDataPtr).GetMPTR();
|
||||
dstCopy.swizzle = dstSurface->swizzle;
|
||||
dstCopy.surfaceFormat = dstSurface->format;
|
||||
dstCopy.heightInTexels = surfOutDst.height;
|
||||
dstCopy.pitch = surfOutDst.pitch;
|
||||
dstCopy.dim = dstSurface->dim;
|
||||
dstCopy.tilemode = dstTilemode;
|
||||
dstCopy.aa = dstSurface->aa;
|
||||
dstCopy.sliceIndex = (sint32)dstSlice;
|
||||
|
||||
LatteSurfaceCopyRect copyRect{};
|
||||
copyRect.x = 0;
|
||||
copyRect.y = 0;
|
||||
copyRect.width = copyWidth;
|
||||
copyRect.height = copyHeight;
|
||||
|
||||
LatteAsyncCommand_queueTextureCopy(srcCopy, dstCopy, copyRect);
|
||||
// cpu copies have to be finished by the time GX2CopySurface returns
|
||||
// since we delegate the copy to the Latte thread, we have to wait for it to finish here
|
||||
LatteAsyncCommands_waitUntilAllProcessed();
|
||||
|
||||
debugTestForceCPUCopy = true;
|
||||
}
|
||||
|
||||
// send copy command to GPU
|
||||
if( srcHwTileMode > 0 && srcHwTileMode < 16 && dstHwTileMode > 0 && dstHwTileMode < 16 || requestGPURAMCopy )
|
||||
{
|
||||
GX2::GX2ReserveCmdSpace(1+13*2);
|
||||
|
||||
gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13*2),
|
||||
// src
|
||||
(uint32)srcSurface->imagePtr,
|
||||
(uint32)srcSurface->mipPtr,
|
||||
(uint32)srcSurface->swizzle,
|
||||
(uint32)srcSurface->format.value(),
|
||||
(uint32)srcSurface->width,
|
||||
(uint32)srcSurface->height,
|
||||
(uint32)srcSurface->depth,
|
||||
(uint32)srcSurface->pitch,
|
||||
srcSlice,
|
||||
(uint32)srcSurface->dim.value(),
|
||||
(uint32)srcSurface->tileMode.value(),
|
||||
(uint32)srcSurface->aa,
|
||||
srcMip,
|
||||
// dst
|
||||
(uint32)dstSurface->imagePtr,
|
||||
(uint32)dstSurface->mipPtr,
|
||||
(uint32)dstSurface->swizzle,
|
||||
(uint32)dstSurface->format.value(),
|
||||
(uint32)dstSurface->width,
|
||||
(uint32)dstSurface->height,
|
||||
(uint32)dstSurface->depth,
|
||||
(uint32)dstSurface->pitch,
|
||||
dstSlice,
|
||||
(uint32)dstSurface->dim.value(),
|
||||
(uint32)dstSurface->tileMode.value(),
|
||||
(uint32)dstSurface->aa,
|
||||
dstMip);
|
||||
}
|
||||
|
||||
if (requestGPURAMCopy)
|
||||
return; // if RAM copy happens on the GPU side we skip it here
|
||||
|
||||
// manually exclude expensive CPU texture copies for some known game framebuffer textures
|
||||
// todo - find a better way to solve this
|
||||
bool isDynamicTexCopy = false;
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Trine 2
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 0xA0 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Little Inferno
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Donkey Kong Tropical Freeze
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 640 && srcSurface->height == 320 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW Switch Scramble Circus
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && srcSurface->tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED ); // Affordable Space Adventures
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Affordable Space Adventures
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1152 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 680 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 322 && srcSurface->height == 182 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM ); // Qube
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 640 && srcSurface->height == 360 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x80000003); // Cosmophony
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Cosmophony
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x80000003); // The Fall
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x80000003); // Nano Assault Neo
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Hello Kitty Kruisers
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 1024 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT && dstSurface->resFlag == 0x5); // Art Academy
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 260 && srcSurface->height == 148 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1040 && srcSurface->height == 592 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x3); // Nano Assault Neo
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 576 && srcFormat == Latte::E_GX2SURFFMT::D24_S8_UNORM && srcSurface->resFlag == 0x1); // Skylanders SuperChargers
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1152 && srcSurface->height == 648 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs
|
||||
isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 576 && srcSurface->height == 324 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs
|
||||
|
||||
if( isDynamicTexCopy && debugTestForceCPUCopy == false)
|
||||
{
|
||||
debug_printf("Software tex copy blocked\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sint32 copyWidth = dstMipWidth;
|
||||
sint32 copyHeight = dstMipHeight;
|
||||
if (Latte::IsCompressedFormat(dstHwFormat))
|
||||
{
|
||||
copyWidth = (copyWidth + 3) / 4;
|
||||
copyHeight = (copyHeight + 3) / 4;
|
||||
}
|
||||
// copy via GPU commands
|
||||
// for simplicity and performance Cemu uses a HLE command to handle surface copies,
|
||||
// a more accurate implementation would setup actual drawcalls to copy the texture data
|
||||
GX2::GX2ReserveCmdSpace(23);
|
||||
gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 4+9*2),
|
||||
// copy rect
|
||||
(uint32)0, // x
|
||||
(uint32)0, // y
|
||||
(uint32)copyWidth,
|
||||
(uint32)copyHeight,
|
||||
// src
|
||||
(uint32)MEMPTR<void>(srcDataPtr).GetMPTR(),
|
||||
(uint32)srcSurface->swizzle,
|
||||
(uint32)srcSurface->format.value(),
|
||||
(uint32)surfOutSrc.pitch,
|
||||
(uint32)surfOutSrc.height,
|
||||
srcSlice,
|
||||
(uint32)srcSurface->dim.value(),
|
||||
(uint32)srcTilemode,
|
||||
(uint32)srcSurface->aa,
|
||||
// dst
|
||||
(uint32)MEMPTR<void>(dstDataPtr).GetMPTR(),
|
||||
(uint32)dstSurface->swizzle,
|
||||
(uint32)dstSurface->format.value(),
|
||||
(uint32)surfOutDst.pitch,
|
||||
(uint32)surfOutDst.height,
|
||||
dstSlice,
|
||||
(uint32)dstSurface->dim.value(),
|
||||
(uint32)dstTilemode,
|
||||
(uint32)dstSurface->aa
|
||||
);
|
||||
}
|
||||
|
||||
gx2SurfaceCopySoftware(inputData, surfOutSrc.height, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode,
|
||||
outputData, surfOutDst.height, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode,
|
||||
copyWidth, copyHeight, dstBpp);
|
||||
void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSlice, GX2Surface* dstSurface, uint32 dstMip, uint32 dstSlice)
|
||||
{
|
||||
GX2CopySurfaceInternal(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice);
|
||||
}
|
||||
|
||||
void gx2Export_GX2CopySurface(PPCInterpreter_t* hCPU)
|
||||
@ -471,37 +444,12 @@ void gx2Export_GX2ResolveAAColorBuffer(PPCInterpreter_t* hCPU)
|
||||
// handle format
|
||||
Latte::E_GX2SURFFMT srcFormat = srcSurface->format;
|
||||
Latte::E_GX2SURFFMT dstFormat = dstSurface->format;
|
||||
uint32 srcBPP = Latte::GetFormatBits(srcFormat);
|
||||
uint32 dstBPP = Latte::GetFormatBits(dstFormat);
|
||||
sint32 srcStepX = 1;
|
||||
sint32 srcStepY = 1;
|
||||
sint32 dstStepX = 1;
|
||||
sint32 dstStepY = 1;
|
||||
auto srcHwFormat = Latte::GetHWFormat(srcFormat);
|
||||
auto dstHwFormat = Latte::GetHWFormat(dstFormat);
|
||||
// get texture info
|
||||
LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = {0};
|
||||
GX2::GX2CalculateSurfaceInfo(srcSurface, srcMip, &surfOutSrc);
|
||||
LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = {0};
|
||||
GX2::GX2CalculateSurfaceInfo(dstSurface, dstMip, &surfOutDst);
|
||||
// get input pointer
|
||||
uint8* inputData = NULL;
|
||||
cemu_assert(srcMip < srcSurface->numLevels);
|
||||
if( srcMip == 0 )
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr);
|
||||
else if( srcMip == 1 )
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr);
|
||||
else
|
||||
inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr+srcSurface->mipOffset[srcMip-1]);
|
||||
// get output pointer
|
||||
uint8* outputData = NULL;
|
||||
cemu_assert(dstMip < dstSurface->numLevels);
|
||||
if( dstMip == 0 )
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr);
|
||||
else if( dstMip == 1 )
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr);
|
||||
else
|
||||
outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr+dstSurface->mipOffset[dstMip-1]);
|
||||
// calculate step size for compressed textures
|
||||
if( Latte::IsCompressedFormat(srcHwFormat) )
|
||||
{
|
||||
@ -513,64 +461,17 @@ void gx2Export_GX2ResolveAAColorBuffer(PPCInterpreter_t* hCPU)
|
||||
dstStepX = 4;
|
||||
dstStepY = 4;
|
||||
}
|
||||
if( srcStepX != dstStepX || srcStepY != dstStepY )
|
||||
assert_dbg();
|
||||
cemu_assert_debug(srcStepX == dstStepX && srcStepY == dstStepY);
|
||||
|
||||
if( srcHwFormat != dstHwFormat )
|
||||
{
|
||||
// mismatching format
|
||||
debug_printf("GX2CopySurface(): Format mismatch\n");
|
||||
debug_printf("GX2ResolveAAColorBuffer(): Format mismatch\n");
|
||||
cemu_assert_unimplemented();
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// src
|
||||
uint32 srcPitch = surfOutSrc.pitch;
|
||||
uint32 srcSwizzle = srcSurface->swizzle;
|
||||
uint32 srcPipeSwizzle = (srcSwizzle>>8)&1;
|
||||
uint32 srcBankSwizzle = ((srcSwizzle>>9)&3);
|
||||
uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode;
|
||||
uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1);
|
||||
// dst
|
||||
uint32 dstPitch = surfOutDst.pitch;
|
||||
uint32 dstSwizzle = dstSurface->swizzle;
|
||||
uint32 dstPipeSwizzle = (dstSwizzle>>8)&1;
|
||||
uint32 dstBankSwizzle = ((dstSwizzle>>9)&3);
|
||||
uint32 dstTileMode = (uint32)surfOutDst.hwTileMode;
|
||||
uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1);
|
||||
|
||||
// send copy command to GPU
|
||||
GX2::GX2ReserveCmdSpace(1 + 13 * 2);
|
||||
gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2),
|
||||
// src
|
||||
(uint32)srcSurface->imagePtr,
|
||||
(uint32)srcSurface->mipPtr,
|
||||
(uint32)srcSurface->swizzle,
|
||||
(uint32)srcSurface->format.value(),
|
||||
(uint32)srcSurface->width,
|
||||
(uint32)srcSurface->height,
|
||||
(uint32)srcSurface->depth,
|
||||
(uint32)srcSurface->pitch,
|
||||
srcSlice,
|
||||
(uint32)srcSurface->dim.value(),
|
||||
(uint32)srcSurface->tileMode.value(),
|
||||
(uint32)srcSurface->aa,
|
||||
srcMip,
|
||||
// dst
|
||||
(uint32)dstSurface->imagePtr,
|
||||
(uint32)dstSurface->mipPtr,
|
||||
(uint32)dstSurface->swizzle,
|
||||
(uint32)dstSurface->format.value(),
|
||||
(uint32)dstSurface->width,
|
||||
(uint32)dstSurface->height,
|
||||
(uint32)dstSurface->depth,
|
||||
(uint32)dstSurface->pitch,
|
||||
dstSlice,
|
||||
(uint32)dstSurface->dim.value(),
|
||||
(uint32)dstSurface->tileMode.value(),
|
||||
(uint32)dstSurface->aa,
|
||||
dstMip);
|
||||
|
||||
GX2CopySurfaceInternal(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice);
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
}
|
||||
|
||||
@ -600,58 +501,11 @@ void gx2Export_GX2ConvertDepthBufferToTextureSurface(PPCInterpreter_t* hCPU)
|
||||
return;
|
||||
}
|
||||
|
||||
// note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible.
|
||||
// src
|
||||
uint32 srcPitch = surfOutSrc.pitch;
|
||||
uint32 srcSwizzle = depthBuffer->surface.swizzle;
|
||||
uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1;
|
||||
uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3);
|
||||
uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode;
|
||||
uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1);
|
||||
// dst
|
||||
uint32 dstPitch = surfOutDst.pitch;
|
||||
uint32 dstSwizzle = dstSurface->swizzle;
|
||||
uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1;
|
||||
uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3);
|
||||
uint32 dstTileMode = (uint32)surfOutDst.hwTileMode;
|
||||
uint32 dstDepth = srcDepth;
|
||||
|
||||
sint32 srcMip = 0;
|
||||
|
||||
uint32 numSlices = std::max<uint32>(depthBuffer->viewNumSlices, 1);
|
||||
GX2::GX2ReserveCmdSpace((1 + 13 * 2) * numSlices);
|
||||
for (uint32 subSliceIndex = 0; subSliceIndex < numSlices; subSliceIndex++)
|
||||
{
|
||||
// send copy command to GPU
|
||||
gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2),
|
||||
// src
|
||||
(uint32)(depthBuffer->surface.imagePtr),
|
||||
(uint32)(depthBuffer->surface.mipPtr),
|
||||
(uint32)(depthBuffer->surface.swizzle),
|
||||
(uint32)(depthBuffer->surface.format.value()),
|
||||
(uint32)(depthBuffer->surface.width),
|
||||
(uint32)(depthBuffer->surface.height),
|
||||
(uint32)(depthBuffer->surface.depth),
|
||||
(uint32)(depthBuffer->surface.pitch),
|
||||
(uint32)(depthBuffer->viewFirstSlice) + subSliceIndex,
|
||||
(uint32)(depthBuffer->surface.dim.value()),
|
||||
(uint32)(depthBuffer->surface.tileMode.value()),
|
||||
(uint32)(depthBuffer->surface.aa),
|
||||
srcMip,
|
||||
// dst
|
||||
(uint32)(dstSurface->imagePtr),
|
||||
(uint32)(dstSurface->mipPtr),
|
||||
(uint32)(dstSurface->swizzle),
|
||||
(uint32)(dstSurface->format.value()),
|
||||
(uint32)(dstSurface->width),
|
||||
(uint32)(dstSurface->height),
|
||||
(uint32)(dstSurface->depth),
|
||||
(uint32)(dstSurface->pitch),
|
||||
dstSlice + subSliceIndex,
|
||||
(uint32)(dstSurface->dim.value()),
|
||||
(uint32)(dstSurface->tileMode.value()),
|
||||
(uint32)(dstSurface->aa),
|
||||
dstMip);
|
||||
GX2CopySurfaceInternal(&depthBuffer->surface, depthBuffer->viewMip.value(), depthBuffer->viewFirstSlice.value() + subSliceIndex, dstSurface, dstMip, dstSlice + subSliceIndex);
|
||||
}
|
||||
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
|
||||
@ -43,4 +43,16 @@ void ExpressionParser_test()
|
||||
cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > 2") == 0.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > 2) -> 0.0
|
||||
cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > -2") == 1.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > -2) -> 1.0
|
||||
cemu_assert_debug(_testEvaluateToType<float>("(5 == 5) > (5 == 6)") == 1.0f);
|
||||
|
||||
// reloc modifier behavior
|
||||
ep = {};
|
||||
ep.AddConstant("test", 5.0);
|
||||
cemu_assert_debug(ep.IsValidExpression("test@ha") == true);
|
||||
cemu_assert_debug(ep.IsValidExpression("test+15@lo") == true);
|
||||
cemu_assert_debug(ep.IsValidExpression("test@ha + test@ha") == true); // technically not legal but we allow it for backwards compatibility (BotW extended memory pack relies on this)
|
||||
cemu_assert_debug(ep.IsValidExpression("test@ha + test@lo") == false); // mixed modifier not allowed
|
||||
|
||||
cemu_assert_debug(ep.Evaluate("test+15@lo") == 20.0f);
|
||||
cemu_assert_debug(ep.Evaluate("test+15@hi") == 0.0f);
|
||||
|
||||
}
|
||||
@ -29,11 +29,43 @@ inline std::from_chars_result _convFastFloatResult(fast_float::from_chars_result
|
||||
template<class TType = double>
|
||||
class TExpressionParser
|
||||
{
|
||||
// starting with Cemu 2.7 reloc modifiers (like @ha or @l) have been changed to match LLVM/GAS behavior
|
||||
// they may only be used once in an expression and get applied to the final result of the expression, regardless of their location
|
||||
// E.g. "var@ha + 0x20" was intepreted as ha(var) + 0x20 before, now it's interpreted as ha(var+0x20)
|
||||
enum class RelocModifier
|
||||
{
|
||||
None = 0,
|
||||
High, // @hi @h
|
||||
HighArithmetic, // @ha
|
||||
Low, // @lo @l
|
||||
};
|
||||
|
||||
public:
|
||||
static_assert(std::is_arithmetic_v<TType>);
|
||||
using ConstantCallback_t = TType(*)(std::string_view var_name);
|
||||
using FunctionCallback_t = TType(*)(std::string_view var_name, TType parameter);
|
||||
|
||||
static TType ExpressionFuncHA(TType input)
|
||||
{
|
||||
uint32 addr = (uint32)input;
|
||||
addr = (((addr >> 16) + ((addr & 0x8000) ? 1 : 0)) & 0xffff);
|
||||
return (TType)addr;
|
||||
}
|
||||
|
||||
static TType ExpressionFuncHI(TType input)
|
||||
{
|
||||
uint32 addr = (uint32)input;
|
||||
addr = (addr >> 16) & 0xffff;
|
||||
return (TType)addr;
|
||||
}
|
||||
|
||||
static TType ExpressionFuncLO(TType input)
|
||||
{
|
||||
uint32 addr = (uint32)input;
|
||||
addr &= 0xffff;
|
||||
return (TType)addr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Evaluate(std::string_view expression) const
|
||||
{
|
||||
@ -45,6 +77,7 @@ public:
|
||||
{
|
||||
std::queue<std::shared_ptr<TokenBase>> output;
|
||||
std::stack<std::shared_ptr<TokenBase>> operators;
|
||||
RelocModifier relocModifier = RelocModifier::None;
|
||||
|
||||
if (expression.empty())
|
||||
{
|
||||
@ -73,11 +106,11 @@ public:
|
||||
auto converted = (TType)ConvertString(view, &offset);
|
||||
output.emplace(std::make_shared<TokenNumber>(converted));
|
||||
i += offset;
|
||||
|
||||
last_operator_token = false;
|
||||
// check for relocation modifier suffix (e.g. 74@ha)
|
||||
i += ParseRelocModifier(expression.substr(i), relocModifier, false); // can throw
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for variables
|
||||
if (isalpha(c) || c == '_' || c == '$')
|
||||
{
|
||||
@ -92,8 +125,14 @@ public:
|
||||
}
|
||||
|
||||
const size_t len = j - i;
|
||||
const std::string_view view = expression.substr(i, len);
|
||||
std::string_view view = expression.substr(i, len);
|
||||
|
||||
// check for relocation modifier
|
||||
if (auto suffixPos = view.find_last_of('@'); suffixPos != std::string::npos)
|
||||
{
|
||||
sint32 modifierLength = ParseRelocModifier(view.substr(suffixPos), relocModifier, true);
|
||||
view.remove_suffix(modifierLength);
|
||||
}
|
||||
// check for function
|
||||
if (m_function_callback)
|
||||
{
|
||||
@ -358,6 +397,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (relocModifier == RelocModifier::High)
|
||||
evaluation.top() = ExpressionFuncHI(evaluation.top());
|
||||
else if (relocModifier == RelocModifier::HighArithmetic)
|
||||
evaluation.top() = ExpressionFuncHA(evaluation.top());
|
||||
else if (relocModifier == RelocModifier::Low)
|
||||
evaluation.top() = ExpressionFuncLO(evaluation.top());
|
||||
|
||||
return evaluation.top();
|
||||
}
|
||||
|
||||
@ -418,7 +464,6 @@ public:
|
||||
{
|
||||
m_function_callback = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, TType> m_constants;
|
||||
ConstantCallback_t m_constant_callback = nullptr;
|
||||
@ -429,6 +474,64 @@ private:
|
||||
return str.find('.') != std::string_view::npos;
|
||||
}
|
||||
|
||||
// update current relocation modifier for the expression
|
||||
void SetRelocationModifier(RelocModifier& relocModifier, RelocModifier newModifier) const
|
||||
{
|
||||
if (relocModifier == newModifier)
|
||||
return;
|
||||
// catch mismatching relocation modifiers (e.g. sym@ha + sym2@lo)
|
||||
if (relocModifier != RelocModifier::None)
|
||||
throw std::runtime_error(fmt::format("Mismatching relocation modifiers (suffix @..) in expression"));
|
||||
relocModifier = newModifier;
|
||||
}
|
||||
|
||||
// parse modifiers like @ha, @l. Throws on parse error (unless ignored). Returns length of parsed suffix including @ symbol
|
||||
sint32 ParseRelocModifier(std::string_view str, RelocModifier& relocModifier, bool ignoreParseError) const
|
||||
{
|
||||
auto origStr = str;
|
||||
if (str.empty() || str[0] != '@')
|
||||
return 0;
|
||||
// skip the @
|
||||
str.remove_prefix(1);
|
||||
if (str.empty())
|
||||
return 0;
|
||||
char c0 = std::tolower(str[0]);
|
||||
// check for two character modifiers: lo, ha, hi
|
||||
if (str.size() >= 2)
|
||||
{
|
||||
char c1 = std::tolower(str[1]);
|
||||
if (c0 == 'h' && c1 == 'a')
|
||||
{
|
||||
SetRelocationModifier(relocModifier, RelocModifier::HighArithmetic);
|
||||
return 3;
|
||||
}
|
||||
else if (c0 == 'h' && c1 == 'i')
|
||||
{
|
||||
SetRelocationModifier(relocModifier, RelocModifier::High);
|
||||
return 3;
|
||||
}
|
||||
else if (c0 == 'l' && c1 == 'o')
|
||||
{
|
||||
SetRelocationModifier(relocModifier, RelocModifier::Low);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
// check for single character modifiers
|
||||
if (c0 == 'h')
|
||||
{
|
||||
SetRelocationModifier(relocModifier, RelocModifier::High);
|
||||
return 2;
|
||||
}
|
||||
else if (c0 == 'l')
|
||||
{
|
||||
SetRelocationModifier(relocModifier, RelocModifier::Low);
|
||||
return 2;
|
||||
}
|
||||
if (!ignoreParseError)
|
||||
throw std::runtime_error(fmt::format("Unknown relocation modifier (only @lo, @hi, @ha, @l, @h are supported) at: {}", origStr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
double ConvertString(std::string_view str, size_t* index_after = nullptr) const
|
||||
{
|
||||
const char* strInitial = str.data();
|
||||
|
||||
@ -42,6 +42,7 @@ enum
|
||||
OP_FORM_OP3_A_IMM, // rA, rS, rB is imm - has RC bit
|
||||
OP_FORM_BRANCH_S16,
|
||||
OP_FORM_BRANCH_S24,
|
||||
OP_FORM_BRANCH_BCCLR, // conditional BLR
|
||||
OP_FORM_OP2_D_HSIMM, // rD, signed imm shifted imm (high half)
|
||||
OP_FORM_RLWINM,
|
||||
OP_FORM_RLWINM_EXTENDED, // alternative mnemonics of rlwinm
|
||||
@ -632,12 +633,6 @@ public:
|
||||
std::string_view svExpressionPart(startPtr, endPtr - startPtr);
|
||||
std::string_view svRegPart(memoryRegBegin, memoryRegEnd - memoryRegBegin);
|
||||
sint32 memGpr = _parseRegIndex(svRegPart, "r");
|
||||
//if (_ppcAssembler_parseRegister(svRegPart, "r", memGpr) == false || (memGpr < 0 || memGpr >= 32))
|
||||
//{
|
||||
// sprintf(_assemblerErrorMessageDepr, "\'%.*s\' is not a valid GPR", (int)(memoryRegEnd - memoryRegBegin), memoryRegBegin);
|
||||
// ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr);
|
||||
// return false;
|
||||
//}
|
||||
if (memGpr < 0 || memGpr >= 32)
|
||||
{
|
||||
ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Memory operand register \"{}\" is not a valid GPR (expected r0 - r31)", svRegPart));
|
||||
@ -1089,12 +1084,12 @@ PPCInstructionDef ppcInstructionTable[] =
|
||||
|
||||
{PPCASM_OP_BLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS, nullptr},
|
||||
|
||||
{PPCASM_OP_BLTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, // less
|
||||
{PPCASM_OP_BGTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, // greater
|
||||
{PPCASM_OP_BEQLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, // equal
|
||||
{PPCASM_OP_BLELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, // less or equal (not greater)
|
||||
{PPCASM_OP_BGELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, // greater or equal (not less)
|
||||
{PPCASM_OP_BNELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, // not equal
|
||||
{PPCASM_OP_BLTLR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, // less
|
||||
{PPCASM_OP_BGTLR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, // greater
|
||||
{PPCASM_OP_BEQLR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, // equal
|
||||
{PPCASM_OP_BLELR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, // less or equal (not greater)
|
||||
{PPCASM_OP_BGELR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, // greater or equal (not less)
|
||||
{PPCASM_OP_BNELR, 0, 19, 16, OPC_NONE, OP_FORM_BRANCH_BCCLR, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, // not equal
|
||||
|
||||
{PPCASM_OP_ISYNC, 0, 19, 150, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, 0, 0, nullptr},
|
||||
|
||||
@ -1604,6 +1599,18 @@ void ppcAssembler_disassemble(uint32 virtualAddress, uint32 opcode, PPCDisassemb
|
||||
disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM;
|
||||
disInstr->operand[0].immU32 = dest;
|
||||
}
|
||||
else if (iDef->instructionForm == OP_FORM_BRANCH_BCCLR)
|
||||
{
|
||||
uint32 BO, BI, BD;
|
||||
PPC_OPC_TEMPL_XL(opcode, BO, BI, BD);
|
||||
uint32 crIndex = BI/4;
|
||||
if (crIndex != 0) // cr0 is implicit
|
||||
{
|
||||
disInstr->operandMask |= operand0Bit;
|
||||
disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR;
|
||||
disInstr->operand[0].registerIndex = crIndex;
|
||||
}
|
||||
}
|
||||
else if (iDef->instructionForm == OP_FORM_OP2_D_HSIMM)
|
||||
{
|
||||
sint32 rD, rA;
|
||||
@ -2286,9 +2293,72 @@ bool _ppcAssembler_emitDataDirective(PPCAssemblerContext& internalInfo, ASM_DATA
|
||||
ppcAssembler_setError(internalInfo.ctx, "String constants must end with a quotation mark. Example: \"text\"");
|
||||
return false;
|
||||
}
|
||||
expressionStr.remove_prefix(1);
|
||||
expressionStr.remove_suffix(1);
|
||||
// unescape C-style characters
|
||||
std::vector<uint8> stringData;
|
||||
stringData.reserve(expressionStr.size());
|
||||
while (!expressionStr.empty())
|
||||
{
|
||||
char c = expressionStr.front();
|
||||
expressionStr.remove_prefix(1);
|
||||
if (c != '\\')
|
||||
{
|
||||
stringData.push_back(c);
|
||||
continue;
|
||||
}
|
||||
if (expressionStr.empty())
|
||||
break;
|
||||
c = expressionStr.front();
|
||||
expressionStr.remove_prefix(1);
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
c -= ('A' - 'a');
|
||||
if (c == '\\')
|
||||
stringData.push_back(c);
|
||||
else if (c == 'n')
|
||||
stringData.push_back('\n');
|
||||
else if (c == 't')
|
||||
stringData.push_back('\t');
|
||||
else if (c == 'r')
|
||||
stringData.push_back('\r');
|
||||
else if (c == '\"')
|
||||
stringData.push_back('\"');
|
||||
else if (c == '\'')
|
||||
stringData.push_back('\'');
|
||||
else if (c == 'x')
|
||||
{
|
||||
uint32 value = 0;
|
||||
bool hasDigits = false;
|
||||
while (!expressionStr.empty())
|
||||
{
|
||||
char h = expressionStr.front();
|
||||
uint32 digit;
|
||||
if (h >= '0' && h <= '9')
|
||||
digit = h - '0';
|
||||
else if (h >= 'a' && h <= 'f')
|
||||
digit = h - 'a' + 10;
|
||||
else if (h >= 'A' && h <= 'F')
|
||||
digit = h - 'A' + 10;
|
||||
else
|
||||
break;
|
||||
hasDigits = true;
|
||||
value = (value << 4) | digit;
|
||||
expressionStr.remove_prefix(1);
|
||||
}
|
||||
if (hasDigits)
|
||||
stringData.push_back((uint8)value);
|
||||
else
|
||||
{
|
||||
ppcAssembler_setError(internalInfo.ctx, "String contains invalid hex escape sequence");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
stringData.push_back('\\'); // output as backward slash if unhandled
|
||||
}
|
||||
// write string bytes + null-termination character
|
||||
size_t strConstantLength = expressionStr.size() - 2;
|
||||
internalInfo.ctx->outputData.insert(internalInfo.ctx->outputData.end(), expressionStr.data() + 1, expressionStr.data() + 1 + strConstantLength);
|
||||
internalInfo.ctx->outputData.insert(internalInfo.ctx->outputData.end(), stringData.data(), stringData.data() + stringData.size());
|
||||
internalInfo.ctx->outputData.emplace_back(0);
|
||||
continue;
|
||||
}
|
||||
@ -2790,6 +2860,16 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut*
|
||||
if (_ppcAssembler_processBranchOperandS26(internalInfo, 0) == false)
|
||||
return false;
|
||||
}
|
||||
else if (iDef->instructionForm == OP_FORM_BRANCH_BCCLR)
|
||||
{
|
||||
// check for implicit cr
|
||||
sint32 crIndex = 0;
|
||||
if (internalInfo.listOperandStr.size() >= 1)
|
||||
{
|
||||
if (_ppcAssembler_processCROperand(internalInfo, 0, 18, false) == false)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (iDef->instructionForm == OP_FORM_XL_CR)
|
||||
{
|
||||
if (_ppcAssembler_processBIOperand(internalInfo, 0, 21) == false)
|
||||
|
||||
@ -95,7 +95,7 @@ void ExceptionHandler_LogGeneralInfo()
|
||||
MPTR currentStackVAddr = hCPU->gpr[1];
|
||||
CrashLog_WriteLine("");
|
||||
CrashLog_WriteHeader("PPC stack trace");
|
||||
DebugLogStackTrace(currentThread, currentStackVAddr, true);
|
||||
DebugLogStackTrace(currentThread, currentStackVAddr);
|
||||
|
||||
// stack dump
|
||||
CrashLog_WriteLine("");
|
||||
|
||||
@ -624,7 +624,7 @@ inline uint32 GetTitleIdLow(uint64 titleId)
|
||||
#include "Cafe/HW/Espresso/PPCCallback.h"
|
||||
|
||||
// PPC stack trace printer
|
||||
void DebugLogStackTrace(struct OSThread_t* thread, MPTR sp, bool printSymbols = false);
|
||||
void DebugLogStackTrace(struct OSThread_t* thread, MPTR sp);
|
||||
|
||||
// generic formatter for enums (to underlying)
|
||||
template <typename Enum>
|
||||
|
||||
@ -436,7 +436,7 @@ int CemuApp::FilterEvent(wxEvent& event)
|
||||
}
|
||||
|
||||
// track if debugger window or its child windows are focused
|
||||
if (g_debugger_window && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
|
||||
if (s_debuggerWindow && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
|
||||
{
|
||||
wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow);
|
||||
|
||||
@ -449,12 +449,12 @@ int CemuApp::FilterEvent(wxEvent& event)
|
||||
wxWindow* window_it = target_window;
|
||||
while (window_it)
|
||||
{
|
||||
if (window_it == g_debugger_window) g_window_info.debugger_focused = true;
|
||||
if (window_it == s_debuggerWindow) g_window_info.debugger_focused = true;
|
||||
window_it = window_it->GetParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!g_debugger_window)
|
||||
else if (!s_debuggerWindow)
|
||||
{
|
||||
g_window_info.debugger_focused = false;
|
||||
}
|
||||
|
||||
@ -443,6 +443,12 @@ wxString MainWindow::GetInitialWindowTitle()
|
||||
|
||||
void MainWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
if (m_debugger_window)
|
||||
{
|
||||
m_debugger_window->CleanupForDestroy();
|
||||
m_debugger_window->Destroy();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
|
||||
if(m_game_list)
|
||||
m_game_list->OnClose(event);
|
||||
|
||||
@ -101,42 +101,40 @@ void BreakpointWindow::OnUpdateView()
|
||||
Freeze();
|
||||
|
||||
m_breakpoints->DeleteAllItems();
|
||||
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
|
||||
|
||||
if (!debuggerState.breakpoints.empty())
|
||||
uint32 i = 0;
|
||||
for (const auto bpBase : breakpoints)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
for (const auto bpBase : debuggerState.breakpoints)
|
||||
DebuggerBreakpoint* bp = bpBase;
|
||||
while (bp)
|
||||
{
|
||||
DebuggerBreakpoint* bp = bpBase;
|
||||
while (bp)
|
||||
{
|
||||
wxListItem item = {};
|
||||
item.SetId(i++);
|
||||
wxListItem item = {};
|
||||
item.SetId(i++);
|
||||
|
||||
const auto index = m_breakpoints->InsertItem(item);
|
||||
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
|
||||
const char* typeName = "UKN";
|
||||
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
|
||||
typeName = "X";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
|
||||
typeName = "LOG";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
|
||||
typeName = "XS";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
|
||||
typeName = "R";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
typeName = "W";
|
||||
const auto index = m_breakpoints->InsertItem(item);
|
||||
m_breakpoints->SetItemPtrData(index, (uintptr_t)bp->id);
|
||||
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
|
||||
const char* typeName = "UKN";
|
||||
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
|
||||
typeName = "X";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
|
||||
typeName = "LOG";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
|
||||
typeName = "XS";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
|
||||
typeName = "R";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
typeName = "W";
|
||||
|
||||
m_breakpoints->SetItem(index, ColumnType, typeName);
|
||||
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
|
||||
m_breakpoints->CheckItem(index, bp->enabled);
|
||||
m_breakpoints->SetItemPtrData(index, (wxUIntPtr)bp);
|
||||
m_breakpoints->SetItem(index, ColumnType, typeName);
|
||||
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
|
||||
m_breakpoints->CheckItem(index, bp->enabled);
|
||||
|
||||
bp = bp->next;
|
||||
}
|
||||
bp = bp->next;
|
||||
}
|
||||
}
|
||||
|
||||
debugger_unlockBreakpoints();
|
||||
Thaw();
|
||||
}
|
||||
|
||||
@ -147,15 +145,16 @@ void BreakpointWindow::OnGameLoaded()
|
||||
|
||||
void BreakpointWindow::OnBreakpointToggled(wxListEvent& event)
|
||||
{
|
||||
const int32_t index = event.GetIndex();
|
||||
if (0 <= index && index < m_breakpoints->GetItemCount())
|
||||
const sint32 index = event.GetIndex();
|
||||
uint64 bpId = (uint64)event.GetData();
|
||||
debugger_lockBreakpoints();
|
||||
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
|
||||
if (bp)
|
||||
{
|
||||
const bool state = m_breakpoints->IsItemChecked(index);
|
||||
wxString line = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
DebuggerBreakpoint* bp = (DebuggerBreakpoint*)m_breakpoints->GetItemData(index);
|
||||
const uint32 address = std::stoul(line.ToStdString(), nullptr, 16);
|
||||
debugger_toggleBreakpoint(address, state, bp);
|
||||
debugger_toggleBreakpoint(bp->address, state, bp);
|
||||
}
|
||||
debugger_unlockBreakpoints();
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
@ -178,8 +177,7 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
{
|
||||
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
g_debuggerDispatcher.MoveToAddressInDisassembly(address);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -192,22 +190,37 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment);
|
||||
if (x <= comment_width)
|
||||
{
|
||||
if (index >= debuggerState.breakpoints.size())
|
||||
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
|
||||
if (index >= breakpoints.size())
|
||||
{
|
||||
debugger_unlockBreakpoints();
|
||||
return;
|
||||
}
|
||||
DebuggerBreakpoint* bp = breakpoints[index];
|
||||
auto bpId = bp->id;
|
||||
|
||||
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
|
||||
|
||||
auto it = debuggerState.breakpoints.begin();
|
||||
std::advance(it, index);
|
||||
|
||||
const wxString dialogTitle = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Enter a new logging message") : _("Enter a new comment");
|
||||
const wxString dialogMessage = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
|
||||
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, (*it)->comment);
|
||||
bool isLoggingBP = bp->bpType == DEBUGGER_BP_T_LOGGING;
|
||||
const wxString dialogTitle = isLoggingBP ? _("Enter a new logging message") : _("Enter a new comment");
|
||||
const wxString dialogMessage = isLoggingBP ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
|
||||
const wxString existingComment = bp->comment;
|
||||
debugger_unlockBreakpoints();
|
||||
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, existingComment);
|
||||
if (set_comment_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
(*it)->comment = set_comment_dialog.GetValue().ToStdWstring();
|
||||
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
|
||||
if (isLoggingBP)
|
||||
{
|
||||
debugger_lockBreakpoints();
|
||||
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
|
||||
if (bp)
|
||||
{
|
||||
bp->comment = set_comment_dialog.GetValue().ToStdWstring();
|
||||
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
|
||||
}
|
||||
debugger_unlockBreakpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,20 +255,27 @@ void BreakpointWindow::OnRightDown(wxMouseEvent& event)
|
||||
|
||||
void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt)
|
||||
{
|
||||
auto& breakpoints = debugger_lockBreakpoints();
|
||||
long selectedIndex = m_breakpoints->GetFirstSelected();
|
||||
if (selectedIndex == wxNOT_FOUND || selectedIndex < 0 || selectedIndex >= (long)breakpoints.size())
|
||||
{
|
||||
debugger_unlockBreakpoints();
|
||||
return;
|
||||
}
|
||||
BreakpointId bpId = (uint64)m_breakpoints->GetItemData(selectedIndex);
|
||||
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
|
||||
if (!bp)
|
||||
{
|
||||
debugger_unlockBreakpoints();
|
||||
return;
|
||||
}
|
||||
if (evt.GetId() == MENU_ID_DELETE_BP)
|
||||
{
|
||||
long sel = m_breakpoints->GetFirstSelected();
|
||||
if (sel == wxNOT_FOUND || sel < 0 || sel >= m_breakpoints->GetItemCount())
|
||||
return;
|
||||
|
||||
auto it = debuggerState.breakpoints.begin();
|
||||
std::advance(it, sel);
|
||||
|
||||
debugger_deleteBreakpoint(*it);
|
||||
|
||||
debugger_deleteBreakpoint(bpId);
|
||||
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
|
||||
wxPostEvent(this->m_parent, evt);
|
||||
}
|
||||
debugger_unlockBreakpoints();
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
|
||||
|
||||
@ -54,7 +54,7 @@ wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent);
|
||||
wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
|
||||
@ -65,7 +65,7 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
|
||||
EVT_CLOSE(DebuggerWindow2::OnClose)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_IP, DebuggerWindow2::OnMoveIP)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_TO_DISASM_ADDR, DebuggerWindow2::OnMoveToDisasmAddr)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
|
||||
@ -79,8 +79,25 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
|
||||
EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
DebuggerModuleInfo::DebuggerModuleInfo(RPLModule* module)
|
||||
{
|
||||
moduleName = module->moduleName2;
|
||||
patchCRC = module->patchCRC;
|
||||
textArea.base = module->regionMappingBase_text.GetMPTR();
|
||||
textArea.size = module->regionSize_text;
|
||||
dataArea.base = module->regionMappingBase_data;
|
||||
dataArea.size = module->regionSize_data;
|
||||
|
||||
DebuggerWindow2* g_debugger_window;
|
||||
}
|
||||
|
||||
struct DebuggerModuleInfoNotify : public wxClientData
|
||||
{
|
||||
DebuggerModuleInfo moduleInfo;
|
||||
|
||||
DebuggerModuleInfoNotify(RPLModule* module) : moduleInfo(module) {};
|
||||
};
|
||||
|
||||
DebuggerWindow2* s_debuggerWindow;
|
||||
|
||||
void DebuggerConfig::Load(XMLConfigParser& parser)
|
||||
{
|
||||
@ -130,7 +147,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
|
||||
const auto comment = element.get("Comment", "");
|
||||
|
||||
// calculate absolute address
|
||||
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data;
|
||||
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->moduleInfo.textArea.base : this->moduleInfo.dataArea.base;
|
||||
uint32 address = module_base_address + relative_address;
|
||||
|
||||
// don't change anything if there's already a breakpoint
|
||||
@ -179,49 +196,52 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
|
||||
void DebuggerModuleStorage::Save(XMLConfigParser& parser)
|
||||
{
|
||||
auto breakpoints_parser = parser.set("Breakpoints");
|
||||
for (const auto& bp : debuggerState.breakpoints)
|
||||
auto& breakpoints = debugger_lockBreakpoints();
|
||||
for (const auto& bp : breakpoints)
|
||||
{
|
||||
// check breakpoint type
|
||||
if (bp->dbType != DEBUGGER_BP_T_DEBUGGER)
|
||||
continue;
|
||||
|
||||
// check whether the breakpoint is part of the current module being saved
|
||||
RPLModule* address_module;
|
||||
if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
|
||||
else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address);
|
||||
else continue;
|
||||
|
||||
if (!address_module || !(address_module->moduleName2 == this->module_name && address_module->patchCRC == this->crc_hash))
|
||||
uint32 relativeAddress;
|
||||
if (moduleInfo.textArea.ContainsAddress(bp->address))
|
||||
relativeAddress = bp->address - moduleInfo.textArea.base;
|
||||
else if (moduleInfo.dataArea.ContainsAddress(bp->address))
|
||||
relativeAddress = bp->address - moduleInfo.dataArea.base;
|
||||
else
|
||||
continue;
|
||||
|
||||
uint32_t relative_address = bp->address - (bp->isMemBP() ? address_module->regionMappingBase_data : address_module->regionMappingBase_text.GetMPTR());
|
||||
auto entry = breakpoints_parser.set("Entry");
|
||||
entry.set("Address", fmt::format("{:#10x}", relative_address));
|
||||
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
|
||||
entry.set("Comment", boost::nowide::narrow(bp->comment).c_str());
|
||||
entry.set("Type", bp->bpType);
|
||||
entry.set("Enabled", bp->enabled);
|
||||
|
||||
if (this->delete_breakpoints_after_saving) debugger_deleteBreakpoint(bp);
|
||||
if (this->delete_breakpoints_after_saving)
|
||||
debugger_deleteBreakpoint(bp->id);
|
||||
this->delete_breakpoints_after_saving = false;
|
||||
}
|
||||
debugger_unlockBreakpoints();
|
||||
|
||||
auto comments_parser = parser.set("Comments");
|
||||
for (const auto& comment_entry : rplDebugSymbol_getSymbols())
|
||||
{
|
||||
// check comment type
|
||||
const auto comment_address = comment_entry.first;
|
||||
const auto commentAddress = comment_entry.first;
|
||||
const auto comment = static_cast<rplDebugSymbolComment*>(comment_entry.second);
|
||||
if (!comment || comment->type != RplDebugSymbolComment)
|
||||
continue;
|
||||
|
||||
// check whether it's part of the current module being saved
|
||||
RPLModule* address_module = RPLLoader_FindModuleByCodeAddr(comment_entry.first);
|
||||
if (!address_module || !(address_module->moduleName2 == module_name && address_module->patchCRC == this->crc_hash))
|
||||
// check whether the comment is part of the current module being saved
|
||||
uint32 relativeAddress;
|
||||
if (moduleInfo.textArea.ContainsAddress(commentAddress))
|
||||
relativeAddress = commentAddress - moduleInfo.textArea.base;
|
||||
else
|
||||
continue;
|
||||
|
||||
uint32_t relative_address = comment_address - address_module->regionMappingBase_text.GetMPTR();
|
||||
auto entry = comments_parser.set("Entry");
|
||||
entry.set("Address", fmt::format("{:#10x}", relative_address));
|
||||
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
|
||||
entry.set("Comment", boost::nowide::narrow(comment->comment).c_str());
|
||||
}
|
||||
}
|
||||
@ -256,28 +276,29 @@ void DebuggerWindow2::CreateToolBar()
|
||||
|
||||
}
|
||||
|
||||
void DebuggerWindow2::SaveModuleStorage(const RPLModule* module, bool delete_breakpoints)
|
||||
void DebuggerWindow2::LoadModuleStorage(const DebuggerModuleInfo& moduleInfo)
|
||||
{
|
||||
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
|
||||
for (auto& module_storage : m_modules_storage)
|
||||
{
|
||||
if (module_storage->data().module_name == module->moduleName2 && module_storage->data().crc_hash == module->patchCRC)
|
||||
{
|
||||
module_storage->data().delete_breakpoints_after_saving = delete_breakpoints;
|
||||
module_storage->Save(path);
|
||||
if (delete_breakpoints) m_modules_storage.erase(std::find(m_modules_storage.begin(), m_modules_storage.end(), module_storage));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DebuggerWindow2::LoadModuleStorage(const RPLModule* module)
|
||||
{
|
||||
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
|
||||
bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
|
||||
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
|
||||
bool already_loaded = std::any_of(m_modulesStorage.begin(), m_modulesStorage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
|
||||
if (!path.empty() && !already_loaded)
|
||||
{
|
||||
m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load();
|
||||
m_modulesStorage.emplace_back(new XMLDebuggerModuleConfig(path, { moduleInfo, false }))->Load();
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow2::SaveModuleStorage(const DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage)
|
||||
{
|
||||
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
|
||||
for (auto& moduleStorage : m_modulesStorage)
|
||||
{
|
||||
if (moduleStorage->data().moduleInfo.moduleName == moduleInfo.moduleName && moduleStorage->data().moduleInfo.patchCRC == moduleInfo.patchCRC)
|
||||
{
|
||||
moduleStorage->data().delete_breakpoints_after_saving = deleteModuleStorage; // make sure breakpoints are deleted when the storage is removed
|
||||
moduleStorage->Save(path);
|
||||
if (deleteModuleStorage)
|
||||
m_modulesStorage.erase(std::find(m_modulesStorage.begin(), m_modulesStorage.end(), moduleStorage));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,8 +312,8 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
|
||||
m_config.SetFilename(file.generic_wstring());
|
||||
m_config.Load();
|
||||
|
||||
debuggerState.breakOnEntry = m_config.data().break_on_start;
|
||||
debuggerState.logOnlyMemoryBreakpoints = m_config.data().log_memory_breakpoints;
|
||||
debugger_setOptionBreakOnEntry(m_config.data().break_on_start);
|
||||
debugger_setOptionLogOnlyMemoryBreakpoints(m_config.data().log_memory_breakpoints);
|
||||
|
||||
m_main_position = parent.GetPosition();
|
||||
m_main_size = parent.GetSize();
|
||||
@ -305,21 +326,22 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// load configs for already loaded modules
|
||||
// load configs and module storage for already loaded modules
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
for (sint32 i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
LoadModuleStorage(module);
|
||||
DebuggerModuleInfo moduleInfo(module);
|
||||
LoadModuleStorage(moduleInfo);
|
||||
}
|
||||
|
||||
wxString label_text = _("> no modules loaded");
|
||||
if (module_count != 0)
|
||||
{
|
||||
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
|
||||
if (current_rpl_module)
|
||||
label_text = wxString::Format("> %s", current_rpl_module->moduleName2.c_str());
|
||||
RPLModule* currentModule = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
|
||||
if (currentModule)
|
||||
label_text = wxString::Format("> %s", currentModule->moduleName2.c_str());
|
||||
else
|
||||
label_text = _("> unknown module");
|
||||
}
|
||||
@ -345,26 +367,16 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
|
||||
OnParentMove(m_main_position, m_main_size);
|
||||
m_config.data().pin_to_main = value;
|
||||
|
||||
g_debugger_window = this;
|
||||
s_debuggerWindow = this;
|
||||
}
|
||||
|
||||
DebuggerWindow2::~DebuggerWindow2()
|
||||
{
|
||||
g_debuggerDispatcher.ClearDebuggerCallbacks();
|
||||
|
||||
debuggerState.breakOnEntry = false;
|
||||
g_debugger_window = nullptr;
|
||||
s_debuggerWindow = nullptr;
|
||||
|
||||
// save configs for all modules that are still loaded
|
||||
// doesn't delete breakpoints since that should (in the future) be done by unloading the rpl modules when exiting the current game
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
for (sint32 i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
if (module)
|
||||
SaveModuleStorage(module, false);
|
||||
}
|
||||
CleanupForDestroy();
|
||||
|
||||
if (m_register_window && m_register_window->IsShown())
|
||||
m_register_window->Close(true);
|
||||
@ -381,12 +393,30 @@ DebuggerWindow2::~DebuggerWindow2()
|
||||
if (m_symbol_window && m_symbol_window->IsShown())
|
||||
m_symbol_window->Close(true);
|
||||
|
||||
// reenable recompiler if force interpreter option was enabled
|
||||
if (m_forceInterpreter)
|
||||
{
|
||||
PPCRecompiler_Enable();
|
||||
m_forceInterpreter = false;
|
||||
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler");
|
||||
}
|
||||
}
|
||||
|
||||
// note: Can be called twice in some circumstances where early cleanup is needed. Will also always be called from destructor
|
||||
void DebuggerWindow2::CleanupForDestroy()
|
||||
{
|
||||
debugger_setOptionBreakOnEntry(false);
|
||||
// save module storage
|
||||
while (!m_modulesStorage.empty())
|
||||
{
|
||||
SaveModuleStorage(m_modulesStorage[0]->data().moduleInfo, true); // deleting the module storage will also delete breakpoints belonging to the module's data
|
||||
}
|
||||
m_config.Save();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
debuggerState.breakOnEntry = false;
|
||||
debugger_setOptionBreakOnEntry(false);
|
||||
|
||||
const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE);
|
||||
wxPostEvent(m_parent, parentEvent);
|
||||
@ -394,11 +424,13 @@ void DebuggerWindow2::OnClose(wxCloseEvent& event)
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnMoveIP(wxCommandEvent& event)
|
||||
void DebuggerWindow2::OnMoveToDisasmAddr(wxCommandEvent& event)
|
||||
{
|
||||
const auto ip = debuggerState.debugSession.instructionPointer;
|
||||
UpdateModuleLabel(ip);
|
||||
m_disasm_ctrl->CenterOffset(ip);
|
||||
MPTR address = (uint32)(uint64)event.GetClientData();
|
||||
if (!address)
|
||||
return;
|
||||
UpdateModuleLabel(address);
|
||||
m_disasm_ctrl->CenterOffset(address);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event)
|
||||
@ -431,8 +463,8 @@ void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& m
|
||||
|
||||
void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
|
||||
{
|
||||
RPLModule* module = (RPLModule*)event.GetClientData();
|
||||
LoadModuleStorage(module);
|
||||
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
|
||||
LoadModuleStorage(moduleNotif->moduleInfo);
|
||||
m_module_window->OnGameLoaded();
|
||||
m_symbol_window->OnGameLoaded();
|
||||
m_disasm_ctrl->Init();
|
||||
@ -440,8 +472,8 @@ void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
|
||||
|
||||
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
|
||||
{
|
||||
RPLModule* module = (RPLModule*)event.GetClientData(); // todo - the RPL module is already unloaded at this point. Find a better way to handle this
|
||||
SaveModuleStorage(module, true);
|
||||
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
|
||||
SaveModuleStorage(moduleNotif->moduleInfo, true);
|
||||
m_module_window->OnGameLoaded();
|
||||
m_symbol_window->OnGameLoaded();
|
||||
m_disasm_ctrl->Init();
|
||||
@ -507,7 +539,12 @@ std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint
|
||||
|
||||
void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event)
|
||||
{
|
||||
const auto ip = debuggerState.debugSession.instructionPointer;
|
||||
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
|
||||
if (!hCPU)
|
||||
return;
|
||||
MPTR ip = hCPU->instructionPointer;
|
||||
debugger_unlockDebugSession(hCPU);
|
||||
|
||||
UpdateModuleLabel(ip);
|
||||
|
||||
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)"));
|
||||
@ -537,17 +574,17 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
|
||||
break;
|
||||
case TOOL_ID_PAUSE:
|
||||
if (debugger_isTrapped())
|
||||
debugger_resume();
|
||||
debugger_stepCommand(DebuggerStepCommand::Run);
|
||||
else
|
||||
debugger_forceBreak();
|
||||
debugger_requestBreak();
|
||||
break;
|
||||
case TOOL_ID_STEP_INTO:
|
||||
if (debugger_isTrapped())
|
||||
debuggerState.debugSession.stepInto = true;
|
||||
debugger_stepCommand(DebuggerStepCommand::StepInto);
|
||||
break;
|
||||
case TOOL_ID_STEP_OVER:
|
||||
if (debugger_isTrapped())
|
||||
debuggerState.debugSession.stepOver = true;
|
||||
debugger_stepCommand(DebuggerStepCommand::StepOver);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -576,27 +613,30 @@ void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event)
|
||||
{
|
||||
const bool value = !m_config.data().break_on_start;
|
||||
m_config.data().break_on_start = value;
|
||||
debuggerState.breakOnEntry = value;
|
||||
debugger_setOptionBreakOnEntry(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS:
|
||||
{
|
||||
const bool value = !m_config.data().log_memory_breakpoints;
|
||||
m_config.data().log_memory_breakpoints = value;
|
||||
debuggerState.logOnlyMemoryBreakpoints = value;
|
||||
debugger_setOptionLogOnlyMemoryBreakpoints(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_OPTIONS_SWITCH_CPU_MODE:
|
||||
{
|
||||
if (ppcRecompilerEnabled)
|
||||
if (m_forceInterpreter)
|
||||
{
|
||||
ppcRecompilerEnabled = false;
|
||||
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to interpreter");
|
||||
}
|
||||
else {
|
||||
ppcRecompilerEnabled = true;
|
||||
PPCRecompiler_Enable();
|
||||
m_forceInterpreter = false;
|
||||
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler");
|
||||
}
|
||||
else
|
||||
{
|
||||
PPCRecompiler_Disable();
|
||||
m_forceInterpreter = true;
|
||||
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to interpreter");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -746,16 +786,10 @@ void DebuggerWindow2::NotifyRun()
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::MoveIP()
|
||||
void DebuggerWindow2::MoveToAddressInDisassembly(MPTR address)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_MOVE_IP);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::NotifyModuleLoaded(void* module)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
|
||||
evt->SetClientData(module);
|
||||
auto* evt = new wxCommandEvent(wxEVT_MOVE_TO_DISASM_ADDR);
|
||||
evt->SetClientData((void*)(uintptr_t)address);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
@ -765,9 +799,18 @@ void DebuggerWindow2::NotifyGraphicPacksModified()
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::NotifyModuleUnloaded(void* module)
|
||||
void DebuggerWindow2::NotifyModuleLoaded(RPLModule* module)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
|
||||
evt->SetClientData(module);
|
||||
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
|
||||
evt->SetClientObject(notifData);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::NotifyModuleUnloaded(RPLModule* module)
|
||||
{
|
||||
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
|
||||
evt->SetClientObject(notifData);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
@ -21,12 +21,12 @@ wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent);
|
||||
|
||||
extern class DebuggerWindow2* g_debugger_window;
|
||||
extern class DebuggerWindow2* s_debuggerWindow;
|
||||
|
||||
struct DebuggerConfig
|
||||
{
|
||||
@ -49,11 +49,30 @@ struct DebuggerConfig
|
||||
};
|
||||
typedef XMLDataConfig<DebuggerConfig> XMLDebuggerConfig;
|
||||
|
||||
struct DebuggerModuleInfo
|
||||
{
|
||||
struct ModuleArea
|
||||
{
|
||||
MPTR base;
|
||||
uint32 size;
|
||||
|
||||
bool ContainsAddress(MPTR addr)
|
||||
{
|
||||
return addr >= base && (addr < (base+size));
|
||||
}
|
||||
};
|
||||
|
||||
std::string moduleName;
|
||||
uint32 patchCRC;
|
||||
ModuleArea textArea;
|
||||
ModuleArea dataArea;
|
||||
|
||||
DebuggerModuleInfo(RPLModule* module);
|
||||
};
|
||||
|
||||
struct DebuggerModuleStorage
|
||||
{
|
||||
std::string module_name;
|
||||
uint32_t crc_hash;
|
||||
const RPLModule* rpl_module;
|
||||
DebuggerModuleInfo moduleInfo;
|
||||
bool delete_breakpoints_after_saving;
|
||||
|
||||
void Load(XMLConfigParser& parser);
|
||||
@ -73,10 +92,11 @@ class DebuggerWindow2 : public wxFrame, public DebuggerCallbacks
|
||||
{
|
||||
public:
|
||||
void CreateToolBar();
|
||||
void LoadModuleStorage(const RPLModule* module);
|
||||
void SaveModuleStorage(const RPLModule* module, bool delete_breakpoints);
|
||||
void LoadModuleStorage(const struct DebuggerModuleInfo& moduleInfo);
|
||||
void SaveModuleStorage(const struct DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage);
|
||||
DebuggerWindow2(wxFrame& parent, const wxRect& display_size);
|
||||
~DebuggerWindow2();
|
||||
void CleanupForDestroy();
|
||||
|
||||
void OnParentMove(const wxPoint& position, const wxSize& size);
|
||||
void OnGameLoaded();
|
||||
@ -97,7 +117,7 @@ private:
|
||||
void OnExit(wxCommandEvent& event);
|
||||
void OnShow(wxShowEvent& event);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnMoveIP(wxCommandEvent& event);
|
||||
void OnMoveToDisasmAddr(wxCommandEvent& event);
|
||||
void OnNotifyModuleLoaded(wxCommandEvent& event);
|
||||
void OnNotifyModuleUnloaded(wxCommandEvent& event);
|
||||
void OnNotifyGraphicPacksModified(wxCommandEvent& event);
|
||||
@ -110,13 +130,13 @@ private:
|
||||
void UpdateViewThreadsafe() override;
|
||||
void NotifyDebugBreakpointHit() override;
|
||||
void NotifyRun() override;
|
||||
void MoveIP() override;
|
||||
void NotifyModuleLoaded(void* module) override;
|
||||
void MoveToAddressInDisassembly(MPTR address) override;
|
||||
void NotifyGraphicPacksModified() override;
|
||||
void NotifyModuleUnloaded(void* module) override;
|
||||
void NotifyModuleLoaded(struct RPLModule* module) override;
|
||||
void NotifyModuleUnloaded(struct RPLModule* module) override;
|
||||
|
||||
XMLDebuggerConfig m_config;
|
||||
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modules_storage;
|
||||
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modulesStorage;
|
||||
|
||||
wxPoint m_main_position;
|
||||
wxSize m_main_size;
|
||||
@ -135,6 +155,7 @@ private:
|
||||
uint32 m_module_address;
|
||||
wxStaticText* m_module_label;
|
||||
|
||||
bool m_forceInterpreter{false};
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
@ -189,13 +189,19 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR
|
||||
|
||||
ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr);
|
||||
|
||||
const bool is_active_bp = debuggerState.debugSession.isTrapped && debuggerState.debugSession.instructionPointer == virtualAddress;
|
||||
bool hasActiveBP = false;
|
||||
PPCInterpreter_t* sessionCpu = debugger_lockDebugSession();
|
||||
if (sessionCpu)
|
||||
{
|
||||
hasActiveBP = sessionCpu->instructionPointer == virtualAddress;
|
||||
debugger_unlockDebugSession(sessionCpu);
|
||||
}
|
||||
|
||||
// write virtual address
|
||||
wxColour background_colour;
|
||||
if (is_active_bp && bp != nullptr)
|
||||
if (hasActiveBP && bp != nullptr)
|
||||
background_colour = theme_lineBreakpointAndCurrentInstruction;
|
||||
else if (is_active_bp)
|
||||
else if (hasActiveBP)
|
||||
background_colour = theme_lineCurrentInstruction;
|
||||
else if (bp != nullptr)
|
||||
background_colour = bp->bpType == DEBUGGER_BP_T_NORMAL ? theme_lineBreakpointSet : theme_lineLoggingBreakpointSet;
|
||||
@ -219,7 +225,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR
|
||||
position.x += OFFSET_ADDRESS_RELATIVE;
|
||||
|
||||
// draw arrow to clearly indicate instruction pointer
|
||||
if(is_active_bp)
|
||||
if(hasActiveBP)
|
||||
dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false);
|
||||
|
||||
// handle data symbols
|
||||
@ -604,36 +610,35 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// debugger currently in break state
|
||||
if (debuggerState.debugSession.isTrapped)
|
||||
PPCInterpreter_t* debugCpu = debugger_lockDebugSession();
|
||||
if (debugCpu)
|
||||
{
|
||||
switch (key_code)
|
||||
{
|
||||
case WXK_F5:
|
||||
case WXK_F5:
|
||||
{
|
||||
debuggerState.debugSession.run = true;
|
||||
return;
|
||||
debugger_stepCommand(DebuggerStepCommand::Run);
|
||||
break;
|
||||
}
|
||||
case WXK_F10:
|
||||
case WXK_F10:
|
||||
{
|
||||
debuggerState.debugSession.stepOver = true;
|
||||
return;
|
||||
debugger_stepCommand(DebuggerStepCommand::StepOver);
|
||||
break;
|
||||
}
|
||||
case WXK_F11:
|
||||
case WXK_F11:
|
||||
{
|
||||
debuggerState.debugSession.stepInto = true;
|
||||
debugger_stepCommand(DebuggerStepCommand::StepInto);
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugger_unlockDebugSession(debugCpu);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (key_code)
|
||||
if (key_code == WXK_F5)
|
||||
{
|
||||
case WXK_F5:
|
||||
{
|
||||
debuggerState.debugSession.shouldBreak = true;
|
||||
}
|
||||
debugger_requestBreak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,8 +252,6 @@ void DumpCtrl::CenterOffset(uint32 offset)
|
||||
|
||||
RefreshControl();
|
||||
//RefreshLine(line);
|
||||
|
||||
debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer);
|
||||
}
|
||||
|
||||
uint32 DumpCtrl::LineToOffset(uint32 line)
|
||||
|
||||
@ -129,6 +129,5 @@ void ModuleWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
|
||||
if (address == 0)
|
||||
return;
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
debugger_jumpToAddressInDisasm(address);
|
||||
}
|
||||
|
||||
@ -231,10 +231,15 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value,
|
||||
|
||||
void RegisterWindow::OnUpdateView()
|
||||
{
|
||||
// m_register_ctrl->RefreshControl();
|
||||
PPCSnapshot snapshot = {};
|
||||
if (PPCInterpreter_t* hCPU = debugger_lockDebugSession(); hCPU)
|
||||
{
|
||||
snapshot = debugger_getSnapshotFromSession(hCPU);
|
||||
debugger_unlockDebugSession(hCPU);
|
||||
}
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.gpr[i];
|
||||
const uint32 registerValue = snapshot.gpr[i];
|
||||
const bool hasChanged = registerValue != m_prev_snapshot.gpr[i];
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueR0 + i));
|
||||
wxASSERT(value);
|
||||
@ -246,7 +251,7 @@ void RegisterWindow::OnUpdateView()
|
||||
|
||||
// update LR
|
||||
{
|
||||
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.spr_lr;
|
||||
const uint32 registerValue = snapshot.spr_lr;
|
||||
const bool hasChanged = registerValue != m_prev_snapshot.spr_lr;
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueLR));
|
||||
wxASSERT(value);
|
||||
@ -257,7 +262,7 @@ void RegisterWindow::OnUpdateView()
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int;
|
||||
const uint64_t register_value = snapshot.fpr[i].fp0int;
|
||||
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
|
||||
wxASSERT(value);
|
||||
@ -271,14 +276,14 @@ void RegisterWindow::OnUpdateView()
|
||||
continue;
|
||||
|
||||
if(m_show_double_values)
|
||||
value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
|
||||
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp0));
|
||||
else
|
||||
value->ChangeValue(wxString::Format("%016llx", register_value));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int;
|
||||
const uint64_t register_value = snapshot.fpr[i].fp1int;
|
||||
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
|
||||
wxASSERT(value);
|
||||
@ -292,7 +297,7 @@ void RegisterWindow::OnUpdateView()
|
||||
continue;
|
||||
|
||||
if (m_show_double_values)
|
||||
value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
|
||||
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp1));
|
||||
else
|
||||
value->ChangeValue(wxString::Format("%016llx", register_value));
|
||||
}
|
||||
@ -303,7 +308,7 @@ void RegisterWindow::OnUpdateView()
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueCR0 + i));
|
||||
wxASSERT(value);
|
||||
|
||||
auto cr_bits_ptr = debuggerState.debugSession.ppcSnapshot.cr + i * 4;
|
||||
auto cr_bits_ptr = snapshot.cr + i * 4;
|
||||
auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4;
|
||||
|
||||
const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp);
|
||||
@ -330,44 +335,53 @@ void RegisterWindow::OnUpdateView()
|
||||
value->ChangeValue(fmt::format("{}", fmt::join(joinArray, ", ")));
|
||||
}
|
||||
|
||||
memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot));
|
||||
m_prev_snapshot = snapshot;
|
||||
}
|
||||
|
||||
void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
|
||||
{
|
||||
if (!debuggerState.debugSession.isTrapped)
|
||||
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
|
||||
if (!debugSession)
|
||||
{
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
|
||||
debugger_unlockDebugSession(debugSession);
|
||||
debugSession = nullptr;
|
||||
|
||||
const auto id = event.GetId();
|
||||
if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueR0;
|
||||
const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index];
|
||||
const uint32 register_value = ppcSnapshot.gpr[register_index];
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("%08x", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
|
||||
debuggerState.debugSession.hCPU->gpr[register_index] = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value;
|
||||
if (debugSession = debugger_lockDebugSession(); debugSession)
|
||||
{
|
||||
debugSession->gpr[register_index] = new_value;
|
||||
debugger_unlockDebugSession(debugSession);
|
||||
}
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueFPR0_0;
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0;
|
||||
const double register_value = ppcSnapshot.fpr[register_index].fp0;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP0_%d value"), register_index), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value;
|
||||
if (debugSession = debugger_lockDebugSession(); debugSession)
|
||||
{
|
||||
debugSession->fpr[register_index].fp0 = new_value;
|
||||
debugger_unlockDebugSession(debugSession);
|
||||
}
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
@ -377,16 +391,18 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
|
||||
if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueFPR1_0;
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1;
|
||||
const double register_value = ppcSnapshot.fpr[register_index].fp1;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP1_%d value"), register_index), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value;
|
||||
if (debugSession = debugger_lockDebugSession(); debugSession)
|
||||
{
|
||||
debugSession->fpr[register_index].fp1 = new_value;
|
||||
debugger_unlockDebugSession(debugSession);
|
||||
}
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -397,6 +413,12 @@ void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
|
||||
{
|
||||
m_show_double_values = !m_show_double_values;
|
||||
|
||||
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
|
||||
if (!debugSession)
|
||||
return;
|
||||
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
|
||||
debugger_unlockDebugSession(debugSession);
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const auto value0 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
|
||||
@ -406,13 +428,13 @@ void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
|
||||
|
||||
if (m_show_double_values)
|
||||
{
|
||||
value0->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
|
||||
value1->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
|
||||
value0->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp0));
|
||||
value1->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp1));
|
||||
}
|
||||
else
|
||||
{
|
||||
value0->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int));
|
||||
value1->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int));
|
||||
value0->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp0int));
|
||||
value1->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp1int));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,8 +112,7 @@ void SymbolListCtrl::OnLeftDClick(wxListEvent& event)
|
||||
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
|
||||
if (address == 0)
|
||||
return;
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
g_debuggerDispatcher.MoveIP();
|
||||
debugger_jumpToAddressInDisasm(address);
|
||||
}
|
||||
|
||||
void SymbolListCtrl::OnRightClick(wxListEvent& event)
|
||||
|
||||
@ -272,7 +272,7 @@ void DebugPPCThreadsWindow::RefreshThreadList()
|
||||
void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr));
|
||||
DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]), true);
|
||||
DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]));
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std::unordered_map<VAddr, uint32>& samples)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user