diff options
| author | hachem <im@hachem.wtf> | 2026-09-22 06:57:16 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-22 06:57:16 +0200 |
| commit | d7c1ced08d5d78fcb239550404db4e67d4357a19 (patch) | |
| tree | 9c0e218b096d138075cbf6a48e2562d33a8f94ed /src/core | |
| parent | 270eda6f558c972ec86ad0580f1d3d18afc83a94 (diff) | |
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/application.cpp | 56 | ||||
| -rw-r--r-- | src/core/application.h | 4 | ||||
| -rw-r--r-- | src/core/log.h | 11 | ||||
| -rw-r--r-- | src/core/window.cpp | 17 | ||||
| -rw-r--r-- | src/core/window.h | 2 |
5 files changed, 72 insertions, 18 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp index e53e420..de318fb 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -4,10 +4,13 @@ #include "settings_manager.h" #include "platform/opengl/opengl_device.h" -#include "platform/vulkan/vulkan_device.h" +#ifndef DONUT_NO_VULKAN + #include "platform/vulkan/vulkan_device.h" +#endif #include <GLFW/glfw3.h> #include <imgui.h> +#include <imgui_impl_glfw.h> // ImGui_ImplGlfw_GetContentScaleForWindow #include <glm/glm.hpp> #include <algorithm> #include <thread> @@ -29,6 +32,7 @@ namespace Donut } Application::Application(const std::string& name, int width, int height) + : m_title(name), m_init_w(width), m_init_h(height) { s_instance = this; @@ -42,11 +46,31 @@ namespace Donut ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL); } +#ifdef DONUT_NO_VULKAN + // generated without a Vulkan SDK, so the backend isn't in this binary at all + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + DONUT_WARN("built without the Vulkan backend, falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + } +#else + if (RendererAPI::get_api() == RendererAPI::API::Vulkan && !RHI::vulkan_runtime_available()) + { + DONUT_WARN("no Vulkan runtime on this machine (vulkan-1.dll), falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + } if (RendererAPI::get_api() == RendererAPI::API::Vulkan) RHI::vulkan_prepare_glfw(); // must precede glfwInit() inside the Window ctor +#endif m_window = create_scope<Window>(name, width, height); m_window->set_event_callback([this](Event& event) { on_event(event); }); + if (!m_window->get_native_window()) + { + DONUT_ERROR("no window, no app (GLFW said why above)"); + m_running = false; + return; + } on_init(); } @@ -62,15 +86,33 @@ namespace Donut int w = 0, h = 0; glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h); +#ifndef DONUT_NO_VULKAN if (RendererAPI::get_api() == RendererAPI::API::Vulkan) m_device = RHI::create_vulkan_device(); else +#endif m_device = RHI::create_opengl_device(); RHI::NativeWindow native{ m_window->get_native_window(), w, h }; if (!m_device->init(native)) { + m_device->shutdown(); + m_device.reset(); +#ifndef DONUT_NO_VULKAN + // a Vulkan window can't host a GL context (GLFW_NO_API), so rebuild it + // and try once more on OpenGL. the saved setting stays as the user left it. + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + DONUT_WARN("Vulkan device init failed (see above), falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + m_window.reset(); + m_window = create_scope<Window>(m_title, m_init_w, m_init_h); + m_window->set_event_callback([this](Event& event) { on_event(event); }); + if (m_window->get_native_window()) { on_init(); return; } + } +#endif DONUT_ERROR("RHI device initialization failed"); + m_running = false; // nothing to draw with; run() just returns return; } @@ -80,9 +122,13 @@ namespace Donut m_ui = create_scope<UILayer>(); m_device->init_imgui(); + // windows hands out physical pixels with no scaling, so a 4k desktop at 200% + // would get a half-size ui. the glfw backend reports 1.0 on macos (retina + // scaling lives in the framebuffer there), so the mac is untouched. + const float dpi = ImGui_ImplGlfw_GetContentScaleForWindow((GLFWwindow*)m_window->get_native_window()); // the UI layer owns its typography and its imgui.ini section; both go into // the device-created ImGui context before its first NewFrame reads the ini. - m_ui->init(); + m_ui->init(dpi); g_prev_scroll = glfwSetScrollCallback((GLFWwindow*)m_window->get_native_window(), donut_scroll_callback); @@ -99,8 +145,8 @@ namespace Donut { double frame_start = glfwGetTime(); glfwPollEvents(); - if (!m_minimized && m_device) - render_frame(); + if (m_minimized) { glfwWaitEventsTimeout(0.1); continue; } // nothing to draw, so don't spin a core + if (m_device) render_frame(); // with vsync off, cap to the target FPS (vsync itself paces otherwise). if (!SettingsManager::get_v_sync_enabled()) @@ -179,7 +225,7 @@ namespace Donut { ExportConfig cfg; cfg.width = 1280; cfg.height = 720; - int written = export_frame(cfg); + [[maybe_unused]] const int written = export_frame(cfg); // only the debug log reads it DONUT_INFO("autoexport wrote {} file(s)", written); close(); } diff --git a/src/core/application.h b/src/core/application.h index bb88919..3c24ee8 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -69,6 +69,10 @@ namespace Donut bool m_running = true; bool m_minimized = false; + // kept for rebuilding the window when Vulkan fails and we retry on OpenGL + std::string m_title; + int m_init_w = 1280, m_init_h = 720; + static Application* s_instance; }; } diff --git a/src/core/log.h b/src/core/log.h index 4237d1c..ca96b26 100644 --- a/src/core/log.h +++ b/src/core/log.h @@ -141,16 +141,15 @@ namespace Donut }; } +// trace/info are debug-only chatter. warnings and errors stay in every build: +// a release build that fails on someone else's machine should still say why. #if defined(DONUT_DEBUG) #define DONUT_TRACE(format, ...) ::Donut::Logger::trace(format __VA_OPT__(,) __VA_ARGS__) #define DONUT_INFO(format, ...) ::Donut::Logger::info(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_WARN(format, ...) ::Donut::Logger::warn(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_ERROR(format, ...) ::Donut::Logger::error(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format __VA_OPT__(,) __VA_ARGS__) #else #define DONUT_TRACE(format, ...) {} #define DONUT_INFO(format, ...) {} - #define DONUT_WARN(format, ...) {} - #define DONUT_ERROR(format, ...) {} - #define DONUT_FATAL(format, ...) {} #endif +#define DONUT_WARN(format, ...) ::Donut::Logger::warn(format __VA_OPT__(,) __VA_ARGS__) +#define DONUT_ERROR(format, ...) ::Donut::Logger::error(format __VA_OPT__(,) __VA_ARGS__) +#define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format __VA_OPT__(,) __VA_ARGS__) diff --git a/src/core/window.cpp b/src/core/window.cpp index 9a8d1ec..8bb4d7e 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -24,6 +24,8 @@ namespace Donut { DONUT_INFO("Initializing window: ", m_title, " (", m_width, "x", m_height, ")"); + glfwSetErrorCallback(glfw_error_callback); // before init, so a failing init says why + if (!s_glfw_initialized) { int success = glfwInit(); @@ -44,23 +46,26 @@ namespace Donut } else { -#ifdef __APPLE__ - // macOS caps OpenGL at 4.1 Core Profile and demands a forward-compat - // core-profile context for anything modern (>= 3.3) to even compile. - // skip these hints and GLFW hands you a dusty 2.1 context and every - // single shader in the project refuses to build. so, we ask nicely: + // the shaders are GLSL 4.10 core, so ask for exactly that everywhere. + // macOS caps OpenGL at 4.1 and also wants forward-compat for anything + // past 2.1, or every shader in the project refuses to build. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif } + // size the window in physical pixels for the monitor's scale (windows/x11; + // a no-op on macos, where the framebuffer scales instead) + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr); if (!m_window) { DONUT_ERROR("Could not create GLFW window!"); glfwTerminate(); + s_glfw_initialized = false; return; } @@ -70,7 +75,6 @@ namespace Donut glfwMakeContextCurrent(m_window); glfwSetWindowUserPointer(m_window, this); - glfwSetErrorCallback(glfw_error_callback); glfwSetWindowCloseCallback(m_window, glfw_window_close_callback); glfwSetWindowSizeCallback(m_window, glfw_window_size_callback); glfwSetWindowFocusCallback(m_window, glfw_window_focus_callback); @@ -87,6 +91,7 @@ namespace Donut auto Window::shutdown() -> void { DONUT_INFO("Shutting down window: ", m_title); + if (!m_window) return; // creation failed, and init() already cleaned up GLFW glfwDestroyWindow(m_window); s_glfw_window_count--; diff --git a/src/core/window.h b/src/core/window.h index b8b59c5..b97874d 100644 --- a/src/core/window.h +++ b/src/core/window.h @@ -53,7 +53,7 @@ namespace Donut static auto glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos) -> void; private: - GLFWwindow* m_window; + GLFWwindow* m_window = nullptr; EventHandler m_event_handler; std::string m_title; |
