Files
Shokunin ec7bc14ad9 GL: Fit to Window stays within the native-wide surface limit
A native-wide surface is g_wide_w*S wide and the driver limit bounds it
(1024 native columns at 16x on a 16384 limit, about 38:9 at a 320-px
display). An uncapped Fit to Window aspect could ask for more; wide_fbo_for
then refused the surface and the frame fell back to a 1x CPU present with
no native-wide margins.

update_adaptive_widescreen now narrows the Fit aspect to the widest the
surface holds (gl_renderer_fit_wide_aspect: psx_gl_max_wide_width at the
context's scale, psx_gl_fit_wide_aspect at the current display width,
gpu_ws_display_width). It changes nothing unless the surface would be
refused. The refusal stays for a fixed aspect on a wide display mode; its
comment now gives the right limit and what the frame falls back to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:52:23 -07:00

155 lines
6.7 KiB
C

/* gl_scale_limits.h — how large an internal-resolution scale the OpenGL
* backend may allocate.
*
* Pure arithmetic, no GL or SDL, so a test can pin it. The GL backend keeps the
* authoritative VRAM as one 1024*S x 512*S colour texture plus a same-size
* depth-stencil renderbuffer (gpu_gl_renderer.c). Two things bound S:
*
* - the driver's size limits: GL_MAX_TEXTURE_SIZE, GL_MAX_RENDERBUFFER_SIZE
* and GL_MAX_VIEWPORT_DIMS (the caller passes their minimum). Apple's GL
* on Metal reports 16384, so the full-VRAM surface tops out at S = 16;
* - a memory budget. On unified-memory machines this is system RAM.
*
* A request that does not fit is clamped here, never turned into a failed
* context: the old behaviour (allocation failure -> the whole GL backend fell
* back to the software renderer) lost far more than the resolution. */
#pragma once
#include <stdint.h>
#define PSX_GL_VRAM_W 1024
#define PSX_GL_VRAM_H 512
/* RGBA8 colour + D24S8 depth-stencil per internal pixel. The copy scratch is
* sized lazily to the largest copy, so it is not part of the fixed cost. */
#define PSX_GL_HR_BYTES_PER_PX 8u
/* Default memory budget for the full-VRAM surface: 2 GiB (S = 22 by memory;
* the texture limit binds first on 16384-limit GPUs). */
#define PSX_GL_DEFAULT_BUDGET_MB 2048u
/* Why a scale was reduced. */
enum {
PSX_GL_SCALE_OK = 0,
PSX_GL_SCALE_TEXTURE = 1, /* 1024*S exceeds the driver size limit */
PSX_GL_SCALE_BUDGET = 2, /* surface exceeds the memory budget */
PSX_GL_SCALE_CEILING = 4 /* above the compile-time ceiling */
};
/* Bytes of the full-VRAM hr surface (colour + depth-stencil) at scale s. */
static inline uint64_t psx_gl_full_vram_bytes(int s) {
if (s < 1) s = 1;
return (uint64_t)PSX_GL_VRAM_W * (uint64_t)s *
(uint64_t)PSX_GL_VRAM_H * (uint64_t)s * PSX_GL_HR_BYTES_PER_PX;
}
/* 1 if a full-VRAM surface at scale s fits max_dim and budget_bytes.
* max_dim <= 0 means "unknown" (no size check). budget_bytes == 0 means no
* budget. */
static inline int psx_gl_full_vram_fits(int s, int max_dim, uint64_t budget_bytes) {
if (s < 1) return 0;
if (max_dim > 0 && (int64_t)PSX_GL_VRAM_W * s > (int64_t)max_dim) return 0;
if (max_dim > 0 && (int64_t)PSX_GL_VRAM_H * s > (int64_t)max_dim) return 0;
if (budget_bytes && psx_gl_full_vram_bytes(s) > budget_bytes) return 0;
return 1;
}
/* Largest scale <= req whose full-VRAM surface fits. Never below 1. When
* reason is non-NULL it receives a PSX_GL_SCALE_* mask of what bound it. */
static inline int psx_gl_clamp_full_vram_scale(int req, int ceiling, int max_dim,
uint64_t budget_bytes, int *reason) {
int why = PSX_GL_SCALE_OK;
int s = req < 1 ? 1 : req;
if (ceiling > 0 && s > ceiling) { s = ceiling; why |= PSX_GL_SCALE_CEILING; }
while (s > 1) {
int tex_ok = !(max_dim > 0 &&
((int64_t)PSX_GL_VRAM_W * s > (int64_t)max_dim ||
(int64_t)PSX_GL_VRAM_H * s > (int64_t)max_dim));
int mem_ok = !(budget_bytes && psx_gl_full_vram_bytes(s) > budget_bytes);
if (tex_ok && mem_ok) break;
if (!tex_ok) why |= PSX_GL_SCALE_TEXTURE;
if (!mem_ok) why |= PSX_GL_SCALE_BUDGET;
s--;
}
if (reason) *reason = why;
return s;
}
/* Widest native-wide surface (in native px) that fits max_dim at scale s.
* The GL wide surfaces are wide_w*S x 512*S. Returns 0 when nothing fits. */
static inline int psx_gl_max_wide_width(int s, int max_dim) {
if (s < 1) s = 1;
if (max_dim <= 0) return 1 << 30;
if ((int64_t)PSX_GL_VRAM_H * s > (int64_t)max_dim) return 0;
return max_dim / s;
}
/* Native-wide surface width (native px) for a display disp_w px wide at aspect
* num:den: the display plus two margins of disp_w*(3*num - 4*den)/(8*den),
* rounded exactly as gpu.c's ws_nw_configured_offset rounds them (16:9, 21:9
* and 32:9 at 320 wide give 426, 560 and 854). */
static inline int psx_gl_native_wide_width(int disp_w, int num, int den) {
if (disp_w <= 0 || num <= 0 || den <= 0 ||
(int64_t)num * 3 <= (int64_t)den * 4)
return disp_w;
int64_t numr = (int64_t)3 * num - (int64_t)4 * den;
return disp_w + 2 * (int)(((int64_t)disp_w * numr + (int64_t)4 * den) /
((int64_t)8 * den));
}
/* Narrow num:den, if needed, to the widest aspect whose native-wide surface
* is at most max_w native px (psx_gl_max_wide_width) at display width disp_w;
* 4:3 when not even one more column fits. 4*(max_w-1) : 3*disp_w rounds to at
* most max_w columns. Returns 1 when it changed num:den. */
static inline int psx_gl_fit_wide_aspect(int disp_w, int max_w, int *num, int *den) {
if (disp_w <= 0 || max_w <= 0 || !num || !den ||
psx_gl_native_wide_width(disp_w, *num, *den) <= max_w)
return 0;
int64_t n = 4, d = 3;
if (max_w - 1 > disp_w) {
n = 4 * (int64_t)(max_w - 1);
d = 3 * (int64_t)disp_w;
int64_t a = n, b = d;
while (b) { int64_t t = a % b; a = b; b = t; }
n /= a; d /= a;
}
*num = (int)n;
*den = (int)d;
return 1;
}
/* Windowed high-resolution mode: the authoritative VRAM stays at 1x and only
* a window of the displayed columns (window_w native px, full 512-row height)
* is kept at S. Largest S <= req whose smallest useful window (min_window_w,
* e.g. a 320-px framebuffer) fits max_dim and budget_bytes. */
static inline uint64_t psx_gl_window_bytes(int s, int window_w) {
if (s < 1) s = 1;
return (uint64_t)window_w * (uint64_t)s * (uint64_t)PSX_GL_VRAM_H * (uint64_t)s *
PSX_GL_HR_BYTES_PER_PX;
}
static inline int psx_gl_clamp_window_scale(int req, int ceiling, int max_dim,
uint64_t budget_bytes, int min_window_w) {
int s = req < 1 ? 1 : req;
if (ceiling > 0 && s > ceiling) s = ceiling;
while (s > 1) {
int dim_ok = !(max_dim > 0 &&
((int64_t)PSX_GL_VRAM_H * s > (int64_t)max_dim ||
(int64_t)min_window_w * s > (int64_t)max_dim));
int mem_ok = !(budget_bytes && psx_gl_window_bytes(s, min_window_w) > budget_bytes);
if (dim_ok && mem_ok) break;
s--;
}
return s;
}
/* Parse a budget override in MiB (PSX_GL_VRAM_BUDGET_MB). NULL/empty/invalid
* returns the default. "0" disables the budget. */
static inline uint64_t psx_gl_budget_bytes_from_env(const char *mb) {
uint64_t v = 0;
const char *p = mb;
if (!p || !*p) return (uint64_t)PSX_GL_DEFAULT_BUDGET_MB << 20;
while (*p >= '0' && *p <= '9') { v = v * 10u + (uint64_t)(*p - '0'); p++; if (v > (1u << 24)) break; }
if (*p) return (uint64_t)PSX_GL_DEFAULT_BUDGET_MB << 20;
return v << 20;
}