hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-22 06:45:33 +0200
committerhachem <im@hachem.wtf>2026-09-22 06:45:33 +0200
commit32ee3d9f57b37723ee97c3b798be6968f98df1b1 (patch)
tree10838a06f522a48f64645e2d31c3d72eb1a58643
parente321beb543311dcb591942ec42464e38cbeae0f0 (diff)
fix: windows support
-rw-r--r--src/core/application.cpp50
-rw-r--r--src/core/application.h3
-rw-r--r--src/core/log.h9
-rw-r--r--src/core/window.cpp8
-rw-r--r--src/core/window.h2
-rw-r--r--src/main.cpp19
-rw-r--r--src/platform/opengl/shader.cpp13
-rw-r--r--src/platform/opengl/texture.cpp13
-rw-r--r--src/ui/ui_layer.cpp13
-rw-r--r--src/ui/ui_layer.h3
10 files changed, 105 insertions, 28 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp
index e406511..ecaaa56 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>
#include <glm/glm.hpp>
#include <algorithm>
#include <thread>
@@ -27,6 +30,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;
@@ -38,11 +42,30 @@ namespace Donut
? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL);
}
+#ifdef DONUT_NO_VULKAN
+ 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();
+#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();
}
@@ -58,15 +81,31 @@ 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
+ 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;
return;
}
@@ -76,7 +115,8 @@ namespace Donut
m_ui = create_scope<UILayer>();
m_device->init_imgui();
- m_ui->init();
+ const float dpi = ImGui_ImplGlfw_GetContentScaleForWindow((GLFWwindow*)m_window->get_native_window());
+ m_ui->init(dpi);
g_prev_scroll = glfwSetScrollCallback((GLFWwindow*)m_window->get_native_window(), donut_scroll_callback);
@@ -93,8 +133,8 @@ namespace Donut
{
double frame_start = glfwGetTime();
glfwPollEvents();
- if (!m_minimized && m_device)
- render_frame();
+ if (m_minimized) { glfwWaitEventsTimeout(0.1); continue; }
+ if (m_device) render_frame();
if (!SettingsManager::get_v_sync_enabled())
{
@@ -165,7 +205,7 @@ namespace Donut
{
ExportConfig cfg;
cfg.width = 1280; cfg.height = 720;
- int written = export_frame(cfg);
+ [[maybe_unused]] const int written = export_frame(cfg);
DONUT_INFO("autoexport wrote {} file(s)", written);
close();
}
diff --git a/src/core/application.h b/src/core/application.h
index 5a4957b..1fc65b9 100644
--- a/src/core/application.h
+++ b/src/core/application.h
@@ -62,6 +62,9 @@ namespace Donut
bool m_running = true;
bool m_minimized = false;
+ 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..cc99aa1 100644
--- a/src/core/log.h
+++ b/src/core/log.h
@@ -144,13 +144,10 @@ namespace Donut
#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 d3c1ff5..090fc47 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);
+
if (!s_glfw_initialized)
{
int success = glfwInit();
@@ -43,19 +45,21 @@ namespace Donut
}
else
{
-#ifdef __APPLE__
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
}
+ 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;
}
@@ -65,7 +69,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);
@@ -82,6 +85,7 @@ namespace Donut
auto Window::shutdown() -> void
{
DONUT_INFO("Shutting down window: ", m_title);
+ if (!m_window) return;
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;
diff --git a/src/main.cpp b/src/main.cpp
index 323d141..3d5b685 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,7 +1,24 @@
#include "core/application.h"
-int main()
+#include <filesystem>
+
+static auto chdir_to_project_root(std::filesystem::path start) -> bool
+{
+ namespace fs = std::filesystem;
+ std::error_code ec;
+ for (fs::path p = start; !p.empty(); p = p.parent_path())
+ {
+ if (fs::exists(p / "assets" / "shaders", ec)) { fs::current_path(p, ec); return !ec; }
+ if (p == p.root_path()) break;
+ }
+ return false;
+}
+
+int main(int, char** argv)
{
+ std::error_code ec;
+ if (!chdir_to_project_root(std::filesystem::current_path(ec)) && argv && argv[0])
+ chdir_to_project_root(std::filesystem::absolute(argv[0], ec).parent_path());
Donut::Application* app = new Donut::Application("Donut Engine - Black Hole Simulation", 1280, 720);
app->run();
delete app;
diff --git a/src/platform/opengl/shader.cpp b/src/platform/opengl/shader.cpp
index b815021..21335dd 100644
--- a/src/platform/opengl/shader.cpp
+++ b/src/platform/opengl/shader.cpp
@@ -6,6 +6,8 @@
#include <fstream>
#include <iostream>
#include <vector>
+#include <filesystem>
+#include "core/log.h"
namespace Donut
{
@@ -89,6 +91,13 @@ namespace Donut
in.read(&result[0], size);
}
}
+ else
+ {
+ std::error_code ec;
+ const auto cwd = std::filesystem::current_path(ec).u8string();
+ DONUT_ERROR("shader file not found: {} (cwd {}; generated shaders live in assets/shaders/generated)",
+ filepath, std::string(cwd.begin(), cwd.end()));
+ }
return result;
}
@@ -136,7 +145,7 @@ namespace Donut
int max_length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
std::vector<char> info_log(max_length);
- glGetShaderInfoLog(shader, max_length, &max_length, &info_log[0]);
+ if (max_length > 0) glGetShaderInfoLog(shader, max_length, &max_length, info_log.data());
glDeleteShader(shader);
for (auto id : glShaderIDs)
glDeleteShader(id);
@@ -160,7 +169,7 @@ namespace Donut
int max_length = 0;
glGetProgramiv(m_renderer_id, GL_INFO_LOG_LENGTH, &max_length);
std::vector<char> info_log(max_length);
- glGetProgramInfoLog(m_renderer_id, max_length, &max_length, &info_log[0]);
+ if (max_length > 0) glGetProgramInfoLog(m_renderer_id, max_length, &max_length, info_log.data());
glDeleteProgram(m_renderer_id);
for (auto id : glShaderIDs)
glDeleteShader(id);
diff --git a/src/platform/opengl/texture.cpp b/src/platform/opengl/texture.cpp
index 11ae1e3..5cd7e74 100644
--- a/src/platform/opengl/texture.cpp
+++ b/src/platform/opengl/texture.cpp
@@ -180,15 +180,14 @@ namespace Donut
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- auto equirect_shader = Shader::create("assets/shaders/equirect_to_cubemap.glsl");
- if (!equirect_shader)
+ auto equirect_shader = Shader::create("assets/shaders/generated/equirect_to_cubemap.glsl");
+ uint32_t shader_program = equirect_shader ? equirect_shader->get_renderer_id() : 0;
+ if (!shader_program)
{
- DONUT_ERROR("Failed to create equirectangular to cubemap shader");
+ DONUT_ERROR("equirect_to_cubemap shader missing or failed to compile; the sky stays dark");
return;
}
- uint32_t shader_program = equirect_shader->get_renderer_id();
-
float vertices[] =
{
-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
@@ -221,7 +220,7 @@ namespace Donut
glUseProgram(shader_program);
glUniform1i(glGetUniformLocation(shader_program, "u_EquirectangularMap"), 0);
- glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_Projection"), 1, GL_TRUE, &capture_projection[0][0]);
+ glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_projection"), 1, GL_TRUE, &capture_projection[0][0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdr_texture);
@@ -229,7 +228,7 @@ namespace Donut
glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
for (unsigned int i = 0; i < 6; ++i)
{
- glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_View"), 1, GL_TRUE, &capture_views[i][0][0]);
+ glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_view"), 1, GL_TRUE, &capture_views[i][0][0]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_renderer_id, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(cube_vao);
diff --git a/src/ui/ui_layer.cpp b/src/ui/ui_layer.cpp
index 69e2e41..6cbdc9d 100644
--- a/src/ui/ui_layer.cpp
+++ b/src/ui/ui_layer.cpp
@@ -121,7 +121,7 @@ namespace Donut
out->append("\n");
}
- static auto apply_donut_style() -> void
+ static auto apply_donut_style(float dpi_scale) -> void
{
ImGuiStyle& s = ImGui::GetStyle();
s.WindowRounding = 8.0f; s.ChildRounding = 6.0f; s.FrameRounding = 6.0f;
@@ -197,6 +197,8 @@ namespace Donut
c[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.025f);
c[ImGuiCol_TextSelectedBg] = amber_dim;
c[ImGuiCol_DragDropTarget] = kAmberHi;
+
+ if (dpi_scale != 1.0f) { s.ScaleAllSizes(dpi_scale); s.FontScaleDpi = dpi_scale; }
}
static auto prop(const char* label, float column = 116.0f) -> void
@@ -238,8 +240,9 @@ namespace Donut
}
}
- auto UILayer::init() -> void
+ auto UILayer::init(float dpi_scale) -> void
{
+ m_dpi_scale = dpi_scale;
ImGuiIO& io = ImGui::GetIO();
m_font_body = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-Regular.ttf", kBodySize);
m_font_heading = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-SemiBold.ttf", kHeadSize);
@@ -302,7 +305,7 @@ namespace Donut
auto UILayer::draw(const UIContext& ctx) -> View
{
static bool styled = false;
- if (!styled) { apply_donut_style(); styled = true; }
+ if (!styled) { apply_donut_style(m_dpi_scale); styled = true; }
ImGuizmo::BeginFrame();
@@ -909,7 +912,11 @@ namespace Donut
};
char buf[64];
row("Device", ctx.device_name.empty() ? "GPU" : ctx.device_name.c_str());
+#ifdef __APPLE__
row("API", RendererAPI::get_api() == RendererAPI::API::Vulkan ? "Vulkan (MoltenVK)" : "OpenGL 4.1");
+#else
+ row("API", RendererAPI::get_api() == RendererAPI::API::Vulkan ? "Vulkan" : "OpenGL");
+#endif
row("Workspace", tab.label);
std::snprintf(buf, sizeof(buf), "%.0f x %.0f", io.DisplaySize.x * io.DisplayFramebufferScale.x,
io.DisplaySize.y * io.DisplayFramebufferScale.y);
diff --git a/src/ui/ui_layer.h b/src/ui/ui_layer.h
index eac5273..3108c21 100644
--- a/src/ui/ui_layer.h
+++ b/src/ui/ui_layer.h
@@ -19,7 +19,7 @@ namespace Donut
public:
UILayer();
- auto init() -> void;
+ auto init(float dpi_scale = 1.0f) -> void;
auto draw(const UIContext& ctx) -> View;
private:
@@ -62,6 +62,7 @@ namespace Donut
int m_active_tab = 0;
int m_tab_request = -1;
bool m_first_frame = true;
+ float m_dpi_scale = 1.0f;
int m_gizmo_op = 0;