wasmdraw

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

README

wasmdraw

wasmdraw provides a Go API shaped like the browser's Canvas 2D API while batching rendering work across the WASM/JavaScript boundary.

Canvas calls made inside Context.OnFrame are recorded into a reusable binary command buffer. When the callback returns, the library copies that buffer to a persistent JavaScript typed array once and executes the complete batch. There is no public frame or flush lifecycle.

ctx.OnFrame = func(dt, width, height float64) {
	ctx.ClearRect(0, 0, width, height)
	ctx.FillStyle = "#ff22aa"
	ctx.FillRect(20, 20, 100, 100)
}

// A Go WASM program must remain alive for browser callbacks.
select {}

Retained paths live in the path2d package:

triangle := path2d.New()
triangle.MoveTo(0, -1)
triangle.LineTo(1, 1)
triangle.LineTo(-1, 1)
triangle.ClosePath()

ctx.Fill(triangle)

A path is materialized as a native JavaScript Path2D the first time it is drawn. Later frames send only its numeric handle unless the path changes.

API coverage

Coverage is described by feature area rather than as a percentage. Browser APIs contain overloads, legacy aliases, optional extensions, and newer experimental members, so a raw method count would give a misleading result.

API Coverage Included Not currently covered
Canvas 2D Broad core coverage Drawing state, transforms, paths and retained Path2D, fills/strokes/clipping, text and metrics, gradients/patterns, images, pixel data, hit testing, compositing, shadows, filtering, reset, context attributes, and offscreen canvases Focus-ring helpers; Path2D copy/string constructors and addPath; multiple or per-corner roundRect radii; and color-space/pixel-format options on image-data operations
WebGL Near-complete WebGL 1 core Context options and lifecycle, shaders/programs, buffers, attributes, uniforms, textures, framebuffers/renderbuffers, render state, drawing, queries, readback, and extension access Some JavaScript overload shapes are represented by typed Go methods instead of one-to-one overloads; newer canvas color-management properties are not exposed
WebGL2 Broad WebGL 2 coverage, plus the complete WebGL 1 surface Vertex arrays, instancing, transform feedback, queries, samplers, uniform buffers, multiple render targets, framebuffer blits/invalidation, multisampling, 3D and immutable textures, unsigned uniforms, non-square matrices, and sync objects Less-common reflection/indexed-query methods, integer constant vertex-attribute setters, and several typed/source overloads for 3D texture upload and pixel readback

The table tracks API availability, not browser support. A covered call can still depend on the browser, GPU, texture format, or WebGL extension in use. Frame-time operations are batched where the binary protocol supports them; synchronous queries and data transfers deliberately act as ordering barriers.

Images

Images load and decode in the browser. WASM stores only a numeric resource handle, so decoded pixels are never copied into Go.

player := wasmdraw.LoadImage("/assets/player.png")
player.OnError = func(err error) { fmt.Println(err) }

ctx.OnFrame = func(dt, width, height float64) {
	// Drawing an image that is not ready yet is a no-op.
	ctx.DrawImage(player, 20, 20)
	ctx.DrawImage(player, 100, 20, 64, 64)
	ctx.DrawImage(player, 0, 0, 32, 32, 200, 20, 64, 64)
}

For a decoded, transferable browser resource, use wasmdraw.LoadImageBitmap. Call Close when the bitmap is no longer needed.

OffscreenCanvas

An offscreen context records commands without starting its own animation loop. When an offscreen canvas is passed to DrawImage, its pending commands are inserted at that exact point in the visible frame's command stream.

layer := wasmdraw.NewOffscreenCanvas(256, 256)
layerCtx, err := ctx2d.GetOffscreenContext(layer, nil)
if err != nil {
	panic(err)
}

layerCtx.FillStyle = "#2255ff"
layerCtx.FillRect(0, 0, 256, 256)

ctx.OnFrame = func(dt, width, height float64) {
	ctx.DrawImage(layer, 0, 0)
}

Offscreen and visible commands still use one typed-array copy and one JavaScript executor call for the complete visible frame.

Canvas 2D state and queries

Canvas-style properties include text alignment and baseline, compositing, filtering, shadows, smoothing, line joins/caps/dashes, and advanced text state:

