Skip to content

feat: v3 - #152

Open
triniwiz wants to merge 57 commits into
masterfrom
v3-v8
Open

triniwiz wants to merge 57 commits into
masterfrom
v3-v8

Conversation

@triniwiz

Copy link
Copy Markdown
Member

No description provided.

triniwiz added 30 commits August 4, 2026 00:37
Nx's CopyAssetsHandler only reads the root .gitignore/.nxignore (see the
TODO in copy-assets-handler.js), so it never applies the nested
nativescript-v8/Headers/.gitignore when copying vendored V8 headers into
dist/.../NativeScriptV8/Headers -- that's what let v8.h etc. through in the
first place. But the .gitignore file itself matches the "**/*" glob and
gets copied into dist alongside the headers it was written to protect.
npm pack, run against that dist output, then reads the copied .gitignore
and re-excludes everything under include/* except the libffi negations,
silently stripping v8.h/v8config.h/etc. right back out of the published
tarball (confirmed against the published 3.0.0-alpha.3, which is missing
them).

Adding an explicit asset-glob "ignore" for the .gitignore file itself
(a feature CopyAssetsHandler already supports) keeps it out of dist, so
npm pack no longer has anything to re-exclude with.
Brings in #144-#149. Conflict resolution notes:

* #147 (V8 14 native bridge) overlapped almost entirely with the V8 14
  migration this branch already carries. Both sides fix the same API break;
  master does it with dual-version shims (canvas::GetAlignedPointer,
  canvas::Receiver, CANVAS_FAST_FUNCTION, #if V8_MAJOR_VERSION >= 14) so one
  tree can build against V8 10.3 and 14, while this branch targets a single
  vendored V8 14.9 (tools/scripts/download-v8.sh) and calls the native APIs
  directly. 118 of the 127 conflicting sources were that difference alone --
  after rewriting master's shims into this branch's idiom, 112 compared
  byte-identical -- so those keep this branch's spelling and the now-unused
  shim block is dropped from Common.h.

* Master's shims also compile fast API calls out on V8 >= 14
  (CANVAS_FAST_FUNCTION -> v8::CFunction{}, c_function -> nullptr). V8 14.9
  still declares both FunctionTemplate::New(..., const CFunction*) and
  NewWithCFunctionOverloads, and this branch's fast paths are built against
  it, so taking that would have silently disabled every fast call in the
  binding layer. Helpers.h keeps this branch's version; the four
  CANVAS_FAST_FUNCTION call sites that auto-merged into
  OES_vertex_array_objectImpl and WEBGL_draw_buffersImpl -- no conflict was
  raised for those -- are restored to v8::CFunction::Make.

* Master's *Array fast overloads are all guarded #if V8_MAJOR_VERSION < 14,
  so they are dead on 14 and equivalent to this branch having removed them.

* #149 (ImageData double free) applies unchanged. canvas_native_image_data_get_data
  borrows its argument and returns a U8Buffer holding a second refcounted
  handle to the same pixels, so ImageDataBuffer must release only the buffer --
  ~ImageDataImpl already releases the ImageData. The comment is reworded from
  master's, which described the buffer as owning a clone of the pixel storage;
  it is a shared handle, and that is what makes the JS data view live.

* Package versions stay on the 3.0.0-alpha line.
* canvas-release.aar keeps this branch's binary; it predates the Android
  render fixes in 638a265 and needs rebuilding from the merged sources.
SetFastMethodWithOverLoads took the overload set as `const v8::CFunction *`
and sized it with NUM(&method_overloads). That macro is
sizeof(a)/sizeof(*a) applied to a pointer-to-pointer, so it evaluated to 1
at every call site regardless of how many overloads the array held.

Across the 18 registration sites, 41 overloads are declared and 18 were
registered. The rest never reached V8, so those argument shapes always took
the slow FunctionCallback path:

  fill              1 of 4    clip, draw, isPointInPath   1 of 3
  bufferData        1 of 3    putImageData, stroke, roundRect,
  setTransform, isPointInStroke, bindBuffer, useProgram,
  bufferSubData, pixelStorei                              1 of 2

bindTexture, bindFramebuffer and bindRenderbuffer are the worst of these:
their null variant is listed first, so the registered overload is the one
for a null binding and the common non-null bind had no fast path.

Taking the array by reference deduces the extent at each call site, so no
call site changes. All 17 overload sets are declared as arrays in the same
translation unit, so N is available at every one.
Two problems kept `cargo check`/`cargo test` from running on the workspace,
which is why the existing unit tests had never been executed.

gpu/mod.rs compiled `metal` on every Apple target, but that module imports
objc2-metal, which is an optional dependency only enabled by the `mtl`
feature. Any Apple build without `mtl` failed to resolve the crate instead
of simply omitting Metal support, so gate the module on the feature too.

With that fixed, `--features mtl` then failed on its own: objc2-metal ships
one feature per generated header, and the set enabled here was missing
MTLBlitCommandEncoder, MTLCommandEncoder, MTLBuffer, MTLResource and
MTLTypes -- all of which gpu/metal.rs uses (blit encoding, buffer creation,
MTLOrigin/MTLRegion/MTLSize). Add them.

canvas-core still needs `2d` for skia-safe; the bare no-feature build fails
separately on that and is left alone here.
triniwiz/rust-skia d4c4011 -> 91bf15b, which is skia-safe 0.99.0 -> 0.101.0
and carries Skia m152. The fork's visionOS platform support is on that line
(d4c4011 is an ancestor of it) and picks up a small fix, so nothing is lost
by moving to the branch tip.

The only API break affecting this workspace is FontMgr::new_from_data, which
now takes an SkData rather than a byte slice; both call sites in
global_fonts.rs wrap their bytes in Data::new_copy, which is what the old
signature did internally.

wgpu is unchanged: triniwiz/wgpu's trunk is exactly the rev already pinned
here, so there is nothing newer to move to without first updating that fork.
… them in CI

Nothing in CI ran tests, and the four crates that already had #[test]
modules could not be built on the host at all, so none of them had ever
executed. With the canvas-core build fixed they do; `make test` and two new
CI jobs run them.

ImageData ownership (crates/canvas-c/src/c2d/image_data.rs)
  ImageData is a manually refcounted handle -- Clone copies the raw pixel
  pointer and bumps an Arc, and the storage is freed only when the last
  handle drops. canvas_native_image_data_get_data borrows and hands back a
  second handle to the same pixels, which is what makes the JS `data` view
  live and what #149 turned on. The tests pin all three parts: the buffer
  aliases the ImageData's pixels, releasing the buffer leaves the ImageData
  usable (the double free #149 fixed), and the buffer keeps the pixels alive
  when the ImageData handle is dropped first.

V8 bridge invariants (tools/tests/check-v8-bridge-invariants.py)
  Static checks over the bridge sources for things the compiler accepts but
  that break at runtime or silently cost performance: internal-field and
  external-pointer accessors that are missing their V8 14 tag (an untagged
  read returns null rather than failing to build), V8 14-removed APIs coming
  back, dual-version shims re-entering a branch that ships one vendored V8,
  and the overload helper regressing to a bare pointer -- which is exactly
  how 23 of the 41 fast-call overloads went unregistered. Both of the
  regressions this merge introduced by auto-merge would have been caught
  here.
…ffers work

Ten call sites built a std::vector with reserve(n), filled it through
data() (or operator[]), then passed data() together with size(). reserve
only sets capacity, so size() stayed 0 and every one of these handed the FFI
an empty array; writing through data()/operator[] past size() is also out of
bounds, which any hardened or debug STL would trap.

The affected entry points silently did nothing:

  ctx.setLineDash([...])                     slow path and fast path
  ctx.roundRect(x, y, w, h, [radii])         slow path and fast path
  path2d.roundRect(x, y, w, h, [radii])      slow path and fast path
  WEBGL_draw_buffers.drawBuffersWEBGL([...]) fast path
  gl2.drawBuffers([...])                     fast path
  gl2.invalidateFramebuffer(...)             fast path
  gl2.invalidateSubFramebuffer(...)          fast path

The 2D ones are the worst of these because the slow path is affected too, so
setLineDash and roundRect-with-radii never worked; the WebGL ones only break
once V8 tiers the call up, which makes them look intermittent.

resize(n) gives the elements the vector needs to report the right length.
Path2D::FastRoundRectArray additionally passed nullptr as the copy
destination instead of its own buffer, so it had nothing to send either way.

check-v8-bridge-invariants.py grows a check for the pattern; it flags a
reserve whose buffer is later passed as data(), size() with nothing having
grown it, and leaves the 27 legitimate reserve + push_back sites alone.
triniwiz/wgpu 7e0f39f -> 8bf3e5f, which is the v30.0.0 tag on the fork's
trunk: wgpu-core/hal/types 29.0.0 -> 30.0.0.

Not the branch tip (b421632). That merge brings in post-v30.0.0 upstream
trunk, where the id-based Global architecture is gone -- wgpu-core no longer
has the global, hub, identity or registry modules, and resources are reached
through Arc handles instead of typed ids. crates/canvas-c/src/webgpu is
built entirely on that API: ~110 sites bind a Global and call 101 distinct
methods on it across 29 files. Porting to the new model is a real piece of
work and wants to be its own change; v30.0.0 still has Global, so it gives
the version bump without it.

The five breaks at v30.0.0 are all mechanical:

  * Limits gained max_buffers_and_acceleration_structures_per_shader_stage,
    max_ray_dispatch_count and max_ray_recursion_depth. Canvas exposes no ray
    tracing -- the acceleration-structure limits beside these are already
    pinned to 0 -- so the ray limits stay 0; the combined limit is documented
    as the sum of the storage, uniform and vertex buffer limits and is
    derived rather than copying wgpu's default of 28.
  * SurfaceConfiguration gained color_space. Auto is the default and is
    defined to reproduce the pre-30 behaviour.
  * RenderPassDescriptor now takes depth_stencil_attachment and
    timestamp_writes by value rather than by reference.
  * render_bundle_encoder_finish borrows the encoder (&mut) instead of
    consuming the Box. The Box stays owned here and drops at the end of the
    function, so the encoder is still freed exactly once.
triniwiz/rust-skia 91bf15b -> 1eafe30 (skia-safe 0.101.0 -> 0.153.4). No
source changes needed: FontMgr::new_from_data, fixed up in the 0.101 bump,
is the only break either step introduced, and canvas-2d, canvas-core and
canvas-svg all build clean.

The fork's platform support grew tvOS alongside visionOS
(skia-bindings/build_support/platform/{tvos,visionos}.rs), which lines up
with the tvOS targets #144 added to the Makefile.
Brings in #150 and #151.

#151 (window timer / EventTarget aliases) is TypeScript only and merged
clean.

#150 (TextMetrics for empty measureText) conflicted on the one line this
branch spells differently: master reads the length through its
canvas::Utf8Length shim, this branch calls Utf8LengthV2 directly. Kept this
branch's call and took master's fix, which is the removal of the
`if (text_utf8_len == 0) return;` early exit.

The rest of MeasureText is safe at length 0: prefix_len is at least 3, so
the scratch buffer is never empty, WriteUtf8V2 with a zero length writes
nothing, and the empty string measures to a zero-width TextMetrics rather
than returning undefined.
AudioContextNative is Foundation + AVFoundation over libopus/libvorbis with
no UIKit and no AVAudioSession, so nothing in the sources needed a
TARGET_OS_TV guard -- this is all build configuration.

* build_opus_deps_ios.sh cross-compiles the third_party libs for appletvos
  and appletvsimulator. tvOS gets the same treatment as visionOS: a target
  triple rather than -m*-version-min (it spells device vs simulator
  unambiguously) and no bitcode. arm64 only, matching the scope canvas uses
  in canvas-ios/pre-build.sh -- an x86_64 simulator slice would need an
  x86_64 build of every dep here.

* The Xcode project gains appletvos/appletvsimulator in SUPPORTED_PLATFORMS,
  TVOS_DEPLOYMENT_TARGET, and device family 3. Nothing else was needed: the
  header and library search paths already key off
  $(PLATFORM_NAME)-$(CURRENT_ARCH), so they find the new prefixes on their own.

* build.sh builds both tvOS slices and adds them to the xcframework, which
  now carries six: ios, ios-sim, xros, xros-sim, tvos, tvos-sim.

Two things had to be fixed to get there, both of which were already broken
for visionOS:

* The scheme had buildImplicitDependencies = YES, so Xcode matched -lvorbis
  against the libvorbis macosx/Vorbis.xcodeproj bundled in third_party and
  built it as an implicit dependency. That project is macOS-oriented, cannot
  resolve its own ogg headers under a cross-SDK build, and is redundant --
  the framework has no target dependencies and links the prebuilt archives by
  path. Turning it off fixes iOS, visionOS and tvOS alike.

* The visionOS and tvOS *device* steps passed ARCHS=arm64
  ONLY_ACTIVE_ARCH=NO. Device SDKs are arm64-only already, so it was
  redundant there; the simulator steps keep it, since their third_party
  archives are arm64-only.

Verified with cleared DerivedData: all six SDKs build without errors, the
tvOS binaries report platform 3 (tvOS) and 8 (tvOS simulator) with the
decoder symbols exported, and -create-xcframework accepts all six slices.

build_opus_deps_ios.sh also now checks each target actually produced the
archives the framework links. Every configure/make in it is `|| true`, so a
dep that fails leaves a prefix that looks fine until the link fails -- which
is exactly what a stale libogg tree did here.
Pins wgpu at the fork's trunk tip (b421632), where wgpu-core has no global,
hub, identity or registry modules. DOES NOT COMPILE YET -- 100 errors left,
listed below. Branch is deliberately separate from v3-v8, which stays on the
v30.0.0 tag and stays green.

Done -- the three structural changes:

1. Resource handles. All 19 wrapper structs now hold Arc<T> instead of a
   typed id: Arc<Device>, Arc<Buffer>, Arc<Texture>, Arc<Surface> and so on.
   CanvasWebGPUInstance wraps Arc<Instance> (Instance::new already returns an
   Arc, so the extra Arc::new is gone) and exposes .instance() where it used
   to expose .global().

2. Error model. wgpu-core now implements the WebGPU error model itself --
   Device::push_error_scope / pop_error_scope / on_uncaptured_error -- and
   every infallible create_* reports into it. Keeping our own scope stack
   beside it would mean those creates never reach our stack, so the FFI
   entry points delegate: push/pop go straight to the device, and
   set_uncaptured_error_callback installs a handler on the device that
   forwards to the registered C callback. ErrorSinkRaw stays for the
   device-lost path, which wgpu handles separately.

   handle_error/handle_error_fatal lose their first parameter -- it was the
   Global and was already unused (`_context`) -- which is what let ~100 call
   sites stop binding one.

3. Drops. 15 Drop impls whose whole body was a *_drop call are deleted;
   lifetime is refcounting now. Two kept a real job and were rewritten:
   CanvasGPUDevice still polls to idle before release, and CanvasGPUTexture
   still hands an acquired-but-never-presented swapchain image back via
   Surface::discard.

Also converted: the device create_* family, render/compute pass encoders,
and the 11 command::bundle_ffi::wgpu_render_bundle_* free functions, which
are now RenderBundleEncoder methods. Buffer is complete, including
map_async returning Option<SubmissionIndex> rather than a Result.

Left, by file:

  gpu_render_pass_encoder   36   arguments are Arc<T> where ids were passed
  gpu_canvas_context        28   surface configure/acquire/present path
  gpu_device                19   remaining create_* argument types
  gpu_command_encoder       14   begin_render_pass now takes a
                                 ResolvedRenderPassDescriptor, so the colour
                                 and depth attachments have to be resolved to
                                 Arcs before the call -- this one is a real
                                 signature change, not a receiver swap
  gpu_render_bundle_encoder 10
  gpu_queue                  9   TexelCopyTextureInfo moved out of
                                 wgpu_core::command
  gpu_compute_pass_encoder   9
  the rest                  ~9

None of this is exercised by a test. The repo has no GPU test and WebGPU
needs a device, so compiling is the only signal available here -- treat the
whole branch as unverified until it runs on hardware.
Error path is settled: the infallible create_*/encode methods report into
wgpu's device sink, which push/pop_error_scope now read, so our duplicate
Err(cause) -> handle_error blocks are gone rather than being rewired.

