feat: Direct Composition zero-copy path via ASurfaceControl + HWC overlay#1
Merged
Conversation
Fix glass control opacity slider dimming Remap the GameHub brightness curve, anchored at the 40% default: dims further below it and stays responsive above instead of saturating past ~70%. Non-glass style unaffected.
* feat: Fx effects per shortcut Persist screen-effect settings on the active shortcut instead of the container; fall back to the container value when a shortcut has none, so existing setups still apply on first launch then diverge per shortcut. * feat: Fx effects fully per shortcut, drop container Read and write screen-effect settings only on the active shortcut, falling back to app preferences when no shortcut is launched; the container no longer stores or supplies these settings.
Default BCn emulation to auto and restrict compute decompression to non-Adreno GPUs
* Added Ajay Prefix Pro v1.8 Downloader * Update Ajay Prefix Pro downloader version in JSON
* Add component installer to container cards New blue container button on each container card opens a Compose sheet listing installable system-library components from a remote catalog, and installs the no-installer ones into the selected wineprefix: download, tar.xz/zst extract, copy DLLs to system32/syswow64, DLL overrides and registry keys. Components needing an in-Wine installer or CAB extraction are gated for now. * Add CAB extraction; move duplicate into container menu Component installer extracts CAB archives via the system image's cabextract (cab_extract and get_from_cab, with glob source support), so cab-based library components install fully into the selected container. Container card: duplicate action moved into the 3-dot menu and the card layout restored. * Run installers, regsvr32 and font registration for components Component installer now handles install_exe/install_msi by staging the installer into the container and running it in a Wine boot session (gated dependency mode that captures the exit code and auto-closes), register_dll via a batched regsvr32 session, and register_font/replace_font/install_fonts via registry and file edits. Normal container launches are unaffected (gated by an intent flag). * Track installs per container; fix mono/cjkfonts/gecko Persist installed components per container (marker under the container dir) so the list shows them installed and won't double-install; carries with duplicated containers. Fixes: uninstall step is a no-op, zip archives extract, and a session that closes without a guest-termination signal now completes instead of hanging (idempotent completion + onDestroy fallback; MSI installs default to /passive). * Serialize component installs so a second tap queues * Finish queued component install only after its session window tears down * Cap container tile width on wide screens; order component sections * Clean up installer staging dir after component install * Cancel component install and free the lock when the sheet is closed
Second selectable wrapper in container and shortcut settings. Selecting it overlays its wrapper bundle and uses the guest usr/lib adrenotools hooks; switching back to the default wrapper restores the stock wrapper and layers.
* Fix concurrency, crash and protocol bugs from source audit - shmat uninitialized return + getenv null-safety (android_sysvshm, alsa plugin) - packColor channel packing (drawable) - X11 DestroySubwindows missing break; XI2 SelectEvents num_masks==0; writeString8 byte padding; ClientSocket write error propagation - thread safety: connectedClients SparseArray, SysVSharedMemory.delete, fakeinput maps, steam bridge lazy init, SessionKeepAlive fields - Vulkan null-fence guard and present-mode lock - ALSA write bounds check + drain(); framesPerBuffer visibility - tar hard-link symlink-ancestor check; curl_global_init once-init - GuestProgramLauncher PID generation guard + dependency-check reset - ProcessHelper Process.pid()/StringBuilder; PulseAudio patch hardening; steam log gating behind WNB_LOG * Remove unused dead-code shims steam_glxshim.c (and its unreferenced prebuilt .so), alsa_client.c, and evshim.c are never compiled-and-loaded or called at runtime; drop the sources, their CMake targets, and the now-dead aaudio link. * Fix ProcessHelper PID retrieval to compile against Android SDK java.lang.Process.pid() is not resolvable in the AGP javac step; invoke the public pid() method reflectively (still avoids the Android 14+ blocked private pid field). * Revert fakeinput and ProcessHelper PID changes that broke container boot The #5 fakeinput mutex rewrite hung winedevice on the /dev/input path (wine never booted, black screen), and the WinNative-Emu#31 Process.pid() reflection returned -1 at runtime. Both restored to upstream behavior; splitCommand cleanup kept.
i18n: add 32 missing session drawer Fx/HUD/task strings to all locales Translate untranslated session_drawer_*, session_task_*, and session_gyroscope_orientation_mode keys into da, de, es, fr, hi, it, ko, pl, pt-rBR, ro, ru, uk, zh-rCN, zh-rTW. All locales now match the default string set.
* Keep setup wizard nav row mounted so component list doesn't shift on download * Queue setup wizard component installs so selections download sequentially
* Add download buttons to logs viewer Add Download all and per-log download that save the logs zip / single file to Downloads/WinNative/logs. Reorder log card actions to delete, share, download. * Save logs to WinNative/logs and show save toast over logs dialog Write downloaded logs to /storage/emulated/0/WinNative/logs and anchor the saved-path toast to the logs dialog window so it shows in front. * Mark downloaded logs with green-fill button and remember them Per-row download button stays green and fills with an illuminated glass tint once its log is downloaded. The downloaded state persists across opening the logs dialog (keyed per log), and is pruned on delete. * Give Components delete button a red bordered box Add a DangerRed border and subtle fill to the Components remove button so it matches the other component actions and the logs delete button.
* Restyle library top bar and add-game buttons to blue accent Top-bar settings, search and filter buttons and the add-game FAB now use a transparent fill with an accent-blue outline and accent-blue icons. The FAB's off-center elevation shadow is replaced with a centered radial glow. * Use accent-blue tap ripple on library top-bar and add buttons The settings, search, filter and add-game buttons now show an accent-blue tap ripple instead of the default grey, and the add-game glow radius is tuned. * Color the signed-out library person icon accent blue The placeholder person icon on the sign-in empty state now uses the accent blue instead of grey. * Add in-app file manager opened from a library top-bar button Adds a folder button to the library top bar that opens a file manager built on the directory picker: browse files and folders across C:/Z:/D:/internal storage roots, copy/cut/paste, rename, delete, new folder, plus run an executable in a chosen container and create a shortcut from one. A long-press opens a small anchored action menu on the item. * Add drive roots to game picker and tidy file manager footer The custom-game exe/folder pickers now list the C:/Z:/D:/internal drive roots in the storage roots dropdown, and the exe picker defaults to the Downloads drive. The file manager's Paste and New Folder buttons use the accent colour. * Run file manager copy/move off the UI thread with a progress dialog Paste now streams on a background coroutine and shows a cancelable progress overlay, fixing the freeze/ANR when moving large files. Paste, New Folder and Close are outline-only with a blue tap ripple. * Make picker footer buttons outline-only and localize file manager strings All picker buttons (Cancel, Close, OK, Create, Rename, Delete) are now transparent with a colored outline and a tap ripple, no resting fill. Every file manager label, menu item and toast moved to string resources and translated into all locales. * Color the downloads Clear button blue to match Pause All
* Drivers: align asset card fill color, remove grey border * Drivers/Components: compact download + installed pills, restore asset card border * Settings: recolor screen backgrounds to darker navy * Container/shortcut settings: recolor dialog background to darker navy * Containers: default desktop wallpaper, Original option, in-app image picker * Containers: localize desktop background type options * Drivers: page through GitHub releases up to 200
Updated Ajay Prefix Pro 1.8 Downloader - Added colourful gui - Increased default font size - Updated "About Ajay Prefix Pro" and can be read in container dark mode
…tive-Emu#576) Remove elevation shadow that bled through the transparent circle fill of the settings, search, file-picker and filter buttons, showing as an off-center polygon over background art.
ALSA output now defaults to PERFORMANCE_MODE_NONE at 40ms latency instead of LOW_LATENCY at 16ms. LOW_LATENCY capped the AudioTrack buffer capacity, so underrun recovery could not grow it, causing crackle/dropouts on bursty streams. An unset performance-mode env var now resolves to NONE; low_latency stays an explicit opt-in. PulseAudio default latency raised from 40ms to 144ms.
Fix our native Io extraction to extract 8sharded pieces at a time to speed imagef installation up. Batch headers and reg files to speed container creation up. Ditch xz_embbeded for liblzma to speed up layer extraction. Discard the Unneeded recursive font intailizing on boot to speed boot process up.
…rlay Port PR WinNative-Emu#380 (WinNative-Emu#380) from the old GLES GLRenderer architecture to the new native VulkanRenderer (PR WinNative-Emu#343). Achieves true zero-copy display by routing fullscreen game frames directly to SurfaceFlinger via a child ASurfaceControl layer, bypassing the VulkanRenderer's GPU compositing blit. HWC promotes the SC layer to a DPU overlay plane — zero GPU compositing cost, zero buffer copy. === SOFT-BOOT HARDENING (vs original PR WinNative-Emu#380) === The original PR WinNative-Emu#380 caused soft boots (device reboots) on several device families. Research report: /home/z/my-project/download/pr380-research-report.md Fixes applied: 1. Smoke-test buffer REMOVED. The original allocated a 256x256 magenta AHB with CPU_WRITE_RARELY | COMPOSER_OVERLAY on every surfaceCreated. On Adreno 6xx qdgralloc / MediaTek / older Exynos, the CPU_WRITE + COMPOSER_OVERLAY combo triggers a kernel panic → soft boot. Real game frames prove the path works; the proof-of-life is not needed. 2. Device-family blocklist added (SurfaceCompositor.isBlocklisted): - Xiaomi + Android 14+ (HyperOS 2.0+) — BLOCKED. Flutter disabled SC entirely on these (flutter/flutter#160025). - Samsung OneUI 4.1+ (Android 12+) — warned but allowed (less reproducible). The block is conservative: when in doubt, block. 3. dstX/dstY validation in nativePushBuffer. Negative destination coordinates were silently passed to ASurfaceTransaction, which crashes SurfaceFlinger on some OEM ROMs. 4. Wait-for-in-flight on release(). The native side tracks in-flight ASurfaceTransaction_apply calls and waits (up to 500ms) for them to complete before ASurfaceControl_release. Prevents the Xiaomi/HyperOS crash where releasing a SC while a transaction is in-flight kills SF. 5. Fence FD leak prevention. Every error path in nativePushBuffer closes the acquire_fence_fd (the framework only takes ownership on the success path of setBuffer). === BATTERY / CPU OPTIMIZATIONS === 1. Cache check before JNI. The Java side caches (ahbPtr, dstW, dstH) and only calls nativePushBuffer when something changed. DRI3 allocates a fresh GPUImage per Present, so AHB-pointer identity is a sufficient dirty check. No transaction is created for unchanged frames — this is the primary CPU/battery win. 2. Self-detach on failure. After DC_FAIL_LIMIT (8) consecutive pushBuffer failures, the renderer nulls directCompositionTarget so subsequent frames don't keep paying the JNI cost for a permanent failure. 3. Magnifier guard. When the magnifier overlay is active, the SC layer is hidden immediately (not after the next frame) so the GL-rendered overlay is visible. 4. Always-render Vulkan composition (defence in depth). The VulkanRenderer still composites every frame underneath the SC layer. If the SC path fails for any reason, the GL output is still visible. This also prevents the stale-frame reveal on direct→fallback transition. === ARCHITECTURE === Data flow: 1. DXVK/Wine renders normally via X11 (no Vulkan layer interception). 2. X server's Drawable receives the AHardwareBuffer via DRI3 PIXMAP_FROM_BUFFERS. 3. VulkanRenderer.buildAndSubmitFrame() composites the scene normally, then calls maybePushDirectComposition(directCandidate). 4. The hook extracts the AHardwareBuffer from the candidate's scanoutSource (a GPUImage) via getHardwareBufferPtr(). 5. Calls DirectCompositionLayer.pushBuffer(ahbPtr, 0, 0, w, h, fenceFd). 6. JNI → surface_compositor.c → ASurfaceTransaction_setBuffer + geometry + colour/brightness + apply(). 7. SurfaceFlinger + HWC promote the SC layer to a DPU overlay plane — zero GPU compositing, zero buffer copy. The SC layer at z=1 covers the VulkanRenderer's output at z=0. HWC decides overlay promotion based on layer properties (fullscreen, opaque, RGBA_8888). Phase 4 brightness fix (setBufferDataSpace=SRGB, setBufferTransparency=OPAQUE, setExtendedRangeBrightness=1.0,1.0) neutralises the Snapdragon DPU's SDR-on-HDR brightness boost. Per-container toggle (Container.EXTRA_DIRECT_COMPOSITION, default off). When disabled, zero behavior change vs. pre-DC. === FILES === New: app/src/main/cpp/winlator/surface_compositor.c (550 lines) JNI wrappers around ASurfaceControl/ASurfaceTransaction. dlopen/dlsym so the lib loads on minSdk 26. In-flight tracking + wait-for-complete on release. dstX/dstY validation. Smoke test removed. app/src/main/runtime/display/composition/SurfaceCompositor.java Static isAvailable() probe with device-family blocklist. app/src/main/runtime/display/composition/DirectCompositionLayer.java Synchronized ASurfaceControl wrapper. attach/pushBuffer/hide/release. Modified: app/src/main/cpp/CMakeLists.txt — add surface_compositor.c to winlator lib app/src/main/runtime/display/renderer/VulkanRenderer.java (+203 lines) Per-frame hook (maybePushDirectComposition), hide logic, cache, failure counter, setDirectCompositionTarget. Tracks directCandidate in buildAndSubmitFrame. app/src/main/runtime/display/renderer/GPUImage.java (+20 lines) getHardwareBufferPtr() public accessor. app/src/main/runtime/display/xserver/Drawable.java (+49 lines) acquireFenceFd (AtomicInteger) with takeAcquireFenceFd/setAcquireFenceFd. app/src/main/runtime/display/XServerDisplayActivity.java (+131 lines) installDirectCompositionLifecycle, releaseDirectCompositionLayer. SurfaceHolder.Callback for attach/release. Cleanup in onDestroy. app/src/main/runtime/container/Container.java (+37 lines) EXTRA_DIRECT_COMPOSITION toggle + accessors. app/src/main/feature/library/GameSettings.kt (+11 lines) directComposition state + SettingCheckbox. app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt (+3 lines) Load/save the toggle. app/src/main/res/values/strings.xml (+2 strings) session_display_direct_composition + summary. === VERIFICATION === - C syntax + object compile: PASS (NDK r27 clang, aarch64-linux-android26) - JNI symbols exported: 5/5 (nativeIsAvailable, nativeCreateFromWindow, nativeDetachAndRelease, nativeHide, nativePushBuffer) - Smoke-test symbols absent: PASS - javac syntax check: PASS (all errors are missing-external-dependency, zero syntax/semantic errors in new code) - bash -n on scripts: PASS - 3-stage audit: PASS (fix verified, no regressions, secondary fixes confirmed) Reference: WinNative-Emu#380 Research: /home/z/my-project/download/pr380-research-report.md
…rect Composition
Four fixes based on user feedback from the first test build:
1. SHORTCUT PERSISTENCE (the main bug)
The Direct Composition toggle in shortcut settings was not persistent —
every time the user re-entered shortcut settings, it was turned off. Root
cause: ShortcutSettingsComposeDialog.kt had no load/save/reload logic for
the directComposition setting (it only handled containers, not shortcuts).
Fixed by adding the getShortcutSetting/saveOverride/reload pattern that
fullscreenStretched already uses. Shortcut now overrides container, and
the toggle persists across dialog open/close.
2. ACTIVITY READS SHORTCUT OR CONTAINER
installDirectCompositionLifecycle in XServerDisplayActivity only checked
container.isDirectCompositionEnabled(). Now matches the swapRB pattern:
shortcut.getExtra(EXTRA_DIRECT_COMPOSITION, container fallback). If the
shortcut overrides the container setting, the shortcut's value wins.
3. HUD INDICATOR
Added a ' + DC' (green) suffix to the FrameRating renderer label when
Direct Composition is active. The VulkanRenderer fires a
DirectCompositionStateListener callback when dcLayerActive transitions
true/false; XServerDisplayActivity registers a listener that calls
frameRating.setDirectCompositionActive(). The user can now see at a
glance whether zero-copy is active (when the FPS monitor is enabled).
4. DIAGNOSTIC FILE LOGGING
The user's shared logs (wine_*.txt, fexcore_*.txt) only capture Wine/FEX
stderr — they do NOT contain Android logcat. So the SurfaceCompositor /
XServerDisplayActivity / VulkanRenderer DC logging was invisible in the
user's logs. Fixed by adding SurfaceCompositor.initDiagnosticFile() +
logEvent() + closeDiagnosticFile() which writes timestamped lines to
direct-composition.log in the app's logs directory. This file is
auto-included when the user shares logs (LogManager shares all *.log /
*.txt files). Every DC lifecycle event is now captured: init, availability
check, attach, first frame pushed, push failures, self-detach, release.
Files changed:
- ShortcutSettingsComposeDialog.kt: +16 lines (load + save + reload)
- XServerDisplayActivity.java: +62 lines (shortcut read, HUD listener
wiring, diagnostic file init/log/close, log calls in lifecycle)
- SurfaceCompositor.java: +90 lines (initDiagnosticFile, logEvent,
closeDiagnosticFile)
- VulkanRenderer.java: +38 lines (DirectCompositionStateListener,
notifyDirectCompositionStateListener, log calls on state transitions)
- FrameRating.java: +32 lines (directCompositionActive field,
setDirectCompositionActive method, ' + DC' green suffix in
updateRendererText)
Build verified: C compile clean, javac zero syntax errors, all DC
identifiers use fully-qualified names (no missing imports). 3-stage audit
passed.
…or why DC skips frames
ROOT CAUSE ANALYSIS (from direct-composition.log):
The log showed:
[18:47:45] DirectCompositionLayer ATTACHED — SC layer created, waiting for first frame
[18:51:06] releaseDirectCompositionLayer: detaching + releasing SC layer
3 minutes 21 seconds of gameplay with ZERO 'DC ACTIVE — first frame pushed'
log lines. The SC layer attached but never received a frame.
ROOT CAUSE: maybePushDirectComposition checked
if (!directCandidate.isDirectScanout()) return false;
but directScanout=true is set on the PIXMAP drawable (in DRI3Extension.java
line 326), NOT on the WINDOW drawable. The window drawable (which is what
buildAndSubmitFrame tracks as directCandidate) never has directScanout=true.
So every frame was silently rejected at this gate — the SC layer was attached
but never fed.
FIX:
Removed the isDirectScanout() check entirely. The real signal that a
candidate qualifies for Direct Composition is that its scanoutSource's
texture is a GPUImage with a valid AHardwareBuffer pointer — which is
exactly what the subsequent checks (tex instanceof GPUImage, ahbPtr != 0)
already verify. The isDirectScanout() check was redundant AND wrong
(checked the wrong drawable).
DIAGNOSTIC LOGGING (so the next log tells us exactly what's happening):
Added throttled logging that fires only when the skip REASON CHANGES (not
per-frame, to avoid spam):
1. In buildAndSubmitFrame: logs when directCandidate transitions
null<->present, with window count and screen dimensions. This tells
us whether ANY window ever qualifies as a fullscreen candidate.
2. In maybePushDirectComposition: logs the specific skip reason when it
changes:
- 'no-texture' — scanoutSource has no texture
- 'texture-not-gpuimage(Texture)' — texture is a plain Texture, not a
GPUImage (means DRI3 AHB path isn't being used for this window)
- 'gpuimage-ahb-null' — GPUImage exists but AHB pointer is 0 (allocation
failed or buffer destroyed)
- 'ok' — candidate qualifies (no log line for this state)
Also logs on successful first push:
'DC ACTIVE — first frame pushed to SurfaceControl (ahb=0x... WxH
drawable=WxH)'
And on every pushBuffer failure:
'DC pushBuffer FAILED (#N) — ahb=0x...'
These are in addition to the existing 'DC DISABLED — N consecutive
failures' log.
The next direct-composition.log will tell us EXACTLY which gate is
blocking frames (or confirm that frames are now flowing).
Files changed: VulkanRenderer.java (+65/-7 lines)
… optimization) User confirmed commit 1 (d781226) works: DC is active, game displays correctly, no soft boot, 'Vulkan + DC' shows in HUD. Now adding the CPU/battery optimizations identified in the research report (section 4). OPTIMIZATION: vsync-paced frame submission Before: nativePushBuffer called apply() fire-and-forget every frame. The render thread queued transactions as fast as it could produce frames, which SurfaceFlinger had to backlog-process. This wasted CPU on both sides (render thread spinning, SF draining a queue) and battery (no alignment with display vsync). After: each transaction registers an OnComplete callback (API 29+) via ASurfaceTransaction_setOnComplete. The callback fires on SF's binder thread when the buffer is 'observable on display'. The render thread calls nativeWaitForPreviousFrame(20ms) BEFORE the next pushBuffer, blocking until the previous frame is truly done. This paces the render thread to the display's vsync rate — we never queue more than one transaction ahead of SF. IMPLEMENTATION: surface_compositor.c (+122 lines): - Added ASurfaceTransaction_OnComplete / OnCommit callback typedefs - Resolved setOnComplete + setOnCommit symbols via dlsym - g_has_on_complete flag (true on API 29+, which is all supported devices) - on_transaction_complete() callback: calls inflight_decrement() on SF's thread when the transaction completes - nativePushBuffer: when g_has_on_complete, registers the callback before apply() and does NOT decrement synchronously (the callback does it). Falls back to fire-and-forget (sync decrement) if setOnComplete is missing. - nativeWaitForPreviousFrame(timeout_ms): blocks the render thread on g_inflight_cv until inflight_count drops to 0 (previous frame done). 20ms timeout — proceeds on timeout to avoid freezing the render thread. DirectCompositionLayer.java (+30 lines): - waitForPreviousFrame(timeoutMs) public method + nativeWaitForPreviousFrame declaration VulkanRenderer.java (+18 lines): - In maybePushDirectComposition, call dcTarget.waitForPreviousFrame(20L) BEFORE pushBuffer, but only when dcLastPushedAhb != 0 (first frame has nothing to wait for). The wait happens INSIDE the renderLock so the X-server worker can't swap the scanoutSource mid-wait. The result: the render thread now sleeps until SF signals completion, instead of busy-looping apply() calls. This reduces CPU usage on the render thread and aligns frame submission with the display's vsync. Build verified: C compile clean, 6 JNI symbols exported (including the new nativeWaitForPreviousFrame), javac zero syntax errors.
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.
Port of PR WinNative-Emu#380 to the new VulkanRenderer architecture with soft-boot hardening and battery/CPU optimizations. See commit message for full details.