summaryrefslogtreecommitdiffstats
path: root/src/game/client/io/video.cc
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-12-30 13:20:33 +0500
committeruntodesu <kirill@untode.su>2025-12-30 13:20:33 +0500
commita1c83d56f41e6f2e0ad86dcd76d1446bfc60a37c (patch)
tree5754f0cd6ef3678c2e9d9c31174ae435d463c8ed /src/game/client/io/video.cc
parent49d3dff9e98e70e599dfd3059f85bb08ae247fe5 (diff)
downloadvoxelius-a1c83d56f41e6f2e0ad86dcd76d1446bfc60a37c.tar.bz2
voxelius-a1c83d56f41e6f2e0ad86dcd76d1446bfc60a37c.zip
begin working on qf ports (#21)
Diffstat (limited to 'src/game/client/io/video.cc')
-rw-r--r--src/game/client/io/video.cc289
1 files changed, 289 insertions, 0 deletions
diff --git a/src/game/client/io/video.cc b/src/game/client/io/video.cc
new file mode 100644
index 0000000..361b167
--- /dev/null
+++ b/src/game/client/io/video.cc
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: BSD-2-Clause
+// Copyright (c) 2025 Kirill Dmitrievich
+// File: video.cc; Created: Tue Dec 30 2025 13:00:24
+// Description: Video mode handling
+
+#include "client/pch.hh"
+
+#include "client/io/video.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/string.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+#include "core/version.hh"
+
+#include "shared/splash.hh"
+
+#include "client/gui/settings.hh"
+
+#include "client/const.hh"
+#include "client/globals.hh"
+
+static glm::ivec2 last_windowed_size;
+static config::Boolean enable_vsync(true);
+static config::String current_mode("windowed");
+
+static GLFWmonitor* fullscreen_monitor;
+static std::vector<VideoMode> fullscreen_modes;
+
+static void on_glfw_error(int error, const char* description)
+{
+ spdlog::error("video: GLFW error [{}]: {}", error, description);
+}
+
+static void on_glfw_framebuffer_size(GLFWwindow* window, int wide, int tall)
+{
+ globals::width = wide;
+ globals::height = tall;
+ globals::aspect = static_cast<float>(wide) / static_cast<float>(tall);
+
+ if(nullptr == glfwGetWindowMonitor(window)) {
+ last_windowed_size.x = wide;
+ last_windowed_size.y = tall;
+ }
+
+ globals::dispatcher.trigger(FramebufferSizeEvent(wide, tall));
+}
+
+static void on_glfw_window_close(GLFWwindow* window)
+{
+ // We don't really have a good way to
+ // pass "i want to quit" boolean to the main loop,
+ // so instead we just raise an external interrupt signal
+ // which handler latches an internal flag in the main loop
+ std::raise(SIGINT);
+}
+
+static void on_window_focus_glfw(GLFWwindow* window, int focused)
+{
+ ImGui_ImplGlfw_WindowFocusCallback(window, focused);
+}
+
+void video::init(void)
+{
+ last_windowed_size.x = BASE_WIDTH;
+ last_windowed_size.y = BASE_HEIGHT;
+
+ globals::client_config.add_value("video.enable_vsync", enable_vsync);
+ globals::client_config.add_value("video.current_mode", current_mode);
+
+#ifdef __unix__
+ // Wayland constantly throws random bullshit at me
+ // when I'm dealing with pretty much anything cross-platform
+ // on pretty much any kind of UNIX and Linux distribution
+ glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
+#endif
+
+ glfwSetErrorCallback(&on_glfw_error);
+
+ if(!glfwInit()) {
+ spdlog::critical("glfw: initialize failed");
+ std::terminate();
+ }
+
+ unsigned int monitor_index;
+ auto monitor_arg = cmdline::get("monitor");
+ auto monitor_check = std::from_chars(monitor_arg.data(), monitor_arg.data() + monitor_arg.size(), monitor_index);
+
+ if(monitor_check.ec == std::errc()) {
+ int monitor_count;
+ const auto monitors = glfwGetMonitors(&monitor_count);
+
+ if(monitor_index < static_cast<unsigned int>(monitor_count)) {
+ fullscreen_monitor = monitors[monitor_index];
+ }
+ else {
+ // If the user wants to, say run the game on a monitor
+ // number 2 and there are only 2 monitors (remember, zero-based index)
+ // it's a good idea to silently fall back to the primary monitor
+ fullscreen_monitor = glfwGetPrimaryMonitor();
+ }
+ }
+ else {
+ fullscreen_monitor = glfwGetPrimaryMonitor();
+ }
+
+ int video_mode_count;
+ const auto video_modes = glfwGetVideoModes(fullscreen_monitor, &video_mode_count);
+
+ fullscreen_modes.clear();
+ fullscreen_modes.reserve(video_mode_count);
+
+ for(int i = 0; i < video_mode_count; ++i) {
+ auto& mode = video_modes[i];
+
+ // Only allow video modes that are at least as large as the base resolution
+ // to be used by the video subsystem, otherwise we're going to end up with
+ // some bizzare UI scaling issues because there is no UI scale less than 1.0
+ if(mode.width >= BASE_WIDTH && mode.height >= BASE_HEIGHT) {
+ fullscreen_modes.push_back(VideoMode(mode));
+ }
+ }
+
+ // Setup GLFW window hints
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+
+#if defined(__APPLE__)
+ // Enable forward compatibility because Apple
+ // just decided to be the autistic kid of the class
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+#endif
+
+ globals::window = glfwCreateWindow(BASE_WIDTH, BASE_HEIGHT, "Client", nullptr, nullptr);
+
+ if(globals::window == nullptr) {
+ spdlog::critical("glfw: window creation failed");
+ std::terminate();
+ }
+
+ glfwSetWindowSizeLimits(globals::window, BASE_WIDTH, BASE_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
+ glfwMakeContextCurrent(globals::window);
+ glfwSwapInterval(1);
+
+ glfwSetFramebufferSizeCallback(globals::window, &on_glfw_framebuffer_size);
+ glfwSetWindowCloseCallback(globals::window, &on_glfw_window_close);
+ glfwSetWindowFocusCallback(globals::window, &on_window_focus_glfw);
+
+ if(auto image = resource::load<Image>("textures/gui/window_icon.png")) {
+ GLFWimage icon_image;
+ icon_image.width = image->size.x;
+ icon_image.height = image->size.y;
+ icon_image.pixels = reinterpret_cast<unsigned char*>(image->pixels);
+ glfwSetWindowIcon(globals::window, 1, &icon_image);
+ }
+
+ settings::add_video_mode_select(0, settings_location::VIDEO, "video.current_mode");
+ settings::add_checkbox(1, enable_vsync, settings_location::VIDEO, "video.enable_vsync", true);
+
+ update_window_title();
+}
+
+void video::init_late(void)
+{
+ std::string mode_string(current_mode.get_value());
+
+ if(0 == mode_string.compare("windowed")) {
+ request_windowed(last_windowed_size.x, last_windowed_size.y);
+ return;
+ }
+
+ int target_width;
+ int target_height;
+ int target_refresh_rate;
+
+ if(3 == std::sscanf(mode_string.c_str(), "%d:%d:%d", &target_width, &target_height, &target_refresh_rate)) {
+ request_fullscreen(target_width, target_height, target_refresh_rate);
+ return;
+ }
+
+ int current_width;
+ int current_height;
+ glfwGetFramebufferSize(globals::window, &current_width, &current_height);
+ on_glfw_framebuffer_size(globals::window, current_width, current_height);
+}
+
+void video::shutdown(void)
+{
+ glfwDestroyWindow(globals::window);
+ glfwTerminate();
+}
+
+void video::update(void)
+{
+ glfwGetFramebufferSize(globals::window, &globals::width, &globals::height);
+ globals::aspect = static_cast<float>(globals::width) / static_cast<float>(globals::height);
+}
+
+void video::update_late(void)
+{
+ glfwSwapInterval(enable_vsync.get_value() ? 1 : 0);
+}
+
+void video::query_current_mode(int& wide, int& tall) noexcept
+{
+ glfwGetFramebufferSize(globals::window, &wide, &tall);
+}
+
+void video::query_current_mode(int& wide, int& tall, bool& fullscreen) noexcept
+{
+ glfwGetFramebufferSize(globals::window, &wide, &tall);
+ fullscreen = static_cast<bool>(glfwGetWindowMonitor(globals::window));
+}
+
+const std::vector<VideoMode>& video::query_fullscreen_modes(void) noexcept
+{
+ return fullscreen_modes;
+}
+
+void video::request_fullscreen(int wide, int tall, int rate) noexcept
+{
+ assert(wide >= BASE_WIDTH);
+ assert(tall >= BASE_HEIGHT);
+
+ std::size_t best_index = 0;
+ auto best_score = std::numeric_limits<int>::max();
+
+ for(std::size_t i = 0; i < fullscreen_modes.size(); ++i) {
+ const auto& mode = fullscreen_modes[i];
+
+ auto score_width = std::abs(mode.wide() - wide);
+ auto score_height = std::abs(mode.tall() - tall);
+ auto score_refresh_rate = std::abs(mode.rate() - rate);
+ auto total_score = score_width + score_height + score_refresh_rate;
+
+ if(total_score < best_score) {
+ best_score = total_score;
+ best_index = i;
+ }
+ }
+
+ const auto& best_mode = fullscreen_modes[best_index];
+
+ glfwSetWindowMonitor(globals::window, fullscreen_monitor, 0, 0, best_mode.wide(), best_mode.tall(), best_mode.rate());
+ glfwSetWindowAttrib(globals::window, GLFW_AUTO_ICONIFY, GLFW_FALSE);
+ glfwSetWindowAttrib(globals::window, GLFW_RESIZABLE, GLFW_FALSE);
+
+ current_mode.set(std::format("{}:{}:{}", best_mode.wide(), best_mode.tall(), best_mode.rate()));
+
+ spdlog::debug("video: set mode to: {}x{} ({} Hz)", best_mode.wide(), best_mode.tall(), best_mode.rate());
+}
+
+void video::request_windowed(int wide, int tall) noexcept
+{
+ glfwSetWindowMonitor(globals::window, nullptr, 0, 0, wide, tall, GLFW_DONT_CARE);
+ glfwSetWindowAttrib(globals::window, GLFW_AUTO_ICONIFY, GLFW_FALSE);
+ glfwSetWindowAttrib(globals::window, GLFW_RESIZABLE, GLFW_TRUE);
+
+ int workarea_xpos, workarea_ypos;
+ int workarea_width, workarea_height;
+ glfwGetMonitorWorkarea(fullscreen_monitor, &workarea_xpos, &workarea_ypos, &workarea_width, &workarea_height);
+
+ auto center_x = workarea_xpos + (workarea_width - wide) / 2;
+ auto center_y = workarea_ypos + (workarea_height - tall) / 2;
+
+ glfwSetWindowPos(globals::window, center_x, center_y);
+
+ current_mode.set("windowed");
+
+ spdlog::debug("video: set mode to: windowed {}x{}", wide, tall);
+}
+
+void video::request_windowed(void) noexcept
+{
+ request_windowed(last_windowed_size.x, last_windowed_size.y);
+}
+
+void video::update_window_title(void)
+{
+ auto title = std::format("Voxelius {}: {}", version::triplet, splash::get());
+ glfwSetWindowTitle(globals::window, title.c_str());
+}