Shader compilation messages move with it: create_shader_module no longer
returns an error carrying them, so getCompilationInfo now reads
ShaderModule::compilation_info. The new SourceLocation counts UTF-8 bytes
where GPUCompilationMessage is specified in UTF-16 code units, so
from_compilation_message keeps the conversion the error-based path did.

Also: Limits gained per-stage storage buffer/texture caps, DeviceDescriptor
gained default_queue, TextureViewDescriptor gained swizzle, index/vertex
buffer sizes are Option<u64> rather than Option<NonZeroU64>, and
Buffer::map_async signals 'queued, still needs polling' as Some(index)
rather than Ok.

100 -> 74 errors. gpu_canvas_context (27) and gpu_command_encoder (14) are
what is left of substance.
… wgpu

gpu_canvas_context was the last file: the surface is an Arc<Surface> now, so
create/configure/get_current_texture/present/discard/get_capabilities are all
methods on it, and surface_drop is gone -- assigning a new Arc over the old
one is the release. Surface-backed textures compare by Arc::ptr_eq rather
than by id equality.

Whole crate is at 0 errors.
The Metal backend's reported alpha modes changed between wgpu v30.0.0 and
trunk: v30.0.0 advertises [Opaque, PostMultiplied], trunk advertises
[Opaque, PreMultiplied]. The demo hardcoded PostMultiplied, so on trunk
surface configuration fails validation and nothing renders.

