diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index e5e41143..16202b8f 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -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 diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.h b/src/Cafe/GraphicPack/GraphicPack2Patches.h index 34750533..45505062 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.h +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.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; diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp index 10e9743b..5b3f53dc 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp @@ -49,30 +49,6 @@ bool GraphicPack2::ResolvePresetConstant(const std::string& varname, double& val return false; } -template -T _expressionFuncHA(T input) -{ - uint32 u32 = (uint32)input; - u32 = (((u32 >> 16) + ((u32 & 0x8000) ? 1 : 0)) & 0xffff); - return (T)u32; -} - -template -T _expressionFuncHI(T input) -{ - uint32 u32 = (uint32)input; - u32 = (u32 >> 16) & 0xffff; - return (T)u32; -} - -template -T _expressionFuncLO(T input) -{ - uint32 u32 = (uint32)input; - u32 &= 0xffff; - return (T)u32; -} - template 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(value); - } - else if (tokenOnly == "h" || tokenOnly == "hi") - { - value = _expressionFuncHI(value); - } - else if (tokenOnly == "l" || tokenOnly == "lo") - { - value = _expressionFuncLO(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(value); + value = TExpressionParser::ExpressionFuncHA(value); else if (funcnameLC == "hi" || funcnameLC == "hi16") - value = _expressionFuncHI(value); + value = TExpressionParser::ExpressionFuncHI(value); else if (funcnameLC == "lo" || funcnameLC == "lo16") - value = _expressionFuncLO(value); + value = TExpressionParser::ExpressionFuncLO(value); else if (funcnameLC == "reloc") value = _expressionFuncReloc(value); else @@ -606,7 +540,8 @@ bool _resolverPass(PatchContext_t& patchContext, std::vectorgetLineNumber(); + patchContext.errorHandler.printError(resolverState.currentGroup, lineNumber, "Unable to parse expression or other internal error"); it++; } } diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index 02f23497..e4a22387 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -13,13 +13,50 @@ #include #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 breakpoints; + // patches + std::vector 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 stepCommand{DebuggerStepCommand::None}; + }debugSession; +}; + +DebuggerState s_debuggerState{}; + +std::vector& 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 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,13 +453,13 @@ 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) { if (bpItr2->isMemBP() && bpItr2 != bp) - { + { bpItr2->enabled = false; } bpItr2 = bpItr2->next; @@ -410,9 +503,9 @@ void debugger_createPatch(uint32 address, std::span patchData) } } // merge with existing patches if the ranges touch - for(sint32 i=0; i= 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 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 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(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(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(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(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(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(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 module_tmp(module_count); - for (int i = 0; i < module_count; i++) + const auto moduleCount = RPLLoader_GetModuleCount(); + const auto moduleList = RPLLoader_GetModuleList(); + std::vector 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]); } } + 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); + } +} - for (sint32 i = 0; i < 32; i++) - ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); -} \ No newline at end of file +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; +} diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 03602ff0..2f95f67d 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -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 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 breakpoints; - std::vector 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& 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 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_handleEntryBreakpoint(uint32 address); +void debugger_enterTW(PPCInterpreter_t* hCPU, bool isSingleStep = false); +void debugger_jumpToAddressInDisasm(MPTR address); +void debugger_addParserSymbols(class ExpressionParser& ep); -void debugger_enterTW(PPCInterpreter_t* hCPU); -void debugger_shouldBreak(PPCInterpreter_t* hCPU); - -void debugger_addParserSymbols(class ExpressionParser& ep); \ No newline at end of file +// options +void debugger_setOptionBreakOnEntry(bool isEnabled); +bool debugger_getOptionBreakOnEntry(); +void debugger_setOptionLogOnlyMemoryBreakpoints(bool isEnabled); +bool debugger_getOptionLogOnlyMemoryBreakpoints(); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 6125c7da..fae51279 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -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 targetQueue; - std::vector invalidationRanges; -}PPCRecompilerState; - -RangeStore rangeStore_ppcRanges; + std::vector invalidationRanges; + std::atomic_int_fast32_t recompilerEnableCount{0}; + // recompiler thread + std::thread workerThread; + std::atomic_bool workerThreadStopSignal{false}; + // function storage + RangeStore 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>& 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 entryAddresses; - entryAddresses.emplace(address); - - PPCRecompilerState.recompilerSpinlock.unlock(); + s_ppcRecompilerState.recompilerSpinlock.unlock(); std::vector> 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) @@ -680,15 +656,16 @@ void PPCRecompiler_initPlatform() #else void PPCRecompiler_initPlatform() { - + } #endif 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 @@ -714,28 +691,29 @@ void PPCRecompiler_init() PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); PPCRecompiler_initPlatform(); - + 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; ippcRecompilerFuncTable[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--; +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h index 47902630..fc864076 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -1,4 +1,5 @@ #pragma once +#include #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 list_ranges; + boost::container::small_vector 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); diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 99468801..548944ec 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -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(); diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp index 4b114ddf..12873b05 100644 --- a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp +++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp @@ -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 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(); diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h index f3ba80cf..b0859066 100644 --- a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h +++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h @@ -1,7 +1,11 @@ #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(); -void LatteAsyncCommands_checkAndExecute(); \ No newline at end of file +void LatteAsyncCommands_checkAndExecute(); diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp index e466bf3a..de18df33 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -8,261 +8,897 @@ uint32 g_currentCacheChronon = 0; -template -class IntervalTree2 +template +class IntervalTree { - // TNodeObject will be interfaced with via callbacks to static methods + static constexpr TRangeType MAX_VALUE = std::numeric_limits::max(); + static constexpr uint32 ROOT_NODE_INDEX = 0; + static constexpr uint32 INVALID_NODE_INDEX = 0xFFFFFFFFu; - // static TNodeObject* Create(TRangeData rangeBegin, TRangeData rangeEnd, std::span overlappingObjects) - // Create a new node with the given range. overlappingObjects contains all the nodes that are replaced by this operation. The callee has to delete all objects in overlappingObjects (Delete callback wont be invoked) + static constexpr sint32 NUM_SLOTS = 16; + static constexpr sint32 MIN_CHILDREN = 7; // try to merge nodes below this count - // static void Delete(TNodeObject* nodeObject) - // Delete a node object. Replacement operations won't trigger this callback and instead pass the objects to Create() - - // static void Resize(TNodeObject* nodeObject, TRangeData rangeBegin, TRangeData rangeEnd) - // Shrink or extend an existing range - - // static TNodeObject* Split(TNodeObject* nodeObject, TRangeData firstRangeBegin, TRangeData firstRangeEnd, TRangeData secondRangeBegin, TRangeData secondRangeEnd) - // Cut a hole into an existing range and split it in two. Should return the newly created node object after the hole - - static_assert(!std::is_pointer_v, "TNodeObject must be a non-pointer type"); - - struct InternalRange + struct TreeNode { - InternalRange() = default; - InternalRange(TRangeData _rangeBegin, TRangeData _rangeEnd) : rangeBegin(_rangeBegin), rangeEnd(_rangeEnd) { cemu_assert_debug(_rangeBegin < _rangeEnd); }; - - TRangeData rangeBegin; - TRangeData rangeEnd; - - bool operator<(const InternalRange& rhs) const - { - // use <= instead of < because ranges are allowed to touch (e.g. 10-20 and 20-30 dont get merged) - return this->rangeEnd <= rhs.rangeBegin; - } - + TRangeType values[NUM_SLOTS]; + sint32 indices[NUM_SLOTS]; // for the second to last layer these are indices into value nodes vector. Otherwise its a relative byte offset to a tree node + uint32 selfIndex{ INVALID_NODE_INDEX }; + uint32 parentNodeIndex{ INVALID_NODE_INDEX }; + uint8 parentSlot{ 0 }; + uint8 usedCount{ 0 }; }; - std::map m_map; - std::vector m_tempObjectArray; + struct ValueNode + { + ValueNode() = default; + ValueNode(TNodeObject* _ptr, TRangeType _rangeBegin, TRangeType _rangeEnd, uint32 _selfIndex) : ptr(_ptr), rangeBegin(_rangeBegin), rangeEnd(_rangeEnd), selfIndex(_selfIndex) {} + TNodeObject* ptr{ nullptr }; + TRangeType rangeBegin{}; + TRangeType rangeEnd{}; + uint32 selfIndex{ INVALID_NODE_INDEX }; + uint32 parentNodeIndex{ INVALID_NODE_INDEX }; + uint8 parentSlot{ 0 }; + }; public: - TNodeObject* getRange(TRangeData rangeBegin, TRangeData rangeEnd) + IntervalTree() { - auto itr = m_map.find(InternalRange(rangeBegin, rangeEnd)); - if (itr == m_map.cend()) - return nullptr; - if (rangeBegin < (*itr).first.rangeBegin) - return nullptr; - if (rangeEnd > (*itr).first.rangeEnd) - return nullptr; - return (*itr).second; + // create root node + m_treeNodes.push_back({}); + m_treeNodes[0].selfIndex = 0; + m_freeTreeNodeIndices.push_back(0); + TreeNode& rootNode = AllocateTreeNode(INVALID_NODE_INDEX, 0); + cemu_assert_debug(rootNode.selfIndex == 0); + ReserveNodes(); } - TNodeObject* getRangeByPoint(TRangeData rangeOffset) + TNodeObject* GetRange(TRangeType rangeBegin) { - auto itr = m_map.find(InternalRange(rangeOffset, rangeOffset+1)); // todo - better to use custom comparator instead of +1? - if (itr == m_map.cend()) + cemu_assert_debug(rangeBegin < MAX_VALUE); + if (IsEmpty()) return nullptr; - cemu_assert_debug(rangeOffset >= (*itr).first.rangeBegin); - cemu_assert_debug(rangeOffset < (*itr).first.rangeEnd); - return (*itr).second; + const ValueNode* valueNode = FindFloorValueNode(rangeBegin); + if (valueNode == nullptr) + return nullptr; + if (rangeBegin >= valueNode->rangeEnd) + return nullptr; + return valueNode->ptr; } - void addRange(TRangeData rangeBegin, TRangeData rangeEnd) + void GetOverlappingRanges(TRangeType rangeBegin, TRangeType rangeEnd, std::vector& results) { - if (rangeEnd == rangeBegin) + results.clear(); + cemu_assert_debug(rangeBegin < rangeEnd); + if (IsEmpty()) return; - InternalRange range(rangeBegin, rangeEnd); - auto itr = m_map.find(range); - if (itr == m_map.cend()) + if (rangeBegin >= rangeEnd) + return; + const ValueNode* valueNode = FindFloorValueNode(rangeBegin); + if (!valueNode) + valueNode = GetLeftmostValue(); + if (rangeBegin < valueNode->rangeEnd && rangeEnd > valueNode->rangeBegin) + results.emplace_back(valueNode->ptr); + valueNode = GetSuccessorValue(valueNode); + while (valueNode != nullptr) { - // new entry - m_map.emplace(range, TNodeObject::Create(rangeBegin, rangeEnd, std::span())); - } - else - { - // overlap detected - if (rangeBegin >= (*itr).first.rangeBegin && rangeEnd <= (*itr).first.rangeEnd) - return; // do nothing if added range is already covered - rangeBegin = (std::min)(rangeBegin, (*itr).first.rangeBegin); - // DEBUG - make sure this is the start point of the merge process (the first entry that starts below minValue) -#ifdef CEMU_DEBUG_ASSERT - if (itr != m_map.cbegin()) - { - // check previous result - auto itrCopy = itr; - --itrCopy; - if ((*itrCopy).first.rangeEnd > rangeBegin) - { - assert_dbg(); // n-1 entry is also overlapping - rangeBegin = (std::min)(rangeBegin, (*itrCopy).first.rangeBegin); - } - } -#endif - // DEBUG - END - // collect and remove all overlapping ranges - size_t count = 0; - while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) - { - rangeEnd = (std::max)(rangeEnd, (*itr).first.rangeEnd); - if (m_tempObjectArray.size() <= count) - m_tempObjectArray.resize(count + 8); - m_tempObjectArray[count] = (*itr).second; - count++; - auto tempItr = itr; - ++itr; - m_map.erase(tempItr); - } - - // create callback - TNodeObject* newObject = TNodeObject::Create(rangeBegin, rangeEnd, std::span(m_tempObjectArray.data(), count)); - m_map.emplace(InternalRange(rangeBegin, rangeEnd), newObject); + if (valueNode->rangeBegin >= rangeEnd) + break; + if (valueNode->rangeEnd > rangeBegin) + results.emplace_back(valueNode->ptr); + valueNode = GetSuccessorValue(valueNode); } } - void removeRange(TRangeData rangeBegin, TRangeData rangeEnd) + // will assert if no exact match was found + void RemoveRange(TRangeType rangeBegin, TRangeType rangeEnd) { - InternalRange range(rangeBegin, rangeEnd); - auto itr = m_map.find(range); - if (itr == m_map.cend()) - return; - cemu_assert_debug(itr == m_map.lower_bound(range)); - while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) + ValueNode* valueNode = FindFloorValueNode(rangeBegin); + cemu_assert(valueNode); + cemu_assert(valueNode->rangeBegin == rangeBegin && valueNode->rangeEnd == rangeEnd); + TreeNode* parentNode = &m_treeNodes[valueNode->parentNodeIndex]; + sint32 parentSlot = valueNode->parentSlot; + // remove value from parent + ReduceUsedCount(parentNode, 1); + for (sint32 i=parentSlot; iusedCount; i++) + SetChildNode(*parentNode, i, m_valueNodes[parentNode->indices[i+1]]); + if (parentSlot == 0 && parentNode->usedCount > 0) + PropagateMinValue(parentNode); + // release value + ReleaseValueNode(valueNode->selfIndex); + // if parent node now has few nodes then merge/redistribute it + CollapseNode(parentNode, m_treeDepth-1); + ShortenTreeIfPossible(); + } + + FORCE_INLINE TreeNode* GetTreeNodeChild(TreeNode* treeNode, sint32 slot) + { + cemu_assert_debug(slot >= 0 && slot < NUM_SLOTS); + char* base = reinterpret_cast(treeNode); + return reinterpret_cast(base + treeNode->indices[slot]); + } + + void SetChildNode(TreeNode& parent, sint32 slot, TreeNode* child) + { + cemu_assert_debug(slot >= 0 && slot < NUM_SLOTS); + cemu_assert_debug(child->usedCount > 0); // cant determine value if node has no children + uint32 parentIndex = parent.selfIndex; + child->parentNodeIndex = parentIndex; + child->parentSlot = slot; + parent.values[slot] = child->values[0]; + parent.indices[slot] = (sint32)(child - &parent) * (sint32)sizeof(TreeNode); + } + + void SetChildNode(TreeNode& parent, sint32 slot, ValueNode& child) + { + cemu_assert_debug(slot >= 0 && slot < NUM_SLOTS); + uint32 childIndex = child.selfIndex; + uint32 parentIndex = parent.selfIndex; + child.parentNodeIndex = parentIndex; + child.parentSlot = slot; + parent.values[slot] = child.rangeBegin; + parent.indices[slot] = childIndex; + } + + void AddRange(TRangeType rangeBegin, TRangeType rangeEnd, TNodeObject* nodeObject) + { + ReserveNodes(); + cemu_assert_debug(rangeBegin < rangeEnd); + if (IsEmpty()) [[unlikely]] { - if ((*itr).first.rangeBegin >= rangeBegin && (*itr).first.rangeEnd <= rangeEnd) + // special case, insert into empty tree + TreeNode& rootNode = GetRootNode(); + cemu_assert_debug(rootNode.usedCount == 0); + cemu_assert_debug(m_treeDepth == 0); + rootNode.usedCount = 1; + SetChildNode(rootNode, 0, AllocateValueNode(rangeBegin, rangeEnd, nodeObject)); + m_treeDepth = 1; + return; + } + // find the preceding value, we insert after it in the same parent + TreeNode* insertNode = nullptr; + sint32 insertSlotIndex = -1; + { + ValueNode* valueNode = FindFloorValueNode(rangeBegin); + if (valueNode) { - // delete entire range - auto itrCopy = itr; - TNodeObject* t = (*itr).second; - ++itr; - m_map.erase(itrCopy); - TNodeObject::Delete(t); - continue; - } - if (rangeBegin > (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd) - { - // cut hole into existing range - TRangeData firstRangeBegin = (*itr).first.rangeBegin; - TRangeData firstRangeEnd = rangeBegin; - TRangeData secondRangeBegin = rangeEnd; - TRangeData secondRangeEnd = (*itr).first.rangeEnd; - TNodeObject* newObject = TNodeObject::Split((*itr).second, firstRangeBegin, firstRangeEnd, secondRangeBegin, secondRangeEnd); - // modify key - auto nh = m_map.extract(itr); - nh.key().rangeBegin = firstRangeBegin; - nh.key().rangeEnd = firstRangeEnd; - m_map.insert(std::move(nh)); - // insert new object after hole - m_map.emplace(InternalRange(secondRangeBegin, secondRangeEnd), newObject); - return; // done - } - // shrink (trim either beginning or end) - TRangeData newRangeBegin; - TRangeData newRangeEnd; - if ((rangeBegin <= (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd)) - { - // trim from beginning - newRangeBegin = (std::max)((*itr).first.rangeBegin, rangeEnd); - newRangeEnd = (*itr).first.rangeEnd; - } - else if ((rangeBegin > (*itr).first.rangeBegin && rangeEnd >= (*itr).first.rangeEnd)) - { - // trim from end - newRangeBegin = (*itr).first.rangeBegin; - newRangeEnd = (std::min)((*itr).first.rangeEnd, rangeBegin); + insertSlotIndex = valueNode->parentSlot+1; // insert after } else { - assert_dbg(); // should not happen + // special case where we insert at the very left + valueNode = GetLeftmostValue(); + insertSlotIndex = 0; } - TNodeObject::Resize((*itr).second, newRangeBegin, newRangeEnd); - // modify key and increment iterator - auto itrCopy = itr; - ++itr; - auto nh = m_map.extract(itrCopy); - nh.key().rangeBegin = newRangeBegin; - nh.key().rangeEnd = newRangeEnd; - m_map.insert(std::move(nh)); + cemu_assert_debug(valueNode->parentNodeIndex != INVALID_NODE_INDEX); + insertNode = &m_treeNodes[valueNode->parentNodeIndex]; } - } - - // remove existing range that matches given begin and end - void removeRangeSingle(TRangeData rangeBegin, TRangeData rangeEnd) - { - InternalRange range(rangeBegin, rangeEnd); - auto itr = m_map.find(range); - cemu_assert_debug(itr != m_map.cend()); - if (itr == m_map.cend()) - return; - cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); - // delete entire range - TNodeObject* t = (*itr).second; - m_map.erase(itr); - TNodeObject::Delete(t); - } - - // remove existing range that matches given begin and end without calling delete callback - void removeRangeSingleWithoutCallback(TRangeData rangeBegin, TRangeData rangeEnd) - { - InternalRange range(rangeBegin, rangeEnd); - auto itr = m_map.find(range); - cemu_assert_debug(itr != m_map.cend()); - if (itr == m_map.cend()) - return; - cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); - // delete entire range - TNodeObject* t = (*itr).second; - m_map.erase(itr); - } - - void splitRange(TRangeData rangeOffset) - { - // not well tested - removeRange(rangeOffset, rangeOffset+1); - } - - template - void forEachOverlapping(TRangeData rangeBegin, TRangeData rangeEnd, TFunc f) - { - InternalRange range(rangeBegin, rangeEnd); - auto itr = m_map.find(range); - if (itr == m_map.cend()) - return; - cemu_assert_debug(itr == m_map.lower_bound(range)); - while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) + // handle the case where the node can still fit more entries + if (insertNode->usedCount < NUM_SLOTS) { - f((*itr).second, rangeBegin, rangeEnd); - ++itr; - } - } - - void validate() - { - if (m_map.empty()) + for (sint32 i=insertNode->usedCount-1; i>=insertSlotIndex; i--) + SetChildNode(*insertNode, i+1, m_valueNodes[insertNode->indices[i]]); + insertNode->usedCount++; + SetChildNode(*insertNode, insertSlotIndex, AllocateValueNode(rangeBegin, rangeEnd, nodeObject)); + if (insertSlotIndex == 0) + PropagateMinValue(insertNode); return; - auto itr = m_map.begin(); - if ((*itr).first.rangeBegin > (*itr).first.rangeEnd) - assert_dbg(); - TRangeData currentLoc = (*itr).first.rangeEnd; - ++itr; - while (itr != m_map.end()) + } + // split is necessary + InsertNode(*insertNode, nullptr, &AllocateValueNode(rangeBegin, rangeEnd, nodeObject)); + } + + void WriteDotFile(const char* filePath) + { + std::ofstream outFile(filePath, std::ios::trunc); + if (!outFile.is_open()) + return; + outFile << "digraph IntervalTree {\n"; + outFile << " rankdir=TB;\n"; + outFile << " splines=polyline;\n"; + outFile << " node [shape=record, fontname=\"Consolas\", fontsize=10];\n"; + outFile << " edge [arrowhead=vee, arrowsize=0.8, penwidth=1.2];\n"; + if (IsEmpty()) { - if ((*itr).first.rangeBegin >= (*itr).first.rangeEnd) - assert_dbg(); // negative or zero size ranges are not allowed - if (currentLoc > (*itr).first.rangeBegin) - assert_dbg(); // stored ranges must not overlap - currentLoc = (*itr).first.rangeEnd; - ++itr; + outFile << " t0 [label=\"{Tree 0|empty}\"];\n"; + outFile << "}\n"; + return; + } + WriteDotTreeNodeRecursive(outFile, GetRootNode(), m_treeDepth); + outFile << "}\n"; + outFile.flush(); + } + + void ValidateTree(TreeNode& treeNode, sint32 remainingTreeDepth, TRangeType& minChildValue, TRangeType& maxChildValue) + { + minChildValue = std::numeric_limits::max(); + maxChildValue = std::numeric_limits::min(); + // basic validation + cemu_assert(treeNode.usedCount > 0); // empty nodes are not allowed + for (uint32 i=0; i 0); + if (remainingTreeDepth == 1) + { + // children are value nodes (leaf) + for (uint32 i=0; ivalues[0] : valueNode->rangeBegin; + if (nodeToInsertInto.usedCount == NUM_SLOTS) + { + // if the target node is full then try to move a child left + TreeNode* leftNeighbor = GetTreeNodeLeftNeighbor(&nodeToInsertInto); + if (leftNeighbor && leftNeighbor->usedCount < NUM_SLOTS) + { + cemu_assert_debug(rangeBegin > nodeToInsertInto.values[0]); // if this is not true we are not allowed to move the child + MigrateSubnodesFromRight(&nodeToInsertInto, leftNeighbor, 1, !treeNode); + cemu_assert_debug(rangeBegin > leftNeighbor->values[leftNeighbor->usedCount-1]); + } + else + { + // try offload into right neighbor if possible + TreeNode* rightNeighbor = GetTreeNodeRightNeighbor(&nodeToInsertInto); + if (rightNeighbor && rightNeighbor->usedCount < NUM_SLOTS) + { + cemu_assert_debug(nodeToInsertInto.usedCount == NUM_SLOTS); + if (rangeBegin > nodeToInsertInto.values[NUM_SLOTS-1]) + { + // insert into right neighbor instead + InsertNode(*rightNeighbor, treeNode, valueNode); + return; + } + // offload one child + MigrateSubnodesFromLeft(&nodeToInsertInto, rightNeighbor, 1, !treeNode); + } + } + } - const std::map& getAll() const { return m_map; }; + if (nodeToInsertInto.usedCount < NUM_SLOTS) + { + sint32 insertSlotIndex = 0; + if (rangeBegin >= nodeToInsertInto.values[0]) + insertSlotIndex = (sint32)FindFloorElementIndexMinBound(nodeToInsertInto, rangeBegin) + 1; + // we can determine from the parameter the children type of nodeToInsertInto. Tree nodes cannot have mixed tree/value nodes as children + if (treeNode) + { + for (sint32 i=nodeToInsertInto.usedCount-1; i>=insertSlotIndex; i--) + SetChildNode(nodeToInsertInto, i+1, GetTreeNodeChild(&nodeToInsertInto, i)); + } + else + { + for (sint32 i=nodeToInsertInto.usedCount-1; i>=insertSlotIndex; i--) + SetChildNode(nodeToInsertInto, i+1, m_valueNodes[nodeToInsertInto.indices[i]]); + } + nodeToInsertInto.usedCount++; + if (treeNode) + SetChildNode(nodeToInsertInto, insertSlotIndex, treeNode); + else + SetChildNode(nodeToInsertInto, insertSlotIndex, *valueNode); + if (insertSlotIndex == 0) + PropagateMinValue(&nodeToInsertInto); + return; + } + // target node is full and we need to split + if (nodeToInsertInto.parentNodeIndex == INVALID_NODE_INDEX) + { + // splitting root requires creating a new tree node inbetween and increasing tree depth + // then we can split the new node like usual + TreeNode& newTreeNode = AllocateTreeNode(INVALID_NODE_INDEX, 0); + cemu_assert_debug(nodeToInsertInto.usedCount == NUM_SLOTS); + cemu_assert_debug(m_treeDepth >= 0); + if (valueNode) + { + for (int i=0; i= splitRight.values[0] ? &splitRight : &splitLeft; + sint32 insertSlotIndex = 0; + if (rangeBegin >= nodeToInsertInto.values[0]) + insertSlotIndex = (sint32)FindFloorElementIndexMinBound(*insertNode, rangeBegin) + 1; + if (valueNode) + { + for (sint32 i=insertNode->usedCount-1; i>=insertSlotIndex; i--) + SetChildNode(*insertNode, i+1, m_valueNodes[insertNode->indices[i]]); + } + else + { + for (sint32 i=insertNode->usedCount-1; i>=insertSlotIndex; i--) + SetChildNode(*insertNode, i+1, GetTreeNodeChild(insertNode, i)); + } + insertNode->usedCount++; + if (treeNode) + SetChildNode(*insertNode, insertSlotIndex, treeNode); + else + SetChildNode(*insertNode, insertSlotIndex, *valueNode); + // propagate min value + if (insertSlotIndex == 0 && insertNode == &splitLeft) + PropagateMinValue(&splitLeft); + // insert splitRight into parent + InsertNode(m_treeNodes[splitLeft.parentNodeIndex], &splitRight, nullptr); + } + + bool IsEmpty() const + { + return m_treeDepth == 0; + } + + void PrintStats() + { + cemuLog_log(LogType::Force, "--- IntervalTree info ---"); + cemuLog_log(LogType::Force, "Depth: {}", m_treeDepth); + size_t numTreeNodes = m_treeNodes.size() - m_freeTreeNodeIndices.size(); + size_t numValueNodes = m_valueNodes.size() - m_freeValueNodeIndices.size(); + cemuLog_log(LogType::Force, "NumTreeNodes: {}", numTreeNodes); + cemuLog_log(LogType::Force, "NumValueNodes: {}", numValueNodes); + size_t fillCountTotal = 0; + for (auto& treeNode : m_treeNodes) + { + if (treeNode.usedCount == 0) + continue; + fillCountTotal += (size_t)treeNode.usedCount; + } + cemuLog_log(LogType::Force, "AvgFillAmount: {:.2f}", (double)fillCountTotal / (double)numTreeNodes); + } +private: + TreeNode& GetRootNode() + { + return m_treeNodes[ROOT_NODE_INDEX]; + } + + const TreeNode& GetRootNode() const + { + return m_treeNodes[ROOT_NODE_INDEX]; + } + + TreeNode& GetNode(uint32 nodeIndex) + { + cemu_assert_debug(nodeIndex < m_treeNodes.size()); + return m_treeNodes[nodeIndex]; + } + + const TreeNode& GetNode(uint32 nodeIndex) const + { + cemu_assert_debug(nodeIndex < m_treeNodes.size()); + return m_treeNodes[nodeIndex]; + } + + void InitializeTreeNode(TreeNode& node, uint32 selfIndex, uint32 parentNodeIndex, uint8 parentSlot) + { + node.selfIndex = selfIndex; + node.parentNodeIndex = parentNodeIndex; + node.parentSlot = parentSlot; + node.usedCount = 0; + for (auto& it : node.values) + it = MAX_VALUE; + for (auto& it : node.indices) + it = 0; + } + + void ReduceUsedCount(TreeNode* node, sint32 num) + { + cemu_assert_debug(num >= 0); + cemu_assert_debug(num <= node->usedCount); + node->usedCount -= (uint8)num; + for (int i=0; ivalues[node->usedCount + i] = MAX_VALUE; + } + } + + // assumes value >= node.values[0] + FORCE_INLINE ptrdiff_t FindFloorElementIndexMinBound(TreeNode& node, TRangeType value) + { + static_assert(NUM_SLOTS == 16); // this function needs to be updated if the count changes + cemu_assert_debug(value >= node.values[0]); + TRangeType* ptr = node.values; + if (value >= ptr[8]) + ptr += 8; + if (value >= ptr[4]) + ptr += 4; + if (value >= ptr[2]) + ptr += 2; + if (value >= ptr[1]) + ptr += 1; + return (ptr - node.values); + } + + void ReserveNodes() + { + // this function guarantees a minimum amount of available TreeNode and ValueNode in the pool, enough to avoid vector invalidation in AddRange() + if (m_freeTreeNodeIndices.size() < 16) + { + uint32 curNodeCount = (uint32)m_treeNodes.size(); + m_treeNodes.resize(curNodeCount + 64); + for (uint32 i=0; i<64; i++) + m_treeNodes[curNodeCount + i].selfIndex = curNodeCount + i; + uint32 freeIndexCount = (uint32)m_freeTreeNodeIndices.size(); + m_freeTreeNodeIndices.resize(freeIndexCount + 64); + for (uint32 i=0; i<64; i++) + m_freeTreeNodeIndices[freeIndexCount + i] = curNodeCount + i; + } + if (m_freeValueNodeIndices.size() < 16) + { + uint32 curNodeCount = (uint32)m_valueNodes.size(); + m_valueNodes.resize(curNodeCount + 128); + for (uint32 i=0; i<128; i++) + m_valueNodes[curNodeCount + i].selfIndex = curNodeCount + i; + uint32 freeIndexCount = (uint32)m_freeValueNodeIndices.size(); + m_freeValueNodeIndices.resize(freeIndexCount + 128); + for (uint32 i=0; i<128; i++) + m_freeValueNodeIndices[freeIndexCount + i] = curNodeCount + i; + } + } + + void WriteDotTreeNodeRecursive(std::ofstream& outFile, TreeNode& treeNode, sint32 remainingDepth) + { + auto writeHex = [&outFile](TRangeType value) + { + outFile << "0x" << std::hex << static_cast(value) << std::dec; + }; + outFile << " t" << treeNode.selfIndex << " [label=\""; + for (uint32 i = 0; i < NUM_SLOTS; i++) + { + if (i != 0) + outFile << "|"; + outFile << ""; + if (i < treeNode.usedCount) + writeHex(treeNode.values[i]); + } + outFile << "\"];\n"; + if (remainingDepth <= 1) + { + for (uint32 i = 0; i < treeNode.usedCount; i++) + { + const ValueNode& valueNode = m_valueNodes[treeNode.indices[i]]; + outFile << " v" << valueNode.selfIndex << " [shape=box, label=\""; + writeHex(valueNode.rangeBegin); + outFile << "\\n"; + writeHex(valueNode.rangeEnd); + outFile << "\"];\n"; + outFile << " t" << treeNode.selfIndex << ":s" << i << ":s -> v" << valueNode.selfIndex << ":nw;\n"; + } + return; + } + for (uint32 i = 0; i < treeNode.usedCount; i++) + { + TreeNode* childNode = GetTreeNodeChild(&treeNode, i); + outFile << " t" << treeNode.selfIndex << ":s" << i << ":s -> t" << childNode->selfIndex << ":nw;\n"; + WriteDotTreeNodeRecursive(outFile, *childNode, remainingDepth - 1); + } + } + + TreeNode& AllocateTreeNode(uint32 parentNodeIndex, uint8 parentSlot) + { + cemu_assert(!m_freeTreeNodeIndices.empty()); + uint32 nodeIndex; + nodeIndex = m_freeTreeNodeIndices.back(); + m_freeTreeNodeIndices.pop_back(); + InitializeTreeNode(m_treeNodes[nodeIndex], nodeIndex, parentNodeIndex, parentSlot); + return m_treeNodes[nodeIndex]; + } + + void ReleaseTreeNode(TreeNode* treeNode, bool unlinkFromParent) + { + uint32 nodeIndex = treeNode->selfIndex; + if (unlinkFromParent) + { + cemu_assert_debug(treeNode->parentNodeIndex != INVALID_NODE_INDEX); + TreeNode& parentNode = m_treeNodes[treeNode->parentNodeIndex]; + sint32 slotIndex = treeNode->parentSlot; + ReduceUsedCount(&parentNode, 1); + for (sint32 i=slotIndex; i= m_treeNodes.size()) + return; + InitializeTreeNode(m_treeNodes[nodeIndex], nodeIndex, INVALID_NODE_INDEX, 0); + m_freeTreeNodeIndices.emplace_back(nodeIndex); + } + + ValueNode& AllocateValueNode(TRangeType beginValue, TRangeType endValue, TNodeObject* nodeObject) + { + cemu_assert(!m_freeValueNodeIndices.empty()); + uint32 valueIndex; + valueIndex = m_freeValueNodeIndices.back(); + m_freeValueNodeIndices.pop_back(); + ValueNode& valueNode = m_valueNodes[valueIndex]; + valueNode.ptr = nodeObject; + valueNode.rangeBegin = beginValue; + valueNode.rangeEnd = endValue; + valueNode.selfIndex = valueIndex; + valueNode.parentNodeIndex = INVALID_NODE_INDEX; + valueNode.parentSlot = 0; + return valueNode; + } + + void ReleaseValueNode(uint32 valueIndex) + { + cemu_assert_debug(valueIndex < m_valueNodes.size()); + ValueNode& valueNode = m_valueNodes[valueIndex]; + valueNode.ptr = nullptr; + valueNode.rangeBegin = TRangeType{}; + valueNode.rangeEnd = TRangeType{}; + valueNode.parentNodeIndex = INVALID_NODE_INDEX; + valueNode.parentSlot = 0; + m_freeValueNodeIndices.emplace_back(valueIndex); + } + + ValueNode* GetLeftmostValue() + { + cemu_assert_debug(!IsEmpty()); + TreeNode* currentNode = &GetRootNode(); + // traverse branches + for (sint32 i=0; iindices[0]]; + return valueNode; + } + + // find the node with the highest rangeBegin that satisfies node->rangeBegin <= beginValue, or null if none exists + ValueNode* FindFloorValueNode(TRangeType beginValue) + { + cemu_assert_debug(beginValue != MAX_VALUE); + cemu_assert_debug(!IsEmpty()); + TreeNode* currentNode = &GetRootNode(); + if (beginValue < currentNode->values[0]) + return nullptr; + ptrdiff_t diff = 0; + auto remainingDepth = m_treeDepth; + do + { + currentNode = reinterpret_cast(reinterpret_cast(currentNode) + diff); + ptrdiff_t floorSlot = FindFloorElementIndexMinBound(*currentNode, beginValue); + ASSUME(floorSlot >= 0 && floorSlot < NUM_SLOTS); + diff = currentNode->indices[floorSlot]; + } + while (--remainingDepth); + return &m_valueNodes[diff]; + } + + ValueNode* GetSuccessorValue(const ValueNode* valueNode) + { + cemu_assert_debug(valueNode); + cemu_assert_debug(valueNode->parentNodeIndex != INVALID_NODE_INDEX); + TreeNode* currentNode = &m_treeNodes[valueNode->parentNodeIndex]; + // straight forward case: There is another value node in the parent tree node + uint32 currentSlot = valueNode->parentSlot; + if (currentSlot + 1 < currentNode->usedCount) + return &m_valueNodes[currentNode->indices[currentSlot + 1]]; + // otherwise traverse towards root until we find a right branch + sint32 remainingTreeDepth = 0; + while (true) + { + if (currentNode->parentNodeIndex == INVALID_NODE_INDEX) + return nullptr; // root reached + currentSlot = currentNode->parentSlot; + currentNode = &m_treeNodes[currentNode->parentNodeIndex]; + if (currentSlot+1 < currentNode->usedCount) + { + currentNode = GetTreeNodeChild(currentNode, currentSlot + 1); + break; + } + remainingTreeDepth++; + } + // and then traverse always left to get the value + for (sint32 i=0; iindices[0]]; + } + + // get the immediate left tree node on the same tree depth, this can be a node on a different parent branch as long as its the same depth + TreeNode* GetTreeNodeLeftNeighbor(const TreeNode* treeNode) + { + cemu_assert_debug(treeNode); + if (treeNode->parentNodeIndex == INVALID_NODE_INDEX) + return nullptr; // root has no neighbors + TreeNode* currentNode = &GetNode(treeNode->parentNodeIndex); + // straight forward case: There is another tree node in the parent tree node + uint32 currentSlot = treeNode->parentSlot; + if (currentSlot > 0) + return GetTreeNodeChild(currentNode, currentSlot - 1); + // otherwise traverse towards root until we find a left branch + sint32 remainingTreeDepth = 0; + while (true) + { + if (currentNode->parentNodeIndex == INVALID_NODE_INDEX) + return nullptr; // root reached + currentSlot = currentNode->parentSlot; + currentNode = &m_treeNodes[currentNode->parentNodeIndex]; + if (currentSlot > 0) + { + currentNode = GetTreeNodeChild(currentNode, currentSlot - 1); + break; + } + remainingTreeDepth++; + } + // and then traverse always right + for (sint32 i=0; iusedCount - 1); + return GetTreeNodeChild(currentNode, currentNode->usedCount - 1); + } + + // get the immediate right tree node on the same tree depth, this can be a node on a different parent branch as long as its the same depth + TreeNode* GetTreeNodeRightNeighbor(TreeNode* treeNode) + { + cemu_assert_debug(treeNode); + if (treeNode->parentNodeIndex == INVALID_NODE_INDEX) + return nullptr; // root has no neighbors + TreeNode* currentNode = &GetNode(treeNode->parentNodeIndex); + // straight forward case: There is another tree node in the parent tree node + uint32 currentSlot = treeNode->parentSlot; + if (currentSlot + 1 < currentNode->usedCount) + return GetTreeNodeChild(currentNode, currentSlot + 1); + // otherwise traverse towards root until we find a right branch + sint32 remainingTreeDepth = 0; + while (true) + { + if (currentNode->parentNodeIndex == INVALID_NODE_INDEX) + return nullptr; // root reached + currentSlot = currentNode->parentSlot; + currentNode = &m_treeNodes[currentNode->parentNodeIndex]; + if (currentSlot+1 < currentNode->usedCount) + { + currentNode = GetTreeNodeChild(currentNode, currentSlot + 1); + break; + } + remainingTreeDepth++; + } + // and then traverse always left + for (sint32 i=0; iusedCount >= MIN_CHILDREN) + { + PropagateMinValue(treeNode); + return; // node full enough to not collapse + } + bool hasValueNodeChildren = (depth == m_treeDepth-1); + // check if we can merge with left node + TreeNode* leftNeighbor = GetTreeNodeLeftNeighbor(treeNode); + if (leftNeighbor && (leftNeighbor->usedCount + treeNode->usedCount) <= NUM_SLOTS && treeNode->usedCount > 0) + { + cemu_assert_debug(treeNode->usedCount > 0); // if there are neighbors then empty nodes should not be possible + // merge left into self + MigrateSubnodesFromLeft(leftNeighbor, treeNode, leftNeighbor->usedCount, hasValueNodeChildren); // also propagates new min value + // left neighbor is now empty so remove it + TreeNode* leftNeighborParent = &m_treeNodes[leftNeighbor->parentNodeIndex]; + ReleaseTreeNode(leftNeighbor, true); + if (leftNeighbor->parentSlot == 0) + PropagateMinValue(leftNeighborParent); + CollapseNode(leftNeighborParent, depth - 1); + PropagateMinValue(treeNode); + return; + } + // check if we can merge with right node + TreeNode* rightNeighbor = GetTreeNodeRightNeighbor(treeNode); + if (rightNeighbor && (rightNeighbor->usedCount + treeNode->usedCount) <= NUM_SLOTS && treeNode->usedCount > 0) + { + cemu_assert_debug(treeNode->usedCount > 0); + // merge right into self + MigrateSubnodesFromRight(rightNeighbor, treeNode, rightNeighbor->usedCount, hasValueNodeChildren); + // right neighbor is now empty so remove it + TreeNode* rightNeighborParent = &m_treeNodes[rightNeighbor->parentNodeIndex]; + ReleaseTreeNode(rightNeighbor, true); + if (rightNeighbor->parentSlot == 0) + PropagateMinValue(rightNeighborParent); + CollapseNode(rightNeighborParent, depth - 1); + return; + } + if (!leftNeighbor && !rightNeighbor) + { + // no neighbors + if (treeNode->usedCount > 0) + { + PropagateMinValue(treeNode); + return; + } + // if this node is empty and there are no neighbors then the tree is empty, collapse nodes and eventually root + if (treeNode->parentNodeIndex == INVALID_NODE_INDEX) + { + cemu_assert_debug(treeNode->selfIndex == ROOT_NODE_INDEX); + m_treeDepth = 0; + cemu_assert_debug(m_freeValueNodeIndices.size() == m_valueNodes.size()); + cemu_assert_debug(m_freeTreeNodeIndices.size() == m_treeNodes.size()-1); // -1 for the root node which is always reserved + cemu_assert_debug(GetRootNode().usedCount == 0); + return; + } + else + { + // unlink empty node and continue collapsing parent chain + TreeNode* parentNode = &m_treeNodes[treeNode->parentNodeIndex]; + ReleaseTreeNode(treeNode, true); + CollapseNode(parentNode, depth - 1); + return; + } + } + // if merging is not possible then only redistribute values + if (treeNode->usedCount == 0) + { + TreeNode* treeNodeParent = (treeNode->parentNodeIndex != INVALID_NODE_INDEX) ? &m_treeNodes[treeNode->parentNodeIndex] : nullptr; + ReleaseTreeNode(treeNode, true); + if (treeNodeParent && treeNodeParent->usedCount > 0) + PropagateMinValue(treeNodeParent); + // note - this will never shrink the tree, since there is at least one left or right neighbor + return; + } + // todo - redistribute values + PropagateMinValue(treeNode); + } + + // helper for CollapseNode(), moves children from left neighbor to center node. Changes left and center node min value + void MigrateSubnodesFromLeft(TreeNode* leftNode, TreeNode* targetNode, sint32 count, bool hasValueNodes) + { + cemu_assert_debug(count > 0); + cemu_assert_debug(leftNode->usedCount >= count); + cemu_assert_debug((targetNode->usedCount + count) <= NUM_SLOTS); + sint32 targetCount = targetNode->usedCount; + sint32 leftStart = leftNode->usedCount - count; + if (hasValueNodes) + { + for (sint32 i=targetCount-1; i>=0; i--) + SetChildNode(*targetNode, count + i, m_valueNodes[targetNode->indices[i]]); + for (sint32 i=0; iindices[leftStart + i]]); + } + else + { + for (sint32 i=targetCount-1; i>=0; i--) + SetChildNode(*targetNode, count + i, GetTreeNodeChild(targetNode, i)); + for (sint32 i=0; iusedCount += count; + PropagateMinValue(targetNode); + } + + // helper for CollapseNode(), moves children from right neighbor to center node. Changes right node min value + void MigrateSubnodesFromRight(TreeNode* rightNode, TreeNode* targetNode, sint32 count, bool hasValueNodes) + { + cemu_assert_debug(rightNode && targetNode); + cemu_assert_debug(rightNode != targetNode); + cemu_assert_debug(count >= 0); + if (count <= 0) + return; + + cemu_assert_debug(rightNode->usedCount >= count); + cemu_assert_debug((targetNode->usedCount + count) <= NUM_SLOTS); + + sint32 targetCount = targetNode->usedCount; + sint32 rightCount = rightNode->usedCount; + sint32 remainingCount = rightCount - count; + + if (hasValueNodes) + { + for (sint32 i=0; iindices[i]]); + for (sint32 i=0; iindices[count + i]]); + } + else + { + for (sint32 i=0; iusedCount += count; + if (rightNode->usedCount > 0) + PropagateMinValue(rightNode); + } + + void ShortenTreeIfPossible() + { + if (m_treeDepth == 0) + return; + TreeNode& rootNode = GetRootNode(); + cemu_assert_debug(rootNode.usedCount > 0); + while (m_treeDepth > 1 && rootNode.usedCount == 1) // if root has exactly one tree child, absorb it and reduce depth + { + TreeNode* onlyChild = GetTreeNodeChild(&rootNode, 0); + cemu_assert_debug(onlyChild->usedCount > 0); + bool childHasValueNodes = (m_treeDepth == 2); + sint32 childCount = onlyChild->usedCount; + if (childHasValueNodes) + { + for (sint32 i=0; iindices[i]]); + } + else + { + for (sint32 i=0; ivalues[0]; + if (node->parentNodeIndex == INVALID_NODE_INDEX) + break; // reached root + TreeNode* parentNode = &m_treeNodes[node->parentNodeIndex]; + parentNode->values[node->parentSlot] = minValue; + if (node->parentSlot != 0) + break; // value wont further propagate + node = parentNode; + } + } + + std::vector m_treeNodes; + std::vector m_freeTreeNodeIndices; + std::vector m_valueNodes; + std::vector m_freeValueNodeIndices; + sint32 m_treeDepth{0}; }; std::unique_ptr g_gpuBufferHeap = nullptr; @@ -277,6 +913,20 @@ class BufferCacheNode static inline constexpr uint64 c_streamoutSig1 = 0x8BE6336411814F4Full; public: + ~BufferCacheNode() + { + if (m_hasCacheAlloc) + g_deallocateQueue.emplace_back(m_cacheOffset); // release after current drawcall + // remove from array + auto temp = s_allCacheNodes.back(); + s_allCacheNodes.pop_back(); + if (this != temp) + { + s_allCacheNodes[m_arrayIndex] = temp; + temp->m_arrayIndex = m_arrayIndex; + } + } + // returns false if not enough space is available bool allocateCacheMemory() { @@ -335,8 +985,6 @@ public: { pageWriteStreamoutSignatures(pageIndex, rangeBegin, rangeEnd); pageIndex++; - //pageInfo->hasStreamoutData = true; - //pageInfo++; } if (numPages > 0) m_hasStreamoutData = true; @@ -428,7 +1076,7 @@ public: { // ideally we would only upload the pages that intersect both the reserve range and the invalidation range // but this would require complex per-page tracking of invalidation. Since this is on a hot path we do a cheap approximation - // where we only track one continous invalidation range + // where we only track one continuous invalidation range // try to bound uploads to the reserve range within the invalidation uint32 resRangeBegin = reservePhysAddress & ~CACHE_PAGE_SIZE_M1; @@ -440,7 +1088,6 @@ public: if (uploadBegin >= uploadEnd) return; // reserve range not within invalidation or range is zero sized - if (uploadBegin == m_invalidationRangeBegin) { m_invalidationRangeBegin = uploadEnd; @@ -546,20 +1193,6 @@ private: s_allCacheNodes.emplace_back(this); }; - ~BufferCacheNode() - { - if (m_hasCacheAlloc) - g_deallocateQueue.emplace_back(m_cacheOffset); // release after current drawcall - // remove from array - auto temp = s_allCacheNodes.back(); - s_allCacheNodes.pop_back(); - if (this != temp) - { - s_allCacheNodes[m_arrayIndex] = temp; - temp->m_arrayIndex = m_arrayIndex; - } - } - uint32 getPageIndexFromAddrAligned(uint32 offset) const { cemu_assert_debug((offset % CACHE_PAGE_SIZE) == 0); @@ -812,7 +1445,7 @@ public: g_deallocateQueue.clear(); } - // drops everything from the cache that isn't considered in use or unrestorable (ranges with streamout) + // drops everything from the cache that isn't considered in use or unrestorable due to containing streamout data static void CleanupCacheAggressive(MPTR excludedRangeBegin, MPTR excludedRangeEnd) { size_t i = 0; @@ -843,8 +1476,6 @@ public: } } - /* callbacks from IntervalTree */ - static BufferCacheNode* Create(MPTR rangeBegin, MPTR rangeEnd, std::span overlappingObjects) { auto newRange = new BufferCacheNode(rangeBegin, rangeEnd); @@ -884,37 +1515,19 @@ public: return newRange; } - static void Delete(BufferCacheNode* nodeObject) - { - delete nodeObject; - } - static void Resize(BufferCacheNode* nodeObject, MPTR rangeBegin, MPTR rangeEnd) { nodeObject->shrink(rangeBegin, rangeEnd); } - - static BufferCacheNode* Split(BufferCacheNode* nodeObject, MPTR firstRangeBegin, MPTR firstRangeEnd, MPTR secondRangeBegin, MPTR secondRangeEnd) - { - auto newRange = new BufferCacheNode(secondRangeBegin, secondRangeEnd); - // todo - add support for splitting BufferCacheNode memory allocations, then we dont need to do a separate allocation - if (!newRange->allocateCacheMemory()) - { - cemuLog_log(LogType::Force, "Out-of-memory in GPU buffer during split operation"); - cemu_assert(false); - } - newRange->syncFromNode(nodeObject); - nodeObject->shrink(firstRangeBegin, firstRangeEnd); - return newRange; - } }; +IntervalTree g_gpuBufferCache; +std::vector s_gpuCacheQueryResult; // keep vector for query results around to reduce runtime allocations std::vector BufferCacheNode::g_deallocateQueue; -IntervalTree2 g_gpuBufferCache; void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node) { - g_gpuBufferCache.removeRangeSingleWithoutCallback(node->GetRangeBegin(), node->GetRangeEnd()); + g_gpuBufferCache.RemoveRange(node->GetRangeBegin(), node->GetRangeEnd()); } BufferCacheNode* LatteBufferCache_reserveRange(MPTR physAddress, uint32 size) @@ -922,19 +1535,31 @@ BufferCacheNode* LatteBufferCache_reserveRange(MPTR physAddress, uint32 size) MPTR rangeStart = physAddress - (physAddress % CACHE_PAGE_SIZE); MPTR rangeEnd = (physAddress + size + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; - auto range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); - if (!range) + BufferCacheNode* range = g_gpuBufferCache.GetRange(physAddress); + if (range && physAddress >= range->GetRangeBegin() && (physAddress+size) <= range->GetRangeEnd()) + return range; + // no containing range found, we need to create a range and potentially merge with any overlapping ranges + g_gpuBufferCache.GetOverlappingRanges(rangeStart, rangeEnd, s_gpuCacheQueryResult); + if (s_gpuCacheQueryResult.empty()) { - g_gpuBufferCache.addRange(rangeStart, rangeEnd); - range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); - cemu_assert_debug(range); + // no overlaps we can just create a new blank range + BufferCacheNode* newRange = BufferCacheNode::Create(rangeStart, rangeEnd, s_gpuCacheQueryResult); + g_gpuBufferCache.AddRange(rangeStart, rangeEnd, newRange); + return newRange; + } + else + { + // merge with overlapping ranges + uint32 mergedRangeStart = std::min(rangeStart, s_gpuCacheQueryResult.front()->GetRangeBegin()); + uint32 mergedRangeEnd = std::max(rangeEnd, s_gpuCacheQueryResult.back()->GetRangeEnd()); + for (auto& it : s_gpuCacheQueryResult) + g_gpuBufferCache.RemoveRange(it->GetRangeBegin(), it->GetRangeEnd()); // remove from interval tree, BufferCacheNode::Create below will delete the range objects + BufferCacheNode* newRange = BufferCacheNode::Create(mergedRangeStart, mergedRangeEnd, s_gpuCacheQueryResult); + g_gpuBufferCache.AddRange(mergedRangeStart, mergedRangeEnd, newRange); + return newRange; } - cemu_assert_debug(range->GetRangeBegin() <= physAddress); - cemu_assert_debug(range->GetRangeEnd() >= (physAddress + size)); - return range; } - uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size) { auto range = LatteBufferCache_reserveRange(physAddress, size); @@ -964,20 +1589,28 @@ void LatteBufferCache_invalidate(MPTR physAddress, uint32 size) { if (size == 0) return; - g_gpuBufferCache.forEachOverlapping(physAddress, physAddress + size, [](BufferCacheNode* node, MPTR invalidationRangeBegin, MPTR invalidationRangeEnd) - { - node->invalidate(invalidationRangeBegin, invalidationRangeEnd); - } - ); + if (physAddress >= 0xFFFFF000) + return; + if ((physAddress+size) < physAddress) + return; + g_gpuBufferCache.GetOverlappingRanges(physAddress, physAddress+size, s_gpuCacheQueryResult); + for (auto& range : s_gpuCacheQueryResult) + { + cemu_assert_debug(physAddress < range->GetRangeEnd() && (physAddress + size) > range->GetRangeBegin()); + range->invalidate(physAddress, physAddress + size); + } } // optimized version of LatteBufferCache_invalidate() if physAddress points to the beginning of a page void LatteBufferCache_invalidatePage(MPTR physAddress) { cemu_assert_debug((physAddress & CACHE_PAGE_SIZE_M1) == 0); - BufferCacheNode* node = g_gpuBufferCache.getRangeByPoint(physAddress); + BufferCacheNode* node = g_gpuBufferCache.GetRange(physAddress); if (node) + { + cemu_assert_debug(physAddress >= node->GetRangeBegin() && physAddress < node->GetRangeEnd()); node->invalidate(physAddress, physAddress+CACHE_PAGE_SIZE); + } } void LatteBufferCache_processDeallocations() @@ -987,7 +1620,7 @@ void LatteBufferCache_processDeallocations() void LatteBufferCache_init(size_t bufferSize) { - cemu_assert_debug(g_gpuBufferCache.empty()); + cemu_assert_debug(g_gpuBufferCache.IsEmpty()); g_gpuBufferHeap.reset(new VHeap(nullptr, (uint32)bufferSize)); g_renderer->bufferCache_init((uint32)bufferSize); } @@ -1002,8 +1635,6 @@ void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); } -FSpinlock g_spinlockDCFlushQueue; - class SparseBitset { static inline constexpr size_t TABLE_MASK = 0xFF; @@ -1062,6 +1693,7 @@ private: size_t m_numNonEmptyVectors{ 0 }; }; +FSpinlock g_spinlockDCFlushQueue; SparseBitset* s_DCFlushQueue = new SparseBitset(); SparseBitset* s_DCFlushQueueAlternate = new SparseBitset(); @@ -1111,10 +1743,9 @@ void LatteBufferCache_incrementalCleanup() auto range = s_allCacheNodes[s_counter]; - if (range->HasStreamoutData()) + if (range->HasStreamoutData() && range->GetFrameAge() < 120) { - // currently we never delete streamout ranges - // todo - check if streamout pages got overwritten + if the range would lose the hasStreamoutData flag + // todo - proper way to check if streamout data has been overwritten (in RAM) and whether it can be invalidated from GPU cache return; } @@ -1123,36 +1754,19 @@ void LatteBufferCache_incrementalCleanup() uint32 allocNum; g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); - if (allocationSize >= (heapSize * 4 / 5)) + sint32 evictionFrameAge; + if (allocationSize >= (heapSize * 4 / 5)) // heap is 80% filled + evictionFrameAge = 2; + else if (allocationSize >= (heapSize * 3 / 4)) // heap is 75-100% filled + evictionFrameAge = 4; + else if (allocationSize >= (heapSize / 2)) // if heap is 50-75% filled + evictionFrameAge = 20; + else // heap is under 50% capacity + evictionFrameAge = 500; + // evict range if above threshold + if (range->GetFrameAge() >= evictionFrameAge) { - // heap is 80% filled - if (range->GetFrameAge() >= 2) - { - g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); - } - } - else if (allocationSize >= (heapSize * 3 / 4)) - { - // heap is 75-100% filled - if (range->GetFrameAge() >= 4) - { - g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); - } - } - else if (allocationSize >= (heapSize / 2)) - { - // if heap is 50-75% filled - if (range->GetFrameAge() >= 20) - { - g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); - } - } - else - { - // heap is under 50% capacity - if (range->GetFrameAge() >= 500) - { - g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); - } + g_gpuBufferCache.RemoveRange(range->GetRangeBegin(), range->GetRangeEnd()); + delete range; } } diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 963c49f7..ceec8d24 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -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; } @@ -1921,4 +1922,4 @@ void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size) } } } -#endif \ No newline at end of file +#endif diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp index 45be6843..fe52547f 100644 --- a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp @@ -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(src.physDataAddr).GetPtr(), src.heightInTexels, src.pitch, 1, src.sliceIndex, src.swizzle, (uint32)src.tilemode, + (uint8*)MEMPTR(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); } } diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.h b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.h new file mode 100644 index 00000000..f817f75d --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.h @@ -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); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 4445fb26..c2d4f05b 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1138,6 +1138,7 @@ void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector& } } +// 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; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index 80dcb4d7..88d3291d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -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); } diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 9ec370c3..fd3ed23b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -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)); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp index 546501b6..5aa1fef5 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp @@ -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; @@ -72,7 +72,7 @@ namespace coreinit break; } } - // truncate file extension + // truncate file extension char tempLibName[512]; strcpy(tempLibName, libName + fileNameStartIndex); tempLen = (sint32)strlen(tempLibName); @@ -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* 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); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 8f3b9e0a..52dc0318 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1483,6 +1483,11 @@ namespace coreinit s_threadToFiber.clear(); } + bool OSIsSchedulerActive() + { + return sSchedulerActive; + } + SysAllocator s_defaultThreads; SysAllocator s_stack; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index a0517d9a..c199e59f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -610,6 +610,7 @@ namespace coreinit // scheduler void OSSchedulerBegin(sint32 numCPUEmulationThreads); void OSSchedulerEnd(); + bool OSIsSchedulerActive(); // internal void __OSAddReadyThreadToRunQueue(OSThread_t* thread); diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp index f52f38e7..e4ac4443 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -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(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(srcPitch, 1); } - // dst - uint32 dstPitch = surfOutDst.pitch; - uint32 dstSwizzle = dstSurface->swizzle; - uint32 dstHwTileMode = (uint32)surfOutDst.hwTileMode; - uint32 dstDepth = std::max(surfOutDst.depth, 1); - uint32 dstBpp = surfOutDst.bpp; + uint32 copyWidth = std::max(dstWidth>>dstMip, 1); + uint32 copyHeight = std::max(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(srcWidth>>srcMip, 1)); + cemu_assert(copyHeight <= std::max(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(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(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(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(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) @@ -363,7 +336,7 @@ void gx2Export_GX2CopySurface(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -typedef struct +typedef struct { sint32 left; sint32 top; @@ -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(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(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(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(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); diff --git a/src/Cemu/ExpressionParser/ExpressionParser.cpp b/src/Cemu/ExpressionParser/ExpressionParser.cpp index 90e3f4cd..351774a9 100644 --- a/src/Cemu/ExpressionParser/ExpressionParser.cpp +++ b/src/Cemu/ExpressionParser/ExpressionParser.cpp @@ -43,4 +43,16 @@ void ExpressionParser_test() cemu_assert_debug(_testEvaluateToType("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("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("(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); + } \ No newline at end of file diff --git a/src/Cemu/ExpressionParser/ExpressionParser.h b/src/Cemu/ExpressionParser/ExpressionParser.h index e8ad6ce2..ddac18c1 100644 --- a/src/Cemu/ExpressionParser/ExpressionParser.h +++ b/src/Cemu/ExpressionParser/ExpressionParser.h @@ -29,11 +29,43 @@ inline std::from_chars_result _convFastFloatResult(fast_float::from_chars_result template 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); 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 T Evaluate(std::string_view expression) const { @@ -45,6 +77,7 @@ public: { std::queue> output; std::stack> operators; + RelocModifier relocModifier = RelocModifier::None; if (expression.empty()) { @@ -73,11 +106,11 @@ public: auto converted = (TType)ConvertString(view, &offset); output.emplace(std::make_shared(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 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(); diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp index 878d5f47..7b89a068 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.cpp +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -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 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) diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index 7530a2eb..1c7e95b5 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -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(""); diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 6a4f3f57..e0373952 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -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 diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index a900f10b..7b381bc0 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -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; } diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 4ef4fe08..d2deef7b 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -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); diff --git a/src/gui/wxgui/debugger/BreakpointWindow.cpp b/src/gui/wxgui/debugger/BreakpointWindow.cpp index 9e569a4e..8dc449d9 100644 --- a/src/gui/wxgui/debugger/BreakpointWindow.cpp +++ b/src/gui/wxgui/debugger/BreakpointWindow.cpp @@ -101,42 +101,40 @@ void BreakpointWindow::OnUpdateView() Freeze(); m_breakpoints->DeleteAllItems(); + std::vector& 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& 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) diff --git a/src/gui/wxgui/debugger/DebuggerWindow2.cpp b/src/gui/wxgui/debugger/DebuggerWindow2.cpp index 4422b61a..e91bc1e0 100644 --- a/src/gui/wxgui/debugger/DebuggerWindow2.cpp +++ b/src/gui/wxgui/debugger/DebuggerWindow2.cpp @@ -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(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& 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& 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(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(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); } diff --git a/src/gui/wxgui/debugger/DebuggerWindow2.h b/src/gui/wxgui/debugger/DebuggerWindow2.h index b6395e06..1b4d3c72 100644 --- a/src/gui/wxgui/debugger/DebuggerWindow2.h +++ b/src/gui/wxgui/debugger/DebuggerWindow2.h @@ -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 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> m_modules_storage; + std::vector> 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(); }; diff --git a/src/gui/wxgui/debugger/DisasmCtrl.cpp b/src/gui/wxgui/debugger/DisasmCtrl.cpp index 08f254d6..33da4774 100644 --- a/src/gui/wxgui/debugger/DisasmCtrl.cpp +++ b/src/gui/wxgui/debugger/DisasmCtrl.cpp @@ -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(); } } } diff --git a/src/gui/wxgui/debugger/DumpCtrl.cpp b/src/gui/wxgui/debugger/DumpCtrl.cpp index 838845a8..16167e39 100644 --- a/src/gui/wxgui/debugger/DumpCtrl.cpp +++ b/src/gui/wxgui/debugger/DumpCtrl.cpp @@ -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) diff --git a/src/gui/wxgui/debugger/ModuleWindow.cpp b/src/gui/wxgui/debugger/ModuleWindow.cpp index 8144eb2b..cadf2e6a 100644 --- a/src/gui/wxgui/debugger/ModuleWindow.cpp +++ b/src/gui/wxgui/debugger/ModuleWindow.cpp @@ -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); } diff --git a/src/gui/wxgui/debugger/RegisterWindow.cpp b/src/gui/wxgui/debugger/RegisterWindow.cpp index c99be90c..17167176 100644 --- a/src/gui/wxgui/debugger/RegisterWindow.cpp +++ b/src/gui/wxgui/debugger/RegisterWindow.cpp @@ -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(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(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(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(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(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; } @@ -396,7 +412,13 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) 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(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)); } } } diff --git a/src/gui/wxgui/debugger/SymbolCtrl.cpp b/src/gui/wxgui/debugger/SymbolCtrl.cpp index 8e70082b..77f55529 100644 --- a/src/gui/wxgui/debugger/SymbolCtrl.cpp +++ b/src/gui/wxgui/debugger/SymbolCtrl.cpp @@ -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) diff --git a/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index d187fbfd..5f7da298 100644 --- a/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -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& samples)