An Overview of NativeScript Canvas
NativeScript Canvas 2.0, powered by Rust, Skia, and WebGPU, is a powerful plugin that provides a native drawing surface for both iOS and Android, allowing developers to create rich interactive games, custom graphics, animations, and visualizations in their NativeScript apps.
After a year of development, NativeScript Canvas v2 is here with a complete rewrite of the native rendering engine, bringing WebGPU to mobile, a Rust-powered core, new GPU backends, and a dramatically expanded web-compatibility layer.
- Why a Full Rewrite?
- WebGPU on iOS and Android
- New GPU Backends for 2D Canvas
- V8 Fast API — Near-Zero Overhead Rendering Calls
- The Full WebGPU Object Graph
- macOS Desktop via NAPI
- Expanded 2D Canvas API
- CSS Font Loading API
- Video as a Canvas Source
- Improved Polyfill: Full Game Engine Compatibility
- Declarative Dom API
- What's Next
- Getting Started
Why a Full Rewrite?
The original NativeScript Canvas implementation was a capable but limited C/C++ layer on top of OpenGL ES. It covered the basics — 2D canvas, WebGL — but as the web platform evolved, the gaps widened. WebGPU was arriving. Skia had matured. The Rust ecosystem around graphics was booming. We had a clear path to something much better.
v2 replaces the entire native layer with a Rust engine built on three pillars:
- Skia — Google's production 2D graphics library, used in Chrome and Flutter. Powers the 2D Canvas context with sub-pixel text rendering, advanced compositing, and hardware-accelerated paths.
- wgpu — the Rust implementation of the WebGPU API. Powers both the WebGPU context and the new Metal/Vulkan 2D backends.
- A clean C FFI — the
canvas-ccrate exposes everything to the JavaScript layer via native modules, with V8 Fast API integration for zero-overhead hot-path calls.
The result is an engine that is faster, more correct, more maintainable, and capable of things the old implementation simply could not do.
WebGPU on iOS and Android
The headline feature of v2 is full WebGPU support on both platforms.
On iOS, WebGPU runs on Metal. On Android, it runs on Vulkan. Both paths go through wgpu-core and wgpu-hal, giving you access to the same WebGPU API you would write for the web — shader modules in WGSL, render pipelines, compute pipelines, bind groups, query sets, the whole spec.
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu');
context.configure({ device, format: 'bgra8unorm' });
function render() {
requestAnimationFrame(render);
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
}
Minimum requirements: iOS 11, Android API 27.
New GPU Backends for 2D Canvas
Even if you are not using WebGPU directly, the 2D Canvas benefits from v2's new backend architecture.
Previously, the 2D context always ran on OpenGL ES. In v2, it can now render through Metal on iOS and Vulkan on Android — the same hardware APIs that power modern games and system UI. This means better driver support, lower latency, and closer alignment with how the OS itself renders.
Canvas.forceGL is available if you need to revert to OpenGL for compatibility reasons, but for most use cases the new backends are strictly better.
V8 Fast API — Near-Zero Overhead Rendering Calls
One of the more technically interesting additions in v2 is V8 Fast API integration.
NativeScript's JavaScript engine is V8. V8 has a "Fast API" mechanism that allows native function calls to bypass the normal JavaScript-to-native overhead when the JIT compiler can statically determine the call is safe. We have exposed the hot-path rendering functions — draw calls, state changes, uniform uploads — through this mechanism.
The practical effect: in tight render loops with many draw calls, the JS-to-native overhead drops to near zero. No more JIT bailouts on canvas-heavy workloads.
The Full WebGPU Object Graph
v2's WebGPU implementation is comprehensive, not a partial shim. The complete object graph is implemented:
| Object | Coverage |
|---|---|
GPUAdapter |
Features, limits, requestDevice, requestAdapterInfo |
GPUDevice |
All creation methods, error scopes, device lost promise |
GPUBuffer |
mapAsync, getMappedRange, unmap, destroy |
GPUTexture / GPUTextureView |
All formats, mip levels, array layers |
GPUSampler |
All filter/wrap/compare modes |
GPUShaderModule |
WGSL, compilationInfo |
GPURenderPipeline / GPUComputePipeline |
Sync and async creation |
GPUCommandEncoder |
Full command recording |
GPURenderPassEncoder / GPUComputePassEncoder |
All draw and dispatch commands |
GPURenderBundleEncoder / GPURenderBundle |
Reusable command bundles |
GPUQuerySet |
Timestamp and occlusion queries |
GPUQueue |
submit, writeBuffer, writeTexture, copyExternalImageToTexture |
GPUCanvasContext |
configure, getCurrentTexture, getCapabilities, presentSurface |
Compute shaders work. Render bundles work. Async pipeline compilation works. If you have a WebGPU demo running in Chrome, the path to running it on iOS and Android with NativeScript v2 is short.
macOS Desktop via NAPI
v2 adds a new canvas-napi Rust crate that compiles the canvas engine as a Node.js native addon via NAPI. This means the same rendering engine that runs on iOS and Android can now run on macOS desktop in a Node.js process — with no changes to your application code.
This opens up server-side rendering of canvas content, desktop tooling, and test environments that run the actual native engine rather than a mocked canvas.
Expanded 2D Canvas API
The 2D canvas implementation is now more complete than ever.
Conic gradients — the third gradient type from the CSS spec is now available:
const conic = ctx.createConicGradient(0, cx, cy);
conic.addColorStop(0, 'red');
conic.addColorStop(0.33, 'green');
conic.addColorStop(0.66, 'blue');
conic.addColorStop(1, 'red');
ctx.fillStyle = conic;
ctx.fillRect(0, 0, 300, 300);
Rounded rectangles — roundRect is now on both CanvasRenderingContext2D and Path2D:
ctx.beginPath();
ctx.roundRect(10, 10, 200, 100, [10, 20, 30, 40]);
ctx.fill();
CSS filters — apply blur, brightness, contrast, and other CSS filter functions directly:
ctx.filter = 'blur(4px) brightness(1.2)';
ctx.drawImage(image, 0, 0);
ctx.filter = 'none';
Text spacing — letterSpacing and wordSpacing are now supported:
ctx.letterSpacing = '3px';
ctx.wordSpacing = '8px';
ctx.fillText('Spaced out text', 10, 50);
Rich TextMetrics — measureText now returns the full spec, including font bounding boxes, em height, and all baseline offsets.
CSS Font Loading API
v2 ships a full implementation of the CSS Font Loading API.
const font = new FontFace('MyFont', 'url(https://example.com/font.woff2)');
await font.load();
document.fonts.add(font);
await document.fonts.ready;
ctx.font = '24px MyFont';
ctx.fillText('Custom font!', 10, 50);
importFontsFromCSS goes further — fetch a CSS file, parse all @font-face declarations, and load them all at once. This means Google Fonts and similar services work out of the box:
await importFontsFromCSS('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700');
ctx.font = 'bold 20px Roboto';
Video as a Canvas Source
@nativescript/canvas-media now integrates deeply with the canvas contexts. A Video element can be passed directly as a source to drawing and texture upload functions:
// 2D
ctx.drawImage(video, 0, 0);
// WebGL texture upload
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
// WebGL2 3D texture
gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, w, h, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, video);
Pair this with requestVideoFrameCallback for synchronized, per-frame video updates — the same API now supported in Chrome and Safari.
Improved Polyfill: Full Game Engine Compatibility
@nativescript/canvas-polyfill is the bridge between game engines written for the browser and NativeScript's native environment. v2 dramatically expands what it covers.
Persistent localStorage:
localStorage.setItem('highScore', '9999');
// Survives app restart — backed by the filesystem with debounced writes
Web Performance API:
performance.mark('renderStart');
doHeavyWork();
performance.mark('renderEnd');
performance.measure('render', 'renderStart', 'renderEnd');
const entries = performance.getEntriesByName('render');
Blob URLs and Workers — URL.createObjectURL, URL.revokeObjectURL, and a Worker polyfill that resolves blob URLs to real file paths. This makes game engines that dynamically create workers via blob URLs work correctly.
navigator.gpu — the WebGPU adapter is now on the navigator object in the polyfill, so any library that accesses navigator.gpu directly will get the native implementation.
Declarative Dom API
For applications that want a component-model approach to canvas drawing without writing raw canvas commands, v2 ships the Dom API.
<canvas:Dom onFrameCallback="onFrame">
<canvas:Group>
<canvas:LinearGradient x1="0" y1="0" x2="200" y2="0">
<canvas:Stop offset="0" color="blue" />
<canvas:Stop offset="1" color="purple" />
</canvas:LinearGradient>
<canvas:Rect x="10" y="10" width="200" height="100" />
<canvas:Circle cx="250" cy="60" r="50" color="red" />
<canvas:Text x="10" y="160" color="white" font="bold 18px sans-serif">Hello</canvas:Text>
</canvas:Group>
</canvas:Dom>
The Dom view drives its own RAF loop internally. Change element properties in onFrameCallback to animate. Use drawAsImage to render a list of elements to a static ImageSource without needing a visible canvas on screen.
What's Next
v2 lays the groundwork for several things coming in future releases:
- OffscreenCanvas — move rendering off the main thread.
- WebCodecs — hardware-accelerated video encode/decode.
- More WebGPU extensions — as device capabilities mature on Android and iOS.
- Improved NAPI desktop support — full parity between mobile and desktop rendering paths.
Getting Started
ns plugin add @nativescript/canvas
For game engines:
ns plugin add @nativescript/canvas-polyfill
ns plugin add @nativescript/canvas-three # or pixi, babylon, phaser
Full documentation, API reference, and migration notes are in DOCS.md. The complete changelog is in CHANGELOG.md.
The entire native engine — every line of Skia integration, every WebGPU object, every OpenGL ES binding — is written in Rust, compiled to a single prebuilt native library for iOS and Android. No extra build steps, no Xcode project configuration, no Gradle flags. Install the plugin and start drawing.
We are excited to see what you build.