MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
WindowManager.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "WindowManager.h"
20
21#include "Logger.h"
22
23#include <GLFW/glfw3.h>
24
25// GL_CONTEXT_LOST is not available on all platforms (especially older OpenGL versions)
26// Define it if not available - we'll check for it but it won't be triggered on those platforms
27#ifndef GL_CONTEXT_LOST
28#define GL_CONTEXT_LOST 0x0507
29#endif
30
31namespace MetaImGUI {
32
33WindowManager::WindowManager(std::string title, int width, int height)
34 : m_title(std::move(title)), m_width(width), m_height(height) {}
35
39
41 if (m_initialized) {
42 return true;
43 }
44
45 // Initialize GLFW
46 glfwSetErrorCallback(ErrorCallback);
47 if (glfwInit() == GLFW_FALSE) {
48 LOG_ERROR("Failed to initialize GLFW");
49 return false;
50 }
51
52 // Set OpenGL version based on platform capabilities
53#ifdef __APPLE__
54 // macOS maximum is OpenGL 4.1 with Core Profile
55 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
56 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
57 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
58 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
59#else
60 // Linux/Windows: Request OpenGL 4.6 with Compatibility Profile
61 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
62 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
63 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
64#endif
65
66 // Create window
67 m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr);
68 if (m_window == nullptr) {
69 LOG_ERROR("Failed to create GLFW window");
70 glfwTerminate();
71 return false;
72 }
73
74 // Store WindowManager pointer in GLFW window for callbacks
75 glfwSetWindowUserPointer(m_window, this);
76
77 glfwMakeContextCurrent(m_window);
78 glfwSwapInterval(1); // Enable vsync
79
80 // Print OpenGL information
81 const GLubyte* version = glGetString(GL_VERSION);
82 const GLubyte* vendor = glGetString(GL_VENDOR);
83 const GLubyte* renderer = glGetString(GL_RENDERER);
84
85 if (version != nullptr) {
86 LOG_INFO("OpenGL version: {}", reinterpret_cast<const char*>(version));
87 } else {
88 LOG_ERROR("Failed to get OpenGL version");
89 }
90
91 if (vendor != nullptr) {
92 LOG_INFO("OpenGL vendor: {}", reinterpret_cast<const char*>(vendor));
93 } else {
94 LOG_ERROR("Failed to get OpenGL vendor");
95 }
96
97 if (renderer != nullptr) {
98 LOG_INFO("OpenGL renderer: {}", reinterpret_cast<const char*>(renderer));
99 } else {
100 LOG_ERROR("Failed to get OpenGL renderer");
101 }
102
103 LOG_INFO("OpenGL context ready");
104
105 m_initialized = true;
106 return true;
107}
108
110 if (!m_initialized) {
111 return;
112 }
113
114 if (m_window != nullptr) {
115 glfwDestroyWindow(m_window);
116 m_window = nullptr;
117 }
118
119 glfwTerminate();
120 m_initialized = false;
121}
122
124 return m_window == nullptr || (glfwWindowShouldClose(m_window) == GLFW_TRUE);
125}
126
128 glfwPollEvents();
129}
130
132 if (m_window == nullptr) {
133 LOG_ERROR("BeginFrame called with null window");
134 return;
135 }
136
137 // Validate context and attempt recovery if needed
138 if (!ValidateContext()) {
139 LOG_ERROR("BeginFrame: Context validation failed - skipping frame");
140 return;
141 }
142
143 int width = 0;
144 int height = 0;
145 glfwGetFramebufferSize(m_window, &width, &height);
146 m_width = width;
147 m_height = height;
148
149 glViewport(0, 0, m_width, m_height);
150 glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
151 glClear(GL_COLOR_BUFFER_BIT);
152
153 // Check for errors after rendering operations
154 const GLenum error = glGetError();
155 if (error == GL_CONTEXT_LOST || error == GL_OUT_OF_MEMORY) {
156 LOG_WARNING("OpenGL error during clear (0x{:X}) - will attempt recovery next frame", error);
157 // Don't attempt immediate recovery here - let ValidateContext handle it next frame
158 }
159}
160
162 if (m_window == nullptr) {
163 LOG_ERROR("EndFrame called with null window");
164 return;
165 }
166
167 // Validate context before swapping (final check for this frame)
168 if (!ValidateContext()) {
169 LOG_ERROR("EndFrame: Context validation failed - skipping swap");
170 return;
171 }
172
173 glfwSwapBuffers(m_window);
174}
175
176void WindowManager::GetFramebufferSize(int& width, int& height) const {
177 if (m_window != nullptr) {
178 glfwGetFramebufferSize(m_window, &width, &height);
179 } else {
180 width = m_width;
181 height = m_height;
182 }
183}
184
185void WindowManager::GetWindowSize(int& width, int& height) const {
186 if (m_window != nullptr) {
187 glfwGetWindowSize(m_window, &width, &height);
188 } else {
189 width = m_width;
190 height = m_height;
191 }
192}
193
195 if (m_window != nullptr) {
196 glfwSetWindowShouldClose(m_window, GLFW_TRUE);
197 }
198}
199
200void WindowManager::SetFramebufferSizeCallback(std::function<void(int, int)> callback) {
201 m_framebufferSizeCallback = callback;
202 if (m_window != nullptr) {
203 glfwSetFramebufferSizeCallback(m_window, FramebufferSizeCallbackInternal);
204 }
205}
206
207void WindowManager::SetKeyCallback(std::function<void(int, int, int, int)> callback) {
208 m_keyCallback = callback;
209 if (m_window != nullptr) {
210 glfwSetKeyCallback(m_window, KeyCallbackInternal);
211 }
212}
213
214void WindowManager::SetWindowCloseCallback(std::function<void()> callback) {
215 m_windowCloseCallback = callback;
216 if (m_window != nullptr) {
217 glfwSetWindowCloseCallback(m_window, WindowCloseCallbackInternal);
218 }
219}
220
222 if (m_window != nullptr) {
223 glfwSetWindowShouldClose(m_window, GLFW_FALSE);
224 }
225}
226
227void WindowManager::SetContextLossCallback(std::function<bool()> callback) {
228 m_contextLossCallback = callback;
229}
230
232 if (m_window == nullptr) {
233 return false;
234 }
235
236 // DEBUG: Simulate context loss for testing
237 // Uncomment these lines to force context loss after 100 frames
238 // static int frameCount = 0;
239 // if (++frameCount == 100) {
240 // LOG_WARNING("DEBUG: Simulating context loss for testing");
241 // return RecreateContext();
242 // }
243
244 // Check if context is still valid
245 if (glfwGetCurrentContext() != m_window) {
246 LOG_WARNING("OpenGL context is no longer valid - attempting recovery");
247 return RecreateContext();
248 }
249
250 // Check for OpenGL errors that indicate context loss
251 const GLenum error = glGetError();
252 if (error == GL_CONTEXT_LOST || error == GL_OUT_OF_MEMORY) {
253 LOG_WARNING("OpenGL context lost (error: 0x{:X}) - attempting recovery", error);
254 return RecreateContext();
255 }
256
257 // Context is valid
258 m_contextRecoveryAttempts = 0; // Reset on success
259 return true;
260}
261
262bool WindowManager::RecreateContext() {
263 m_contextRecoveryAttempts++;
264
265 if (m_contextRecoveryAttempts > MAX_RECOVERY_ATTEMPTS) {
266 LOG_ERROR("Failed to recover OpenGL context after {} attempts - requesting window close",
267 MAX_RECOVERY_ATTEMPTS);
268 glfwSetWindowShouldClose(m_window, GLFW_TRUE);
269 return false;
270 }
271
272 LOG_INFO("Attempting to recreate OpenGL context (attempt {}/{})", m_contextRecoveryAttempts, MAX_RECOVERY_ATTEMPTS);
273
274 // Make context current again (might have been lost)
275 glfwMakeContextCurrent(m_window);
276
277 // Check if making it current worked
278 if (glfwGetCurrentContext() != m_window) {
279 LOG_ERROR("Failed to make context current");
280 return false;
281 }
282
283 // Clear any OpenGL errors
284 while (glGetError() != GL_NO_ERROR) {
285 // Drain error queue
286 }
287
288 // Call application-level recovery callback if set
289 // This allows Application to recreate ImGui/ImPlot contexts
290 if (m_contextLossCallback) {
291 LOG_INFO("Calling context loss callback for application-level recovery");
292 if (!m_contextLossCallback()) {
293 LOG_ERROR("Application-level context recovery failed");
294 return false;
295 }
296 }
297
298 LOG_INFO("OpenGL context successfully recovered");
299 m_contextRecoveryAttempts = 0; // Reset on successful recovery
300 return true;
301}
302
303// Static callbacks
304
305void WindowManager::ErrorCallback(int error, const char* description) {
306 LOG_ERROR("GLFW Error {}: {}", error, description);
307}
308
309void WindowManager::FramebufferSizeCallbackInternal(GLFWwindow* window, int width, int height) {
310 auto* manager = static_cast<WindowManager*>(glfwGetWindowUserPointer(window));
311 if (manager != nullptr && manager->m_framebufferSizeCallback) {
312 manager->m_framebufferSizeCallback(width, height);
313 }
314}
315
316void WindowManager::KeyCallbackInternal(GLFWwindow* window, int key, int scancode, int action, int mods) {
317 auto* manager = static_cast<WindowManager*>(glfwGetWindowUserPointer(window));
318 if (manager != nullptr && manager->m_keyCallback) {
319 manager->m_keyCallback(key, scancode, action, mods);
320 }
321}
322
323void WindowManager::WindowCloseCallbackInternal(GLFWwindow* window) {
324 auto* manager = static_cast<WindowManager*>(glfwGetWindowUserPointer(window));
325 if (manager != nullptr && manager->m_windowCloseCallback) {
326 manager->m_windowCloseCallback();
327 }
328}
329
330} // namespace MetaImGUI
#define LOG_INFO(...)
Definition Logger.h:218
#define LOG_ERROR(...)
Definition Logger.h:220
#define LOG_WARNING(...)
Definition Logger.h:219
#define GL_CONTEXT_LOST
void SetWindowCloseCallback(std::function< void()> callback)
Set window close callback.
void SetKeyCallback(std::function< void(int, int, int, int)> callback)
Set key input callback.
void GetFramebufferSize(int &width, int &height) const
Get the current framebuffer size.
bool ValidateContext()
Check if context is valid and attempt recovery if not.
void CancelClose()
Cancel a close request (clears the should close flag)
void GetWindowSize(int &width, int &height) const
Get the current window size.
void EndFrame()
Present the rendered frame.
void SetFramebufferSizeCallback(std::function< void(int, int)> callback)
Set framebuffer size callback.
WindowManager(std::string title, int width, int height)
Construct a new Window Manager.
void SetContextLossCallback(std::function< bool()> callback)
Set context loss callback.
void BeginFrame()
Prepare the window for a new frame.
void RequestClose()
Request the window to close.
bool ShouldClose() const
Check if the window should close.
void Shutdown()
Shutdown and cleanup the window.
bool Initialize()
Initialize the window and GLFW.
void PollEvents()
Poll for input events.