Worth flagging beyond the demo: this is a behaviour change any app hits.
Anything asking for PostMultiplied on Metal has to switch, or query
getCapabilities and pick a supported mode instead of hardcoding one.
Backends do not agree on how they name premultiplied compositing, and the
name changes between wgpu releases for the same physical behaviour: Metal
reported PostMultiplied up to v30.0.0 and reports PreMultiplied after it. A
caller that hardcodes either one is one wgpu bump away from a surface that
fails validation and never presents -- which is exactly what the playground
demo hit.

configure now matches the requested mode against the surface's capabilities
instead of passing it through. An unsupported Pre/PostMultiplied falls back
to the other spelling (same intent, different name), then to Opaque, then to
whatever the surface does support, with a warning. Auto is left alone since
wgpu accepts it everywhere.

The JS layer already did this in GPUCanvasContext.configure, so apps going
through the JS API were never exposed; direct FFI callers were. Doing it in
the native layer too means both paths behave the same and neither depends on
which names the backend happens to report.

CanvasGPUDevice now keeps its Arc<Adapter> to make that query possible --
wgpu-core's Device::adapter is pub(crate), so there is no route back to the
adapter from a device otherwise.

The demo goes back to requesting PostMultiplied, which now works on trunk.
The cube covers render pipelines, render passes, surface acquire/present and
queue submit. Compute, render bundles, copyExternalImageToTexture and
toDataURL readback were left compile-verified only after the Global removal,
which is the weakest part of that port to leave unexecuted.

