Test-Branch (no testing needed)#380
Conversation
…aceControl
Adds an opt-in per-container "Direct Composition" toggle that, when active
on API 29+, routes fullscreen direct-scanout drawables to a child
ASurfaceControl layer bound to the XServerView's SurfaceView. SurfaceFlinger
+ HWC promote the layer to a hardware overlay plane (Snapdragon DPU
scanout), bypassing in-process GLRenderer composition entirely for
qualifying frames. Verified end-to-end on a OnePlus 15 (Android 16,
Adreno X2-class DPU) running a vkd3d-proton title — SurfaceFlinger
dumpsys shows composition type=DEVICE on the winnative-direct-composition
layer with continuous buffer updates and HWC scaling the native game
frame to display resolution.
Phase 1 — toggle plumbing:
* Container.EXTRA_DIRECT_COMPOSITION + isDirectCompositionEnabled /
setDirectCompositionEnabled accessors backed by existing extraData
JSON; default off, no migration needed.
* ContainerSettingsComposeDialog reads/writes the setting; new
container creation also sets it on the freshly-built Container.
* GameSettingsStateHolder.directComposition + a SettingCheckbox in
the container-edit UI (gated on isContainerEditMode so shortcut
sheets don't show it).
* Two new strings: session_display_direct_composition +
session_display_direct_composition_summary.
Phase 2 — native + lifecycle + push path:
* cpp/winlator/surface_compositor.c — dlopen-resolved JNI wrappers
around ASurfaceControl + ASurfaceTransaction (libandroid.so, API
29+). Probe (nativeIsAvailable), attach/detach (createFromWindow +
reparent-to-null + release), setColor (proof-of-life), pushBuffer
(setBuffer + modern setPosition/setScale/setCrop or fallback
setGeometry, with framework-owned fence FD), allocateTestBuffer +
releaseBuffer (256x256 magenta smoke test, RGBA_8888 +
GPU_SAMPLED_IMAGE + COMPOSER_OVERLAY usage, with retry-without-
OVERLAY for grallocs that reject the combo).
* SurfaceCompositor.java — static isAvailable() probe with API-29
short-circuit + cached-availability + UnsatisfiedLinkError guard.
* DirectCompositionLayer.java — instance class wrapping a single
ASurfaceControl, all public methods synchronized so UI-thread
release() and GLThread pushBuffer() can't race the native pointer.
* Drawable.scanoutSource + directScanout fields made volatile for
cross-thread visibility between the X-server worker and GLThread.
* Drawable.acquireFenceFd as AtomicInteger with takeAcquireFenceFd
(read-and-clear) + setAcquireFenceFd (closes prior FD on overwrite)
— forward-compatible API for future X11 Present wait_fence
plumbing; today always -1, documented as empirical observation
rather than a Mesa contract.
* GPUImage.getHardwareBufferPtr() — public accessor for the
underlying AHardwareBuffer*, used by the renderer hook.
* GLRenderer.directCompositionTarget (volatile) +
setDirectCompositionTarget setter; per-frame
maybePushDirectComposition under Drawable.renderLock with
last-pushed cache to avoid per-frame transaction storm + a
consecutive-failure counter that self-detaches the target after
DC_FAIL_LIMIT to stop wasting JNI calls on permanent failure;
maybeHideDirectComposition on direct->fallback transition,
idempotent and cache-invalidating.
* XServerDisplayActivity.installDirectCompositionLifecycle —
SurfaceHolder.Callback registered against XServerView's holder;
if the surface is already valid by the time we register
(GLSurfaceView's GLThread can fire surfaceCreated before setupUI
returns), synthesize the initial dispatch so we don't miss the
first attach; on surfaceDestroyed clear the renderer pointer
BEFORE releasing the layer; onDestroy removes the callback,
clears the renderer pointer, releases layer + test buffer, in
that order.
* Magnifier-UI overlay forces fallback (and immediate hide) so the
GL-rendered overlay isn't hidden under the SC layer at z=1.
Phase 3 — flicker / fence / overlays:
* Reverted Phase 2.5's GL-skip optimization. The actual perf win is
HWC overlay-plane scanout (DPU instead of GPU), not skipping the
GL render. Always-render keeps the GLSurfaceView backbuffer in
lockstep with the SC layer, so a stale-frame reveal during
direct->fallback transition is impossible — whatever SF reveals
when SC hides is the same frame the user is already seeing.
* Cursor handling: cursor is hidden by the activity for fullscreen
contexts (renderer.setCursorVisible(false) at setupUI), and
direct mode requires fullscreen, so cursor-under-SC is moot in
practice.
Containment:
* Toggle off -> zero behavior change vs. pre-DC. Same code path as
today.
* NDK unavailable (API < 29 or stripped libandroid) -> silent
fallback to GL path, no crashes. dlopen + dlsym means the shared
library still loads on minSdk 26.
* SurfaceControl.isAvailable cached after first probe.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f375f16a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (xServerView != null) { | ||
| xServerView.post(XServerDisplayActivity.this::pushDirectCompositionSmokeTest); | ||
| } else { |
There was a problem hiding this comment.
Remove unconditional Direct Composition smoke-test push
Calling pushDirectCompositionSmokeTest() on every surfaceCreated makes a visible magenta test buffer part of the normal runtime path whenever Direct Composition is enabled. For sessions that never hit the fullscreen direct-scanout path (or where maybePushDirectComposition returns false), that buffer is never explicitly transitioned out because GLRenderer.maybeHideDirectComposition() only hides when dcLayerActive was set by a successful direct push, so users can end up with a persistent overlay artifact in production gameplay/UI.
Useful? React with 👍 / 👎.
…ition layer
Some games rendered visibly brighter through the new SurfaceControl path
than via the legacy GLRenderer composition. Root cause is Snapdragon DPU's
mixed-SDR/HDR composition pipeline: SDR layers without explicit
dataspace / opacity / extended-range metadata are routed through an
HDR-aware path that applies a midtone boost via vendor-tuned LUTs. The GL
composition path bypasses that pipeline; the SC path engages it.
Fix: assert three previously-implicit metadata properties on every
pushBuffer transaction so HWC takes the legacy SDR composition path.
surface_compositor.c
* Resolve three new optional symbols via dlopen/dlsym:
ASurfaceTransaction_setBufferDataSpace (API 29)
ASurfaceTransaction_setBufferTransparency (API 29)
ASurfaceTransaction_setExtendedRangeBrightness (API 34)
Each is independent of the mandatory availability gate — missing
symbols produce a one-shot init log and skip the call rather than
failing the whole DC path.
* nativePushBuffer gains a `jboolean opaque` parameter and, after
setBuffer, calls (when each symbol resolved):
setBufferDataSpace(SRGB) — explicit sRGB tag, no
speculative re-encode
setBufferTransparency(OPAQUE/TRANSL) — opaque skips alpha-blend
stage, OPAQUE only when
caller asserts alpha=1.0
setExtendedRangeBrightness(1.0, 1.0)— assertively opts the layer
out of the SDR-on-HDR
boost path
* One-shot diagnostic log at init prints which colour symbols
resolved so a post-deploy logcat can distinguish "fix didn't apply"
from "fix applied, vendor pipeline still boosting" without rebuild.
DirectCompositionLayer.java
* pushBuffer signature gains `boolean opaque`. Javadoc explains the
Snapdragon DPU rationale and warns against passing true on
translucent buffers.
GLRenderer.maybePushDirectComposition
* Passes opaque=true — DXVK / vkd3d-proton swap-chain frames are
RGBA8888 with alpha=1.0 throughout by Vulkan WSI convention.
XServerDisplayActivity
* pushDirectCompositionSmokeTest: passes opaque=true (magenta swatch
is fully opaque) and shrinks the swatch from 256x256 to 128x128
(user feedback — less obtrusive while DC is still a developer
toggle).
Sharpness side-effect: the same HDR-aware pipeline is responsible for
some of the vendor's content-adaptive enhancement (perceived "too sharp"
look). Neutralising it via the three calls above is expected to reduce
both the brightness mismatch and the residual sharpness simultaneously.
Containment: zero impact when the toggle is off; on devices missing any
of the three new symbols the path degrades to the prior (brighter)
behaviour with a single one-shot log line. JNI signatures match;
reviewer pass cleared.
…aceControl
Adds an opt-in per-container "Direct Composition" toggle that, when active on API 29+, routes fullscreen direct-scanout drawables to a child ASurfaceControl layer bound to the XServerView's SurfaceView. SurfaceFlinger
Phase 1 — toggle plumbing:
Phase 2 — native + lifecycle + push path:
Phase 3 — flicker / fence / overlays:
Containment: