Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a8b4db3
fix: include path case-sensitivity for macOS/Linux build
alfishe May 13, 2026
70237dd
fix: exit hang on shutdown — signal UAE thread to quit
alfishe May 13, 2026
2a73987
fix: misc crashes and correctness issues
alfishe May 13, 2026
f3e5649
fix: vAmiga build on GCC/Clang
alfishe May 13, 2026
d43ca52
fix: main window flickering - render only on new emulator frame
alfishe May 23, 2026
31d04d8
fix: macOS/Linux whole-archive linking for debugger auto-registration…
alfishe May 15, 2026
af7c183
feat: add cross-platform crash handler with stack trace capture
alfishe May 14, 2026
e0e5e63
fix: pass UiViewCreateCtx by pointer to match callback signature
alfishe May 23, 2026
df2fe44
feat(vAmiga): implement debugger Phase 4 gaps (Copper/Blitter/Floppy/…
alfishe May 23, 2026
e6de8c3
feat: add auto-layout for debugger dock windows
alfishe May 23, 2026
d475a4b
fix: debugger window rendering - eliminate flickering and init glitches
alfishe May 23, 2026
71d9dd4
feat: debugger screen preview - aspect-ratio scaling with live resize
alfishe May 23, 2026
c8d6d40
feat: debugger screen preview - aspect-ratio scaling with live resize
alfishe May 23, 2026
e5a9bee
fix: debugger init glitches - red background and menu positioning
alfishe May 23, 2026
fe1e70c
feat: save and restore debugger window layout
alfishe May 23, 2026
22c64a6
Merge branch 'dbg-render-fix' of https://github.com/alfishe/quaesar-n…
alfishe May 23, 2026
11dea39
fix: debugger toolbar position and layout restore timing
alfishe May 23, 2026
445fba9
Fix UAE debugger pause/step/continue via native debug_1() blocking
alfishe May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@ cmake-build-debug
.idea
.vs
*.pdb

# Binary executables
quaesar
quaesar-dbg

# ImGui layout files
*.ini
debugger_layout.ini
imgui.ini
default_layout.ini
24 changes: 24 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,35 @@ target_link_libraries(quaesar PRIVATE
${QUAESAR_EXTRA_LIBS}
)

# Force-load the entire amDebugger static library so that auto-registered debugger
# windows (TS_BEGIN_REFLECT_CLASS static initializers) are not stripped by the linker.
# MSVC uses WHOLEARCHIVE (set in amDebugger CMakeLists.txt INTERFACE option).
# macOS uses -force_load; Linux uses --whole-archive.
if(APPLE)
target_link_options(quaesar PRIVATE "-Wl,-force_load,$<TARGET_FILE:amDebugger>")
elseif(LINUX OR UNIX)
target_link_options(quaesar PRIVATE
"-Wl,--whole-archive" "$<TARGET_FILE:amDebugger>" "-Wl,--no-whole-archive")
endif()


#if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/imgui.ini")
# file(COPY_FILE "${CMAKE_CURRENT_LIST_DIR}/bin/install/default_layout.ini" "${CMAKE_CURRENT_BINARY_DIR}/imgui.ini")
#endif()

# Bundle the bundled default debugger dock layout next to the binary.
# DebuggerApp::initImGui() resolves it via SDL_GetBasePath() (the dir of the running executable)
# and loads it on first launch when no user imgui.ini exists, so the debugger window
# does not come up as an empty (red) dockspace.
# Using a POST_BUILD custom command + $<TARGET_FILE_DIR:quaesar> ensures the layout
# lands next to the actual exe on every platform/generator (incl. multi-config VS/Xcode).
add_custom_command(TARGET quaesar POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_LIST_DIR}/resources/default_layout.ini"
"$<TARGET_FILE_DIR:quaesar>/default_layout.ini"
VERBATIM
)


add_option_edit_and_continue(quaesar)
add_option_edit_and_continue(qd)
Expand Down
7 changes: 6 additions & 1 deletion libs/amDebugger/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ set(AM_SRC)

function(add_source_group SUBDIR)
set(CURDIR "${AM_SRC_INCLUDE_DIR}/amDebugger/${SUBDIR}")
file(GLOB SUB_FILES_SRC "${CURDIR}/*.cpp" "${CURDIR}/*.h")
# CONFIGURE_DEPENDS: ensures CMake re-evaluates the glob when source files
# are added/removed, so incremental builds always pick up changed sources.
# Without it, file(GLOB) is only evaluated at configure time and new/modified
# files are silently missed until a full cmake reconfigure.
file(GLOB SUB_FILES_SRC CONFIGURE_DEPENDS "${CURDIR}/*.cpp" "${CURDIR}/*.h")
# Add files to local lists
list(APPEND AM_SRC ${SUB_FILES_SRC})
# Pass updated lists to the global scope
Expand All @@ -35,6 +39,7 @@ add_source_group("codeAnalyzer")
add_library(${PROJECT_NAME} ${AM_SRC})

