ADD: Window mode system

This commit is contained in:
2025-12-15 22:22:56 +09:00
parent 5e7ad3fc78
commit b882d5e181
3 changed files with 247 additions and 0 deletions

View File

@@ -188,6 +188,23 @@ void VulkanEngine::init()
window_flags
);
_windowMode = WindowMode::Windowed;
_windowDisplayIndex = SDL_GetWindowDisplayIndex(_window);
if (_windowDisplayIndex < 0) _windowDisplayIndex = 0;
{
int wx = 0, wy = 0, ww = 0, wh = 0;
SDL_GetWindowPosition(_window, &wx, &wy);
SDL_GetWindowSize(_window, &ww, &wh);
if (ww > 0 && wh > 0)
{
_windowedRect.x = wx;
_windowedRect.y = wy;
_windowedRect.w = ww;
_windowedRect.h = wh;
_windowedRect.valid = true;
}
}
_deviceManager = std::make_shared<DeviceManager>();
_deviceManager->init_vulkan(_window);
@@ -332,6 +349,125 @@ void VulkanEngine::init()
_isInitialized = true;
}
void VulkanEngine::set_window_mode(WindowMode mode, int display_index)
{
if (!_window) return;
const int num_displays = SDL_GetNumVideoDisplays();
int current_display = SDL_GetWindowDisplayIndex(_window);
if (current_display < 0) current_display = 0;
if (num_displays <= 0)
{
display_index = 0;
}
else
{
if (display_index < 0) display_index = current_display;
display_index = std::clamp(display_index, 0, num_displays - 1);
}
const WindowMode prev_mode = _windowMode;
const bool entering_fullscreen = (prev_mode == WindowMode::Windowed) && (mode != WindowMode::Windowed);
if (entering_fullscreen)
{
int wx = 0, wy = 0, ww = 0, wh = 0;
SDL_GetWindowPosition(_window, &wx, &wy);
SDL_GetWindowSize(_window, &ww, &wh);
if (ww > 0 && wh > 0)
{
_windowedRect.x = wx;
_windowedRect.y = wy;
_windowedRect.w = ww;
_windowedRect.h = wh;
_windowedRect.valid = true;
}
}
// If we are currently in any fullscreen mode, leave fullscreen first so we can safely
// move the window to the target display before re-entering fullscreen.
if (prev_mode != WindowMode::Windowed)
{
if (SDL_SetWindowFullscreen(_window, 0) != 0)
{
fmt::println("[Window] SDL_SetWindowFullscreen(0) failed: {}", SDL_GetError());
}
}
// Move the window to the selected display (applies to both windowed and fullscreen).
SDL_SetWindowPosition(_window,
SDL_WINDOWPOS_CENTERED_DISPLAY(display_index),
SDL_WINDOWPOS_CENTERED_DISPLAY(display_index));
if (mode == WindowMode::Windowed)
{
SDL_SetWindowBordered(_window, SDL_TRUE);
SDL_SetWindowResizable(_window, SDL_TRUE);
int target_w = 0, target_h = 0;
if (_windowedRect.valid)
{
target_w = _windowedRect.w;
target_h = _windowedRect.h;
}
if (target_w <= 0 || target_h <= 0)
{
SDL_GetWindowSize(_window, &target_w, &target_h);
}
if (target_w > 0 && target_h > 0)
{
SDL_SetWindowSize(_window, target_w, target_h);
}
}
else
{
SDL_SetWindowBordered(_window, SDL_FALSE);
const Uint32 fullscreen_flag =
(mode == WindowMode::FullscreenDesktop) ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
if (mode == WindowMode::FullscreenExclusive)
{
SDL_DisplayMode desktop{};
if (SDL_GetDesktopDisplayMode(display_index, &desktop) == 0)
{
// Request desktop mode by default. Future UI can add explicit mode selection.
SDL_SetWindowDisplayMode(_window, &desktop);
}
}
if (SDL_SetWindowFullscreen(_window, fullscreen_flag) != 0)
{
fmt::println("[Window] SDL_SetWindowFullscreen({}) failed: {}",
static_cast<unsigned>(fullscreen_flag),
SDL_GetError());
}
}
_windowMode = mode;
_windowDisplayIndex = SDL_GetWindowDisplayIndex(_window);
if (_windowDisplayIndex < 0) _windowDisplayIndex = display_index;
// Make sure SDL has processed the fullscreen/move requests before we query drawable size for swapchain recreation.
SDL_PumpEvents();
// Recreate swapchain immediately so the very next draw uses correct extent.
if (_swapchainManager)
{
_swapchainManager->resize_swapchain(_window);
if (_swapchainManager->resize_requested)
{
resize_requested = true;
_last_resize_event_ms = 0;
}
else
{
resize_requested = false;
}
}
}
void VulkanEngine::set_logical_render_extent(VkExtent2D extent)
{
extent = clamp_nonzero_extent(extent);

View File

@@ -8,6 +8,7 @@
#include <vector>
#include <string>
#include <unordered_map>
#include <cstdint>
#include "vk_mem_alloc.h"
#include <deque>
#include <functional>
@@ -62,6 +63,13 @@ struct MeshNode : public Node
class VulkanEngine
{
public:
enum class WindowMode : uint8_t
{
Windowed = 0,
FullscreenDesktop = 1, // borderless fullscreen ("fullscreen windowed")
FullscreenExclusive = 2 // exclusive fullscreen (may change display mode)
};
bool _isInitialized{false};
int _frameNumber{0};
@@ -80,6 +88,9 @@ public:
struct SDL_Window *_window{nullptr};
WindowMode _windowMode{WindowMode::Windowed};
int _windowDisplayIndex{0};
FrameResources _frames[FRAME_OVERLAP];
FrameResources &get_current_frame() { return _frames[_frameNumber % FRAME_OVERLAP]; };
@@ -204,6 +215,9 @@ public:
//run main loop
void run();
// Window controls (runtime)
void set_window_mode(WindowMode mode, int display_index);
// Rendering resolution controls:
// - logicalRenderExtent controls camera aspect and picking (letterboxed view).
// - renderScale controls the internal render target pixel count (logical * scale).
@@ -258,6 +272,15 @@ public:
bool freeze_rendering{false};
private:
struct WindowedRect
{
int x{0};
int y{0};
int w{0};
int h{0};
bool valid{false};
} _windowedRect;
void init_frame_resources();
void init_pipelines();

View File

@@ -6,6 +6,8 @@
#include "engine.h"
#include "SDL2/SDL.h"
#include "imgui.h"
#include "ImGuizmo.h"
@@ -23,12 +25,93 @@
#include "device/images.h"
#include "context.h"
#include <core/types.h>
#include <algorithm>
#include <cstring>
#include "mesh_bvh.h"
namespace
{
static void ui_window(VulkanEngine *eng)
{
if (!eng || !eng->_window) return;
int num_displays = SDL_GetNumVideoDisplays();
if (num_displays <= 0)
{
ImGui::Text("No displays reported by SDL (%s)", SDL_GetError());
return;
}
int current_display = SDL_GetWindowDisplayIndex(eng->_window);
if (current_display < 0) current_display = eng->_windowDisplayIndex;
current_display = std::clamp(current_display, 0, num_displays - 1);
const char *cur_display_name = SDL_GetDisplayName(current_display);
if (!cur_display_name) cur_display_name = "Unknown";
ImGui::Text("Current: %s on display %d (%s)",
(eng->_windowMode == VulkanEngine::WindowMode::Windowed)
? "Windowed"
: (eng->_windowMode == VulkanEngine::WindowMode::FullscreenDesktop)
? "Borderless Fullscreen"
: "Exclusive Fullscreen",
current_display,
cur_display_name);
static int pending_display = -1;
static int pending_mode = -1; // 0 windowed, 1 borderless, 2 exclusive
if (pending_display < 0) pending_display = current_display;
if (pending_mode < 0) pending_mode = static_cast<int>(eng->_windowMode);
ImGui::Separator();
if (ImGui::BeginCombo("Monitor", cur_display_name))
{
for (int i = 0; i < num_displays; ++i)
{
const char *name = SDL_GetDisplayName(i);
if (!name) name = "Unknown";
const bool selected = (pending_display == i);
if (ImGui::Selectable(name, selected))
{
pending_display = i;
}
if (selected)
{
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
const char *mode_labels[] = {
"Windowed",
"Borderless (Fullscreen Desktop)",
"Exclusive Fullscreen"
};
ImGui::Combo("Mode", &pending_mode, mode_labels, 3);
ImGui::TextUnformatted("Apply triggers immediate swapchain recreation.");
if (ImGui::Button("Apply"))
{
auto mode = static_cast<VulkanEngine::WindowMode>(std::clamp(pending_mode, 0, 2));
eng->set_window_mode(mode, pending_display);
// Re-sync pending selections with what SDL actually applied.
pending_display = SDL_GetWindowDisplayIndex(eng->_window);
if (pending_display < 0) pending_display = eng->_windowDisplayIndex;
pending_display = std::clamp(pending_display, 0, num_displays - 1);
pending_mode = static_cast<int>(eng->_windowMode);
}
ImGui::SameLine();
if (ImGui::Button("Use Current"))
{
pending_display = current_display;
pending_mode = static_cast<int>(eng->_windowMode);
}
}
// Background / compute playground
static void ui_background(VulkanEngine *eng)
{
@@ -1364,6 +1447,11 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng)
ui_overview(eng);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Window"))
{
ui_window(eng);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Background"))
{
ui_background(eng);