Files
psxrecomp/runtime/tests/test_gl_scale_limits.c
T
Shokunin 2211ecabf3 GL: true 8K past a 16384 texture limit with a high-resolution window
8K is 18x at a 240-line reference, and a full-VRAM surface at 18x is
18432 px wide: over the 16384 limit of Apple's GL, Intel and MoltenVK. A
title only needs its displayed frame at 18x, so past the full-VRAM limit
the GL backend now splits the work:

- The authoritative VRAM stays the ordinary hr surface at 1x, i.e. the
  native renderer unchanged: pack, CPU readback, render-to-texture and VRAM
  copies are exactly the native results (s_hr_scale; s_scale stays the
  presented scale gr_scale reports).
- A presentation-only surface at S covers the displayed columns W =
  [x0, x1) x all 512 rows (R4: 0..319, 5760x9216 at 18x, 405 MiB). Every
  GPU write touching W is mirrored into it: textured and flat batches,
  lines (quads), fills, uploads, the depth24 clear, and VRAM copies (hi to
  hi when the source is in W, otherwise the 1x source upscaled; both staged
  before drawing so overlapping copies read pre-copy pixels). It keeps its
  own stencil. A separate blit program carries the window projection, so
  the canonical shaders are untouched.
- W grows (64-column aligned) the first time a display rectangle outside it
  is presented, seeded by upscaling the 1x content. Present, hold-last,
  interpolation capture, screenshot_hires and the native-wide centre read
  it; a display it cannot hold presents at 1x.

It engages only when the full-VRAM surface cannot hold the request (driver
limit or budget), sized so a 320-wide window fits both; up to 16x on the
M4 nothing changes. PSX_GL_HIRES_WINDOW=0/1 disables or forces it. The
launcher note and video_info (hr_scale, windowed, hires_window_*, hires_fbo)
report it.

Default-off: at 1x and whenever the full-VRAM surface fits, the paths are
the previous ones (same native-VRAM and present digests on a fixed scene at
1x and 2x).

Tests: gl_scale_invariance_test now also forces the window at 2/3/5/9x and
requires its frame at internal resolution to be byte-identical to the
full-VRAM surface at the same scale (copies inside, into and across the
window edge, fills/uploads across it, mask, all blend modes) with native
VRAM identical to 1x, and runs it at 18x; gl_scale_limits_test covers the
window clamp; guards pin that it only engages past the full-VRAM limit.
Verified in R4 on an M4: the 8K preset renders 5760x4320 in a race (rear-
view mirror present) and 5760x8640 on a 480i menu.

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

110 lines
5.9 KiB
C

