Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff986b5
feat(windows): D3D12 and ANGLE backends in canvas-core, patched napi-sys
triniwiz Sep 26, 2026
a755002
fix(windows): build canvas-2d, canvas-webgl and canvas-c for Windows
triniwiz Sep 26, 2026
da294d7
feat(canvas-napi): Node-API rewrite on napi-rs 3
triniwiz Sep 26, 2026
17c06b5
feat(canvas-napi): restore the napi-rs bindings, migrate to napi-rs 3
triniwiz Sep 26, 2026
3c801c1
feat(canvas-napi): images, text, DOMMatrix and module functions match…
triniwiz Sep 26, 2026
12c07c9
feat(windows): 2D on Direct3D 12, presented in a WinUI SwapChainPanel
triniwiz Sep 26, 2026
ac529af
feat(canvas-napi): frame scheduling and view layout for the Windows host
triniwiz Sep 26, 2026
a2a9ff1
feat(windows): the canvas view on NativeScript Windows
triniwiz Sep 26, 2026
76f148b
feat(windows): canvas-polyfill on Windows; svg, media and audio impor…
triniwiz Sep 26, 2026
9593c3f
feat(demo): run apps/demo on NativeScript Windows
triniwiz Sep 26, 2026
a09b603
test(canvas-napi): contract check against the V8 bindings
triniwiz Sep 26, 2026
b4a4c0e
feat(windows): WebGL and WebGL 2 on ANGLE in a SwapChainPanel
triniwiz Sep 26, 2026
ceffeee
feat(windows): WebGPU on wgpu DX12 in a SwapChainPanel
triniwiz Sep 26, 2026
71375ce
feat(windows): TS completeness, WebGL extensions on ANGLE, GPU device…
triniwiz Sep 26, 2026
44e7a22
feat(windows): canvas-svg on Node-API; canvas-polyfill gaps on Windows
triniwiz Sep 26, 2026
2f3f158
fix(windows): canvases take their style size; WebGL buffer calls take…
triniwiz Sep 26, 2026
9b48930
feat(windows): transparent canvases blend with the page
triniwiz Sep 26, 2026
b246f2d
perf(canvas-napi): raw fast members for the hottest calls; benchmark …
triniwiz Sep 26, 2026
b67c2d5
fix(windows): a lost 2D context is restored onto its GPU, not WARP
triniwiz Sep 26, 2026
a16c8f5
ci(windows): build the Node-API modules for x64 and arm64
triniwiz Sep 26, 2026
4972c27
build(windows): commit the x64 and arm64 Node-API modules
triniwiz Sep 26, 2026
e70798b
feat(windows): audio-context on Node-API (#155)
triniwiz Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
54 changes: 54 additions & 0 deletions .github/actions/setup-windows-native/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Setup Windows native build environment
description: >
Installs Rust stable for the given target plus a cargo cache, and points
skia-bindings' from-source build at the runner's LLVM (Skia on Windows has no
prebuilt binaries for the d3d feature, so tools/scripts/build-napi.sh builds
it, as .tools/env.sh sets up locally). The images already ship LLVM (clang-cl,
libclang), ninja, python 3 and the MSVC ARM64 build tools.

inputs:
target:
description: Rust target triple (x86_64-pc-windows-msvc or aarch64-pc-windows-msvc)
required: true

runs:
using: composite
steps:
# Skia's third_party tree (git-sync-deps) goes past MAX_PATH.
- name: Long paths
shell: bash
run: git config --system core.longpaths true

- name: Install Rust stable
shell: bash
run: |
rustup toolchain install stable --profile minimal --target ${{ inputs.target }}
rustup default stable
rustc --version

# MSVC's STL rejects clang older than 19. Installing LLVM over the image's one
# (choco) fails, so the image's is used as is.
- name: Skia toolchain
shell: bash
run: |
LLVM='C:\Program Files\LLVM'
{
echo "LLVM_HOME=$LLVM"
echo "LIBCLANG_PATH=$LLVM\\bin"
echo "CLANG_PATH=$LLVM\\bin\\clang.exe"
# the git CLI honours core.longpaths for the rust-skia checkout and its submodules
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
} >> "$GITHUB_ENV"
"$(cygpath -u "$LLVM")/bin/clang.exe" --version
ninja --version
python --version

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
145 changes: 145 additions & 0 deletions .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
# audio-context-android audiocontext-release.aar packages/audio-context/platforms/android/
# audio-context-ios AudioContextNative.xcframework packages/audio-context/platforms/ios/
# (tar.gz)
# canvas-windows-<arch> canvasnative.node + ANGLE DLLs packages/canvas/platforms/windows/<arch>/
# canvas-svg-windows- canvassvg.node packages/canvas-svg/platforms/windows/<arch>/
# <arch>
# audio-context-windows- audiocontext.node packages/audio-context/platforms/windows/<arch>/
# <arch>
#
# xcframeworks are tarred before upload because upload-artifact does not
# preserve file permissions.
Expand All @@ -18,6 +23,13 @@
# (what `npm run build.canvas.ios.framework.release` runs, minus nx)
# Android: make android, then gradle :canvas:assembleRelease and copy the aar
#
# Windows: tools/scripts/build-napi.sh <triple> release-napi [canvas-svg-napi|audio-context-napi]
# (`make windows`, `make windows-svg`, `make windows-audio`); Skia builds from source
# with the runner's LLVM and ninja, as locally with .tools/env.sh
# (.github/actions/setup-windows-native). arm64 is cross-compiled; the x64 jobs also
# run the Node-API test suites on the shipped modules (on WARP: runners have no GPU;
# audio renders offline and on the 'none' sink: they have no audio device either).
#
# canvas-svg and audio-context change rarely, so their jobs cache the final
# artifact keyed on a hash of their sources and skip the whole build on a hit.
name: Build Native
Expand Down Expand Up @@ -229,3 +241,136 @@ jobs:
name: audio-context-ios
path: audio-context-ios.tar.gz
if-no-files-found: error

canvas-windows:
name: Canvas Windows (${{ matrix.arch }})
runs-on: windows-2025
strategy:
fail-fast: false
matrix:
include:
- arch: x64
target: x86_64-pc-windows-msvc
- arch: arm64
target: aarch64-pc-windows-msvc
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.repository.default_branch || inputs.ref }}
- uses: ./.github/actions/setup-windows-native
with:
target: ${{ matrix.target }}
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Build canvasnative.node (and fetch ANGLE)
run: ./tools/scripts/build-napi.sh ${{ matrix.target }} release-napi
# arm64 is cross-compiled, so only x64 runs them. The runners have no GPU: D3D12 and ANGLE
# fall back to WARP, and the WebGPU suites skip without an adapter.
- name: Node-API tests
if: matrix.arch == 'x64'
env:
CANVAS_NAPI_ADDON: ${{ github.workspace }}\packages\canvas\platforms\windows\x64\canvasnative.node
run: node --expose-gc --test "crates/canvas-napi/__test__/*.test.mjs"
- uses: actions/upload-artifact@v4
with:
name: canvas-windows-${{ matrix.arch }}
path: packages/canvas/platforms/windows/${{ matrix.arch }}/
if-no-files-found: error

