From 90398614bcc9e1c1bde7a0f65a0db66173a1d8b4 Mon Sep 17 00:00:00 2001 From: hachem Date: Thu, 27 Aug 2026 10:50:02 +0200 Subject: add: window manager and opengl context --- src/core/application.cpp | 53 +++++++++++++++-- src/core/application.h | 9 +++ src/core/log.h | 35 +++++++---- src/core/window.cpp | 139 ++++++++++++++++++++++++++++++++++++++++++++ src/core/window.h | 61 +++++++++++++++++++ src/core/window_manager.cpp | 77 ++++++++++++++++++++++++ src/core/window_manager.h | 38 ++++++++++++ 7 files changed, 395 insertions(+), 17 deletions(-) create mode 100644 src/core/window.cpp create mode 100644 src/core/window.h create mode 100644 src/core/window_manager.cpp create mode 100644 src/core/window_manager.h (limited to 'src/core') diff --git a/src/core/application.cpp b/src/core/application.cpp index fd04e62..7c2d9a3 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2,6 +2,10 @@ #include "core/assert.h" #include "core/log.h" +#include "core/window.h" +#include "core/window_manager.h" + +#include #include @@ -13,9 +17,9 @@ namespace Hsdbg constexpr float MAX_DELTA_TIME = 0.25f; - // the run loop has nothing to block on until the window owns it, so it is - // time boxed for now to keep the app from spinning forever - constexpr float PLACEHOLDER_RUN_TIME = 1.0f; + // a minimized window has no framebuffer to draw into, so idle instead of + // burning a core on frames nobody can see + constexpr double MINIMIZED_WAIT_TIMEOUT = 0.1; } Application* Application::s_instance = nullptr; @@ -27,10 +31,23 @@ namespace Hsdbg s_instance = this; Log::info("{}: starting up", m_spec.name); + + m_window_manager = std::make_unique(); + + WindowSpec window_spec; + window_spec.title = m_spec.name; + window_spec.width = m_spec.width; + window_spec.height = m_spec.height; + window_spec.vsync = m_spec.vsync; + + m_window = &m_window_manager->create_window(window_spec); } Application::~Application() { + m_window = nullptr; + m_window_manager.reset(); + Log::info("{}: shutting down after {} frames", m_spec.name, m_frame_count); s_instance = nullptr; @@ -58,7 +75,24 @@ namespace Hsdbg m_time += m_delta_time; m_frame_count += 1; + m_window_manager->poll_events(); + + if (m_window->should_close()) + { + close(); + break; + } + + if (m_window->is_minimized()) + { + m_window_manager->wait_events(MINIMIZED_WAIT_TIMEOUT); + continue; + } + update(); + render(); + + m_window->swap_buffers(); } } @@ -69,7 +103,16 @@ namespace Hsdbg auto Application::update() -> void { - if (m_time >= PLACEHOLDER_RUN_TIME) - close(); + } + + auto Application::render() -> void + { + glViewport(0, + 0, + static_cast(m_window->framebuffer_width()), + static_cast(m_window->framebuffer_height())); + + glClearColor(0.09f, 0.09f, 0.11f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } } diff --git a/src/core/application.h b/src/core/application.h index 1e4a08d..8df5afb 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -1,15 +1,20 @@ #pragma once #include +#include #include namespace Hsdbg { + class Window; + class WindowManager; + struct ApplicationSpec { std::string name = "hsdbg"; uint32_t width = 1600; uint32_t height = 900; + bool vsync = true; }; class Application @@ -28,6 +33,7 @@ namespace Hsdbg auto run() -> void; auto close() -> void; + auto window() -> Window& { return *m_window; } auto spec() const -> const ApplicationSpec& { return m_spec; } auto is_running() const -> bool { return m_running; } auto delta_time() const -> float { return m_delta_time; } @@ -36,10 +42,13 @@ namespace Hsdbg private: auto update() -> void; + auto render() -> void; static Application* s_instance; ApplicationSpec m_spec; + std::unique_ptr m_window_manager; + Window* m_window = nullptr; bool m_running = false; float m_delta_time = 0.0f; float m_time = 0.0f; diff --git a/src/core/log.h b/src/core/log.h index db7440b..a66a61e 100644 --- a/src/core/log.h +++ b/src/core/log.h @@ -35,15 +35,18 @@ namespace Hsdbg::Log Level::Trace; #endif + constexpr std::string_view RESET = "\x1b[0m"; + constexpr std::string_view DIM = "\x1b[90m"; + inline auto label(Level level) -> std::string_view { switch (level) { - case Level::Trace: return "trace"; - case Level::Debug: return "debug"; - case Level::Info: return "info "; - case Level::Warn: return "warn "; - case Level::Error: return "error"; + case Level::Trace: return "TRACE"; + case Level::Debug: return "DEBUG"; + case Level::Info: return "INFO "; + case Level::Warn: return "WARN "; + case Level::Error: return "ERROR"; } return "?????"; @@ -53,11 +56,11 @@ namespace Hsdbg::Log { switch (level) { - case Level::Trace: return "\x1b[90m"; - case Level::Debug: return "\x1b[36m"; - case Level::Info: return "\x1b[32m"; - case Level::Warn: return "\x1b[33m"; - case Level::Error: return "\x1b[31m"; + case Level::Trace: return "\x1b[1;90m"; + case Level::Debug: return "\x1b[1;35m"; + case Level::Info: return "\x1b[1;36m"; + case Level::Warn: return "\x1b[1;33m"; + case Level::Error: return "\x1b[1;31m"; } return ""; @@ -86,9 +89,17 @@ namespace Hsdbg::Log std::ostream& stream = to_stderr ? std::cerr : std::cout; if (is_terminal(to_stderr)) - std::println(stream, "{}{}\x1b[0m {}", color(level), label(level), message); + { + std::println(stream, + "{}{}{} {}│{} {}", + color(level), label(level), RESET, + DIM, RESET, + message); + } else - std::println(stream, "{} {}", label(level), message); + { + std::println(stream, "{} | {}", label(level), message); + } // never let a crash swallow the last lines that explain it stream.flush(); diff --git a/src/core/window.cpp b/src/core/window.cpp new file mode 100644 index 0000000..66500db --- /dev/null +++ b/src/core/window.cpp @@ -0,0 +1,139 @@ +#include "core/window.h" + +#include "core/assert.h" +#include "core/log.h" + +#include + +#include + +namespace Hsdbg +{ + namespace + { + constexpr int GL_VERSION_MAJOR = 3; + constexpr int GL_VERSION_MINOR = 3; + + bool g_glad_loaded = false; + } + + Window::Window(const WindowSpec& spec) + : m_title(spec.title) + , m_width(spec.width) + , m_height(spec.height) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSION_MAJOR); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSION_MINOR); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); + + m_handle = glfwCreateWindow(static_cast(m_width), + static_cast(m_height), + m_title.c_str(), + nullptr, + nullptr); + + HSDBG_ASSERT(m_handle != nullptr, "failed to create the glfw window"); + + glfwSetWindowUserPointer(m_handle, this); + glfwSetWindowSizeCallback(m_handle, on_window_size); + glfwSetFramebufferSizeCallback(m_handle, on_framebuffer_size); + + make_context_current(); + set_vsync(spec.vsync); + + if (!g_glad_loaded) + { + const int version = gladLoadGL(glfwGetProcAddress); + HSDBG_ASSERT(version != 0, "failed to load the opengl function pointers"); + + g_glad_loaded = true; + + Log::info("opengl {}.{}", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version)); + Log::info(" vendor: {}", reinterpret_cast(glGetString(GL_VENDOR))); + Log::info(" renderer: {}", reinterpret_cast(glGetString(GL_RENDERER))); + Log::info(" version: {}", reinterpret_cast(glGetString(GL_VERSION))); + } + + int framebuffer_width = 0; + int framebuffer_height = 0; + glfwGetFramebufferSize(m_handle, &framebuffer_width, &framebuffer_height); + + m_framebuffer_width = static_cast(framebuffer_width); + m_framebuffer_height = static_cast(framebuffer_height); + + Log::info("window '{}' created at {}x{} ({}x{} framebuffer)", + m_title, + m_width, + m_height, + m_framebuffer_width, + m_framebuffer_height); + } + + Window::~Window() + { + if (m_handle == nullptr) + return; + + glfwDestroyWindow(m_handle); + m_handle = nullptr; + } + + auto Window::should_close() const -> bool + { + return glfwWindowShouldClose(m_handle) == GLFW_TRUE; + } + + auto Window::set_should_close(bool value) -> void + { + glfwSetWindowShouldClose(m_handle, value ? GLFW_TRUE : GLFW_FALSE); + } + + auto Window::make_context_current() -> void + { + glfwMakeContextCurrent(m_handle); + } + + auto Window::swap_buffers() -> void + { + glfwSwapBuffers(m_handle); + } + + auto Window::set_vsync(bool enabled) -> void + { + glfwSwapInterval(enabled ? 1 : 0); + m_vsync = enabled; + } + + auto Window::set_title(std::string_view title) -> void + { + m_title = title; + glfwSetWindowTitle(m_handle, m_title.c_str()); + } + + auto Window::is_minimized() const -> bool + { + return m_framebuffer_width == 0 || m_framebuffer_height == 0; + } + + auto Window::on_window_size(GLFWwindow* handle, int width, int height) -> void + { + auto* window = static_cast(glfwGetWindowUserPointer(handle)); + if (window == nullptr) + return; + + window->m_width = static_cast(width); + window->m_height = static_cast(height); + } + + auto Window::on_framebuffer_size(GLFWwindow* handle, int width, int height) -> void + { + auto* window = static_cast(glfwGetWindowUserPointer(handle)); + if (window == nullptr) + return; + + window->m_framebuffer_width = static_cast(width); + window->m_framebuffer_height = static_cast(height); + } +} diff --git a/src/core/window.h b/src/core/window.h new file mode 100644 index 0000000..6a4f747 --- /dev/null +++ b/src/core/window.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +struct GLFWwindow; + +namespace Hsdbg +{ + struct WindowSpec + { + std::string title = "hsdbg"; + uint32_t width = 1600; + uint32_t height = 900; + bool vsync = true; + }; + + class Window + { + public: + explicit Window(const WindowSpec& spec); + ~Window(); + + Window(const Window&) = delete; + Window(Window&&) = delete; + auto operator=(const Window&) -> Window& = delete; + auto operator=(Window&&) -> Window& = delete; + + auto should_close() const -> bool; + auto set_should_close(bool value) -> void; + + auto make_context_current() -> void; + auto swap_buffers() -> void; + + auto set_vsync(bool enabled) -> void; + auto set_title(std::string_view title) -> void; + + auto handle() const -> GLFWwindow* { return m_handle; } + auto title() const -> const std::string& { return m_title; } + auto is_vsync() const -> bool { return m_vsync; } + auto is_minimized() const -> bool; + + auto width() const -> uint32_t { return m_width; } + auto height() const -> uint32_t { return m_height; } + auto framebuffer_width() const -> uint32_t { return m_framebuffer_width; } + auto framebuffer_height() const -> uint32_t { return m_framebuffer_height; } + + private: + static auto on_window_size(GLFWwindow* handle, int width, int height) -> void; + static auto on_framebuffer_size(GLFWwindow* handle, int width, int height) -> void; + + GLFWwindow* m_handle = nullptr; + std::string m_title; + uint32_t m_width = 0; + uint32_t m_height = 0; + uint32_t m_framebuffer_width = 0; + uint32_t m_framebuffer_height = 0; + bool m_vsync = true; + }; +} diff --git a/src/core/window_manager.cpp b/src/core/window_manager.cpp new file mode 100644 index 0000000..f2e4af3 --- /dev/null +++ b/src/core/window_manager.cpp @@ -0,0 +1,77 @@ +#include "core/window_manager.h" + +#include "core/assert.h" +#include "core/log.h" + +#include + +#include + +namespace Hsdbg +{ + WindowManager* WindowManager::s_instance = nullptr; + + WindowManager::WindowManager() + { + HSDBG_ASSERT(s_instance == nullptr, "a WindowManager instance already exists"); + s_instance = this; + + glfwSetErrorCallback(on_error); + + const bool initialized = glfwInit() == GLFW_TRUE; + HSDBG_ASSERT(initialized, "failed to initialize glfw"); + + Log::info("glfw {}", glfwGetVersionString()); + } + + WindowManager::~WindowManager() + { + m_windows.clear(); + + glfwTerminate(); + + s_instance = nullptr; + } + + auto WindowManager::get() -> WindowManager& + { + HSDBG_ASSERT(s_instance != nullptr, "no WindowManager instance has been created"); + return *s_instance; + } + + auto WindowManager::create_window(const WindowSpec& spec) -> Window& + { + return *m_windows.emplace_back(std::make_unique(spec)); + } + + auto WindowManager::destroy_window(const Window& window) -> void + { + const auto entry = std::ranges::find_if(m_windows, [&window](const auto& candidate) + { + return candidate.get() == &window; + }); + + if (entry == m_windows.end()) + { + Log::warn("tried to destroy a window that this manager does not own"); + return; + } + + m_windows.erase(entry); + } + + auto WindowManager::poll_events() -> void + { + glfwPollEvents(); + } + + auto WindowManager::wait_events(double timeout_seconds) -> void + { + glfwWaitEventsTimeout(timeout_seconds); + } + + auto WindowManager::on_error(int code, const char* description) -> void + { + Log::error("glfw error {}: {}", code, description); + } +} diff --git a/src/core/window_manager.h b/src/core/window_manager.h new file mode 100644 index 0000000..d3a915c --- /dev/null +++ b/src/core/window_manager.h @@ -0,0 +1,38 @@ +#pragma once + +#include "core/window.h" + +#include +#include + +namespace Hsdbg +{ + class WindowManager + { + public: + WindowManager(); + ~WindowManager(); + + WindowManager(const WindowManager&) = delete; + WindowManager(WindowManager&&) = delete; + auto operator=(const WindowManager&) -> WindowManager& = delete; + auto operator=(WindowManager&&) -> WindowManager& = delete; + + static auto get() -> WindowManager&; + + auto create_window(const WindowSpec& spec) -> Window&; + auto destroy_window(const Window& window) -> void; + + auto poll_events() -> void; + auto wait_events(double timeout_seconds) -> void; + + auto window_count() const -> size_t { return m_windows.size(); } + + private: + static auto on_error(int code, const char* description) -> void; + + static WindowManager* s_instance; + + std::vector> m_windows; + }; +} -- cgit v1.3