fix(diagnostics): add render-phase crash markers and improve signal handling

Add signal-safe render-phase markers throughout GameScreen::render() and
Application::render() so the crash handler can report which render call was
active when a SIGSEGV occurs. The AMD RADV crash backtrace only shows 2
frames due to missing frame pointers, making it impossible to identify the
actual crash site.

Changes:
- Add volatile g_crashRenderPhase marker updated before each major render call
- Upgrade Linux signal handler to sigaction with SA_SIGINFO for faulting address
- Set ImGui CheckVkResultFn to log silent Vulkan errors in ImGui backend
- Enable -fno-omit-frame-pointer in all build configs (not just Debug/RelWithDebInfo)
This commit is contained in:
Kelsi 2026-04-03 20:19:33 -07:00
parent b092bc2e90
commit 82267320b0
6 changed files with 104 additions and 29 deletions

View file

@ -47,6 +47,11 @@
#include <cctype>
#include <chrono>
#include <ctime>
// Signal-safe render-phase marker — crash handler reads this to identify which
// render call was active when a SIGSEGV occurs (backtrace is unreliable with
// -fomit-frame-pointer).
volatile const char* g_crashRenderPhase = "idle";
#include <unordered_set>
namespace {
@ -298,7 +303,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Process targeting input before UI windows
processTargetInput(gameHandler);
// Player unit frame (top-left)
g_crashRenderPhase = "playerFrame";
renderPlayerFrame(gameHandler);
// Pet frame (below player frame, only when player has an active pet)
@ -353,6 +358,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
}
// ---- New UI elements ----
g_crashRenderPhase = "actionBar";
actionBarPanel_.renderActionBar(gameHandler, settingsPanel_, chatPanel_,
inventoryScreen, spellbookScreen, questLogScreen,
[this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); });
@ -362,31 +368,43 @@ void GameScreen::render(game::GameHandler& gameHandler) {
actionBarPanel_.renderXpBar(gameHandler, settingsPanel_);
actionBarPanel_.renderRepBar(gameHandler, settingsPanel_);
auto spellIconFn = [this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); };
g_crashRenderPhase = "castBar";
combatUI_.renderCastBar(gameHandler, spellIconFn);
renderMirrorTimers(gameHandler);
combatUI_.renderCooldownTracker(gameHandler, settingsPanel_, spellIconFn);
renderQuestObjectiveTracker(gameHandler);
g_crashRenderPhase = "nameplates";
renderNameplates(gameHandler); // player names always shown; NPC plates gated by showNameplates_
g_crashRenderPhase = "bgScore";
combatUI_.renderBattlegroundScore(gameHandler);
combatUI_.renderRaidWarningOverlay(gameHandler);
combatUI_.renderCombatText(gameHandler);
combatUI_.renderDPSMeter(gameHandler, settingsPanel_);
g_crashRenderPhase = "durability";
renderDurabilityWarning(gameHandler);
renderUIErrors(gameHandler, ImGui::GetIO().DeltaTime);
toastManager_.renderEarlyToasts(ImGui::GetIO().DeltaTime, gameHandler);
g_crashRenderPhase = "partyFrames";
if (socialPanel_.showRaidFrames_) {
socialPanel_.renderPartyFrames(gameHandler, chatPanel_, spellIconFn);
}
g_crashRenderPhase = "bossFrames";
socialPanel_.renderBossFrames(gameHandler, spellbookScreen, spellIconFn);
g_crashRenderPhase = "dialogs";
dialogManager_.renderDialogs(gameHandler, inventoryScreen, chatPanel_);
g_crashRenderPhase = "guildRoster";
socialPanel_.renderGuildRoster(gameHandler, chatPanel_);
socialPanel_.renderSocialFrame(gameHandler, chatPanel_);
g_crashRenderPhase = "buffBar";
combatUI_.renderBuffBar(gameHandler, spellbookScreen, spellIconFn);
g_crashRenderPhase = "lootWindow";
windowManager_.renderLootWindow(gameHandler, inventoryScreen, chatPanel_);
windowManager_.renderGossipWindow(gameHandler, chatPanel_);
g_crashRenderPhase = "questWindows";
windowManager_.renderQuestDetailsWindow(gameHandler, chatPanel_, inventoryScreen);
windowManager_.renderQuestRequestItemsWindow(gameHandler, chatPanel_, inventoryScreen);
windowManager_.renderQuestOfferRewardWindow(gameHandler, chatPanel_, inventoryScreen);
g_crashRenderPhase = "vendorTrainer";
windowManager_.renderVendorWindow(gameHandler, inventoryScreen, chatPanel_);
windowManager_.renderTrainerWindow(gameHandler,
[this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); });
@ -398,39 +416,48 @@ void GameScreen::render(game::GameHandler& gameHandler) {
windowManager_.renderBankWindow(gameHandler, inventoryScreen, chatPanel_);
windowManager_.renderGuildBankWindow(gameHandler, inventoryScreen, chatPanel_);
windowManager_.renderAuctionHouseWindow(gameHandler, inventoryScreen, chatPanel_);
g_crashRenderPhase = "dungeonFinder";
socialPanel_.renderDungeonFinderWindow(gameHandler, chatPanel_);
windowManager_.renderInstanceLockouts(gameHandler);
socialPanel_.renderWhoWindow(gameHandler, chatPanel_);
g_crashRenderPhase = "combatLog";
combatUI_.renderCombatLog(gameHandler, spellbookScreen);
g_crashRenderPhase = "achievementSkills";
windowManager_.renderAchievementWindow(gameHandler);
windowManager_.renderSkillsWindow(gameHandler);
windowManager_.renderTitlesWindow(gameHandler);
windowManager_.renderEquipSetWindow(gameHandler);
windowManager_.renderGmTicketWindow(gameHandler);
g_crashRenderPhase = "inspectBook";
socialPanel_.renderInspectWindow(gameHandler, inventoryScreen);
windowManager_.renderBookWindow(gameHandler);
g_crashRenderPhase = "threatBg";
combatUI_.renderThreatWindow(gameHandler);
combatUI_.renderBgScoreboard(gameHandler);
g_crashRenderPhase = "minimap";
if (showMinimap_) {
renderMinimapMarkers(gameHandler);
}
g_crashRenderPhase = "deathLogout";
windowManager_.renderLogoutCountdown(gameHandler);
windowManager_.renderDeathScreen(gameHandler);
windowManager_.renderReclaimCorpseButton(gameHandler);
dialogManager_.renderLateDialogs(gameHandler);
chatPanel_.renderBubbles(gameHandler);
windowManager_.renderEscapeMenu(settingsPanel_);
g_crashRenderPhase = "settings";
settingsPanel_.renderSettingsWindow(inventoryScreen, chatPanel_, [this]() { saveSettings(); });
toastManager_.renderLateToasts(gameHandler);
g_crashRenderPhase = "weather";
renderWeatherOverlay(gameHandler);
// World map (M key toggle handled inside)
g_crashRenderPhase = "worldMap";
renderWorldMap(gameHandler);
// Quest Log (L key toggle handled inside)
g_crashRenderPhase = "questLog";
questLogScreen.render(gameHandler, inventoryScreen);
// Spellbook (P key toggle handled inside)
g_crashRenderPhase = "spellbook";
spellbookScreen.render(gameHandler, services_.assetManager);
// Insert spell link into chat if player shift-clicked a spellbook entry
@ -483,7 +510,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
windowManager_.vendorBagsOpened_ = false;
}
// Bags (B key toggle handled inside)
g_crashRenderPhase = "inventory";
inventoryScreen.setGameHandler(&gameHandler);
inventoryScreen.render(gameHandler.getInventory(), gameHandler.getMoneyCopper());