webgpu_smoke runs each once against the live device and checks a result
rather than just a non-null pointer:

  compute pass      dispatches values[i] = i*2+1 over a storage buffer and
                    reads the numbers back
  render bundle     records a fullscreen triangle, replays it into an
                    offscreen pass over a red clear, checks the pixel is the
                    bundle's green
  external image    uploads known magenta and reads the texture back
  toDataURL         checks the PNG data URL prefix and length

It also surfaced that the demo only rendered on WindowEvent::Resized --
request_redraw was commented out -- so "it ran for 25s" previously meant an
idle window, not frames. RedrawRequested now renders and re-arms, which is
also what gives the acquire/present and Arc release paths real repetition.
Exercises compute passes, render bundles, copyExternalImageToTexture and
toDataURL readback once against the live device, checking results rather
than just non-null pointers. These are the paths the spinning-cube demo
never reaches, so they had no executable coverage at all.

Also fixes the demo only rendering on WindowEvent::Resized: request_redraw
was commented out, so the window sat idle after the first frame.
RedrawRequested now renders and re-arms.

Same suite passes identically here and on the wgpu Arc-handle migration
branch, which is what makes it useful as a before/after oracle.
_decrementStrongRefAndRemove read `loaders.get(this) ?? 0 - 1`, which parses
as `loaders.get(this) ?? (0 - 1)` because ?? binds looser than -. When the
asset was in the map -- the normal case -- it returned the count unchanged,
never reached <= 0, and never deleted the entry.

