fix(windows): stop presents outrunning the display, cap Skia's cache, fix deleteShader - #160
Merged
Merged
Conversation
Swapchains are created with a frame-latency waitable object, so Present no longer throttles, and nothing waited on the object. Presenting faster than the display queued frames (and the GPU work and memory each holds) without bound: a busy canvas grew by ~100 MB/s. A present now checks the waitable without blocking and is skipped when the display hasn't taken the queued frames. The frame scheduler keeps that context dirty and flushes it again on the next requestAnimationFrame, so no frame rate is capped and nothing blocks the UI thread. After 250 ms of skipped presents, one goes through anyway.
Every D3D canvas on a thread shares one DirectContext, which kept Skia's default 256 MB resource cache and never purged it. Its buffers live in D3D12 upload heaps, which grew to 480 MB in the demo app. The budget is now 64 MB, and resources unused for 5 s are freed (checked at most once a second while presenting): the demo app's home screen went from ~770 MB to ~385 MB.
The binding declared a WebGLRenderbuffer, so every deleteShader(shader) threw 'Value is not an instance of class WebGLRenderbuffer' (three.js and PixiJS both call it after linking).
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
triniwiz
added a commit
that referenced
this pull request
Sep 28, 2026
triniwiz
added a commit
that referenced
this pull request
Sep 28, 2026
* feat(gamepad): Gamepad API for iOS, Android and Windows New @nativescript/canvas-gamepad package: navigator.getGamepads() plus gamepadconnected/gamepaddisconnected, standard mapping, up to 4 pads. Native code writes controller state into one shared Float32Array as input arrives; getGamepads() reads it in TS, so polling makes no native calls on iOS/Android and one per frame on Windows. - iOS/tvOS/visionOS: GameController GCExtendedGamepad (ObjC) - Android: KeyEvent/MotionEvent taken at the activity's Window.Callback - Windows: Windows.Gaming.Input poller (crates/canvas-gamepad-napi) - canvas-polyfill: getGamepads() and window events use the package when installed; events go through the iOS runtime's own EventTarget * build(gamepad): Windows canvasgamepad.node for x64 and arm64 * build(windows): rebuild canvasnative.node with #160 * feat(gamepad): name Bluetooth Xbox pads "Xbox Wireless Controller" like Chrome * chore(demo): use core and webpack from NativeScript 54125e1 * chore: 3.0.0-alpha.16
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.
Three Windows fixes found while building a demo app on
3.0.0-alpha.15(2D, WebGL, WebGPU, three.js, PixiJS).Don't present faster than the display
Swapchains are created with
DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, which stopsPresentfrom throttling, but nothing waited on the object.requestAnimationFrameruns at ~66–70 fps here against a 59 Hz display, so frames and the GPU work each holds queued without bound: an opaque canvas running a busy 2D demo grew by ~100 MB/s, reached 21 GB and went black.CompositionSwapChain::acquire_frame()checks the waitable without blocking. When the display hasn't taken the queued frames, the 2D (present_d3d) and WebGL (egl.rs) presents are skipped. The drawing stays in the canvas's own surface or texture. The frame scheduler keeps that context dirty on a retry list and flushes it again fromrequestAnimationFrame, so nothing blocks the UI thread and no frame rate is capped. After 250 ms of skipped presents, one goes through anyway, so a canvas can't stop updating for good.No JS or C ABI changes. The skip reaches the scheduler through a thread-local in
dxgi.rs, since flushes are synchronous on one thread.A blocking wait was tried first. It capped fps at the display rate and made an intermittent startup stall of the XAML dispatcher much more likely (3 of 4 launches, against ~1 in 8 without the wait).
Cap and purge the shared Skia resource cache
All D3D canvases on a thread share one
DirectContext, which kept Skia's default 256 MB resource cache and never purged it. Its buffers live in D3D12 upload heaps, which grew to 480 MB (the allocator's 32 + 64 + 128 + 256 MB blocks). The budget is now 64 MB, and resources unused for 5 s are freed, checked at most once a second while presenting.deleteShadertakes aWebGLShaderThe binding declared a
WebGLRenderbuffer, so everydeleteShader(shader)threwValue is not an instance of class WebGLRenderbuffer. three.js and PixiJS both call it after linking, so neither started.Testing
Windows 11, x64, Intel Iris Xe, 59 Hz,
release-napibuild of this branch in the app:Still open, not addressed here: creating and destroying three.js and PixiJS canvases retains roughly 15 MB per cycle. 2D, WebGL shader and WebGPU canvases return to baseline.