diff --git a/include/rendering/vk_context.hpp b/include/rendering/vk_context.hpp index 105f3c68..3f487d39 100644 --- a/include/rendering/vk_context.hpp +++ b/include/rendering/vk_context.hpp @@ -246,6 +246,9 @@ private: bool createDepthResolveImage(); void destroyDepthResolveImage(); + // Actual Vulkan API version the instance was created with (gates core 1.2 calls) + uint32_t instanceApiVersion_ = VK_API_VERSION_1_1; + // MSAA depth resolve support (for sampling/copying resolved depth) bool depthResolveSupported_ = false; VkResolveModeFlagBits depthResolveMode_ = VK_RESOLVE_MODE_NONE; diff --git a/src/core/application.cpp b/src/core/application.cpp index f0fea809..1a16e5ce 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2133,22 +2133,15 @@ void Application::update(float deltaTime) { } } -// Render-phase marker from game_screen.cpp — updated here for 3D/submit phases -} } // close wowee::core temporarily -extern volatile const char* g_crashRenderPhase; -namespace wowee { namespace core { - void Application::render() { if (!renderer) { return; } - g_crashRenderPhase = "beginFrame"; renderer->beginFrame(); // Only render 3D world when in-game if (state == AppState::IN_GAME) { - g_crashRenderPhase = "renderWorld"; if (world) { renderer->renderWorld(world.get(), gameHandler.get()); } else { @@ -2158,19 +2151,15 @@ void Application::render() { // Render performance HUD (within ImGui frame, before UI ends the frame) if (renderer) { - g_crashRenderPhase = "renderHUD"; renderer->renderHUD(); } // Render UI on top (ends ImGui frame with ImGui::Render()) if (uiManager) { - g_crashRenderPhase = "uiRender"; uiManager->render(state, authHandler.get(), gameHandler.get()); } - g_crashRenderPhase = "endFrame"; renderer->endFrame(); - g_crashRenderPhase = "idle"; } void Application::setupUICallbacks() { diff --git a/src/main.cpp b/src/main.cpp index 8d7f92b2..2bb2f75e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,10 +27,6 @@ static void releaseMouseGrab() { static void releaseMouseGrab() {} #endif -// Render-phase marker set by GameScreen::render() — lets crash handler -// identify which render call was active when backtrace is incomplete. -extern volatile const char* g_crashRenderPhase; - #ifdef __linux__ static void crashHandlerSigaction(int sig, siginfo_t* info, void* /*ucontext*/) { releaseMouseGrab(); @@ -39,15 +35,14 @@ static void crashHandlerSigaction(int sig, siginfo_t* info, void* /*ucontext*/) const char* sigName = (sig == SIGSEGV) ? "SIGSEGV" : (sig == SIGABRT) ? "SIGABRT" : (sig == SIGFPE) ? "SIGFPE" : "UNKNOWN"; - const char* phase = (const char*)g_crashRenderPhase; void* faultAddr = info ? info->si_addr : nullptr; - fprintf(stderr, "\n=== CRASH: signal %s (%d) renderPhase=%s faultAddr=%p ===\n", - sigName, sig, phase ? phase : "?", faultAddr); + fprintf(stderr, "\n=== CRASH: signal %s (%d) faultAddr=%p ===\n", + sigName, sig, faultAddr); backtrace_symbols_fd(frames, n, STDERR_FILENO); FILE* f = fopen("/tmp/wowee_debug.log", "a"); if (f) { - fprintf(f, "\n=== CRASH: signal %s (%d) renderPhase=%s faultAddr=%p ===\n", - sigName, sig, phase ? phase : "?", faultAddr); + fprintf(f, "\n=== CRASH: signal %s (%d) faultAddr=%p ===\n", + sigName, sig, faultAddr); fflush(f); backtrace_symbols_fd(frames, n, fileno(f)); fclose(f); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 280a897f..3f67ca42 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1,5 +1,3 @@ -extern volatile const char* g_crashRenderPhase; - #include "rendering/renderer.hpp" #include "rendering/camera.hpp" #include "rendering/camera_controller.hpp" @@ -734,13 +732,10 @@ void Renderer::applyMsaaChange() { VkSampleCountFlagBits current = vkCtx->getMsaaSamples(); if (samples == current) return; - LOG_WARNING("MSAA change: ", static_cast(current), "x → ", static_cast(samples), "x"); - // Single GPU wait — all subsequent operations are CPU-side object creation vkDeviceWaitIdle(vkCtx->getDevice()); // Set new MSAA and recreate swapchain (render pass, depth, MSAA image, framebuffers) - LOG_WARNING("MSAA: recreating swapchain"); vkCtx->setMsaaSamples(samples); if (!vkCtx->recreateSwapchain(window->getWidth(), window->getHeight())) { LOG_ERROR("MSAA change failed — reverting to 1x"); @@ -749,7 +744,6 @@ void Renderer::applyMsaaChange() { } // Recreate all sub-renderer pipelines (they embed sample count from render pass) - LOG_WARNING("MSAA: recreating terrain/water/wmo/m2/char pipelines"); if (terrainRenderer) terrainRenderer->recreatePipelines(); if (waterRenderer) { waterRenderer->recreatePipelines(); @@ -758,7 +752,6 @@ void Renderer::applyMsaaChange() { if (wmoRenderer) wmoRenderer->recreatePipelines(); if (m2Renderer) m2Renderer->recreatePipelines(); if (characterRenderer) characterRenderer->recreatePipelines(); - LOG_WARNING("MSAA: recreating quest/weather/sky pipelines"); if (questMarkerRenderer) questMarkerRenderer->recreatePipelines(); if (weather) weather->recreatePipelines(); if (lightning) lightning->recreatePipelines(); @@ -775,7 +768,6 @@ void Renderer::applyMsaaChange() { if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines(); } - LOG_WARNING("MSAA: recreating minimap/postprocess/imgui"); if (minimap) minimap->recreatePipelines(); // Selection circle + overlay + FSR use lazy init, just destroy them @@ -804,7 +796,6 @@ void Renderer::applyMsaaChange() { }; ImGui_ImplVulkan_Init(&initInfo); - LOG_WARNING("MSAA change complete"); } void Renderer::beginFrame() { @@ -812,26 +803,15 @@ void Renderer::beginFrame() { if (!vkCtx) return; if (vkCtx->isDeviceLost()) return; - // Diagnostic: log pointer state to detect corrupt this/members - LOG_WARNING("beginFrame: this=", (void*)this, - " vkCtx=", (void*)vkCtx, - " msaaP=", msaaChangePending_, - " ppPipe=", (void*)postProcessPipeline_.get(), - " cam=", (void*)camera.get(), - " swDirty=", vkCtx->isSwapchainDirty()); - // Apply deferred MSAA change between frames (before any rendering state is used) - g_crashRenderPhase = "bf:msaa"; if (msaaChangePending_) { applyMsaaChange(); } // Post-process resource management (§4.3 — delegates to PostProcessPipeline) - g_crashRenderPhase = "bf:pp"; if (postProcessPipeline_) postProcessPipeline_->manageResources(); // Handle swapchain recreation if needed - g_crashRenderPhase = "bf:swap"; if (vkCtx->isSwapchainDirty()) { (void)vkCtx->recreateSwapchain(window->getWidth(), window->getHeight()); // Rebuild water resources that reference swapchain extent/views @@ -843,7 +823,6 @@ void Renderer::beginFrame() { } // Acquire swapchain image and begin command buffer - g_crashRenderPhase = "bf:acquire"; currentCmd = vkCtx->beginFrame(currentImageIndex); if (currentCmd == VK_NULL_HANDLE) { // Swapchain out of date, will retry next frame @@ -851,20 +830,13 @@ void Renderer::beginFrame() { } // FSR2 jitter pattern (§4.3 — delegates to PostProcessPipeline) - g_crashRenderPhase = "bf:jitter"; if (postProcessPipeline_ && camera) postProcessPipeline_->applyJitter(camera.get()); // Update per-frame UBO with current camera/lighting state - g_crashRenderPhase = "bf:ubo"; updatePerFrameUBO(); - // GPU crash diagnostic: skip all pre-passes to isolate crash source - static const bool skipPrePasses = (std::getenv("WOWEE_SKIP_PREPASSES") != nullptr); - - if (!skipPrePasses) { // --- Off-screen pre-passes (before main render pass) --- // Minimap composite (renders 3x3 tile grid into 768x768 render target) - g_crashRenderPhase = "bf:minimap"; if (minimap && minimap->isEnabled() && camera) { glm::vec3 minimapCenter = camera->getPosition(); if (cameraController && cameraController->isThirdPerson()) @@ -872,13 +844,11 @@ void Renderer::beginFrame() { minimap->compositePass(currentCmd, minimapCenter); } // World map composite (renders zone tiles into 1024x768 render target) - g_crashRenderPhase = "bf:worldmap"; if (worldMap) { worldMap->compositePass(currentCmd); } // Character preview composite passes - g_crashRenderPhase = "bf:preview"; for (auto* preview : activePreviews_) { if (preview && preview->isModelLoaded()) { preview->compositePass(currentCmd, vkCtx->getCurrentFrame()); @@ -886,18 +856,14 @@ void Renderer::beginFrame() { } // Shadow pre-pass (before main render pass) - g_crashRenderPhase = "bf:shadow"; if (shadowsEnabled && shadowDepthImage[0] != VK_NULL_HANDLE) { renderShadowPass(); } // Water reflection pre-pass (renders scene from mirrored camera into 512x512 texture) - g_crashRenderPhase = "bf:reflection"; renderReflectionPass(); - } // !skipPrePasses // --- Begin render pass --- - g_crashRenderPhase = "bf:renderpass"; // Select framebuffer: PP off-screen target or swapchain (§4.3 — PostProcessPipeline) VkRenderPassBeginInfo rpInfo{}; rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; diff --git a/src/rendering/vk_context.cpp b/src/rendering/vk_context.cpp index 5b1fdc1c..4add9549 100644 --- a/src/rendering/vk_context.cpp +++ b/src/rendering/vk_context.cpp @@ -270,7 +270,8 @@ bool VkContext::createInstance(SDL_Window* window) { vkb::InstanceBuilder builder; builder.set_app_name("Wowee") .set_app_version(VK_MAKE_VERSION(1, 0, 0)) - .require_api_version(1, 1, 0); + .require_api_version(1, 2, 0) + .set_minimum_instance_version(1, 1, 0); for (auto ext : sdlExts) { builder.enable_extension(ext); @@ -291,7 +292,14 @@ bool VkContext::createInstance(SDL_Window* window) { instance = vkbInstance_.instance; debugMessenger = vkbInstance_.debug_messenger; - LOG_INFO("Vulkan instance created"); + // Query the actual instance API version for gating core 1.2+ calls + uint32_t instVer = VK_API_VERSION_1_1; + if (vkEnumerateInstanceVersion(&instVer) != VK_SUCCESS) + instVer = VK_API_VERSION_1_1; + instanceApiVersion_ = instVer; + LOG_INFO("Vulkan instance created (instance API version: ", + VK_VERSION_MAJOR(instVer), ".", VK_VERSION_MINOR(instVer), ".", + VK_VERSION_PATCH(instVer), ")"); return true; } @@ -337,7 +345,10 @@ bool VkContext::selectPhysicalDevice() { props2.pNext = &dsResolveProps; vkGetPhysicalDeviceProperties2(physicalDevice, &props2); - if (apiVersion >= VK_API_VERSION_1_2) { + // Gate on instance API version — vkCreateRenderPass2 is core 1.2 and only + // available when the instance was created with apiVersion >= 1.2. + // The device may report 1.2+ but a 1.1 instance won't have the function pointer. + if (instanceApiVersion_ >= VK_API_VERSION_1_2) { VkResolveModeFlags modes = dsResolveProps.supportedDepthResolveModes; if (modes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) { depthResolveMode_ = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; @@ -467,7 +478,7 @@ bool VkContext::createAllocator() { allocInfo.instance = instance; allocInfo.physicalDevice = physicalDevice; allocInfo.device = device; - allocInfo.vulkanApiVersion = VK_API_VERSION_1_1; + allocInfo.vulkanApiVersion = instanceApiVersion_; if (vmaCreateAllocator(&allocInfo, &allocator) != VK_SUCCESS) { LOG_ERROR("Failed to create VMA allocator"); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 6646e0fb..bfd572b5 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -48,10 +48,6 @@ #include #include -// 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 namespace { @@ -303,7 +299,6 @@ void GameScreen::render(game::GameHandler& gameHandler) { // Process targeting input before UI windows processTargetInput(gameHandler); - g_crashRenderPhase = "playerFrame"; renderPlayerFrame(gameHandler); // Pet frame (below player frame, only when player has an active pet) @@ -358,7 +353,6 @@ 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); }); @@ -368,43 +362,31 @@ 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); }); @@ -416,48 +398,36 @@ 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); - g_crashRenderPhase = "worldMap"; renderWorldMap(gameHandler); - g_crashRenderPhase = "questLog"; questLogScreen.render(gameHandler, inventoryScreen); - g_crashRenderPhase = "spellbook"; spellbookScreen.render(gameHandler, services_.assetManager); // Insert spell link into chat if player shift-clicked a spellbook entry @@ -510,7 +480,6 @@ void GameScreen::render(game::GameHandler& gameHandler) { windowManager_.vendorBagsOpened_ = false; } - g_crashRenderPhase = "inventory"; inventoryScreen.setGameHandler(&gameHandler); inventoryScreen.render(gameHandler.getInventory(), gameHandler.getMoneyCopper());