ctx.Font = "600 24px sans-serif"
ctx.TextAlign = ctx2d.TextAlignCenter
ctx.TextBaseline = ctx2d.TextBaselineMiddle
ctx.ShadowColor = "rgba(0, 0, 0, .4)"
ctx.ShadowBlur = 8

metrics := ctx.MeasureText("batched canvas")
ctx.FillText("batched canvas", width/2, height/2)

MeasureText, hit testing, GetTransform, and GetImageData are synchronous browser queries. They submit queued commands first so their results observe the correct state. Normal drawing remains automatically batched.

FillStyle and StrokeStyle accept CSS color strings, CanvasGradient, and CanvasPattern. ImageData remains browser-owned until Data or SetData explicitly copies pixels across the WASM boundary.

Examples

Each renderer owns an independent example application:

ctx2d/example
webgl/example
webgl2/example

Build every application from any working directory with either:

./scripts/build.ps1
./scripts/build.bash

The Go WASM runtime is stored once at js/wasm_exec.js. Serve the repository root so each example page can load that shared file. The example URLs are:

/ctx2d/example/public/
/webgl/example/public/
/webgl2/example/public/

The Canvas 2D application contains the existing example suite plus an allocation-free, ImageData-based raytraced solar system inspired by the original Raytracing Test. The WebGL application mirrors the Canvas 2D examples and adds native GPU demos such as a cube, while WebGL2 extends those examples with WebGL2-only techniques.

WebGL

The webgl package provides the WebGL1-compatible foundation shared by webgl2. It includes context attributes and lifecycle control, browser-owned resource wrappers, shader diagnostics, programs, buffers, vertex attributes, uniforms and matrices, textures, framebuffer/renderbuffer operations, render state, draw calls, extensions, and pixel readback.

gl, err := webgl.GetContext(canvas, webgl.DefaultOptions())
if err != nil {
	panic(err)
}

vertexShader, err := gl.CompileShader(webgl.VertexShader, vertexSource)
fragmentShader, err := gl.CompileShader(webgl.FragmentShader, fragmentSource)
program, err := gl.LinkProgram(vertexShader, fragmentShader)

gl.OnFrame = func(dt, width, height float64) {
	gl.Viewport(0, 0, int(width), int(height))
	gl.Clear(webgl.ColorBufferBit | webgl.DepthBufferBit)
	gl.DrawArrays(webgl.Triangles, 0, 3)
}

WebGL calls made inside OnFrame use a compact binary command stream for the frame-time hot path. Viewport/clear/draw calls, resource bindings, scalar uniforms, and matrices cross from WASM to JavaScript in one copy and one call. Resource creation, data uploads, compilation, and queries remain synchronous, which keeps browser errors and returned values correct while moving recurring work off the expensive boundary.

The WebGL example uses TAB to switch between a resolution-independent equilateral triangle, Canvas 2D text composited as a GPU texture, checker gradients, a thick arbitrary path morphing through circles/polygons/stars, and GPU-rendered fireworks on an explicitly opaque black surface that retain the Canvas example's launch, gravity, explosion, and trail behavior. The WebGL cycle also includes Canvas-parity snow and a 500,000-particle mouse swarm whose permanent grid is simulated in the vertex shader. A cached Canvas 2D texture displays FPS and frame-time metrics in the top-left. Text sources automatically commit pending 2D commands when WebGL consumes them and upload without copying pixels through Go.

The additional 3D cube uses indexed geometry and depth testing. Dragging the mouse orbits the camera; releasing it resumes a gentle idle rotation.

All fixed-size frame mutations use the binary proxy. Data uploads and synchronous queries are ordering barriers: they submit preceding commands, perform the direct browser call, and allow later frame calls to resume recording. webgl2.Context embeds this context, so all WebGL1-compatible methods and resource types are immediately available without duplicate wrappers. WebGL2-only methods and resources live on webgl2.Context; a small low-level bridge shares the executor and resource registry without exposing unsupported methods on the WebGL1 context.

WebGL2