/* gl_scale_limits.h: the OpenGL backend clamps an internal-resolution request
* to the driver's size limit and a memory budget instead of failing context
* init (which used to drop the whole backend to software). Pure arithmetic. */
#include "gl_scale_limits.h"
#include <stdio.h>
static int g_failures = 0;
static void expect_int(const char *what, long long got, long long want) {
if (got != want) {
printf("FAIL %s: got %lld want %lld\n", what, got, want);
++g_failures;
}
}
int main(void) {
const uint64_t MiB = 1ull << 20;
const uint64_t budget = (uint64_t)PSX_GL_DEFAULT_BUDGET_MB * MiB;
int why = -1;
/* Surface bytes: colour + depth-stencil, 8 B per internal pixel. */
expect_int("bytes 1x", (long long)psx_gl_full_vram_bytes(1), 4 * MiB);
expect_int("bytes 9x", (long long)psx_gl_full_vram_bytes(9), 324 * MiB);
/* Apple GL on Metal reports 16384: 16x is the largest full-VRAM scale. */
expect_int("16384 -> 16", psx_gl_clamp_full_vram_scale(18, 32, 16384, budget, &why), 16);
expect_int("16384 reason", why, PSX_GL_SCALE_TEXTURE);
expect_int("16384 fits 16", psx_gl_full_vram_fits(16, 16384, budget), 1);
expect_int("16384 fits 17", psx_gl_full_vram_fits(17, 16384, budget), 0);
/* 32768-limit GPUs: 18x fits the texture limit; the 2 GiB budget allows 22. */
expect_int("32768 -> 18", psx_gl_clamp_full_vram_scale(18, 32, 32768, budget, &why), 18);
expect_int("32768 reason", why, PSX_GL_SCALE_OK);
expect_int("32768 budget", psx_gl_clamp_full_vram_scale(32, 32, 32768, budget, &why), 22);
expect_int("32768 budget reason", why, PSX_GL_SCALE_BUDGET);
/* Ceiling. */
expect_int("ceiling", psx_gl_clamp_full_vram_scale(40, 32, 0, 0, &why), 32);
expect_int("ceiling reason", why, PSX_GL_SCALE_CEILING);
/* Preset requests that fit stay exact. */
const int presets[] = { 1, 3, 5, 6, 9, 12 };
for (unsigned i = 0; i < sizeof presets / sizeof presets[0]; i++) {
expect_int("preset exact", psx_gl_clamp_full_vram_scale(presets[i], 32, 16384, budget, &why),
presets[i]);
expect_int("preset reason", why, PSX_GL_SCALE_OK);
}
/* Never below 1, whatever the limits. */
expect_int("tiny limit", psx_gl_clamp_full_vram_scale(9, 32, 512, budget, NULL), 1);
expect_int("zero req", psx_gl_clamp_full_vram_scale(0, 32, 16384, budget, NULL), 1);
/* Unknown limit (0) and no budget: only the ceiling applies. */
expect_int("unknown", psx_gl_clamp_full_vram_scale(20, 32, 0, 0, NULL), 20);
/* Native-wide surfaces: widest width that still fits the limit. */
expect_int("wide 16x", psx_gl_max_wide_width(16, 16384), 1024);
expect_int("wide 18x", psx_gl_max_wide_width(18, 16384), 910);
expect_int("wide too tall", psx_gl_max_wide_width(40, 16384), 0);
/* Native-wide width at a 320-px display, as gpu.c rounds it. */
expect_int("nw 4:3", psx_gl_native_wide_width(320, 4, 3), 320);
expect_int("nw 16:9", psx_gl_native_wide_width(320, 16, 9), 426);
expect_int("nw 21:9", psx_gl_native_wide_width(320, 21, 9), 560);
expect_int("nw 32:9", psx_gl_native_wide_width(320, 32, 9), 854);
/* Fit to Window narrows only an aspect whose surface would not fit. */
{
int n = 32, d = 9; /* 1600x450 at 18x: 854 <= 910, unchanged */
expect_int("fit 32:9 at 18x", psx_gl_fit_wide_aspect(320, 910, &n, &d), 0);
expect_int("fit 32:9 num", n, 32);
n = 6; d = 1; /* 2400x400 at 18x: narrowed to 303:80 (~34:9) */
expect_int("fit 6:1 at 18x", psx_gl_fit_wide_aspect(320, 910, &n, &d), 1);
expect_int("fit 6:1 num", n, 303);
expect_int("fit 6:1 den", d, 80);
expect_int("fit 6:1 width", psx_gl_native_wide_width(320, n, d), 910);
n = 40; d = 9; /* 16x: 1024 columns, 341:80 (~38:9) */
expect_int("fit 40:9 at 16x", psx_gl_fit_wide_aspect(320, 1024, &n, &d), 1);
expect_int("fit 40:9 width", psx_gl_native_wide_width(320, n, d), 1024);
n = 32; d = 9; /* a 512-px display at 18x */
expect_int("fit 512 32:9", psx_gl_fit_wide_aspect(512, 910, &n, &d), 1);
expect_int("fit 512 width", psx_gl_native_wide_width(512, n, d), 910);
n = 16; d = 9; /* no room for a margin: 4:3 */
expect_int("fit none", psx_gl_fit_wide_aspect(320, 321, &n, &d), 1);
expect_int("fit none num", n * 3 == d * 4, 1);
/* Every aspect from 4:3 to 12:1 at 320 and 512 px lands within the limit. */
for (int w = 320; w <= 512; w += 192)
for (int num = 4; num <= 36; num++) {
int a = num, b = 3;
psx_gl_fit_wide_aspect(w, 910, &a, &b);
if (psx_gl_native_wide_width(w, a, b) > 910) {
printf("FAIL fit sweep %d %d:3 -> %d:%d\n", w, num, a, b);
++g_failures;
}
}
}
/* Windowed mode: 512 rows bound S at 32 on a 16384 limit; 8K (18x) fits,
* as does the 2 GiB budget for a 320-wide window (405 MiB at 18x). */
expect_int("window 18x", psx_gl_clamp_window_scale(18, 32, 16384, budget, 320), 18);
expect_int("window 40x", psx_gl_clamp_window_scale(40, 32, 16384, budget, 320), 32);
expect_int("window bytes 18x", (long long)(psx_gl_window_bytes(18, 320) >> 20), 405);
expect_int("window 8192 limit", psx_gl_clamp_window_scale(18, 32, 8192, budget, 320), 16);
expect_int("window budget", psx_gl_clamp_window_scale(18, 32, 16384, 40 * MiB, 320), 5);
/* Budget override parsing. */
expect_int("env null", (long long)psx_gl_budget_bytes_from_env(NULL), (long long)budget);
expect_int("env 64", (long long)psx_gl_budget_bytes_from_env("64"), (long long)(64 * MiB));
expect_int("env 0", (long long)psx_gl_budget_bytes_from_env("0"), 0);
expect_int("env junk", (long long)psx_gl_budget_bytes_from_env("12x"), (long long)budget);
if (g_failures) return 1;
printf("gl_scale_limits: all checks passed\n");
return 0;
}