loaders is a module-level Map holding strong references and every load path
increments it, so every image ever loaded stayed pinned for the life of the
process along with its native bitmap. Repeated texture loads leaked a full
decoded bitmap each time.

Also writes the decremented count back: without it overlapping loads on one
asset could never drain.
lineWidth and miterLimit must ignore zero, negative, infinite and NaN;
lineDashOffset must ignore infinite and NaN; setLineDash must ignore the
call in full if any entry is negative or non-finite. None of them validated,
so ctx.lineWidth = -5 or NaN reached Skia's set_stroke_width and corrupted
stroking instead of being the no-op every browser performs.

Follows the existing set_global_alpha idiom, where NaN falls out of the
comparison rather than needing a separate check.
`fill_text`, `stroke_text` and `measure_text` each built a fresh ParagraphBuilder,
laid it out and threw the result away. None of that prologue depends on the paint,
so it is now cached as a TextBlob plus a few scalars and redrawn with any paint --
including the shadow pass, which used to re-shape the same string a second time.

`Font::new` is memoized too: it runs a regex match and a Ustr intern per family on
every `ctx.font = ...` that differs from the last one.

The cache is keyed on a font-library generation counter kept outside the library
mutex, so a family registered after a string was first drawn still takes effect
without putting a lock on the draw path. State's four Strings become Arc<str> so
that save()/restore() clones stay cheap.