webgl2.Context promotes the complete WebGL1 surface and adds vertex arrays, instanced drawing, transform feedback, queries, samplers, uniform-buffer bindings, multiple draw buffers, framebuffer blits, multisample storage, 3D/array and immutable textures, unsigned uniforms, non-square matrices, and GPU synchronization. VAO and instanced-draw hot paths use the same binary executor rather than a second bridge.

The WebGL2 application reuses the full WebGL1 parity suite and cube, then adds an interactive 3D solar-system diorama that draws its sun, planets, and major moons from one instanced sphere mesh, 50,000 instanced aurora shards, and a 100,000-point procedural star tunnel. The solar-system camera stays fixed until dragged so camera motion cannot visually reverse the outer orbits.

Documentation

Rendered for js/wasm

Index

Constants

View Source
const ErrorElementNotCanvas = "element is not a canvas"
View Source
const ErrorNoCanvasFound = "no canvas found"
View Source
const ErrorSelectorNoCanvasFound = "querySelectorAll returned no canvas elements"
View Source
const ErrorSelectorNotCanvas = "querySelectorAll returned an element that is not a canvas"
View Source
const ErrorSelectorNotFound = "querySelectorAll returned null or undefined"

Variables

This section is empty.

Functions

This section is empty.

Types

type CanvasElement

type CanvasElement struct {
	// contains filtered or unexported fields
}

func GetCanvasById

func GetCanvasById(id string, opts *Options) (canvas *CanvasElement, err error)

func QuerySelector

func QuerySelector(selector string, opts *Options) (canvas *CanvasElement, err error)

func QuerySelectorAll

func QuerySelectorAll(selector string, opts *Options) (canvases []*CanvasElement, err error)

func (*CanvasElement) AddEventListener

func (c *CanvasElement) AddEventListener(eventType EventType, handler EventHandler) (handle *EventHandlerHandle)

func (*CanvasElement) ElementSize

func (c *CanvasElement) ElementSize() (width, height float64)

Returns the size of the canvas element in the DOM (the size of the canvas element as it is displayed on the page).

func (*CanvasElement) GetContext

func (c *CanvasElement) GetContext(contextId string, opts js.Value) (context js.Value)

func (*CanvasElement) RemoveEventListener

func (c *CanvasElement) RemoveEventListener(handle *EventHandlerHandle)

func (*CanvasElement) Revision

func (c *CanvasElement) Revision() (revision uint64)

Revision changes whenever assigning the canvas size resets its rendering context.

func (*CanvasElement) Size

func (c *CanvasElement) Size() (width, height float64)

Returns the size of the canvas's drawing surface (the size of the canvas's internal drawing buffer).

func (*CanvasElement) WasmDrawSource

func (c *CanvasElement) WasmDrawSource() (src Source)

type CanvasImageSource

type CanvasImageSource interface {
	WasmDrawSource() Source
}

CanvasImageSource is implemented by objects accepted by Context.DrawImage.

type EventHandler

type EventHandler func(e js.Value) (o any)

type EventHandlerHandle

type EventHandlerHandle struct {
	// contains filtered or unexported fields
}

type EventType

type EventType uint8
const (
	EventType_KeyDown EventType = iota
	EventType_KeyUp
	EventType_MouseDown
	EventType_MouseUp
	EventType_MouseMove
	EventType_Wheel
	EventType_WebGLContextLost
	EventType_WebGLContextRestored
)

func (EventType) String

func (e EventType) String() (str string)

type FrameCallback

type FrameCallback func(dt, width, height float64)

type FrameLoop

type FrameLoop struct {
	// contains filtered or unexported fields
}

func NewFrameLoop

func NewFrameLoop(surface FrameSurface, callback FrameCallback) (loop *FrameLoop)

NewFrameLoop creates a reusable requestAnimationFrame lifecycle driver.

func (*FrameLoop) Dispose

func (l *FrameLoop) Dispose()

Dispose stops the loop and releases its JavaScript callback.

func (*FrameLoop) GetFPS

func (l *FrameLoop) GetFPS() (fps int)

func (*FrameLoop) GetMSPerFrame

func (l *FrameLoop) GetMSPerFrame() (mean, minValue, maxValue float64)

func (*FrameLoop) Start

func (l *FrameLoop) Start() (self *FrameLoop)

Start begins scheduling frames when the loop is not already running.

