5 #include <backends/imgui_impl_sdl2.h>
7 #include <glad/glversion.h>
9 #define SDL_MAIN_HANDLED
12 #include <config/Config.h>
13 #include <config/ConEntry.h>
14 #include <i18n/TranslationManager.h>
15 #include <input/InputManager.h>
16 #include <loader/image/Image.h>
17 #include <resource/provider/FilesystemResourceProvider.h>
18 #include <render/material/MaterialFrameBuffer.h>
19 #include <render/material/MaterialTextured.h>
20 #include <render/mesh/MeshDataBuilder.h>
22 #include <ui/IPanel.h>
24 using namespace chira;
26 CHIRA_CREATE_LOG(WINDOW);
28 static void setVSync(
bool enable) {
30 SDL_GL_SetSwapInterval(0);
31 }
else if (SDL_GL_SetSwapInterval(-1) == -1) {
33 SDL_GL_SetSwapInterval(1);
37 ConVar win_vsync{
"win_vsync",
true,
"Limit the FPS to your monitor's resolution.", CON_FLAG_CACHE, [](ConVar::CallbackArg newValue) {
38 setVSync(
static_cast<bool>(std::stoi(newValue.data())));
42 ConVar input_raw_mouse_motion{
"input_raw_mouse_motion",
true,
"Get more accurate mouse motion.", CON_FLAG_CACHE};
44 static void setImGuiConfigPath() {
45 static std::string configPath = Config::getConfigFile(
"imgui.ini");
46 ImGui::GetIO().IniFilename = configPath.c_str();
50 SDL_Window* g_Splashscreen =
nullptr;
51 void* g_GLContext =
nullptr;
53 bool Device::initBackendAndCreateSplashscreen(
bool splashScreenVisible) {
54 static bool alreadyRan =
false;
61 #ifdef CHIRA_PLATFORM_WINDOWS
63 SDL_SetHintWithPriority(SDL_HINT_WINDOWS_DPI_SCALING,
"0", SDL_HINT_OVERRIDE);
64 SDL_SetHintWithPriority(SDL_HINT_WINDOWS_DPI_AWARENESS,
"permonitorv2", SDL_HINT_OVERRIDE);
67 if (SDL_Init(SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER)) {
68 LOG_WINDOW.error(
"SDL2 failed to initialize: {}", SDL_GetError());
74 SDL_WINDOW_ALLOW_HIGHDPI |
75 SDL_WINDOW_ALWAYS_ON_TOP |
76 SDL_WINDOW_BORDERLESS |
77 (splashScreenVisible ? SDL_WINDOW_SHOWN : SDL_WINDOW_HIDDEN);
78 int width = 640, height = 480;
80 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
81 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
82 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
83 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
84 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
85 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
86 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
88 g_Splashscreen = SDL_CreateWindow(
"Loading...", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, windowFlags);
89 if (!g_Splashscreen) {
90 LOG_WINDOW.error(
"Splashscreen window creation failed! Error: {}", SDL_GetError());
94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
95 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, GL_VERSION_MAJOR);
96 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, GL_VERSION_MINOR);
99 SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &glContextFlags);
100 glContextFlags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
102 glContextFlags |= SDL_GL_CONTEXT_DEBUG_FLAG;
104 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, glContextFlags);
106 g_GLContext = SDL_GL_CreateContext(g_Splashscreen);
108 LOG_WINDOW.error(
"Splashscreen window context creation failed! Error: {}", SDL_GetError());
111 if (!gladLoadGL((GLADloadfunc) SDL_GL_GetProcAddress)) {
112 LOG_WINDOW.error(
"{} must be available to run this program!", GL_VERSION_STRING_PRETTY);
115 if (SDL_GL_MakeCurrent(g_Splashscreen, g_GLContext)) {
116 LOG_WINDOW.error(
"Splashscreen window context failed to be made current! Error: {}", SDL_GetError());
119 setVSync(win_vsync.getValue<
bool>());
121 Renderer::pushFrameBuffer(g_WindowFramebufferHandle);
122 Renderer::recreateFrameBuffer(&g_WindowFramebufferHandle, width, height, WrapMode::REPEAT, WrapMode::REPEAT, FilterMode::LINEAR,
true);
123 glViewport(0, 0, width, height);
126 plane.
addSquare({}, {2, -2}, SignedAxis::ZN, 0);
127 plane.setMaterial(Resource::getResource<MaterialTextured>(
"file://materials/splashscreen.json").cast<IMaterial>());
128 plane.render(glm::identity<glm::mat4>());
130 glDisable(GL_DEPTH_TEST);
131 Renderer::popFrameBuffer();
134 windowSurface.
addSquare({}, {2, -2}, SignedAxis::ZN, 0);
135 windowSurface.setMaterial(Resource::getResource<MaterialFrameBuffer>(
"file://materials/window.json", &g_WindowFramebufferHandle).cast<IMaterial>());
136 windowSurface.render(glm::identity<glm::mat4>());
138 glEnable(GL_DEPTH_TEST);
139 SDL_GL_SwapWindow(g_Splashscreen);
144 void Device::destroySplashscreen() {
145 if (g_Splashscreen) {
146 SDL_DestroyWindow(g_Splashscreen);
147 g_Splashscreen =
nullptr;
151 void Device::destroyBackend() {
152 Renderer::destroyImGui();
153 Device::destroyAllWindows();
154 SDL_GL_DeleteContext(g_GLContext);
158 std::uint64_t Device::getTicks() {
159 return SDL_GetTicks64();
162 std::array<Device::WindowHandle, 256> g_Windows{};
164 [[nodiscard]]
static int findFreeWindow() {
165 for (
unsigned int i = 0; i < g_Windows.size(); i++) {
166 if (!g_Windows.at(i))
167 return static_cast<int>(i);
173 int freeWindow = findFreeWindow();
174 if (freeWindow == -1)
177 g_Windows[freeWindow] = WindowHandle{};
178 WindowHandle& handle = g_Windows.at(freeWindow);
180 handle.width = width;
181 handle.height = height;
185 SDL_WINDOW_ALLOW_HIGHDPI |
186 SDL_WINDOW_RESIZABLE |
188 handle.window = SDL_CreateWindow(title.data(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, windowFlags);
189 if (!handle.window) {
190 LOG_WINDOW.error(
"Window creation failed! Error: {}", SDL_GetError());
193 SDL_SetWindowData(handle.window,
"handle", &handle);
194 SDL_GL_MakeCurrent(handle.window, g_GLContext);
197 handle.viewport = viewport;
198 handle.viewportIsSelfOwned =
false;
200 handle.viewport =
new Viewport{{width, height}};
201 handle.viewportIsSelfOwned =
true;
204 int iconWidth, iconHeight, bitsPerPixel;
207 auto* sdlIcon = SDL_CreateRGBSurfaceWithFormatFrom(icon, iconWidth, iconHeight, bitsPerPixel, 8, SDL_PIXELFORMAT_RGBA8888);
208 SDL_SetWindowIcon(handle.window, sdlIcon);
209 SDL_FreeSurface(sdlIcon);
210 Image::deleteUncompressedImage(icon);
213 handle.imguiContext = ImGui::CreateContext();
214 ImGui::SetCurrentContext(handle.imguiContext);
215 auto& io = ImGui::GetIO();
216 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_DockingEnable;
217 setImGuiConfigPath();
219 Renderer::initImGui(handle.window, g_GLContext);
221 static bool bakedFonts =
false;
225 auto defaultFont = Resource::getUniqueUncachedResource<Font>(TR(
"resource.font.default"));
226 io.FontDefault = defaultFont->getFont();
233 void Device::refreshWindows() {
235 for (
auto& handle : g_Windows) {
239 if (Device::isWindowAboutToBeDestroyed(&handle)) {
240 Device::destroyWindow(&handle);
244 if (!Device::isWindowVisible(&handle)) {
245 handle.viewport->update();
249 SDL_GL_MakeCurrent(handle.window, g_GLContext);
250 glViewport(0, 0, handle.width, handle.height);
252 ImGui::SetCurrentContext(handle.imguiContext);
253 setImGuiConfigPath();
255 Renderer::pushFrameBuffer(*handle.viewport->getRawHandle());
256 Renderer::startImGuiFrame();
258 handle.viewport->update();
259 handle.viewport->render();
261 for (
auto& [uuid, panel] : handle.panels) {
265 glDisable(GL_DEPTH_TEST);
267 Renderer::endImGuiFrame();
268 Renderer::popFrameBuffer();
271 surface.
addSquare({}, {2, -2}, SignedAxis::ZN, 0);
272 surface.setMaterial(Resource::getUniqueUncachedResource<MaterialFrameBuffer>(
"file://materials/window.json", handle.viewport->getRawHandle()).cast<
IMaterial>());
273 surface.render(glm::identity<glm::mat4>());
275 glEnable(GL_DEPTH_TEST);
277 SDL_GL_SwapWindow(handle.window);
282 while (SDL_PollEvent(&event)) {
283 auto* handle =
reinterpret_cast<Device::WindowHandle*
>(SDL_GetWindowData(SDL_GetWindowFromID(event.window.windowID),
"handle"));
287 ImGui::SetCurrentContext(handle->imguiContext);
288 ImGui_ImplSDL2_ProcessEvent(&event);
290 switch (event.type) {
292 for (
auto& windowHandle : g_Windows) {
294 Device::queueDestroyWindow(&windowHandle,
true);
298 case SDL_WINDOWEVENT:
299 switch (event.window.event) {
300 case SDL_WINDOWEVENT_CLOSE:
301 Device::queueDestroyWindow(handle,
true);
303 case SDL_WINDOWEVENT_RESIZED:
304 case SDL_WINDOWEVENT_SIZE_CHANGED:
305 case SDL_WINDOWEVENT_MAXIMIZED:
306 SDL_GetWindowSizeInPixels(handle->window, &handle->width, &handle->height);
307 handle->viewport->setSize({handle->width, handle->height});
315 for (
const auto& keyEvent : Input::KeyEvent::getEvents()) {
316 if (keyEvent.getEvent() == event.key.keysym.sym && keyEvent.getEventType() == Input::KeyEventType::PRESSED) {
322 for (
const auto& keyEvent : Input::KeyEvent::getEvents()) {
323 if (keyEvent.getEvent() == event.key.keysym.sym && keyEvent.getEventType() == Input::KeyEventType::RELEASED) {
328 case SDL_MOUSEBUTTONDOWN:
329 for (
const auto& mouseEvent : Input::MouseEvent::getEvents()) {
330 if (
static_cast<uint8_t
>(mouseEvent.getEvent()) == event.button.button && mouseEvent.getEventType() == Input::MouseEventType::CLICKED) {
331 mouseEvent(event.button.x, event.button.y, event.button.clicks);
335 case SDL_MOUSEBUTTONUP:
336 for (
const auto& mouseEvent : Input::MouseEvent::getEvents()) {
337 if (
static_cast<uint8_t
>(mouseEvent.getEvent()) == event.button.button && mouseEvent.getEventType() == Input::MouseEventType::RELEASED) {
338 mouseEvent(event.button.x, event.button.y, event.button.clicks);
342 case SDL_MOUSEMOTION:
343 for (
const auto& mouseMotionEvent : Input::MouseMotionEvent::getEvents()) {
344 if (mouseMotionEvent.getEvent() == Input::MouseMotion::MOVEMENT) {
345 mouseMotionEvent(event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel);
350 for (
const auto& mouseMotionEvent : Input::MouseMotionEvent::getEvents()) {
351 if (mouseMotionEvent.getEvent() == Input::MouseMotion::SCROLL) {
352 mouseMotionEvent(event.wheel.x, event.wheel.y, event.wheel.x, event.wheel.y);
364 static const auto* keyStates = SDL_GetKeyboardState(
nullptr);
365 for (
const auto& keyEvent : Input::KeyEvent::getEvents()) {
366 if (keyStates[SDL_GetScancodeFromKey(keyEvent.getEvent())] && keyEvent.getEventType() == Input::KeyEventType::REPEATED) {
372 int Device::getWindowCount() {
374 for (
const auto& handle : g_Windows) {
375 count +=
static_cast<bool>(handle);
380 Viewport* Device::getWindowViewport(WindowHandle* handle) {
381 return handle->viewport;
384 void Device::setWindowTitle(WindowHandle* handle, std::string_view title) {
385 SDL_SetWindowTitle(handle->window, title.data());
388 std::string_view Device::getWindowTitle(WindowHandle* handle) {
389 return SDL_GetWindowTitle(handle->window);
392 void Device::setWindowMaximized(WindowHandle* handle,
bool maximize) {
393 if (Device::isWindowFullscreen(handle))
396 SDL_MaximizeWindow(handle->window);
398 SDL_RestoreWindow(handle->window);
400 SDL_GetWindowSize(handle->window, &handle->width, &handle->height);
401 handle->viewport->setSize({handle->width, handle->height});
404 bool Device::isWindowMaximized(WindowHandle* handle) {
405 return SDL_GetWindowFlags(handle->window) & SDL_WINDOW_MAXIMIZED;
408 void Device::minimizeWindow(WindowHandle* handle,
bool minimize) {
410 SDL_MinimizeWindow(handle->window);
412 SDL_RestoreWindow(handle->window);
416 bool Device::isWindowMinimized(WindowHandle* handle) {
417 return SDL_GetWindowFlags(handle->window) & SDL_WINDOW_MINIMIZED;
420 void Device::setWindowFullscreen(WindowHandle* handle,
bool fullscreen) {
421 SDL_SetWindowFullscreen(handle->window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
424 bool Device::isWindowFullscreen(WindowHandle* handle) {
425 return SDL_GetWindowFlags(handle->window) & SDL_WINDOW_FULLSCREEN_DESKTOP;
428 void Device::setWindowVisibility(WindowHandle* handle,
bool visible) {
430 SDL_ShowWindow(handle->window);
432 SDL_HideWindow(handle->window);
434 handle->hidden = visible;
437 bool Device::isWindowVisible(WindowHandle* handle) {
438 return !handle->hidden;
441 void Device::setWindowSize(WindowHandle* handle,
int width,
int height) {
442 handle->width = width;
443 handle->height = height;
444 handle->viewport->setSize({width, height});
445 SDL_SetWindowSize(handle->window, width, height);
448 glm::vec2i Device::getWindowSize(WindowHandle* handle) {
450 SDL_GetWindowSize(handle->window, &dims.x, &dims.y);
454 void Device::setWindowPosition(WindowHandle* handle,
int width,
int height) {
455 if (Device::isWindowFullscreen(handle))
457 SDL_SetWindowPosition(handle->window, width, height);
460 void Device::setWindowPositionFromCenter(WindowHandle* handle,
int width,
int height) {
461 if (Device::isWindowFullscreen(handle))
464 SDL_GetDisplayBounds(0, &rect);
465 SDL_SetWindowPosition(handle->window, (rect.w / 2) + width, (rect.h / 2) + height);
468 glm::vec2i Device::getWindowPosition(WindowHandle* handle) {
470 SDL_GetWindowPosition(handle->window, &pos.x, &pos.y);
474 void Device::setMousePositionGlobal(
int x,
int y) {
475 SDL_WarpMouseGlobal(x, y);
478 void Device::setMousePositionInWindow(WindowHandle* handle,
int x,
int y) {
479 SDL_WarpMouseInWindow(handle->window, x, y);
482 glm::vec2i Device::getMousePositionGlobal() {
483 glm::vec2i pos{-1, -1};
484 SDL_GetGlobalMouseState(&pos.x, &pos.y);
488 glm::vec2i Device::getMousePositionInFocusedWindow() {
489 glm::vec2i pos{-1, -1};
490 SDL_GetMouseState(&pos.x, &pos.y);
494 void Device::setMouseCapturedWindow(WindowHandle* handle,
bool captured) {
495 SDL_RaiseWindow(handle->window);
496 ImGui::SetCurrentContext(handle->imguiContext);
497 setImGuiConfigPath();
500 SDL_SetRelativeMouseMode(SDL_TRUE);
501 ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouse;
503 SDL_SetRelativeMouseMode(SDL_FALSE);
504 ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
506 handle->mouseCaptured = captured;
509 bool Device::isMouseCapturedWindow(WindowHandle* handle) {
510 return handle->mouseCaptured;
514 void Device::queueDestroyWindow(WindowHandle* handle,
bool free) {
515 handle->shouldClose = free;
518 bool Device::isWindowAboutToBeDestroyed(WindowHandle* handle) {
519 return handle->shouldClose;
522 void Device::destroyWindow(WindowHandle* handle) {
523 if (handle->viewportIsSelfOwned) {
524 delete handle->viewport;
526 Device::removeAllPanelsFromWindow(handle);
527 ImGui::DestroyContext(handle->imguiContext);
528 handle->imguiContext =
nullptr;
529 SDL_DestroyWindow(handle->window);
530 handle->window =
nullptr;
533 void Device::destroyAllWindows() {
534 for (
auto& handle : g_Windows) {
536 Device::destroyWindow(&handle);
541 uuids::uuid Device::addPanelToWindow(WindowHandle* handle,
IPanel* panel) {
542 const auto uuid = UUIDGenerator::getNewUUID();
543 handle->panels[uuid] = panel;
547 [[nodiscard]]
IPanel* Device::getPanelOnWindow(WindowHandle* handle,
const uuids::uuid& panelID) {
548 if (handle->panels.contains(panelID))
549 return handle->panels[panelID];
553 void Device::removePanelFromWindow(WindowHandle* handle,
const uuids::uuid& panelID) {
554 if (handle->panels.contains(panelID)) {
555 delete handle->panels[panelID];
556 handle->panels.erase(panelID);
560 void Device::removeAllPanelsFromWindow(WindowHandle* handle) {
561 for (
const auto& [panelID, panel] : handle->panels) {
564 handle->panels.clear();
567 void Device::popup(std::string_view message, std::string_view title,
unsigned int popupFlags, std::string_view ok) {
568 SDL_MessageBoxButtonData buttons[] {
570 .flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
575 SDL_MessageBoxData data{
578 .title = title.data(),
579 .message = message.data(),
582 .colorScheme =
nullptr,
585 SDL_ShowMessageBox(&data, &buttonID);
588 void Device::popupInfo(std::string_view message, std::string_view title) {
589 Device::popup(message, title, POPUP_INFO);
592 void Device::popupWarning(std::string_view message, std::string_view title) {
593 Device::popup(message, title, POPUP_WARNING);
596 void Device::popupError(std::string_view message, std::string_view title) {
597 Device::popup(message, title, POPUP_ERROR);
600 bool Device::popupChoice(std::string_view message, std::string_view title,
unsigned int popupFlags, std::string_view ok, std::string_view cancel) {
601 SDL_MessageBoxButtonData buttons[] {
603 .flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
608 .flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
610 .text = cancel.data(),
613 SDL_MessageBoxData data{
616 .title = title.data(),
617 .message = message.data(),
620 .colorScheme =
nullptr,
623 SDL_ShowMessageBox(&data, &buttonID);
627 bool Device::popupInfoChoice(std::string_view message, std::string_view title) {
628 return Device::popupChoice(message, title, POPUP_INFO);
631 bool Device::popupWarningChoice(std::string_view message, std::string_view title) {
632 return Device::popupChoice(message, title, POPUP_WARNING);
635 bool Device::popupErrorChoice(std::string_view message, std::string_view title) {
636 return Device::popupChoice(message, title, POPUP_ERROR);
static std::string getResourceAbsolutePath(const std::string &identifier)
Takes a resource identifier and returns the full absolute path, if it exists.
void addSquare(Vertex v1, Vertex v2, Vertex v3, Vertex v4, bool addDuplicate=false)
Vertex v4 forms a face with vertex v1 and v3.