#Whole archive option is needed to ensure that all windows are linked in the final binary
# (debugger windows use static-initializer-based auto-registration via TS_BEGIN_REFLECT_CLASS)
if(MSVC)
target_link_options(${PROJECT_NAME} INTERFACE "/WHOLEARCHIVE:${PROJECT_NAME}")
endif()
Expand Down
10 changes: 10 additions & 0 deletions libs/amDebugger/src/amDebugger/debuggerOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ struct CopperToggleBreakpoint : public amD::operation::OperationArgs {
};


struct PauseEmulation : public amD::operation::OperationArgs {
DECLARE_OPERATION_1(amD::operation::PauseEmulation);
static void setup(qd::operation::OpDesc& d)
{
d.m_name = "Pause";
d.addShortcut(amD::shortcut::EId::PauseEmulation);
}
};


struct ToggleTurboEmulation : public amD::operation::OperationArgs {
DECLARE_OPERATION_1(amD::operation::ToggleTurboEmulation);
static void setup(qd::operation::OpDesc& d)
Expand Down
82 changes: 75 additions & 7 deletions libs/amDebugger/src/amDebugger/debuggerWndApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ namespace amD {
constexpr uint32_t g_nDebuggerWndSizeX = 1368;
constexpr uint32_t g_nDebuggerWndSizeY = 800;

static int resizeEventWatcher(void* data, SDL_Event* event)
{
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
DebuggerApp* app = static_cast<DebuggerApp*>(data);
if (app && SDL_GetWindowID(app->getWindow()) == event->window.windowID)
{
app->updateAppPart(0, 0);
app->renderAppPart();
}
}
return 0;
}


DebuggerApp::DebuggerApp()
{
Expand All @@ -45,13 +59,18 @@ void DebuggerApp::init()
createRenderWindow();
initImGui();

SDL_AddEventWatch(resizeEventWatcher, this);

m_pDebugger->setDbgServiceBridge(create_dummy_connection());

assert(m_pDebugger);
qd::UiNodeCreator mk;
m_pGui = mk.make_<amD::DebuggerDesktop>(this, m_pDebugger);
m_pOperationMgr = m_pGui->getOperationMgr();
assert(m_pOperationMgr);

loadLayoutSettings();
m_bFullyInitialized = true;
}


Expand All @@ -78,6 +97,11 @@ void DebuggerApp::createRenderWindow()
return;
}
m_pWindow = window;

// Clear framebuffer to gray immediately to avoid red/garbage flash
SDL_SetRenderDrawColor(m_pWndRenderer, 128, 128, 128, 255);
SDL_RenderClear(m_pWndRenderer);
SDL_RenderPresent(m_pWndRenderer);
}


Expand All @@ -88,15 +112,22 @@ void DebuggerApp::initImGui()

// Setup Dear ImGui context
ImGuiIO& io = m_pQimGuiCtx->getIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.IniFilename = "debugger_layout.ini";

// Setup Dear ImGui style
qd::imGuiApplyStyleDark();
}


void DebuggerApp::loadLayoutSettings()
{
m_pQimGuiCtx->useCurrent();
ImGui::LoadIniSettingsFromDisk(ImGui::GetIO().IniFilename);
}