func (*FrameLoop) Stop

func (l *FrameLoop) Stop() (self *FrameLoop)

Stop prevents the loop from scheduling another frame.

type FrameSurface

type FrameSurface interface {
	Size() (float64, float64)
}

type Image

type Image struct {
	Source                      Source
	Complete                    bool
	NaturalWidth, NaturalHeight float64
	OnLoad                      func()
	OnError                     func(error)
	// contains filtered or unexported fields
}

Image wraps HTMLImageElement without copying decoded pixels into WASM.

func LoadImage

func LoadImage(src string) (image *Image)

func NewImage

func NewImage() (image *Image)

func (*Image) Close

func (i *Image) Close()

Close detaches browser callbacks held by the image wrapper. It does not cancel a request that the browser has already started.

func (*Image) SetCrossOrigin

func (i *Image) SetCrossOrigin(value string)

func (*Image) SetSrc

func (i *Image) SetSrc(src string)

func (*Image) WasmDrawSource

func (i *Image) WasmDrawSource() (src Source)

type ImageBitmap

type ImageBitmap struct {
	Source        Source
	Width, Height float64
	OnLoad        func()
	OnError       func(error)
	// contains filtered or unexported fields
}

ImageBitmap wraps a decoded browser ImageBitmap.

func LoadImageBitmap

func LoadImageBitmap(src string) (bitmap *ImageBitmap)

func (*ImageBitmap) Close

func (b *ImageBitmap) Close()

func (*ImageBitmap) WasmDrawSource

func (b *ImageBitmap) WasmDrawSource() (src Source)

type OffscreenCanvas

type OffscreenCanvas struct {
	// contains filtered or unexported fields
}

func NewOffscreenCanvas

func NewOffscreenCanvas(width, height float64) (offscreen *OffscreenCanvas)

func (*OffscreenCanvas) GetContext

func (c *OffscreenCanvas) GetContext(contextID string, opts js.Value) (ctx js.Value)

func (*OffscreenCanvas) Revision

func (c *OffscreenCanvas) Revision() (revision uint64)

func (*OffscreenCanvas) SetSize

func (c *OffscreenCanvas) SetSize(width, height float64)

func (*OffscreenCanvas) SetSourcePreparation

func (c *OffscreenCanvas) SetSourcePreparation(prepare func())

SetSourcePreparation installs work that must run before another renderer reads this canvas.

func (*OffscreenCanvas) Size

func (c *OffscreenCanvas) Size() (width, height float64)

func (*OffscreenCanvas) WasmDrawSource

func (c *OffscreenCanvas) WasmDrawSource() (src Source)

type Options

type Options struct {
	// contains filtered or unexported fields
}

func DefaultOptions

func DefaultOptions() (o *Options)

func (*Options) WithAutoResize

func (o *Options) WithAutoResize(autoResize bool) (self *Options)

WithAutoResize sets whether the canvas element should automatically resize when the window is resized.

func (*Options) WithScale

func (o *Options) WithScale(scale float64) (self *Options)

WithScale sets the scale of the canvas relating to the DOM size and the internal drawing buffer size. A scale of 1.0 means that the internal drawing buffer size is the same as the DOM size. A scale of 2.0 means that the internal drawing buffer size is twice the DOM size, which can be useful for high-DPI displays.

func (*Options) WithSize

func (o *Options) WithSize(width, height float64) (self *Options)

WithSize sets the width and height of the canvas element in the DOM (the size of the canvas element as it is displayed on the page).

type Source

type Source struct {
	// contains filtered or unexported fields
}

Source is the opaque browser-resource handle used by rendering packages.

func (Source) ID

func (s Source) ID() (id uint32)

func (Source) Prepare

func (s Source) Prepare()

Prepare makes pending producer work visible before another renderer reads the source.

func (Source) Ready

func (s Source) Ready() (ready bool)

func (Source) Value

func (s Source) Value() (value js.Value)

Directories

Path Synopsis
example/src command
path2d
Package path2d provides retained canvas paths with an API mirroring the browser's Path2D object.
Package path2d provides retained canvas paths with an API mirroring the browser's Path2D object.
internal
example/src command
example/src command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL