Repository navigation
android: A51 — the scan-out image keeps the pool buffer's row stride - #20
Merged
Merged
Conversation
A scan-out image is allocated from the session's pool, whose rows are gralloc's: padded for widths gralloc does not like (1024 px -> 5120 B on Adreno), where a LINEAR Vulkan image is otherwise laid out at width * 4. Created through a DRM-format-modifier list, Turnip picks that tight pitch; the compositor reads the buffer at the buffer's stride, so every row the GPU writes lands a little further along than the row read back — the frame is sheared, repeated across the width, with the tail of the buffer never written. Only a composited frame writes this image. Direct scanout (one screen-sized layer, no filter that needs the shader, no look, no ReShade) is the guest's own buffer, which is why the fault appears exactly when NEAREST/FSR/NIS/SGSR forces vulkan_composite — measured on a 1024x768 session into a 1280x960 panel, where SurfaceFlinger reported the layer's stride as 5120 B while the composite wrote at 4096. At 1280 and 1920 wide the two pitches coincide, so no output this stack has run before could show it. The pitch is not derivable from the geometry, so A51 asks for it: the shim owns the pool and answers `gamenative_drm_scanout_pitch(w, h)` (gamenative-drm). When it answers, the image is created with an explicit LINEAR modifier at that stride instead of the modifier list, which also makes the AddFB2 export report the stride the compositor reads. The symbol is weak, so a real KMS session keeps the driver's own layout. android/patches/gamescope/0049-A51-scanout-image-at-the-pool-row-stride.patch The patch itself (rendervulkan.cpp): the weak declaration beside BInit, and the explicit-modifier create — the modifier list unlinked so the create chain carries one layout authority, with the external-memory and format-list nodes kept as the chain's tail; gated to output images with no imported dma-buf and no memory to reuse. Applies on top of the 48-patch train, and the train re-applies cleanly around it. android/check-payload-closure.sh libnativewindow.so joins the system allowlist: the ICD dlopens it for the guest's AHardwareBuffer receive path and it is present on every Android this payload runs on. The check was failing on that pre-existing edge, not on anything this change adds; RESULT is CLEAN again.
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.
What this fixes
A composited frame was sheared whenever the session's scan-out width was not a multiple of 320 px.
The scan-out image is allocated from the session's pool, whose rows are gralloc's — padded for widths gralloc does not like (
1024 px → 5120 Bon Adreno), where aLINEARVulkan image is otherwise laid out atwidth * 4. Created through a DRM format-modifier list, Turnip picks the tight pitch. The compositor reads the buffer at the buffer's stride, so every row the GPU writes lands further along than the row read back: the frame is sheared, repeated across the width, with the tail of the buffer never written.Only a composited frame writes this image. Direct scan-out (one screen-sized layer, no filter that needs the shader, no look, no ReShade) is the guest's own buffer — which is why the fault appeared exactly when
NEAREST/FSR/NIS/SGSRforcedvulkan_composite. Measured on a1024x768session into a1280x960panel: SurfaceFlinger reported the layer's stride as5120 Bwhile the composite wrote at4096. At 1280 and 1920 wide the two pitches coincide, so no output this stack has run before could show it.The change
The pitch is not derivable from the geometry, so
A51asks for it:gamenative_drm_scanout_pitch(w, h)(declaredweak, resolved fromgamenative-drm'slibgndrm_shim.so);LINEARmodifier at that stride instead of the modifier list — which also makes theAddFB2export report the stride the compositor reads;android/patches/gamescope/0049-A51-scanout-image-at-the-pool-row-stride.patchapplies on top of the 48-patch train, and the train re-applies cleanly around it.android/check-payload-closure.shalso gainslibnativewindow.soin its system allowlist: the ICDdlopens it for the guest'sAHardwareBufferreceive path and it exists on every Android this payload runs on. That check was failing on a pre-existing edge, not on anything added here —RESULT: CLEANagain.Verification
aarch64-linux-androidsucceeds with zero warnings; the train applies cleanly onto a clean 48-patch tree, and clean-train + A51 reproduces the working tree byte for byte.gamenative_drm_scanout_pitchisWEAK UND(as intended), andGLOBAL FUNCin the shim.check-payload-closure.sh:RESULT: CLEAN.1280x960@120Hz, Adreno 740 + Turnip 26.3.0-devel), every session logs the pitch it lays its scan-out image out at:1024x768sessions:output image 1024x768 at the pool's row stride 5120 (tight 4096)— the padded case, and the frame is now coherent withSGSRforcing the composite;1280x960/1280x720sessions:… 5120 (tight 5120)— unchanged, as they must be.geomBufferSize=[0 0 1024 768], bufferstride:5120 bytes, layer transform1.2500→1280x960,composition=DEVICE.Six device cases (container screen size × Upscaling Resolution × filter) were run end to end through the app — the full matrix and screenshots are in utkarshdalal/GameNative#2283, which is the app side of this feature.
Companion PRs
gamenative_drm_scanout_pitchexport this patch consumes.Depends on the shim export landing in gamenative-drm; without it the symbol stays unresolved and this patch is a no-op (the driver's own layout, i.e. the previous behaviour).