canvas-svg-windows:
name: Canvas SVG Windows (${{ matrix.arch }})
runs-on: windows-2025
strategy:
fail-fast: false
matrix:
include:
- arch: x64
target: x86_64-pc-windows-msvc
- arch: arm64
target: aarch64-pc-windows-msvc
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- name: Restore cached module
id: svg-node
uses: actions/cache@v4
with:
path: packages/canvas-svg/platforms/windows/${{ matrix.arch }}/canvassvg.node
key: ${{ env.NATIVE_CACHE_VERSION }}-canvas-svg-windows-${{ matrix.arch }}-${{ hashFiles('crates/canvas-svg/**', 'crates/canvas-svg-c/**', 'crates/canvas-svg-napi/**', 'crates/canvas-2d/**', 'crates/canvas-core/**', 'crates/vendor/napi-sys/**', 'Cargo.toml', 'Cargo.lock', 'tools/scripts/build-napi.sh') }}
- uses: ./.github/actions/setup-windows-native
if: steps.svg-node.outputs.cache-hit != 'true'
with:
target: ${{ matrix.target }}
- uses: actions/setup-node@v4
if: steps.svg-node.outputs.cache-hit != 'true' && matrix.arch == 'x64'
with:
node-version: '22'
- name: Build canvassvg.node
if: steps.svg-node.outputs.cache-hit != 'true'
run: ./tools/scripts/build-napi.sh ${{ matrix.target }} release-napi canvas-svg-napi
- name: Node-API tests
if: steps.svg-node.outputs.cache-hit != 'true' && matrix.arch == 'x64'
env:
CANVAS_SVG_NAPI_ADDON: ${{ github.workspace }}\packages\canvas-svg\platforms\windows\x64\canvassvg.node
run: node --expose-gc --test "crates/canvas-svg-napi/__test__/*.test.mjs"
- uses: actions/upload-artifact@v4
with:
name: canvas-svg-windows-${{ matrix.arch }}
path: packages/canvas-svg/platforms/windows/${{ matrix.arch }}/
if-no-files-found: error