Pinned by parity tests that keep the old paragraph-per-call implementation as a
reference and compare pixels and metrics across every align x baseline x font.
Every WebGL entry point calls make_current before touching GL, so the
"already current?" test runs thousands of times a frame. On Android that asked EGL,
which costs two dispatched driver calls; on Apple it called setCurrentContext:
unconditionally. Both now compare against a thread-local mirror of the binding.

The mirror carries an epoch so a recycled surface (Android) or context (Apple)
address cannot match a stale entry, and anything that binds outside the wrappers
clears it.
copyExternalImageToTexture can now take the decoder's frame directly instead of
locking the pixel buffer, swizzling it into a typed array and uploading it again.
Apple wraps the IOSurface as an MTLTexture; Android imports the AHardwareBuffer
through VK_ANDROID_external_memory_android_hardware_buffer.

Bumps the wgpu fork to e5c0aa02, which adds the Vulkan import entry point, and
drops the temporary [patch] block that pointed at a local checkout.

Frames the platform cannot import fall back to the upload path: on Android a
hardware decoder usually produces YCbCr, which Vulkan can only sample through an
immutable ycbcr-conversion sampler that WebGPU cannot express.
Puts the per-draw-call render, bundle and compute pass methods on V8's fast API.
setIndexBuffer takes the index format as an int (0 = uint16, 1 = uint32) to stay on
that path; the string form still works through the slow path.
createImageBitmap(imageData) and createImageBitmap(canvas) had no entry point in
the C++ binding and fell through to an empty V8 handle, which aborted. Adds both,
plus ImageBitmapRenderingContext and getContext('bitmaprenderer').

Reading a WebGL canvas back needs glReadPixels to be given a component type where
it was being handed a pixel format -- it failed with GL_INVALID_ENUM and wrote
nothing, so such a canvas drew white.

The byte-loading helpers now accept any view over an ArrayBuffer rather than only a
bare one, honouring byteOffset; they previously took an unchecked As<ArrayBuffer>().
- isPointInStroke hit-tests the stroked outline; a line has no area, so
  containment against the path itself was false for every stroke.
