mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-11 01:28:41 +12:00
Merge pull request #778 from wheremyfoodat/dsp-debugger2
Some checks failed
Android Build / x64 (release) (push) Has been cancelled
Android Build / arm64 (release) (push) Has been cancelled
HTTP Server Build / build (push) Has been cancelled
Hydra Core Build / Windows (push) Has been cancelled
Hydra Core Build / MacOS (push) Has been cancelled
Hydra Core Build / Linux (push) Has been cancelled
Hydra Core Build / Android-x64 (push) Has been cancelled
Hydra Core Build / ARM-Libretro (push) Has been cancelled
Linux AppImage Build / build (push) Has been cancelled
Linux Build / build (push) Has been cancelled
MacOS Build / MacOS-arm64 (push) Has been cancelled
MacOS Build / MacOS-x86_64 (push) Has been cancelled
Qt Build / Windows (push) Has been cancelled
Qt Build / MacOS-arm64 (push) Has been cancelled
Qt Build / MacOS-x86_64 (push) Has been cancelled
Qt Build / Linux (push) Has been cancelled
Windows Build / build (push) Has been cancelled
iOS Simulator Build / build (push) Has been cancelled
MacOS Build / MacOS-Universal (push) Has been cancelled
Qt Build / MacOS-Universal (push) Has been cancelled
Some checks failed
Android Build / x64 (release) (push) Has been cancelled
Android Build / arm64 (release) (push) Has been cancelled
HTTP Server Build / build (push) Has been cancelled
Hydra Core Build / Windows (push) Has been cancelled
Hydra Core Build / MacOS (push) Has been cancelled
Hydra Core Build / Linux (push) Has been cancelled
Hydra Core Build / Android-x64 (push) Has been cancelled
Hydra Core Build / ARM-Libretro (push) Has been cancelled
Linux AppImage Build / build (push) Has been cancelled
Linux Build / build (push) Has been cancelled
MacOS Build / MacOS-arm64 (push) Has been cancelled
MacOS Build / MacOS-x86_64 (push) Has been cancelled
Qt Build / Windows (push) Has been cancelled
Qt Build / MacOS-arm64 (push) Has been cancelled
Qt Build / MacOS-x86_64 (push) Has been cancelled
Qt Build / Linux (push) Has been cancelled
Windows Build / build (push) Has been cancelled
iOS Simulator Build / build (push) Has been cancelled
MacOS Build / MacOS-Universal (push) Has been cancelled
Qt Build / MacOS-Universal (push) Has been cancelled
Add DSP debugger
This commit is contained in:
commit
3cae1bd256
17 changed files with 476 additions and 66 deletions
|
@ -314,7 +314,7 @@ else()
|
|||
message(FATAL_ERROR "Currently unsupported CPU architecture")
|
||||
endif()
|
||||
|
||||
add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(third_party/teakra)
|
||||
add_subdirectory(third_party/fdk-aac)
|
||||
|
||||
set(CAPSTONE_ARCHITECTURE_DEFAULT OFF)
|
||||
|
@ -416,7 +416,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
|||
include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp
|
||||
include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp
|
||||
include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp
|
||||
include/screen_layout.hpp include/services/service_map.hpp
|
||||
include/screen_layout.hpp include/services/service_map.hpp include/audio/dsp_binary.hpp
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
|
@ -686,8 +686,8 @@ set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERN
|
|||
${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES})
|
||||
target_sources(AlberCore PRIVATE ${ALL_SOURCES})
|
||||
|
||||
target_link_libraries(AlberCore PRIVATE dynarmic glad resources_console_fonts teakra fdk-aac)
|
||||
target_link_libraries(AlberCore PUBLIC glad capstone fmt::fmt)
|
||||
target_link_libraries(AlberCore PRIVATE dynarmic glad resources_console_fonts fdk-aac)
|
||||
target_link_libraries(AlberCore PUBLIC glad capstone fmt::fmt teakra)
|
||||
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1")
|
||||
|
@ -733,12 +733,13 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
|
|||
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
||||
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
||||
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
|
||||
src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp
|
||||
src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp src/panda_qt/dsp_debugger.cpp
|
||||
)
|
||||
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
||||
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
||||
include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/disabled_widget_overlay.hpp
|
||||
include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/dsp_debugger.hpp
|
||||
include/panda_qt/disabled_widget_overlay.hpp
|
||||
)
|
||||
|
||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||
|
|
|
@ -29,11 +29,11 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
std::vector<u32> returnPCs;
|
||||
|
||||
// Vector value of (-0.0, -0.0, -0.0, -0.0) for negating vectors via pxor
|
||||
Label negateVector;
|
||||
Xbyak::Label negateVector;
|
||||
// Vector value of (1.0, 1.0, 1.0, 1.0) for SLT(i)/SGE(i)
|
||||
Label onesVector;
|
||||
Xbyak::Label onesVector;
|
||||
// Vector value of (0xFF, 0xFF, 0xFF, 0) for setting the w component to 0 in DP3
|
||||
Label dp3Vector;
|
||||
Xbyak::Label dp3Vector;
|
||||
|
||||
u32 recompilerPC = 0; // PC the recompiler is currently recompiling @
|
||||
u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop)
|
||||
|
@ -47,7 +47,7 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
bool codeHasExp2 = false;
|
||||
// Whether to compile this shader using accurate, safe, non-IEEE multiplication (slow) or faster but less accurate mul
|
||||
bool useSafeMUL = false;
|
||||
|
||||
|
||||
Xbyak::Label log2Func, exp2Func;
|
||||
Xbyak::Label emitLog2Func();
|
||||
Xbyak::Label emitExp2Func();
|
||||
|
@ -72,8 +72,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
|
||||
// Load register with number "srcReg" indexed by index "idx" into the xmm register "reg"
|
||||
template <int sourceIndex>
|
||||
void loadRegister(Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
|
||||
void storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
|
||||
void loadRegister(Xbyak::Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
|
||||
void storeRegister(Xbyak::Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
|
||||
|
||||
const vec4f& getSourceRef(const PICAShader& shader, u32 src);
|
||||
const vec4f& getDestRef(const PICAShader& shader, u32 dest);
|
||||
|
|
|
@ -2,39 +2,37 @@
|
|||
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
#include "xbyak/xbyak.h"
|
||||
using namespace Xbyak;
|
||||
using namespace Xbyak::util;
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
|
||||
#define PANDA3DS_MS_ABI
|
||||
constexpr Reg32 arg1 = ecx; // register where first arg is stored
|
||||
constexpr Reg32 arg2 = edx; // register where second arg is stored
|
||||
constexpr Reg32 arg3 = r8d; // register where third arg is stored
|
||||
constexpr Reg32 arg4 = r9d; // register where fourth arg is stored
|
||||
constexpr Xbyak::Reg32 arg1 = Xbyak::util::ecx; // register where first arg is stored
|
||||
constexpr Xbyak::Reg32 arg2 = Xbyak::util::edx; // register where second arg is stored
|
||||
constexpr Xbyak::Reg32 arg3 = Xbyak::util::r8d; // register where third arg is stored
|
||||
constexpr Xbyak::Reg32 arg4 = Xbyak::util::r9d; // register where fourth arg is stored
|
||||
|
||||
// Similar for floating point and vector arguemnts.
|
||||
constexpr Xmm arg1f = xmm0;
|
||||
constexpr Xmm arg2f = xmm1;
|
||||
constexpr Xmm arg3f = xmm2;
|
||||
constexpr Xmm arg4f = xmm3;
|
||||
constexpr Xbyak::Xmm arg1f = Xbyak::util::xmm0;
|
||||
constexpr Xbyak::Xmm arg2f = Xbyak::util::xmm1;
|
||||
constexpr Xbyak::Xmm arg3f = Xbyak::util::xmm2;
|
||||
constexpr Xbyak::Xmm arg4f = Xbyak::util::xmm3;
|
||||
|
||||
constexpr bool isWindows() { return true; }
|
||||
|
||||
#else // System V calling convention
|
||||
#define PANDA3DS_SYSV_ABI
|
||||
constexpr Reg32 arg1 = edi;
|
||||
constexpr Reg32 arg2 = esi;
|
||||
constexpr Reg32 arg3 = edx;
|
||||
constexpr Reg32 arg4 = ecx;
|
||||
constexpr Xbyak::Reg32 arg1 = Xbyak::util::edi;
|
||||
constexpr Xbyak::Reg32 arg2 = Xbyak::util::esi;
|
||||
constexpr Xbyak::Reg32 arg3 = Xbyak::util::edx;
|
||||
constexpr Xbyak::Reg32 arg4 = Xbyak::util::ecx;
|
||||
|
||||
constexpr Xmm arg1f = xmm0;
|
||||
constexpr Xmm arg2f = xmm1;
|
||||
constexpr Xmm arg3f = xmm2;
|
||||
constexpr Xmm arg4f = xmm3;
|
||||
constexpr Xmm arg5f = xmm4;
|
||||
constexpr Xmm arg6f = xmm5;
|
||||
constexpr Xmm arg7f = xmm6;
|
||||
constexpr Xmm arg8f = xmm7;
|
||||
constexpr Xbyak::Xmm arg1f = Xbyak::util::xmm0;
|
||||
constexpr Xbyak::Xmm arg2f = Xbyak::util::xmm1;
|
||||
constexpr Xbyak::Xmm arg3f = Xbyak::util::xmm2;
|
||||
constexpr Xbyak::Xmm arg4f = Xbyak::util::xmm3;
|
||||
constexpr Xbyak::Xmm arg5f = Xbyak::util::xmm4;
|
||||
constexpr Xbyak::Xmm arg6f = Xbyak::util::xmm5;
|
||||
constexpr Xbyak::Xmm arg7f = Xbyak::util::xmm6;
|
||||
constexpr Xbyak::Xmm arg8f = Xbyak::util::xmm7;
|
||||
|
||||
constexpr bool isWindows() { return false; }
|
||||
#endif
|
||||
|
|
29
include/audio/dsp_binary.hpp
Normal file
29
include/audio/dsp_binary.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
struct Dsp1 {
|
||||
// All sizes are in bytes unless otherwise specified
|
||||
u8 signature[0x100];
|
||||
u8 magic[4];
|
||||
u32 size;
|
||||
u8 codeMemLayout;
|
||||
u8 dataMemLayout;
|
||||
u8 pad[3];
|
||||
u8 specialType;
|
||||
u8 segmentCount;
|
||||
u8 flags;
|
||||
u32 specialStart;
|
||||
u32 specialSize;
|
||||
u64 zeroBits;
|
||||
|
||||
struct Segment {
|
||||
u32 offs; // Offset of the segment data
|
||||
u32 dspAddr; // Start of the segment in 16-bit units
|
||||
u32 size;
|
||||
u8 pad[3];
|
||||
u8 type;
|
||||
u8 hash[0x20];
|
||||
};
|
||||
|
||||
Segment segments[10];
|
||||
};
|
|
@ -1,8 +1,5 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -63,6 +60,24 @@ namespace Audio {
|
|||
|
||||
Samples& getSamples() { return sampleBuffer; }
|
||||
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
|
||||
|
||||
virtual Type getType() = 0;
|
||||
virtual void* getRegisters() { return nullptr; }
|
||||
|
||||
// Read a word from program memory. By default, just perform a regular DSP RAM read for the HLE cores
|
||||
// The LLE cores translate the address, accounting for the way Teak memory is mapped
|
||||
virtual u16 readProgramWord(u32 address) {
|
||||
u8* dspRam = getDspMemory();
|
||||
|
||||
auto readByte = [&](u32 addr) {
|
||||
if (addr >= 256_KB) return u8(0);
|
||||
return dspRam[addr];
|
||||
};
|
||||
|
||||
u16 lsb = u16(readByte(address));
|
||||
u16 msb = u16(readByte(address + 1));
|
||||
return u16(lsb | (msb << 8));
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<DSPCore> makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
|
|
|
@ -213,6 +213,7 @@ namespace Audio {
|
|||
void runAudioFrame(u64 eventTimestamp) override;
|
||||
|
||||
u8* getDspMemory() override { return dspRam.rawMemory.data(); }
|
||||
DSPCore::Type getType() override { return DSPCore::Type::HLE; }
|
||||
|
||||
u16 recvData(u32 regId) override;
|
||||
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Audio {
|
|||
void runAudioFrame(u64 eventTimestamp) override;
|
||||
|
||||
u8* getDspMemory() override { return dspRam.data(); }
|
||||
DSPCore::Type getType() override { return DSPCore::Type::Null; }
|
||||
|
||||
u16 recvData(u32 regId) override;
|
||||
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready
|
||||
|
|
|
@ -90,6 +90,9 @@ namespace Audio {
|
|||
|
||||
void setAudioEnabled(bool enable) override;
|
||||
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }
|
||||
void* getRegisters() override;
|
||||
DSPCore::Type getType() override { return DSPCore::Type::Teakra; }
|
||||
u16 readProgramWord(u32 address) override { return teakra.ProgramRead(address); }
|
||||
|
||||
u16 recvData(u32 regId) override { return teakra.RecvData(regId); }
|
||||
bool recvDataIsReady(u32 regId) override { return teakra.RecvDataIsReady(regId); }
|
||||
|
|
|
@ -126,6 +126,7 @@ class Emulator {
|
|||
Memory& getMemory() { return memory; }
|
||||
Kernel& getKernel() { return kernel; }
|
||||
Scheduler& getScheduler() { return scheduler; }
|
||||
Audio::DSPCore* getDSP() { return dsp.get(); }
|
||||
|
||||
EmulatorConfig& getConfig() { return config; }
|
||||
Cheats& getCheats() { return cheats; }
|
||||
|
|
47
include/panda_qt/dsp_debugger.hpp
Normal file
47
include/panda_qt/dsp_debugger.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QScrollBar>
|
||||
#include <QWidget>
|
||||
|
||||
#include "emulator.hpp"
|
||||
#include "panda_qt/disabled_widget_overlay.hpp"
|
||||
|
||||
class DSPDebugger : public QWidget {
|
||||
Q_OBJECT
|
||||
Emulator* emu;
|
||||
|
||||
QListWidget* disasmListWidget;
|
||||
QScrollBar* verticalScrollBar;
|
||||
QPlainTextEdit* registerTextEdit;
|
||||
QTimer* updateTimer;
|
||||
QLineEdit* addressInput;
|
||||
|
||||
DisabledWidgetOverlay* disabledOverlay;
|
||||
DisabledWidgetOverlay* disabledRegisterEditOverlay;
|
||||
|
||||
bool enabled = false;
|
||||
bool followPC = false;
|
||||
|
||||
public:
|
||||
DSPDebugger(Emulator* emulator, QWidget* parent = nullptr);
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
private:
|
||||
// Get the full PC value of the DSP, including the current progrma page value
|
||||
u32 getPC();
|
||||
|
||||
// Update the state of the disassembler. Qt events should always call update, not updateDisasm/updateRegister
|
||||
// As update properly handles thread safety
|
||||
void update();
|
||||
void updateDisasm();
|
||||
void updateRegisters();
|
||||
void scrollToPC();
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
};
|
|
@ -18,6 +18,7 @@
|
|||
#include "panda_qt/cheats_window.hpp"
|
||||
#include "panda_qt/config_window.hpp"
|
||||
#include "panda_qt/cpu_debugger.hpp"
|
||||
#include "panda_qt/dsp_debugger.hpp"
|
||||
#include "panda_qt/patch_window.hpp"
|
||||
#include "panda_qt/screen.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
|
@ -112,6 +113,7 @@ class MainWindow : public QMainWindow {
|
|||
PatchWindow* patchWindow;
|
||||
ShaderEditorWindow* shaderEditor;
|
||||
CPUDebugger* cpuDebugger;
|
||||
DSPDebugger* dspDebugger;
|
||||
ThreadDebugger* threadDebugger;
|
||||
|
||||
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <utility>
|
||||
|
||||
#include "audio/aac_decoder.hpp"
|
||||
#include "audio/dsp_binary.hpp"
|
||||
#include "audio/dsp_simd.hpp"
|
||||
#include "config.hpp"
|
||||
#include "services/dsp.hpp"
|
||||
|
@ -89,6 +90,36 @@ namespace Audio {
|
|||
Helpers::warn("Loading DSP component when already loaded");
|
||||
}
|
||||
|
||||
// We load the DSP binary into DSP memory even though we don't use it in HLE, so that we can
|
||||
// still see the DSP code in the DSP debugger
|
||||
u8* dspCode = dspRam.rawMemory.data();
|
||||
u8* dspData = dspCode + 0x40000;
|
||||
|
||||
Dsp1 dsp1;
|
||||
std::memcpy(&dsp1, data.data(), sizeof(dsp1));
|
||||
|
||||
// TODO: verify DSP1 signature & hashes
|
||||
// Load DSP segments to DSP RAM
|
||||
for (uint i = 0; i < dsp1.segmentCount; i++) {
|
||||
auto& segment = dsp1.segments[i];
|
||||
u32 addr = segment.dspAddr << 1;
|
||||
u8* src = data.data() + segment.offs;
|
||||
u8* dst = nullptr;
|
||||
|
||||
switch (segment.type) {
|
||||
case 0:
|
||||
case 1: dst = dspCode + addr; break;
|
||||
default: dst = dspData + addr; break;
|
||||
}
|
||||
|
||||
std::memcpy(dst, src, segment.size);
|
||||
}
|
||||
|
||||
bool loadSpecialSegment = (dsp1.flags >> 1) & 0x1;
|
||||
if (loadSpecialSegment) {
|
||||
log("LoadComponent: special segment not supported");
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame);
|
||||
}
|
||||
|
|
|
@ -5,37 +5,11 @@
|
|||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "audio/dsp_binary.hpp"
|
||||
#include "services/dsp.hpp"
|
||||
|
||||
using namespace Audio;
|
||||
|
||||
struct Dsp1 {
|
||||
// All sizes are in bytes unless otherwise specified
|
||||
u8 signature[0x100];
|
||||
u8 magic[4];
|
||||
u32 size;
|
||||
u8 codeMemLayout;
|
||||
u8 dataMemLayout;
|
||||
u8 pad[3];
|
||||
u8 specialType;
|
||||
u8 segmentCount;
|
||||
u8 flags;
|
||||
u32 specialStart;
|
||||
u32 specialSize;
|
||||
u64 zeroBits;
|
||||
|
||||
struct Segment {
|
||||
u32 offs; // Offset of the segment data
|
||||
u32 dspAddr; // Start of the segment in 16-bit units
|
||||
u32 size;
|
||||
u8 pad[3];
|
||||
u8 type;
|
||||
u8 hash[0x20];
|
||||
};
|
||||
|
||||
Segment segments[10];
|
||||
};
|
||||
|
||||
TeakraDSP::TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService, EmulatorConfig& config)
|
||||
: DSPCore(mem, scheduler, dspService, config), pipeBaseAddr(0), running(false) {
|
||||
// Set up callbacks for Teakra
|
||||
|
@ -343,3 +317,5 @@ void TeakraDSP::unloadComponent() {
|
|||
teakra.RecvData(2);
|
||||
running = false;
|
||||
}
|
||||
|
||||
void* TeakraDSP::getRegisters() { return &teakra.GetRegisterState(); }
|
|
@ -69,7 +69,7 @@ CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), d
|
|||
|
||||
// Setup overlay for when the widget is disabled
|
||||
disabledOverlay = new DisabledWidgetOverlay(this, tr("Pause the emulator to use the CPU Debugger"));
|
||||
disabledOverlay->resize(size()); // Fill the whole screen
|
||||
disabledOverlay->resize(size()); // Fill the whole widget
|
||||
disabledOverlay->raise();
|
||||
disabledOverlay->hide();
|
||||
|
||||
|
|
301
src/panda_qt/dsp_debugger.cpp
Normal file
301
src/panda_qt/dsp_debugger.cpp
Normal file
|
@ -0,0 +1,301 @@
|
|||
#include "panda_qt/dsp_debugger.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QListWidget>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "teakra/disassembler.h"
|
||||
|
||||
#undef Assert
|
||||
#undef UNREACHABLE
|
||||
#include "teakra/impl/register.h"
|
||||
|
||||
// TODO: Make this actually thread-safe by having it only work when paused
|
||||
static int getLinesInViewport(QListWidget* listWidget) {
|
||||
auto viewportHeight = listWidget->viewport()->height();
|
||||
QFontMetrics fm = QFontMetrics(listWidget->font());
|
||||
auto lineHeight = fm.height();
|
||||
|
||||
return int(viewportHeight / lineHeight);
|
||||
}
|
||||
|
||||
static std::pair<int, int> getVisibleLineRange(QListWidget* listWidget, QScrollBar* scrollBar) {
|
||||
int firstLine = scrollBar->value();
|
||||
int lineCount = getLinesInViewport(listWidget);
|
||||
|
||||
return {firstLine, lineCount};
|
||||
}
|
||||
|
||||
DSPDebugger::DSPDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), QWidget(parent, Qt::Window) {
|
||||
setWindowTitle(tr("DSP debugger"));
|
||||
resize(1000, 600);
|
||||
|
||||
QGridLayout* gridLayout = new QGridLayout(this);
|
||||
QHBoxLayout* horizontalLayout = new QHBoxLayout();
|
||||
|
||||
// Set up the top line widgets
|
||||
QPushButton* goToAddressButton = new QPushButton(tr("Go to address"), this);
|
||||
QPushButton* goToPCButton = new QPushButton(tr("Go to PC"), this);
|
||||
QCheckBox* followPCCheckBox = new QCheckBox(tr("Follow PC"), this);
|
||||
addressInput = new QLineEdit(this);
|
||||
|
||||
horizontalLayout->addWidget(goToAddressButton);
|
||||
horizontalLayout->addWidget(goToPCButton);
|
||||
horizontalLayout->addWidget(followPCCheckBox);
|
||||
horizontalLayout->addWidget(addressInput);
|
||||
|
||||
followPCCheckBox->setChecked(followPC);
|
||||
connect(followPCCheckBox, &QCheckBox::toggled, this, [&](bool checked) { followPC = checked; });
|
||||
|
||||
addressInput->setPlaceholderText(tr("Address to jump to"));
|
||||
addressInput->setMaximumWidth(150);
|
||||
|
||||
gridLayout->addLayout(horizontalLayout, 0, 0);
|
||||
|
||||
// Disassembly list on the left, scrollbar in the middle, register view on the right
|
||||
disasmListWidget = new QListWidget(this);
|
||||
gridLayout->addWidget(disasmListWidget, 1, 0);
|
||||
|
||||
verticalScrollBar = new QScrollBar(Qt::Vertical, this);
|
||||
gridLayout->addWidget(verticalScrollBar, 1, 1);
|
||||
|
||||
registerTextEdit = new QPlainTextEdit(this);
|
||||
registerTextEdit->setEnabled(true);
|
||||
registerTextEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
registerTextEdit->setMaximumWidth(800);
|
||||
gridLayout->addWidget(registerTextEdit, 1, 2);
|
||||
|
||||
// Setup overlay for when the debugger is disabled
|
||||
disabledOverlay = new DisabledWidgetOverlay(this, tr("Pause the emulator to use the DSP Debugger"));
|
||||
disabledOverlay->resize(size()); // Fill the whole widget
|
||||
disabledOverlay->raise();
|
||||
disabledOverlay->hide();
|
||||
|
||||
// Setup overlay for when the register widget is disabled
|
||||
disabledRegisterEditOverlay = new DisabledWidgetOverlay(registerTextEdit, tr("Register view is only supported\nwith LLE DSP"));
|
||||
disabledRegisterEditOverlay->resize(registerTextEdit->size());
|
||||
disabledRegisterEditOverlay->raise();
|
||||
disabledRegisterEditOverlay->hide();
|
||||
|
||||
// Use a monospace font for the disassembly to align it
|
||||
QFont mono_font = QFont("Courier New");
|
||||
mono_font.setStyleHint(QFont::Monospace);
|
||||
disasmListWidget->setFont(mono_font);
|
||||
registerTextEdit->setFont(mono_font);
|
||||
|
||||
// Forward scrolling from the list widget to our scrollbar
|
||||
disasmListWidget->installEventFilter(this);
|
||||
|
||||
// Annoyingly, due to a Qt limitation we can't set it to U32_MAX
|
||||
verticalScrollBar->setRange(0, std::numeric_limits<s32>::max());
|
||||
verticalScrollBar->setSingleStep(8);
|
||||
verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget));
|
||||
verticalScrollBar->show();
|
||||
connect(verticalScrollBar, &QScrollBar::valueChanged, this, &DSPDebugger::updateDisasm);
|
||||
registerTextEdit->setReadOnly(true);
|
||||
|
||||
connect(goToPCButton, &QPushButton::clicked, this, [&]() { scrollToPC(); });
|
||||
|
||||
// We have a QTimer that triggers every 500ms to update our widget when it's active
|
||||
updateTimer = new QTimer(this);
|
||||
connect(updateTimer, &QTimer::timeout, this, &DSPDebugger::update);
|
||||
|
||||
// Go to address when the "Go to address" button is pressed, or when we press enter inside the address input box
|
||||
connect(goToAddressButton, &QPushButton::clicked, this, [&]() {
|
||||
QString text = addressInput->text().trimmed();
|
||||
|
||||
bool validAddr = false;
|
||||
u32 addr = text.toUInt(&validAddr, 16); // Parse address as hex
|
||||
if (validAddr) {
|
||||
verticalScrollBar->setValue(addr);
|
||||
} else {
|
||||
addressInput->setText(tr("Invalid hexadecimal address"));
|
||||
}
|
||||
});
|
||||
connect(addressInput, &QLineEdit::returnPressed, goToAddressButton, &QPushButton::click);
|
||||
|
||||
disable();
|
||||
hide();
|
||||
}
|
||||
|
||||
void DSPDebugger::enable() {
|
||||
enabled = true;
|
||||
|
||||
disabledOverlay->hide();
|
||||
scrollToPC();
|
||||
|
||||
// Update the widget every 500ms
|
||||
updateTimer->start(500);
|
||||
update();
|
||||
}
|
||||
|
||||
void DSPDebugger::disable() {
|
||||
enabled = false;
|
||||
|
||||
updateTimer->stop();
|
||||
disabledOverlay->show();
|
||||
}
|
||||
|
||||
void DSPDebugger::update() {
|
||||
if (enabled) {
|
||||
if (followPC) {
|
||||
scrollToPC();
|
||||
}
|
||||
|
||||
updateDisasm();
|
||||
updateRegisters();
|
||||
}
|
||||
}
|
||||
|
||||
void DSPDebugger::updateDisasm() {
|
||||
int currentRow = disasmListWidget->currentRow();
|
||||
disasmListWidget->clear();
|
||||
|
||||
auto [firstLine, lineCount] = getVisibleLineRange(disasmListWidget, verticalScrollBar);
|
||||
const u32 startPC = firstLine;
|
||||
|
||||
auto dsp = emu->getDSP();
|
||||
auto dspRam = dsp->getDspMemory();
|
||||
|
||||
auto& mem = emu->getMemory();
|
||||
u32 pc = getPC();
|
||||
|
||||
std::string disassembly;
|
||||
|
||||
for (u32 addr = startPC, instructionCount = 0; instructionCount < lineCount; instructionCount++) {
|
||||
const u16 instruction = dsp->readProgramWord(addr);
|
||||
const bool needExpansion = Teakra::Disassembler::NeedExpansion(instruction);
|
||||
|
||||
const u16 expansion = needExpansion ? dsp->readProgramWord(addr + 2) : u16(0);
|
||||
|
||||
std::string disassembly = Teakra::Disassembler::Do(instruction, expansion);
|
||||
disassembly = fmt::format("{:08X} | {}", addr, disassembly);
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(disassembly));
|
||||
if (addr == pc) {
|
||||
item->setBackground(Qt::darkGreen);
|
||||
}
|
||||
|
||||
disasmListWidget->addItem(item);
|
||||
addr += needExpansion ? sizeof(u32) : sizeof(u16);
|
||||
}
|
||||
|
||||
disasmListWidget->setCurrentRow(currentRow);
|
||||
}
|
||||
|
||||
// This is only supported on the Teakra core, as other cores don't actually have a register contexts
|
||||
u32 DSPDebugger::getPC() {
|
||||
auto dsp = emu->getDSP();
|
||||
auto dspType = dsp->getType();
|
||||
|
||||
if (dspType == Audio::DSPCore::Type::Teakra) {
|
||||
auto regs = (Teakra::RegisterState*)dsp->getRegisters();
|
||||
return regs->pc | (u32(regs->prpage) << 18);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DSPDebugger::scrollToPC() {
|
||||
u32 pc = getPC();
|
||||
verticalScrollBar->setValue(pc);
|
||||
}
|
||||
|
||||
void DSPDebugger::updateRegisters() {
|
||||
auto dsp = emu->getDSP();
|
||||
auto dspType = dsp->getType();
|
||||
|
||||
if (dspType == Audio::DSPCore::Type::Teakra) {
|
||||
std::string text = "";
|
||||
text.reserve(4096);
|
||||
|
||||
auto regs = (Teakra::RegisterState*)dsp->getRegisters();
|
||||
text += fmt::format(
|
||||
"PC: 0x{:05X}\nProgram Page: 0x{:01X}\nStack Pointer: 0x{:04X}\n", regs->pc & 0x3FFFF, regs->prpage & 0xF, regs->sp
|
||||
);
|
||||
|
||||
text += "\nGeneral Purpose Registers\n";
|
||||
for (int i = 0; i < 8; i++) {
|
||||
text += fmt::format("r{:01d}: 0x{:08X}\n", i, regs->r[i]);
|
||||
}
|
||||
|
||||
text += "\nAccumulators (40-bit)\n";
|
||||
text += fmt::format("a0: 0x{:010X}\n", regs->a[0] & 0xFFFFFFFFFFull);
|
||||
text += fmt::format("a1: 0x{:010X}\n", regs->a[1] & 0xFFFFFFFFFFull);
|
||||
text += fmt::format("b0: 0x{:010X}\n", regs->b[0] & 0xFFFFFFFFFFull);
|
||||
text += fmt::format("b1: 0x{:010X}\n", regs->b[1] & 0xFFFFFFFFFFull);
|
||||
text += fmt::format("a1s: 0x{:010X}\n", regs->a1s & 0xFFFFFFFFFFull);
|
||||
text += fmt::format("b1s: 0x{:010X}\n", regs->b1s & 0xFFFFFFFFFFull);
|
||||
|
||||
text += "\nMultiplication Unit\n";
|
||||
text += fmt::format("x0: 0x{:04X}\n", regs->x[0]);
|
||||
text += fmt::format("x1: 0x{:04X}\n", regs->x[1]);
|
||||
text += fmt::format("y0: 0x{:04X}\n", regs->y[0]);
|
||||
text += fmt::format("y1: 0x{:04X}\n", regs->y[1]);
|
||||
text += fmt::format("p0: 0x{:08X}\n", regs->p[0]);
|
||||
text += fmt::format("p1: 0x{:08X}\n", regs->p[1]);
|
||||
|
||||
text += "\nOther Registers\n";
|
||||
text += fmt::format("mixp: 0x{:04X}\n", regs->mixp);
|
||||
text += fmt::format("sv: 0x{:04X}\n", regs->sv);
|
||||
text += fmt::format("Shift mode: {}\n", regs->s ? "Logic" : "Arithmetic");
|
||||
|
||||
registerTextEdit->setPlainText(QString::fromStdString(text));
|
||||
disabledRegisterEditOverlay->hide();
|
||||
} else {
|
||||
registerTextEdit->setPlainText(QString::fromStdString(""));
|
||||
disabledRegisterEditOverlay->show();
|
||||
}
|
||||
}
|
||||
|
||||
bool DSPDebugger::eventFilter(QObject* obj, QEvent* event) {
|
||||
// Forward scroll events from the list widget to the scrollbar
|
||||
if (obj == disasmListWidget && event->type() == QEvent::Wheel) {
|
||||
QWheelEvent* wheelEvent = (QWheelEvent*)event;
|
||||
|
||||
int wheelSteps = wheelEvent->angleDelta().y() / 60;
|
||||
int newScrollValue = verticalScrollBar->value() - wheelSteps;
|
||||
newScrollValue = qBound(verticalScrollBar->minimum(), newScrollValue, verticalScrollBar->maximum());
|
||||
verticalScrollBar->setValue(newScrollValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void DSPDebugger::showEvent(QShowEvent* event) {
|
||||
QWidget::showEvent(event);
|
||||
|
||||
enable();
|
||||
}
|
||||
|
||||
// Scroll 1 instruction up or down when the arrow keys are pressed and we're at the edge of the disassembly list
|
||||
void DSPDebugger::keyPressEvent(QKeyEvent* event) {
|
||||
constexpr usize instructionSize = sizeof(u16);
|
||||
|
||||
if (event->key() == Qt::Key_Up) {
|
||||
verticalScrollBar->setValue(verticalScrollBar->value() - instructionSize);
|
||||
} else if (event->key() == Qt::Key_Down) {
|
||||
verticalScrollBar->setValue(verticalScrollBar->value() + instructionSize);
|
||||
} else {
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void DSPDebugger::resizeEvent(QResizeEvent* event) {
|
||||
QWidget::resizeEvent(event);
|
||||
disabledOverlay->resize(event->size());
|
||||
disabledRegisterEditOverlay->resize(registerTextEdit->size());
|
||||
|
||||
verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget));
|
||||
update();
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "cheats.hpp"
|
||||
#include "input_mappings.hpp"
|
||||
#include "panda_qt/dsp_debugger.hpp"
|
||||
#include "sdl_sensors.hpp"
|
||||
#include "services/dsp.hpp"
|
||||
#include "version.hpp"
|
||||
|
@ -67,6 +68,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
||||
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
||||
auto cpuDebuggerAction = toolsMenu->addAction(tr("Open CPU Debugger"));
|
||||
auto dspDebuggerAction = toolsMenu->addAction(tr("Open DSP Debugger"));
|
||||
auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger"));
|
||||
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
||||
|
||||
|
@ -76,6 +78,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
||||
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
||||
connect(cpuDebuggerAction, &QAction::triggered, this, [this]() { cpuDebugger->show(); });
|
||||
connect(dspDebuggerAction, &QAction::triggered, this, [this]() { dspDebugger->show(); });
|
||||
connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); });
|
||||
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
||||
|
||||
|
@ -97,6 +100,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
||||
threadDebugger = new ThreadDebugger(emu, this);
|
||||
cpuDebugger = new CPUDebugger(emu, this);
|
||||
dspDebugger = new DSPDebugger(emu, this);
|
||||
|
||||
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
||||
if (shaderEditor->supported) {
|
||||
|
|
2
third_party/teakra
vendored
2
third_party/teakra
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 01db7cdd00aabcce559a8dddce8798dabb71949b
|
||||
Subproject commit e34a86799efd65e3c44b915a4d65b3514d34df4f
|
Loading…
Add table
Add a link
Reference in a new issue