DebuggerApp::~DebuggerApp()
{
assert(!m_init);
Expand All @@ -105,12 +136,22 @@ DebuggerApp::~DebuggerApp()

qd::EFlow DebuggerApp::applyOperationMsgProcImp(qd::operation::BaseOpArgs* p_msg)
{
return m_pDebugger->applyOperationMsgProcImp(p_msg);
qd::EFlow r = m_pDebugger->applyOperationMsgProcImp(p_msg);

// Also forward to the real emulator thread (if callback registered)
// so that emulator control operations (pause, continue, step, etc.)
// reach the actual running emulator, not just the dummy VM.
if (m_forwardOpToEmulatorCb)
m_forwardOpToEmulatorCb(p_msg);

return r;
}


void DebuggerApp::destroy()
{
SDL_DelEventWatch(resizeEventWatcher, this);

if (m_pOperationMgr)
m_pOperationMgr->destroy();
m_pOperationMgr = nullptr;
Expand All @@ -119,6 +160,12 @@ void DebuggerApp::destroy()
m_pGui->destroy();
m_pGui = nullptr;

// Save layout before destroying context
if (m_pQimGuiCtx)
{
m_pQimGuiCtx->useCurrent();
ImGui::SaveIniSettingsToDisk(ImGui::GetIO().IniFilename);
}
SAFE_DESTROY(m_pQimGuiCtx);
SDL_DestroyRenderer(m_pWndRenderer);
m_pWndRenderer = nullptr;
Expand All @@ -131,15 +178,27 @@ void DebuggerApp::destroy()

void DebuggerApp::updateAppPart(float /*dt*/, float /*time*/)
{
if (isWndVisible())
if (!isWndVisible())
{
m_pQimGuiCtx->skipFrame();
return;
}

uint64_t now = SDL_GetTicks64();
if (now - m_lastRenderTimeMs < kMinFrameIntervalMs)
{
m_pQimGuiCtx->skipFrame();
return;
}
m_lastRenderTimeMs = now;

m_pQimGuiCtx->newFrame();
if (m_bFullyInitialized && m_pGui && m_pDebugger)
{
m_pQimGuiCtx->newFrame();
getDbg()->fetchVmState();
m_pGui->drawImGuiMainFrame();
m_pQimGuiCtx->endFrame();
}
else
m_pQimGuiCtx->skipFrame();
m_pQimGuiCtx->endFrame();
}


Expand All @@ -163,11 +222,20 @@ void DebuggerApp::setWndVisible(bool v)
{
if (v)
{
// Clear to gray before showing to avoid uninitialized framebuffer flash
SDL_SetRenderDrawColor(m_pWndRenderer, 128, 128, 128, 255);
SDL_RenderClear(m_pWndRenderer);
SDL_RenderPresent(m_pWndRenderer);

SDL_ShowWindow(m_pWindow);
setPartRenderable(true);
}
else
{
// Save layout before hiding
m_pQimGuiCtx->useCurrent();
ImGui::SaveIniSettingsToDisk(ImGui::GetIO().IniFilename);

SDL_HideWindow(m_pWindow);
setPartRenderable(false);
}
Expand Down
20 changes: 19 additions & 1 deletion libs/amDebugger/src/amDebugger/debuggerWndApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "qd/base/base.h"
#include "qd/base/classIdCC.h"
#include "qd/qui/uiOperation.h"
#include <EASTL/fixed_set.h>
#include <functional>
#include "qd/stl/fixed_vector.h"
#include "qd/stl/string.h"

Expand Down Expand Up @@ -49,14 +49,18 @@ class DebuggerApp
qd::QImGuiContext* m_pQimGuiCtx = nullptr;
uint32_t m_nCurDbgClientIdx = 0;
int m_init = false;
uint64_t m_lastRenderTimeMs = 0;
static constexpr uint64_t kMinFrameIntervalMs = 66; // ~15 FPS max

public:
bool m_bFullyInitialized = false;
ref_ptr<amD::Debugger> m_pDebugger = nullptr; // current debugger client
ref_ptr<amD::DebuggerDesktop> m_pGui;
qd::OperationsRegistry* m_pOperationMgr = nullptr;

public:
DebuggerApp();
SDL_Window* getWindow() const { return m_pWindow; }
SDL_Renderer* getRenderer() const { return m_pWndRenderer; }
uint32_t getCurDbgClientIdx() const { return m_nCurDbgClientIdx; }

Expand All @@ -77,9 +81,23 @@ class DebuggerApp

qd::EFlow applyOperationMsgProcImp(qd::operation::BaseOpArgs* p_msg) override;

// Callback to forward operations to the real emulator.
// Set by the application to bridge debugger operations to the actual emulator thread.
using ForwardOpToEmulatorCb = std::function<void(qd::operation::BaseOpArgs*)>;
ForwardOpToEmulatorCb m_forwardOpToEmulatorCb;
void setForwardOpToEmulatorCb(ForwardOpToEmulatorCb cb) { m_forwardOpToEmulatorCb = std::move(cb); }

// Forward an operation to the real emulator via the registered callback.
// Called from DebuggerDesktop before dispatching to the dummy VM.
void forwardOpToEmulator(qd::operation::BaseOpArgs* args) {
if (m_forwardOpToEmulatorCb)
m_forwardOpToEmulatorCb(args);
}

private:
void createRenderWindow();
void initImGui();
void loadLayoutSettings();
virtual ~DebuggerApp() override;

}; // class DebuggerApp
Expand Down
1 change: 1 addition & 0 deletions libs/amDebugger/src/amDebugger/shortcutsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace shortcut {
SHORTCUT(CopperTraceStep, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F11).addKey(ImGuiMod_Shift).setRepeat(); }) \
SHORTCUT(ToggleTurboEmulation, [](qd::Shortcut& s) { s.addKey(ImGuiKey_NumLock); }) \
SHORTCUT(ResetAmigaEmu, [](qd::Shortcut&) {}) \
SHORTCUT(PauseEmulation, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F8).addKey(ImGuiMod_Ctrl); }) \
SHORTCUT(AlwaysOnTopEmu, [](qd::Shortcut& s) { s.addKey(ImGuiKey_T).addKey(ImGuiMod_Ctrl); }) \
SHORTCUT(ShowDebuggerWnd, [](qd::Shortcut& s) { s.addKey(ImGuiKey_F12).addKey(ImGuiMod_Shift); }) \
SHORTCUT(ShowUaeOptionsWnd, [](qd::Shortcut& s) { s.addKey(ImGuiKey_P).addKey(ImGuiMod_Ctrl); }) \
Expand Down
Loading