- Point-in-path takes device-space coordinates, per spec.
- globalAlpha is re-applied after fillStyle, which used to discard it.
- Patterns and filters clamp with Decal, so they fade out rather than smearing
  edge pixels.
- DOMMatrix multiplication goes through Skia's pre_* helpers: set_translate writes
  a row-major array into column-major storage and put the translation in the
  perspective row.
- Non-finite arguments are a no-op rather than a NaN matrix, an unparseable filter
  leaves the previous one in place, and a bad entry discards the whole dash list.
clear_if_composited wipes the whole buffer with the scissor test off, which is
only correct when there is no scissor rect to respect. The enable/disable state was
never recorded, so every clear ignored the rect and left SCISSOR_TEST off
afterwards.

Also adds the WebGL 2 pnames the WebGL 1 table does not know, which fell through to
WebGLResult::None and read back as an object in JS.
Both libraries apply the device pixel ratio themselves. Chart.js's retinaScale
multiplies chart.width by the ratio, and Pixi scales the backing store by
`resolution`, so handing either device pixels applied the ratio twice -- a DPR^2
backing store.

NativeScript has no CSS layer, so Pixi's autoDensity (which writes a CSS size onto
the element) is made inert; on the web that `px` is a CSS pixel, here it is a device
pixel and it resized the view.
getImageData on the GPU path costs 412us for a 64x64 region on a Galaxy A53,
against 3.8us with willReadFrequently. The option was already implemented on both
platforms but appeared nowhere in the README.
`make android` and the iOS builds now work outside a CI shell: the NDK armv7
triple is remapped, ANDROID_NDK and the per-target CC/CXX/AR/linker are exported,
and a failed cargo build no longer exits 0 having produced no .so.

Adds `make test` (host suite plus a V8 bridge invariant checker over 162 sources),
the on-device canvas 2D, WebGL and ImageBitmap perf harnesses, a spec suite, and
the WebView A/B pages -- rasterization, call-bound and star-warp -- which load the
same benchmark file as the NativeScript side so the two cannot drift.

tvOS needs IPHONEOS_DEPLOYMENT_TARGET's tvOS twin exported for the `cc` crate,
which has no built-in default and otherwise tags C/asm objects with the SDK minos.
Canvas packages to 3.0.0-alpha.5, audio-context to 2.0.0-alpha.2.
Android AAR and the iOS/visionOS/tvOS xcframework, rebuilt against the pinned
wgpu rev and carrying the changes in this branch.
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 18d8cfc4-0e5d-4dd3-896f-d1c9035fdc53

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread .github/workflows/build-native.yml Fixed
Comment thread .github/workflows/pull-request.yml Fixed
- Canvas/utils.ts: export the getContext type-name union as CanvasContextType
  and reuse it for handleContextOptions's param
- Canvas/index.{ios,android}.ts: type __create2DContext's `type` param as
  CanvasContextType instead of `string` so it matches handleContextOptions
- Canvas/index.ios.ts: add @ts-ignore above the width/height setters to match
  the suppression already used on their getters (View defines these as plain
  properties; TS only flags the first accessor declared in source order,
  which is the setter here but the getter on android)
- Canvas/index.d.ts: drop the stale `canvas`, `parentElement` and `flush()`
  members — they no longer exist on the real Canvas classes, which broke
  passing `this` into ImageBitmapRenderingContext's constructor
- Canvas/index.{ios,android}.ts: cast `this` when constructing
  ImageBitmapRenderingContext; the ambient index.d.ts Canvas and the concrete
  platform class are structurally close but not identical (private field
  branding, getContext overload shape), same as the existing `as never` cast
  a few lines below
- WebGPU/Utils.ts: cast the depthStencilAttachment literal — depthLoadOp/
  depthStoreOp are intentionally left unset here and assigned conditionally
  after, per the comment above it
triniwiz and others added 4 commits September 14, 2026 13:47
…execution of untrusted code'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…ntain permissions'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants