fix(runtime): present preserves the display aspect instead of pillarboxing narrow buffers - #177
Open
smmathews wants to merge 1 commit into
Open
fix(runtime): present preserves the display aspect instead of pillarboxing narrow buffers#177smmathews wants to merge 1 commit into
smmathews wants to merge 1 commit into
Conversation
…oxing narrow buffers The present step scaled the guest's decoded display buffer to fit the host window at its native 1:1 pixel aspect and centered it. A buffer as wide as the window was a no-op, but a narrower buffer -- e.g. a 512-column DISPLAY/DISPFB buffer in the 640-wide window -- was shrunk to its own column count and padded with black side bars instead of filling the display the way a full-width buffer does. The buffer's column count is not the display's width: the GS scans any column count across the full display region, so the picture should occupy the whole display. Drive the destination rectangle from the intended display region -- the full display width at the current decoded height -- rather than the buffer's column count, via computePresentDstRect(), which fits that region into the window preserving its aspect and centering it. A narrow buffer now fills the display region, and the window at native size, exactly as a full-width buffer does; when the window's shape differs from the display's (a resized desktop window, or the 960x544 Vita window versus the 640x448 region) the region is letterboxed rather than stretched, so the picture keeps its aspect on every platform. The source rectangle and UploadFrame's decoding are unchanged. The geometry is a graphics- type-free header so it can be unit-tested without a live presentation window.
smmathews
marked this pull request as ready for review
July 22, 2026 17:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PS2Runtime::run()'s present step built its destination rectangle from the decodedbuffer's own pixel dimensions: it scaled by
min(screenWidth / srcWidth, screenHeight / srcHeight)and centred the result. For abuffer as wide as the window that is a no-op, but any display buffer narrower than
the window was shrunk to its own column count and padded with black down both sides.
A title programming a 512-column display buffer rendered correct content, pillarboxed,
in the 640-wide window — where a 640-column buffer already filled it.
Fix
The buffer's column count is not the display's width. The GS scans a
DISPLAY/DISPFBbuffer of any column count across the full display region, so a 512-column buffer is the
same full-screen picture as a 640-column one, produced from fewer columns.
computePresentDstRect(dispWidth, dispHeight, screenWidth, screenHeight)now fits thedisplay region —
FB_WIDTHat the current decoded height — into the window,preserving that region's aspect and centring it. The source rectangle still samples the
decoded buffer, so a narrow buffer is stretched across the region and fills the window
exactly as a full-width one does.
UploadFrame's decoding and the source rectangle areunchanged; only the destination rectangle moves.
The helper lives in
ps2xRuntime/include/runtime/present_layout.h, which includes only<algorithm>— no windowing or graphics type — so the geometry is testable without alive presentation window.
No title check and no per-game dimension: one destination-rectangle computation that runs
identically for every frame.
Basis
The GS scans a display buffer across the full display region regardless of its column
count. Treating the column count as the picture's on-screen width is what pillarboxed
narrow buffers.
Testing
PresentLayoutsuite, inps2xTest/src/present_layout_tests.cpp:the window
Build and run:
Mutation evidence: which mutation falsifies which property
Each is a one-line change to
computePresentDstRect, applied alone.{0, 0, screenWidth, screenHeight}unconditionally (pure window fill){0, 0, 0, 0}The first row is why the degenerate cases need their own guard mutation: a pure-fill
mutation cannot distinguish them.
Risk and not in scope
agree when the window already matches the display aspect (640x448).
the fixed 960x544 Vita window against a 640x448 region — the region is letterboxed or
pillarboxed rather than stretched, so the picture keeps its aspect on every platform.
An earlier iteration that simply filled the raw window horizontally stretched 640x448
content on the Vita screen; preserving the display aspect avoids that.
edge-to-edge instead of inset with black bars. That observation is not reproducible
from this repository.