audio-context-windows:
name: Audio Context Windows (${{ matrix.arch }})
runs-on: windows-2025
strategy:
fail-fast: false
matrix:
include:
- arch: x64
target: x86_64-pc-windows-msvc
- arch: arm64
target: aarch64-pc-windows-msvc
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- name: Restore cached module
id: audio-node
uses: actions/cache@v4
with:
path: packages/audio-context/platforms/windows/${{ matrix.arch }}/audiocontext.node
key: ${{ env.NATIVE_CACHE_VERSION }}-audio-context-windows-${{ matrix.arch }}-${{ hashFiles('crates/audio-context-napi/**', 'crates/vendor/napi-sys/**', 'Cargo.toml', 'Cargo.lock', 'tools/scripts/build-napi.sh') }}
# No Skia here; the action's Rust toolchain and cargo cache are what this job needs.
- uses: ./.github/actions/setup-windows-native
if: steps.audio-node.outputs.cache-hit != 'true'
with:
target: ${{ matrix.target }}
- uses: actions/setup-node@v4
if: steps.audio-node.outputs.cache-hit != 'true' && matrix.arch == 'x64'
with:
node-version: '22'
- name: Build audiocontext.node
if: steps.audio-node.outputs.cache-hit != 'true'
run: ./tools/scripts/build-napi.sh ${{ matrix.target }} release-napi audio-context-napi
# Runners have no audio device: the suite renders offline and on the 'none' sink.
- name: Node-API tests
if: steps.audio-node.outputs.cache-hit != 'true' && matrix.arch == 'x64'
env:
AUDIO_CONTEXT_NAPI_ADDON: ${{ github.workspace }}\packages\audio-context\platforms\windows\x64\audiocontext.node
run: node --expose-gc --test "crates/audio-context-napi/__test__/*.test.mjs"
- uses: actions/upload-artifact@v4
with:
name: audio-context-windows-${{ matrix.arch }}
path: packages/audio-context/platforms/windows/${{ matrix.arch }}/
if-no-files-found: error
11 changes: 10 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# CI for pushes and pull requests.
#
# All native artifacts (Android AARs + iOS/visionOS xcframeworks for canvas,
# canvas-svg and audio-context) are built by the reusable build-native.yml
# canvas-svg and audio-context; the Windows x64/arm64 Node-API modules for
# canvas and canvas-svg) are built by the reusable build-native.yml
# workflow. On pushes to master (and manual dispatch) the npm job then packages
# everything with those freshly built artifacts and publishes a `pr`-tagged
# prerelease of each package.
Expand Down Expand Up @@ -93,6 +94,14 @@ jobs:
tar -xzf native-artifacts/canvas-svg-ios/canvas-svg-ios.tar.gz -C packages/canvas-svg/platforms/ios
rm -rf packages/audio-context/platforms/ios/AudioContextNative.xcframework
tar -xzf native-artifacts/audio-context-ios/audio-context-ios.tar.gz -C packages/audio-context/platforms/ios
# Node-API modules (+ ANGLE DLLs) per Windows arch
for arch in x64 arm64; do
for pkg in canvas canvas-svg audio-context; do
rm -rf "packages/$pkg/platforms/windows/$arch"
mkdir -p "packages/$pkg/platforms/windows/$arch"
cp native-artifacts/$pkg-windows-$arch/* "packages/$pkg/platforms/windows/$arch/"
done
done

- name: Install dependencies
run: npm install
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ rust-skia
# .gitignore keeps git ignoring them for commits while staying invisible to
# the asset copier.
/.v8-prebuilt/
/.angle-prebuilt/
/packages/canvas/src-native/canvas-android/canvas/src/main/cpp/include/*
!/packages/canvas/src-native/canvas-android/canvas/src/main/cpp/include/canvas_native.h
!/packages/canvas/src-native/canvas-android/canvas/src/main/cpp/include/canvas_android.h
Expand All @@ -80,4 +81,9 @@ rust-skia
# Build-time-only libNativeScript.so stub, built from the pinned V8 release by
# tools/scripts/build-android-v8-stub.sh (see /V8_RELEASE) -- not committed,
# regenerated on demand. Real apps supply their own copy at runtime.
/packages/canvas/src-native/canvas-android/canvas/src/main/jniLibs/*/libNativeScript.so
/packages/canvas/src-native/canvas-android/canvas/src/main/jniLibs/*/libNativeScript.so
# Local benchmark results (machine-specific)
crates/canvas-napi/bench/results/
# Internal notes (local only)
crates/canvas-napi/PORTING.md
crates/vendor/napi-sys/PATCHED.md
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ members = [
"crates/canvas-svg",
"crates/canvas-svg-c",
"crates/canvas-napi",
"crates/canvas-svg-napi",
"crates/audio-context-napi",
"crates/canvas-svg-ios",
"crates/canvas-svg-android",
]
Expand Down Expand Up @@ -42,6 +44,15 @@ opt-level = 3
[profile.release.package.skia-bindings]
opt-level = 3

# Node-API addons (canvas-napi). The host app must survive a Rust panic: `unwind` lets the
# binding turn it into a JS exception. Line tables + PDBs are kept for crash symbolication.
[profile.release-napi]
inherits = "release"
panic = "unwind"
opt-level = 3
debug = "line-tables-only"
strip = "none"

# Aggressively size-optimize heavy utility crates
[profile.release.package.regex]
opt-level = "z"
Expand All @@ -56,6 +67,11 @@ opt-level = "z"
opt-level = "z"


# Node-API hosts that export napi_* from a DLL rather than the executable (NativeScript Windows)
# need the patched symbol lookup (the `[canvas patch]` hunks in crates/vendor/napi-sys/src/functions.rs).
[patch.crates-io]
napi-sys = { path = "crates/vendor/napi-sys" }

[workspace.dependencies.wgt]
package = "wgpu-types"
git = "https://github.com/triniwiz/wgpu"
Expand All @@ -69,6 +85,7 @@ env_logger = "0.11.5"
log = "0.4.29"
parking_lot = { version = "0.12.3" }
raw-window-handle = "0.6.2"
windows = "0.62.2"
canvas-2d = { path = "./crates/canvas-2d" }
canvas-core = { path = "./crates/canvas-core" }
canvas-webgl = { path = "./crates/canvas-webgl" }
Expand Down
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ ARCHS_IOS = x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim
ARCHS_VISIONOS = aarch64-apple-visionos aarch64-apple-visionos-sim
ARCHS_TVOS = aarch64-apple-tvos aarch64-apple-tvos-sim
ARCHS_ANDROID = i686-linux-android x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
# Node-API hosts (crates/canvas-napi).
ARCHS_WINDOWS = x86_64-pc-windows-msvc aarch64-pc-windows-msvc

XCFRAMEWORK = CanvasNative.xcframework
RUST_LIB = canvasnative
Expand All @@ -16,6 +18,12 @@ tvos: $(ARCHS_TVOS)

android: GENERATE_ANDROID

windows: $(ARCHS_WINDOWS)

windows-svg: $(addsuffix _svg_napi,$(ARCHS_WINDOWS))

windows-audio: $(addsuffix _audio_napi,$(ARCHS_WINDOWS))

ios-svg: GENERATE_IOS_SVG

visionos-svg: GENERATE_VISIONOS_SVG
Expand Down Expand Up @@ -78,6 +86,18 @@ $(ARCHS_TVOS): %:
$(ARCHS_ANDROID): %:
./tools/scripts/build-android.sh $@

.PHONY: windows $(ARCHS_WINDOWS)
$(ARCHS_WINDOWS): %:
./tools/scripts/build-napi.sh $@

.PHONY: windows-svg $(addsuffix _svg_napi,$(ARCHS_WINDOWS))
$(addsuffix _svg_napi,$(ARCHS_WINDOWS)): %_svg_napi:
./tools/scripts/build-napi.sh $* release-napi canvas-svg-napi

.PHONY: windows-audio $(addsuffix _audio_napi,$(ARCHS_WINDOWS))
$(addsuffix _audio_napi,$(ARCHS_WINDOWS)): %_audio_napi:
./tools/scripts/build-napi.sh $* release-napi audio-context-napi

.PHONY: GENERATE_ANDROID
GENERATE_ANDROID: $(ARCHS_ANDROID)

Expand Down
8 changes: 6 additions & 2 deletions apps/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"@nativescript/canvas-polyfill": "file:../../packages/canvas-polyfill",
"@nativescript/canvas-svg": "file:../../packages/canvas-svg",
"@nativescript/canvas-three": "file:../../packages/canvas-three",
"@nativescript/core": "9.1.1",
"@nativescript/core": "https://pkg.pr.new/@nativescript/core@11272",
"@nativescript/font-manager": "file:../../node_modules/@nativescript/font-manager",
"@pixi-essentials/svg": "file:../../node_modules/@pixi-essentials/svg",
"babylonjs": "file:../../node_modules/babylonjs",
"babylonjs-materials": "file:../../node_modules/babylonjs-materials",
Expand All @@ -35,6 +36,9 @@
"@nativescript/android": "9.1.1",
"@nativescript/devtools": "~0.0.1",
"@nativescript/ios": "9.0.4-next.2026-08-13-31742124335",
"@nativescript/visionos": "~9.0.1"
"@nativescript/visionos": "~9.0.1",
"@nativescript/webpack": "https://pkg.pr.new/@nativescript/webpack@11272",
"@nativescript/windows": "beta",
"typescript": "~5.6.0"
}
}
Loading
Loading