aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-27 10:50:02 +0200
committerhachem <im@hachem.wtf>2026-08-27 10:50:02 +0200
commit90398614bcc9e1c1bde7a0f65a0db66173a1d8b4 (patch)
tree1cd0e18d46e6eb16f33afcb9561aad4d4dfb3ba3 /src
parent4b64b2bd17433f427197af3679c9c8f5ad5ad1a4 (diff)
add: window manager and opengl context
Diffstat (limited to 'src')
-rw-r--r--src/core/application.cpp53
-rw-r--r--src/core/application.h9
-rw-r--r--src/core/log.h35
-rw-r--r--src/core/window.cpp139
-rw-r--r--src/core/window.h61
-rw-r--r--src/core/window_manager.cpp77
-rw-r--r--src/core/window_manager.h38
7 files changed, 395 insertions, 17 deletions
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 <glad/gl.h>
#include <chrono>
@@ -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<WindowManager>();
+
+ 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<GLsizei>(m_window->framebuffer_width()),
+ static_cast<GLsizei>(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 <cstdint>
+#include <memory>
#include <string>
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<WindowManager> 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 <glad/gl.h>
+
+#include <GLFW/glfw3.h>
+
+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<int>(m_width),
+ static_cast<int>(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<const char*>(glGetString(GL_VENDOR)));
+ Log::info(" renderer: {}", reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
+ Log::info(" version: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
+ }
+
+ int framebuffer_width = 0;
+ int framebuffer_height = 0;
+ glfwGetFramebufferSize(m_handle, &framebuffer_width, &framebuffer_height);
+
+ m_framebuffer_width = static_cast<uint32_t>(framebuffer_width);
+ m_framebuffer_height = static_cast<uint32_t>(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<Window*>(glfwGetWindowUserPointer(handle));
+ if (window == nullptr)
+ return;
+
+ window->m_width = static_cast<uint32_t>(width);
+ window->m_height = static_cast<uint32_t>(height);
+ }
+
+ auto Window::on_framebuffer_size(GLFWwindow* handle, int width, int height) -> void
+ {
+ auto* window = static_cast<Window*>(glfwGetWindowUserPointer(handle));
+ if (window == nullptr)
+ return;
+
+ window->m_framebuffer_width = static_cast<uint32_t>(width);
+ window->m_framebuffer_height = static_cast<uint32_t>(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 <cstdint>
+#include <string>
+#include <string_view>
+
+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 <GLFW/glfw3.h>
+
+#include <algorithm>
+
+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<Window>(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 <memory>
+#include <vector>
+
+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<std::unique_ptr<Window>> m_windows;
+ };
+}