From 63265cc22f3055a782a0114e92c57a36bef723b2 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Sun, 23 Aug 2026 17:06:05 -0500 Subject: [PATCH] Dynamically size the fullscreen window; present design space via LETTERBOX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SDL_CreateWindow hardcoded a 1920x1080 backbuffer while SDL_WINDOW_FULLSCREEN let compositors run a different output mode; the mismatched buffer reached the panel unscaled — quarter-size in the top-left on 4K sets, cropped ("4x too large") on smaller non-1080p panels under cage/Wayland. Logical presentation never got to map the fixed design space because its target (the backbuffer) was pinned to stale dimensions. Size the fullscreen window from SDL_GetCurrentDisplayMode so the first commit already matches scanout 1:1, switch logical presentation to LETTERBOX (aspect-safe bars on non-16:9 panels), and route both through new kDesignWidth/kDesignHeight constants. Harden getLayoutSize() to always write its out params and null-check the renderer/render-window before use. Log requested/window/pixel-buffer/render-output sizes at startup and on every resize event so field units self-diagnose this bug class into ~/.local/radiantwave/log.txt. SDL_WINDOW_HIGH_PIXEL_DENSITY stays off on purpose: kiosk panels are not HiDPI, and keeping the pixel buffer equal to the display mode keeps compositor scanout unscaled. --- src/logical_size.h | 48 ++++++++++++++++++++++++++----- src/main.cpp | 71 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/logical_size.h b/src/logical_size.h index 239f492..c7f38f1 100644 --- a/src/logical_size.h +++ b/src/logical_size.h @@ -4,14 +4,48 @@ namespace radiantwave { -inline void getLayoutSize(SDL_Renderer* renderer, int* w, int* h) +// Design resolution every page lays out against. The renderer's logical +// presentation (LETTERBOX) scales this fixed space onto whatever +// window/backbuffer the display actually provides. +inline constexpr int kDesignWidth = 1920; +inline constexpr int kDesignHeight = 1080; + +// Returns the size pages should lay out against: the renderer's logical size +// when presentation is active, otherwise the real window size. The output +// params are always written — design constants are the last-resort fallback. +inline void getLayoutSize(SDL_Renderer *renderer, int *w, int *h) { - SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED; - if (!SDL_GetRenderLogicalPresentation(renderer, w, h, &mode) || - mode == SDL_LOGICAL_PRESENTATION_DISABLED || *w <= 0 || *h <= 0) - { - SDL_GetWindowSize(SDL_GetRenderWindow(renderer), w, h); - } + if (!w || !h) + return; + *w = kDesignWidth; + *h = kDesignHeight; + + if (!renderer) + return; + + SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED; + int logicalW = 0; + int logicalH = 0; + if (SDL_GetRenderLogicalPresentation(renderer, &logicalW, &logicalH, + &mode) && + mode != SDL_LOGICAL_PRESENTATION_DISABLED && logicalW > 0 && + logicalH > 0) { + *w = logicalW; + *h = logicalH; + return; + } + + SDL_Window *window = SDL_GetRenderWindow(renderer); + if (!window) + return; + + int windowW = 0; + int windowH = 0; + SDL_GetWindowSize(window, &windowW, &windowH); + if (windowW > 0 && windowH > 0) { + *w = windowW; + *h = windowH; + } } } // namespace radiantwave diff --git a/src/main.cpp b/src/main.cpp index 491038b..d5dfa55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include "app_state.h" #include "home.h" +#include "logical_size.h" #include "logger/logger.h" #include "provisioning.h" @@ -32,11 +33,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { auto *state = new AppState{}; - // SDL_WINDOW_FULLSCREEN fills the desktop's native resolution, so the - // compositor never has to scale the moving scroller (scaling moving text - // causes flicker). - state->window = - SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN); + // Size the fullscreen window from the display's actual mode so the very + // first buffer commit already matches what the compositor scanouts 1:1. + // Requesting a hardcoded size left the backbuffer at that size on + // displays running a different mode, and frames reached the panel + // unscaled (quarter-size on 4K, cropped on smaller panels). + const SDL_DisplayMode *displayMode = + SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay()); + int windowWidth = + (displayMode && displayMode->w > 0) ? displayMode->w + : radiantwave::kDesignWidth; + int windowHeight = + (displayMode && displayMode->h > 0) ? displayMode->h + : radiantwave::kDesignHeight; + + // Borderless desktop fullscreen: the compositor never has to scale the + // moving scroller (scaling moving text causes flicker). Deliberately no + // SDL_WINDOW_HIGH_PIXEL_DENSITY — kiosk panels are not HiDPI, so keeping + // the pixel buffer equal to the display mode keeps scanout 1:1. + state->window = SDL_CreateWindow("RadiantWave", windowWidth, windowHeight, + SDL_WINDOW_FULLSCREEN); if (!state->window) { SDL_Log("Failed to create window: %s", SDL_GetError()); delete state; @@ -59,16 +75,38 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { std::string(SDL_GetError()) + ")"); } - // Lock the renderer to a 1920x1080 logical coordinate space. All page - // layout code already targets this resolution, so everything renders at - // the correct size on any display — the GPU scales to native pixels. - if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080, - SDL_LOGICAL_PRESENTATION_STRETCH)) { + // All page layout targets the fixed design resolution; LETTERBOX maps it + // onto the real window with aspect preserved (bars on non-16:9 panels). + if (!SDL_SetRenderLogicalPresentation(state->renderer, + radiantwave::kDesignWidth, + radiantwave::kDesignHeight, + SDL_LOGICAL_PRESENTATION_LETTERBOX)) { radiantwave::logger::Logger::instance().warning( "Graphics", "Could not set logical render size; layout may be " "wrong on HiDPI displays"); } + { + // Record how the requested mode, window, pixel buffer, and render + // output actually compare — this is the evidence trail for + // display-scaling problems on field units. + int windowW = 0, windowH = 0; + SDL_GetWindowSize(state->window, &windowW, &windowH); + int pixelW = 0, pixelH = 0; + SDL_GetWindowSizeInPixels(state->window, &pixelW, &pixelH); + int outputW = 0, outputH = 0; + SDL_GetRenderOutputSize(state->renderer, &outputW, &outputH); + radiantwave::logger::Logger::instance().info( + "Graphics", + "Requested " + std::to_string(windowWidth) + "x" + + std::to_string(windowHeight) + ", window " + + std::to_string(windowW) + "x" + std::to_string(windowH) + + ", pixels " + std::to_string(pixelW) + "x" + + std::to_string(pixelH) + ", render output " + + std::to_string(outputW) + "x" + std::to_string(outputH) + + ", presentation LETTERBOX"); + } + SDL_HideCursor(); // Initialize configuration, database, and network manager @@ -157,6 +195,19 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { state.quitRequested = true; } + if (event->type == SDL_EVENT_WINDOW_RESIZED) { + radiantwave::logger::Logger::instance().info( + "Graphics", "Window resized to " + + std::to_string(event->window.data1) + "x" + + std::to_string(event->window.data2)); + // Logical presentation persists across resizes; re-apply defensively + // in case backend state was reset underneath us. + SDL_SetRenderLogicalPresentation(state.renderer, + radiantwave::kDesignWidth, + radiantwave::kDesignHeight, + SDL_LOGICAL_PRESENTATION_LETTERBOX); + } + if (!state.keybinds.handleEvent(*event)) { state.pages.handleEvent(*event); }