Skip to content

Repository files navigation

WGX

A dependency-free, tree-shakable TypeScript toolkit for building portable WebGPU and WebGL2 render pipelines without hiding the underlying graphics API.

WGX provides one backend-neutral resource and render-pass model for application code while keeping the existing low-level WebGL2 helpers available for direct control.

Installation

bun add @obvia/wgx

Quick Start

import { createWGX } from "@obvia/wgx"

const canvas = document.querySelector("canvas")!
const wgx = await createWGX({
  canvas,
  backend: "auto",
  powerPreference: "high-performance"
})

const vertices = wgx.createBuffer({
  data: new Float32Array([
    -1, -1,
     1, -1,
     0,  1
  ]),
  usage: ["vertex"]
})

const shader = wgx.createShader({
  webgpu: `
    struct VertexInput {
      @location(0) position: vec2f,
    }

    @vertex
    fn vertexMain(input: VertexInput) -> @builtin(position) vec4f {
      return vec4f(input.position, 0.0, 1.0);
    }

    @fragment
    fn fragmentMain() -> @location(0) vec4f {
      return vec4f(0.12, 0.55, 1.0, 1.0);
    }
  `,
  webgl2: {
    vertex: `#version 300 es
      layout(location = 0) in vec2 aPosition;
      void main() {
        gl_Position = vec4(aPosition, 0.0, 1.0);
      }`,
    fragment: `#version 300 es
      precision mediump float;
      out vec4 fragmentColor;
      void main() {
        fragmentColor = vec4(0.12, 0.55, 1.0, 1.0);
      }`
  }
})

const pipeline = wgx.createRenderPipeline({
  shader,
  vertex: {
    buffers: [{
      arrayStride: 8,
      attributes: [{
        shaderLocation: 0,
        offset: 0,
        format: "float32x2"
      }]
    }]
  }
})

wgx.render({ clearColor: [0.02, 0.03, 0.05, 1] }, pass => {
  pass.setPipeline(pipeline)
  pass.setVertexBuffer(0, vertices)
  pass.draw(3)
})

wgx.dispose()

With backend: "auto", WGX attempts WebGPU first and falls back to WebGL2. Explicit webgpu or webgl2 selection never silently switches backends.

Design

  • Portable: application code uses one Buffer → Shader → RenderPipeline → RenderPass model across WebGPU and WebGL2.
  • Safe: resources are device-owned, destroyed resources are rejected, cross-device usage fails early, and buffer writes are bounds-checked.
  • Performant: the WebGL2 backend caches program, viewport, VAO and upload bindings instead of querying driver state on the render path.
  • Direct: every high-level resource exposes its native backend representation through native when lower-level integration is required.
  • Capability-aware: backend differences such as compute and timestamp-query support are exposed through normalized device capabilities.
  • Modular: the root package exposes WGX and existing low-level WebGL2 helpers; focused subpaths remain tree-shakable.
  • Dependency-free: the runtime does not add a graphics engine, scene graph, renderer framework, or shader transpiler.

Backend Selection

const automatic = await createWGX({ canvas })
const webgpu = await createWGX({ canvas, backend: "webgpu" })
const webgl2 = await createWGX({ canvas, backend: "webgl2" })

createWGX is always asynchronous so backend selection has one stable call shape even though WebGPU adapter and device acquisition is asynchronous.

Capabilities

Every device exposes a normalized capability snapshot:

console.log(wgx.backend)
console.log(wgx.capabilities.depth)
console.log(wgx.capabilities.stencil)
console.log(wgx.capabilities.compute)
console.log(wgx.capabilities.timestampQuery)
console.log(wgx.capabilities.maxTextureSize)
console.log(wgx.capabilities.maxColorAttachments)
console.log(wgx.capabilities.uniformBufferOffsetAlignment)

Capabilities describe what the selected backend can support. They do not emulate unsupported features; for example, WebGL2 reports compute: false rather than translating compute work into a hidden fallback.

Buffers

Buffers declare their intended usage at creation time and remain owned by the device that created them.

const vertexBuffer = wgx.createBuffer({
  data: vertices,
  usage: ["vertex"]
})

const indexBuffer = wgx.createBuffer({
  data: indices,
  usage: ["index"]
})

vertexBuffer.write(updatedVertices)

Writes are validated against the allocated byte length and the four-byte alignment required by the portable upload path. A buffer created for one device cannot be submitted through another device.

Shaders

WGX does not perform hidden runtime shader transpilation. Portable applications provide WGSL for WebGPU and GLSL ES 3.00 for WebGL2:

const shader = wgx.createShader({
  webgpu: wgslSource,
  webgl2: {
    vertex: vertexGLSL,
    fragment: fragmentGLSL,
    attributes: { aPosition: 0 }
  }
})

When a backend is explicitly selected, only that backend's source is required.

Render Pipelines

const pipeline = wgx.createRenderPipeline({
  shader,
  vertex: {
    entryPoint: "vertexMain",
    buffers: [{
      arrayStride: 12,
      attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
    }]
  },
  fragment: {
    entryPoint: "fragmentMain",
    blend: {
      color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
      alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
    },
    writeMask: ["red", "green", "blue", "alpha"]
  },
  depth: {
    depthWriteEnabled: true,
    depthCompare: "less"
  },
  stencil: {
    front: {
      compare: "equal",
      failOp: "keep",
      depthFailOp: "keep",
      passOp: "replace"
    },
    readMask: 0xff,
    writeMask: 0xff
  },
  primitive: {
    topology: "triangle-list",
    frontFace: "ccw",
    cullMode: "back"
  }
})

The WebGPU backend creates a native render pipeline. The WebGL2 backend links a program once and lazily caches VAOs by pipeline and buffer binding combination. Optional depth and stencil state is translated to a managed WebGPU depth24plus-stencil8 attachment and the matching WebGL2 depth/stencil state without changing the application-facing descriptor. Fragment blend state and color write masks are also portable; WebGL2 caches the matching blend equations, factors, enablement, and write masks instead of rebinding unchanged output state on each draw.

Indexed line-strip and triangle-strip pipelines must declare primitive.stripIndexFormat as uint16 or uint32. WebGPU fixes this value at pipeline creation time, so WGX validates the same rule on WebGL2 and rejects a bound index buffer whose format does not match.

WebGL2 stencil buffers are opt-in context state. Applications that use portable stencil pipelines on the fallback backend should request one while creating WGX:

const wgx = await createWGX({
  canvas,
  webgl2: { stencil: true }
})

The actual context result is exposed through wgx.capabilities.stencil; WGX rejects stencil pipelines and clears when the selected WebGL2 context does not provide a stencil buffer.

Textures, Samplers and Bind Groups

WGX exposes portable RGBA8 textures, samplers, uniform-buffer bindings, sampled textures, and sampler bindings without hiding backend layout differences. With matching @group/@binding declarations in WGSL and GLSL uniform declarations, resources can be described once at the application layer:

const uniforms = wgx.createBuffer({
  size: 256,
  usage: ["uniform"]
})

const texture = wgx.createTexture({
  width: 2,
  height: 2,
  data: new Uint8Array([
    255, 255, 255, 255,
      0,   0,   0, 255,
      0,   0,   0, 255,
    255, 255, 255, 255
  ])
})

const sampler = wgx.createSampler({
  minFilter: "linear",
  magFilter: "linear"
})

const bindings = wgx.createBindGroup({
  pipeline,
  index: 0,
  entries: [
    {
      binding: 0,
      buffer: uniforms,
      webgl2: { uniformBlock: "Scene" }
    },
    {
      binding: 1,
      texture,
      webgl2: { uniform: "uTexture", unit: 0 }
    },
    {
      binding: 2,
      sampler,
      webgl2: { unit: 0 }
    }
  ]
})

The binding values map directly to WebGPU shader bindings. WebGL2 has no equivalent bind-group model, so entries that must run on WebGL2 provide explicit GLSL uniform-block, sampler-uniform, and texture-unit mappings instead of relying on naming conventions or shader reflection magic.

Uniform-buffer offsets and ranges are validated against wgx.capabilities.uniformBufferOffsetAlignment. Portable textures currently use rgba8unorm and accept typed RGBA8 pixel data.

Offscreen Render Targets

A portable texture can also be used as a color render target. Pipelines that render offscreen declare the expected texture format explicitly, while presentation pipelines keep the default canvas target format:

const renderTexture = wgx.createTexture({
  width: 1024,
  height: 1024,
  usage: ["render-attachment", "texture-binding"]
})

const offscreenPipeline = wgx.createRenderPipeline({
  shader: sceneShader,
  fragment: { targetFormat: "rgba8unorm" }
})

wgx.render({ target: renderTexture, clearColor: [0, 0, 0, 1] }, pass => {
  pass.setPipeline(offscreenPipeline)
  pass.draw(3)
})

The target texture dimensions define the default viewport and the bounds used to validate explicit viewport and scissor rectangles. A pipeline created for canvas cannot be submitted to an rgba8unorm texture target, and vice versa. This keeps WebGPU attachment-format requirements visible in the portable contract instead of hiding them behind backend-specific behavior.

Textures intended for post-processing normally include both render-attachment and texture-binding usage. WGX rejects binding the active render target as a sampled texture in the same pass so WebGPU validation errors and WebGL2 feedback loops fail consistently before drawing. WebGL2 creates and caches the required framebuffer/depth-stencil resources lazily and releases them with the owning texture.

Render Passes

wgx.render({
  clearColor: [0, 0, 0, 1],
  clearDepth: 1,
  clearStencil: 0,
  scissor: [32, 32, 640, 360]
}, pass => {
  pass.setPipeline(pipeline)
  pass.setStencilReference(1)
  pass.setBindGroup(0, bindings)
  pass.setVertexBuffer(0, vertexBuffer)
  pass.setIndexBuffer(indexBuffer, "uint16")
  pass.drawIndexed(indices.length)
})

WGX enforces a synchronous, scoped render callback. Returning a promise, re-entering render() on the same device, or using a pass after its callback has returned throws before additional backend work is submitted. Vertex and instance ranges are also validated against the bound portable buffers before draw submission.

When depth pipelines are used, clearDepth accepts values from 0 through 1. clearStencil and setStencilReference() accept unsigned 32-bit integer values. WebGPU keeps one managed depth24plus-stencil8 attachment and recreates it when the active canvas or texture target dimensions change. WebGL2 uses the presentation context depth/stencil buffers for canvas rendering and managed framebuffer attachments for texture targets.

scissor uses an integer [x, y, width, height] rectangle and is applied after attachment clears. This matches WebGPU render-pass clear behavior: a previous pipeline color write mask or WebGL2 scissor state cannot accidentally restrict the next pass clear.

Resource Ownership

All high-level resources are tracked by their creating device:

buffer.destroy()
shader.destroy()
pipeline.destroy()
wgx.dispose()

Individual destroy() calls are idempotent. wgx.dispose() releases every still-owned resource and can also be called repeatedly.

Device Lifecycle and Native State

The common device lifecycle is observable through state and lost:

console.log(wgx.state) // "ready"

wgx.lost.then(info => {
  console.error(info.backend, info.reason, info.message)
})

WebGPU device loss and WebGL2 context loss both move the WGX device to "lost". Resources owned by that device must not be reused; create a new WGX device and recreate application resources before rendering again. dispose() transitions the device to "disposed" and remains idempotent.

The native escape hatch is intentionally available, but direct WebGL2 mutations can make WGX's state cache stale. Call invalidateState() after changing WebGL2 bindings or render state through wgx.native so the next WGX render pass rebinds the required state. WebGPU uses explicit command state, so invalidateState() is a validated no-op there.

Portable API Overview

API Purpose
createWGX Select WebGPU/WebGL2 and create one graphics device
WGXCapabilities Normalize important backend limits and feature availability
createBuffer Allocate, update and track portable GPU buffers
createTexture Allocate, update and optionally render into portable RGBA8 textures
createSampler Create portable texture sampling state
createShader Hold backend-specific WGSL/GLSL shader sources/resources
createRenderPipeline Build one portable draw pipeline, including cull/front-face, blend/write-mask and optional depth/stencil state
createBindGroup Bind uniform buffers, textures and samplers to a pipeline
render Encode one canvas or texture render pass with color/depth/stencil clear, viewport and scissor state
WGXRenderPass Bind pipelines/resources, set stencil reference and issue indexed or non-indexed draws
state / lost Observe the common graphics-device lifecycle
invalidateState Reset cached WebGL2 state after direct native mutations
dispose Release all resources owned by the WGX device

Backend Support

Capability WebGPU WebGL2
Automatic backend selection Yes Yes, as fallback
Vertex/index buffers Yes Yes
Buffer updates Yes Yes
Render pipelines Yes Yes
Indexed drawing Yes Yes
Instanced drawing Yes Yes
Portable vertex layouts Yes Yes
Indexed strip format validation Yes Yes
Depth compare/write state Yes, managed depth/stencil attachment Yes, context depth buffer
Depth clearing Yes Yes
Stencil compare/operations/masks Yes, managed depth/stencil attachment Yes, when context stencil buffer is enabled
Stencil clearing/reference Yes Yes, when context stencil buffer is enabled
Color/alpha blending Yes Yes
Color write masks Yes Yes
Render-pass scissor rectangles Yes Yes
RGBA8 texture render targets Yes Yes, managed framebuffer
Render-target feedback-loop validation Yes Yes
RGBA8 textures and partial updates Yes Yes
Portable samplers Yes Yes
Uniform-buffer bind groups Yes Yes, with explicit GLSL block mapping
Texture/sampler bind groups Yes Yes, with explicit sampler/unit mapping
Device/context loss reporting Yes Yes
Native resource escape hatch Yes Yes
Native state invalidation Validated no-op Resets WGX state cache
Compute capability reporting Yes Reports unsupported
Timestamp-query capability reporting Feature-dependent Extension-dependent

Low-level WebGL2 API

The original WebGL2 helper layer remains available for cases that need direct native constants, textures, framebuffers, uniforms, vertex arrays, context lifecycle handling, or manual resource ownership.

You can import these helpers from the package root or focused subpaths such as @obvia/wgx/framebuffer, @obvia/wgx/uniform, and @obvia/wgx/vertex-array.

API Overview

Module Purpose
Attribute Attribute lookup, binding, enable/disable and instancing
Buffer Generic uploads, partial updates and primitive geometry
Canvas DPR-aware resizing
Context WebGL2 creation and context-loss lifecycle
Framebuffer Texture-backed offscreen render targets
Program Program creation, linking, validation and source pipeline
Render Clear, viewport, indexed and instanced draw calls
Shader Shader creation, compilation, validation and cleanup
Texture 2D texture creation, updates and sampler parameters
Toolkit High-level WebGL2 setup, resource tracking, rendering and cleanup
Uniform Scalars, vectors, matrices, samplers and WebGL2 buffers
Vertex Array Attribute layouts, index bindings and scoped VAO state

Toolkit

The high-level toolkit removes repeated context arguments, buffer targets, strict flags, and manual cleanup while keeping native WebGL2 resources accessible.

API

createToolkit

Creates a high-level toolkit for an existing WebGL2 context.

  • context – Target WebGL2 rendering context
const toolkit = createToolkit(context)

createToolkitFromCanvas

Creates a WebGL2 context and toolkit directly from a canvas element.

  • canvas – Target canvas element
  • options – Standard WebGL2 context attributes
    • strict – Throw error if WebGL2 cannot be created (default: false)
const toolkit = createToolkitFromCanvas(canvas, {
  antialias: true,
  powerPreference: "high-performance",
  strict: true
})

createVertexBuffer

Creates and tracks an ARRAY_BUFFER without repeating the target or strict flag.

  • data – Vertex buffer data
  • options – Optional buffer configuration
    • usage – Buffer usage hint (default: STATIC_DRAW)
const positionBuffer = toolkit.createVertexBuffer(positions)

createIndexBuffer

Creates and tracks an ELEMENT_ARRAY_BUFFER.

const indexBuffer = toolkit.createIndexBuffer(indices)

createMesh

Creates vertex buffers, an optional index buffer, and a vertex array as one drawable resource.

  • options – Mesh configuration
    • attributes – Vertex attribute layouts and typed data
      • location – Shader attribute location
      • size – Number of components per vertex
      • type – Component data type
      • data – Typed vertex data
      • stride – Byte distance between vertices (default: 0)
      • offset – First component byte offset (default: 0)
      • integer – Use integer attribute binding (default: false)
      • divisor – Instancing divisor (default: 0)
    • count – Number of vertices or indices to draw
    • indices – Optional typed index data
    • indexType – Optional index component type
    • indexUsage – Index buffer usage hint (default: STATIC_DRAW)
const mesh = toolkit.createMesh({
  attributes: [{
    location: 0,
    size: 3,
    type: context.FLOAT,
    data: positions
  }],
  indices,
  indexType: context.UNSIGNED_SHORT,
  count: indices.length
})

drawMesh

Binds and draws a toolkit mesh without repeating its vertex array, count, or index type.

toolkit.drawMesh(mesh)
toolkit.drawMesh(mesh, { mode: context.LINES, instances: 10 })

createProgram

Compiles, links, validates, and tracks a program from vertex and fragment sources.

const program = toolkit.createProgram({
  vertex: vertexSource,
  fragment: fragmentSource,
  attributes: { aPosition: 0 }
})

createTexture

Creates and tracks a configured 2D texture.

createFramebuffer

Creates and tracks a texture-backed framebuffer.

createVertexArray

Creates and tracks a WebGL2 vertex array.

withFramebuffer

Executes a scoped framebuffer render pass and restores framebuffer and viewport state.

withVertexArray

Executes work with a scoped vertex array binding.

clear

Clears attachments without repeating the context argument.

draw

Issues array, indexed, or instanced drawing without repeating the context argument.

setViewport

Uses drawing-buffer dimensions by default or accepts explicit dimensions.

dispose

Deletes every buffer, program, texture, framebuffer, renderbuffer, and vertex array created by the toolkit. Calling dispose more than once is safe.

toolkit.dispose()

Attribute

This section documents the helper functions for managing vertex attributes, including binding, enabling, disabling, and validating

API

bindAttribute

Binds a vertex attribute to the currently bound buffer and defines how data is read.

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Attribute binding configuration
    • name – Attribute name in the shader (e.g. "aPosition")
    • size – Number of components per attribute (e.g. 2 for vec2, 3 for vec3)
    • type – Data type of each component (e.g. context.FLOAT, context.UNSIGNED_BYTE)
    • stride – Byte offset between consecutive attributes (default: 0)
    • offset – Byte offset of the first component (default: 0)
    • strict – Throw error if attribute is not found (default: false)
    • normalize – Normalize integer data values to [0,1] or [-1,1] (default: false, WebGL2)
    • divisor – Divisor for instanced rendering (default: 0, WebGL2 only)
    • integer – Use integer attribute binding (vertexAttribIPointer) instead of float (default: false, WebGL2 only)
// Vertex shader example:
// in vec3 aPosition;

// Bind the "aPosition" attribute to a buffer (float attribute)
bindAttribute(context, program, { name: "aPosition", size: 3, type: context.FLOAT })

// Enforce strict mode: throw error if missing
bindAttribute(context, program, {
  name: "aPosition",
  size: 3,
  type: context.FLOAT,
  strict: true
})

// Normalize unsigned byte colors to [0,1]
bindAttribute(context, program, {
  name: "aColor",
  size: 4,
  type: context.UNSIGNED_BYTE,
  normalize: true
})

// Integer attribute binding (WebGL2 only)
// attribute ivec4 aBoneIDs;
bindAttribute(context, program, {
  name: "aBoneIDs",
  size: 4,
  type: context.UNSIGNED_BYTE,
  integer: true
})

// Instanced rendering (WebGL2 only)
bindAttribute(context, program, {
  name: "aOffset",
  size: 2,
  type: context.FLOAT,
  divisor: 1
})

disableAttribute

Disables a vertex attribute in the currently linked shader program.

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Attribute disabling configuration
    • name – Attribute name in the shader (e.g. "aTexCoord")
    • strict – Throw error if attribute is not found (default: false)
// Disable the "aTexCoord" attribute when it's not needed
disableAttribute(context, program, { name: "aTexCoord" })

// Enforce strict mode: throw error if missing
disableAttribute(context, program, {
  name: "aTexCoord",
  strict: true
})

enableAttribute

Enables a vertex attribute in the currently linked shader program.

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Attribute enabling configuration
    • name – Attribute name in the shader (e.g. "aPosition")
    • strict – Throw error if attribute is not found (default: false)
// Enable the "aPosition" attribute
enableAttribute(context, program, { name: "aPosition" })

// Enforce strict mode: throw error if missing
enableAttribute(context, program, {
  name: "aPosition",
  strict: true
})

validateAttribute

Validates a vertex attribute by checking its location in the linked shader program.

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Validation configuration
    • name – Attribute name in the shader (e.g. "aPosition")
    • strict – Throw error if attribute is not found (default: false)
// Validate attribute existence
const location = validateAttribute(context, program, { name: "aPosition" })

// Enforce strict mode: throw error if missing
const location2 = validateAttribute(context, program, {
  name: "aPosition",
  strict: true
})

// Use location in subsequent binding
if (location !== -1) {
  context.enableVertexAttribArray(location)
}

Buffer

By centralizing validation and using a consistent options‑object pattern, it reduces boilerplate, eliminates code duplication, and ensures a clean, predictable API for creating, updating, deleting, and binding buffers.

API

createBuffer

Creates a generic WebGL buffer and uploads data.

  • context – WebGL rendering context
  • options – Buffer configuration
    • target – Buffer target (ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER)
    • data – Vertex or index data (typed array)
    • usage – Buffer usage hint (default: STATIC_DRAW)
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vertices = new Float32Array([0,0, 1,0, 0,1])
const vboDefault = createBuffer(context, {
  target: context.ARRAY_BUFFER,
  data: vertices
})

// Strict mode: throws an error if buffer cannot be created
const indices = new Uint16Array([0,1,2, 2,1,3])
const iboStrict = createBuffer(context, {
  target: context.ELEMENT_ARRAY_BUFFER,
  data: indices,
  strict: true
})

// Custom usage hint (dynamic draw)
const dynamicVertices = new Float32Array([0,0, 1,0, 0,1])
const vboDynamic = createBuffer(context, {
  target: context.ARRAY_BUFFER,
  data: dynamicVertices,
  usage: context.DYNAMIC_DRAW
})

createQuadBuffer

Creates a buffer for a full‑screen quad.

  • context – WebGL rendering context
  • position – Attribute location index for vertex positions
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vboDefault = createQuadBuffer(context, positionLocation)
context.drawArrays(context.TRIANGLE_STRIP, 0, 4)

// Strict mode: throws an error if buffer cannot be created
const vboStrict = createQuadBuffer(context, positionLocation, { strict: true })
context.drawArrays(context.TRIANGLE_STRIP, 0, 4)

// With index buffer
const vbo = createQuadBuffer(context, positionLocation)
const ibo = createQuadIndexBuffer(context)
context.drawElements(context.TRIANGLES, 6, context.UNSIGNED_SHORT, 0)

createQuadIndexBuffer

Creates an index buffer for a full‑screen quad.

  • context – WebGL rendering context
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vbo = createQuadBuffer(context, positionLocation)
const iboDefault = createQuadIndexBuffer(context)
context.drawElements(context.TRIANGLES, 6, context.UNSIGNED_SHORT, 0)

// Strict mode: throws an error if buffer cannot be created
const iboStrict = createQuadIndexBuffer(context, { strict: true })
context.drawElements(context.TRIANGLES, 6, context.UNSIGNED_SHORT, 0)

createTriangleBuffer

Creates a buffer for a full‑screen triangle.

  • context – WebGL rendering context
  • position – Attribute location index for vertex positions
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vboDefault = createTriangleBuffer(context, positionLocation)
context.drawArrays(context.TRIANGLES, 0, 3)

// Strict mode: throws an error if buffer cannot be created
const vboStrict = createTriangleBuffer(context, positionLocation, { strict: true })
context.drawArrays(context.TRIANGLES, 0, 3)

createTriangleIndexBuffer

Creates an index buffer for a full‑screen triangle.

  • context – WebGL rendering context
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vbo = createTriangleBuffer(context, positionLocation)
const iboDefault = createTriangleIndexBuffer(context)
context.drawElements(context.TRIANGLES, 3, context.UNSIGNED_SHORT, 0)

// Strict mode: throws an error if buffer cannot be created
const iboStrict = createTriangleIndexBuffer(context, { strict: true })
context.drawElements(context.TRIANGLES, 3, context.UNSIGNED_SHORT, 0)

createCubeBuffer

Creates a buffer for a unit cube.

  • context – WebGL rendering context
  • position – Attribute location index for vertex positions
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vboDefault = createCubeBuffer(context, positionLocation)
const iboDefault = createCubeIndexBuffer(context)
context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)

// Strict mode: throws an error if buffer cannot be created
const vboStrict = createCubeBuffer(context, positionLocation, { strict: true })
const iboStrict = createCubeIndexBuffer(context, { strict: true })
context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)

createCubeIndexBuffer

Creates an index buffer for a cube.

  • context – WebGL rendering context
  • options – Optional configuration
    • strict – Throw error if buffer cannot be created (default: false)
// Silent mode (default): returns null if buffer cannot be created
const vboDefault = createCubeBuffer(context, positionLocation)
const iboDefault = createCubeIndexBuffer(context)
context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)

// Strict mode: throws an error if buffer cannot be created
const vboStrict = createCubeBuffer(context, positionLocation, { strict: true })
const iboStrict = createCubeIndexBuffer(context, { strict: true })
context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)

updateBuffer

Updates an existing WebGL buffer with new data.

  • context – WebGL rendering context
  • buffer – Existing buffer to update
  • options – Buffer configuration
    • target – Buffer target (ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER)
    • data – New typed array data to upload
    • usage – Buffer usage hint (default: STATIC_DRAW)
    • strict – Throw error if buffer binding fails (default: false)
// Replace the entire buffer with new vertices
const newVertices = new Float32Array([0,0, 1,0, 1,1])
updateBuffer(context, vbo, {
  target: context.ARRAY_BUFFER,
  data: newVertices
})

// Resize the buffer with more vertices (old data discarded)
const largerVertices = new Float32Array([0,0, 1,0, 1,1, 0,1])
updateBuffer(context, vbo, {
  target: context.ARRAY_BUFFER,
  data: largerVertices,
  usage: context.DYNAMIC_DRAW
})

// Strict mode: throws an error if buffer binding fails
updateBuffer(context, vbo, {
  target: context.ARRAY_BUFFER,
  data: newVertices,
  strict: true
})

updateBufferPartial

Partially updates an existing WebGL buffer with new data.

  • context – WebGL rendering context
  • buffer – Existing buffer to update
  • options – Buffer configuration
    • target – Buffer target (ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER)
    • data – Typed array containing new data
    • offset – Byte offset in the buffer where data should be written (default: 0)
    • strict – Throw error if buffer binding fails (default: false)
// Replace only the first vertex (two floats) in the buffer
const newVertex = new Float32Array([0.5, 0.5])
updateBufferPartial(context, vbo, {
  target: context.ARRAY_BUFFER,
  data: newVertex,
  offset: 0
})

// Replace the 3rd vertex (offset = 2 * 4 bytes = 8)
const anotherVertex = new Float32Array([1.0, 1.0])
updateBufferPartial(context, vbo, {
  target: context.ARRAY_BUFFER,
  data: anotherVertex,
  offset: 8,
  strict: true
})

deleteBuffer

Deletes a WebGL buffer and frees GPU memory.

  • context – WebGL rendering context
  • buffer – Buffer object to delete
  • options – Optional configuration
    • strict – Throw error if buffer deletion fails (default: false)
// Silent mode (default): ignores if buffer is null
deleteBuffer(context, vbo)

// Strict mode: throws an error if buffer is null or deletion fails
deleteBuffer(context, vbo, { strict: true })

// Delete both vertex and index buffers when cleaning up
deleteBuffer(context, vbo)
deleteBuffer(context, ibo)

Canvas

By centralizing context creation and resize logic, it provides a unified, predictable API for obtaining WebGL contexts and managing canvas scaling. This reduces repetitive setup code, ensures consistent handling of device pixel ratios, and simplifies error management with optional strict mode.

API

canvasContext

Safely obtain a WebGL rendering context from a canvas element.

  • canvas – Target canvas element to initialize WebGL on
  • options – Optional configuration (extends WebGL context attributes)
    • strict – Throw error if WebGL cannot be created (default: false)
    • * – Inherits all standard WebGL context attributes
// Silent mode (default): returns null if WebGL cannot be created
const contextDefault = canvasContext(canvas)

// Strict mode: throws an error if WebGL cannot be created
const contextStrict = canvasContext(canvas, { strict: true })

// Custom options (disable antialias, depth buffer)
const contextCustom = canvasContext(canvas, { antialias: false, depth: false })

// Create a WebGL2 context
const context = canvasContext(canvas)

if (!contextDefault) {
  // Handle unavailable WebGL2 support
}

resizeCanvas

Resize a canvas element based on its bounding box and device pixel ratio (DPR).

  • canvas – Target canvas element to resize
  • options – Optional configuration
    • max – Maximum constraints
      • dpr – Maximum device pixel ratio (default: 1.5)
      • width – Maximum canvas width (default: Infinity)
      • height – Maximum canvas height (default: Infinity)
    • min – Minimum constraints
      • dpr – Minimum device pixel ratio (default: 1)
      • width – Minimum canvas width (default: 1)
      • height – Minimum canvas height (default: 1)
    • source – Device pixel ratio source (default: window.devicePixelRatio || 1)
    • autoResize – Automatically resize on window resize events (default: false)
    • onResize – Callback invoked after resize with new width/height
    • rounding – Rounding strategy for DPR scaling ("floor" | "round" | "ceil")
// Default usage (uses window.devicePixelRatio)
resizeCanvas(canvas)

// Custom DPR source
resizeCanvas(canvas, { source: 2 })

// Custom min/max constraints
resizeCanvas(canvas, { max: { dpr: 2 }, min: { width: 10, height: 10 } })

// Auto resize with callback
resizeCanvas(canvas, {
  autoResize: true,
  onResize: (w, h) => console.log("Resized :", w, h),
  rounding: "round"
})

Uniform

By centralizing uniform setting logic, these helpers provide a unified, predictable API for assigning values to shader uniforms. This reduces repetitive code, ensures consistent type handling, and simplifies error management with optional strict mode.

API

setUniform1

Sets a single‑component uniform (float, int, boolean, or array).

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Uniform configuration
    • name – Uniform name in the shader
    • value – Value to assign (number | boolean | Float32Array | Int32Array)
    • strict – Throw error if uniform location is not found (default: false)
// Float uniform
setUniform1(context, program, { name: "uTime", value: performance.now() / 1000 })

// Integer uniform
setUniform1(context, program, { name: "uEnabled", value: 1 })

// Boolean uniform
setUniform1(context, program, { name: "uFlag", value: true })

// Float array uniform
setUniform1(context, program, { name: "uWeights", value: new Float32Array([0.1, 0.2, 0.3]) })

// Int array uniform
setUniform1(context, program, { name: "uIndices", value: new Int32Array([1, 2, 3]) })

// Strict mode
setUniform1(context, program, { name: "uMissing", value: 0, strict: true })

setUniform2

Sets a two‑component uniform (vec2).

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Uniform configuration
    • name – Uniform name in the shader
    • value – Value to assign ([number, number] | boolean[] | Float32Array | Int32Array)
    • strict – Throw error if uniform location is not found (default: false)
// Float vec2 uniform
setUniform2(context, program, {
  name: "uOffset",
  value: [0.5, 1.2]
})

// Integer vec2 uniform
setUniform2(context, program, {
  name: "uCoords",
  value: [3, 7]
})

// Boolean vec2 uniform
setUniform2(context, program, {
  name: "uFlags",
  value: [true, false]
})

// Float array uniform
setUniform2(context, program, {
  name: "uWeights",
  value: new Float32Array([0.1, 0.2])
})

// Int array uniform
setUniform2(context, program, {
  name: "uIndices",
  value: new Int32Array([1, 2])
})

// Strict mode example
setUniform2(context, program, {
  name: "uMissing",
  value: [0, 0],
  strict: true
})

setUniform3

Sets a three‑component uniform (vec3).

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Uniform configuration
    • name – Uniform name in the shader
    • value – Value to assign ([number, number, number] | boolean[] | Float32Array | Int32Array)
    • strict – Throw error if uniform location is not found (default: false)
// Float vec3 uniform
setUniform3(context, program, {
  name: "uColor",
  value: [1.0, 0.5, 0.0]
})

// Integer vec3 uniform
setUniform3(context, program, {
  name: "uCoords",
  value: [10, 20, 30]
})

// Boolean vec3 uniform
setUniform3(context, program, {
  name: "uFlags",
  value: [true, false, true]
})

// Float array uniform
setUniform3(context, program, {
  name: "uWeights",
  value: new Float32Array([0.1, 0.2, 0.3])
})

// Int array uniform
setUniform3(context, program, {
  name: "uIndices",
  value: new Int32Array([1, 2, 3])
})

// Strict mode example
setUniform3(context, program, {
  name: "uMissing",
  value: [0, 0, 0],
  strict: true
})

setUniform4

Sets a four‑component uniform (vec4).

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Uniform configuration
    • name – Uniform name in the shader
    • value – Value to assign ([number, number, number, number] | boolean[] | Float32Array | Int32Array)
    • strict – Throw error if uniform location is not found (default: false)
// Float vec4 uniform
setUniform4(context, program, {
  name: "uColor",
  value: [1.0, 0.5, 0.0, 1.0]
})

// Integer vec4 uniform
setUniform4(context, program, {
  name: "uCoords",
  value: [10, 20, 30, 40]
})

// Boolean vec4 uniform
setUniform4(context, program, {
  name: "uFlags",
  value: [true, false, true, false]
})

// Float array uniform
setUniform4(context, program, {
  name: "uWeights",
  value: new Float32Array([0.1, 0.2, 0.3, 0.4])
})

// Int array uniform
setUniform4(context, program, {
  name: "uIndices",
  value: new Int32Array([1, 2, 3, 4])
})

// Strict mode example
setUniform4(context, program, {
  name: "uMissing",
  value: [0, 0, 0, 0],
  strict: true
})

setUniformBlock (WebGL2)

Binds a uniform block to a binding point in a WebGL2 shader program.

  • context – WebGL2 rendering context
  • program – Linked shader program
  • options – Uniform block configuration
    • name – Uniform block name in the shader
    • index – Binding point index
    • strict – Throw error if uniform block is not found (default: false)
// Bind uniform block "Matrices" to binding point 0
setUniformBlock(context, program, {
  name: "Matrices",
  index: 0
})

// With strict mode enabled
setUniformBlock(context, program, {
  name: "Matrices",
  index: 0,
  strict: true
})

setUniformBuffer (WebGL2)

Binds a buffer to a uniform binding point in a WebGL2 shader program.

  • context – WebGL2 rendering context
  • options – Uniform buffer configuration
    • buffer – WebGLBuffer to bind
    • index – Binding point index
    • strict – Throw error if buffer binding fails (default: false)
// Create buffer and bind to binding point 0
const buffer = createBuffer(context, {
  target: context.UNIFORM_BUFFER,
  data: new Float32Array([0.1, 0.2, 0.3, 0.4])
})

setUniformBuffer(context, {
  buffer,
  index: 0
})

// With strict mode enabled
setUniformBuffer(context, {
  buffer,
  index: 0,
  strict: true
})

setUniformMatrix

Sets matrix uniforms (mat2, mat3, mat4, and WebGL2 non‑square variants).

  • context – WebGL rendering context (WebGL2 required for non‑square matrices)
  • program – Linked shader program
  • options – Matrix uniform configuration
    • name – Uniform name in the shader
    • matrix – Matrix values as a Float32Array
    • transpose – Whether to transpose the matrix before uploading (default: false)
    • strict – Throw error if uniform location or type is not found (default: false)
// mat2 (2×2 identity matrix)
const mat2 = new Float32Array([1, 0, 0, 1])
setUniformMatrix(context, program, { name: "uMat2", matrix: mat2 })

// mat3 (3×3 identity matrix with strict mode)
const mat3 = new Float32Array([1,0,0, 0,1,0, 0,0,1])
setUniformMatrix(context, program, { name: "uMat3", matrix: mat3, strict: true })

// mat4 (4×4 identity matrix with transpose)
const mat4 = new Float32Array([
  1,0,0,0,
  0,1,0,0,
  0,0,1,0,
  0,0,0,1
])
setUniformMatrix(context, program, { name: "uMat4", matrix: mat4, transpose: true })

// WebGL2: mat2x3
const mat2x3 = new Float32Array([1,0,0, 0,1,0])
setUniformMatrix(gl2Context, program, { name: "uMat2x3", matrix: mat2x3 })

// WebGL2: mat3x2
const mat3x2 = new Float32Array([1,0,0, 0,1,0])
setUniformMatrix(gl2Context, program, { name: "uMat3x2", matrix: mat3x2 })

// WebGL2: mat2x4
const mat2x4 = new Float32Array([1,0,0,0, 0,1,0,0])
setUniformMatrix(gl2Context, program, { name: "uMat2x4", matrix: mat2x4 })

// WebGL2: mat4x2
const mat4x2 = new Float32Array([1,0, 0,1, 0,0, 0,0])
setUniformMatrix(gl2Context, program, { name: "uMat4x2", matrix: mat4x2 })

// WebGL2: mat3x4
const mat3x4 = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0])
setUniformMatrix(gl2Context, program, { name: "uMat3x4", matrix: mat3x4 })

// WebGL2: mat4x3
const mat4x3 = new Float32Array([1,0,0, 0,1,0, 0,0,1, 0,0,0])
setUniformMatrix(gl2Context, program, { name: "uMat4x3", matrix: mat4x3 })

setUniformSampler

Sets a sampler uniform (texture unit binding).

  • context – WebGL rendering context (WebGL2)
  • program – Linked shader program
  • options – Sampler uniform configuration
    • name – Sampler uniform name in the shader (e.g. "uTexture")
    • unit – Texture unit index (must be ≥ 0, e.g. 0 for TEXTURE0)
    • strict – Throw error if uniform location is not found (default: false)
// Bind texture to unit 0 and assign sampler
context.activeTexture(context.TEXTURE0)
context.bindTexture(context.TEXTURE_2D, texture)

setUniformSampler(context, program, {
  name: "uTexture",
  unit: 0
})

// With strict mode enabled
setUniformSampler(context, program, {
  name: "uTexture",
  unit: 0,
  strict: true
})

setUniformUi (WebGL2)

Sets unsigned integer uniforms (1–4 components).

  • context – WebGL2 rendering context
  • program – Linked shader program
  • options – Unsigned integer uniform configuration
    • name – Uniform name in the shader (e.g. "uCount")
    • values – One to four unsigned integer values
      • [number] → uniform1ui
      • [number, number] → uniform2ui
      • [number, number, number] → uniform3ui
      • [number, number, number, number] → uniform4ui
    • strict – Throw error if uniform location is not found (default: false)
// Single unsigned int
setUniformUi(context, program, { name: "uCount", values: [5] })

// Two unsigned ints
setUniformUi(context, program, { name: "uCoords", values: [10, 20] })

// Three unsigned ints
setUniformUi(context, program, { name: "uTriple", values: [1, 2, 3] })

// Four unsigned ints
setUniformUi(context, program, { name: "uColor", values: [255, 128, 64, 32] })

// Strict mode example
setUniformUi(context, program, { name: "uMissing", values: [0], strict: true })

Context

API

canvasContext

Creates a WebGL2 rendering context from a canvas element.

  • canvas – Target canvas element
  • options – Optional WebGL2 context configuration
    • * – All standard WebGLContextAttributes
    • strict – Throw error if WebGL2 cannot be created (default: false)
const context = canvasContext(canvas, {
  antialias: true,
  alpha: false,
  strict: true
})

observeContext

Subscribes to webglcontextlost and webglcontextrestored and returns an unsubscribe function.

  • canvas – Canvas that owns the WebGL2 context
  • options – Context lifecycle configuration
    • onLost – Callback invoked when the context is lost
    • onRestored – Callback invoked when the context is restored
    • preventDefault – Allow browser restoration after context loss (default: true)
const unsubscribe = observeContext(canvas, {
  onLost: () => stopRendering(),
  onRestored: () => rebuildGpuResources()
})

unsubscribe()

Program

API

createProgramFromSources

Compiles and links a complete program, supports deterministic attribute locations and WebGL2 transform-feedback varyings, then releases intermediate shaders.

  • context – Target WebGL2 rendering context
  • options – Program source configuration
    • vertex – Vertex shader GLSL source
    • fragment – Fragment shader GLSL source
    • attributes – Optional attribute name-to-location mapping
    • transformFeedbackVaryings – Optional transform feedback outputs
    • transformFeedbackMode – Transform feedback storage mode (default: INTERLEAVED_ATTRIBS)
    • strict – Throw error if compilation or linking fails (default: false)
const program = createProgramFromSources(context, {
  vertex: vertexSource,
  fragment: fragmentSource,
  attributes: { aPosition: 0, aNormal: 1 },
  strict: true
})

Low-level helpers are also available: createProgram, linkProgram, validateProgram, useProgram, and deleteProgram.

Shader

API

Use createShader, compileShader, validateShader, attachShader, detachShader, and deleteShader when manual shader lifecycle control is required. Prefer createProgramFromSources for the common path.

Texture

API

createTexture2D

Creates a configured 2D texture from any TexImageSource. Passing null creates a transparent 1×1 placeholder. Creation returns null by default or throws with strict: true.

  • context – Target WebGL2 rendering context
  • image – Texture image source or null for a transparent placeholder
  • options – Texture configuration
    • internalFormat – Internal texture format (default: RGBA)
    • format – Source data format (default: RGBA)
    • type – Source data type (default: UNSIGNED_BYTE)
    • wrapS – Horizontal wrapping mode (default: CLAMP_TO_EDGE)
    • wrapT – Vertical wrapping mode (default: CLAMP_TO_EDGE)
    • minFilter – Minification filter (default: LINEAR)
    • magFilter – Magnification filter (default: LINEAR)
    • generateMipmap – Generate texture mipmaps (default: false)
    • strict – Throw error if creation fails (default: false)
const texture = createTexture2D(context, image, {
  minFilter: context.LINEAR_MIPMAP_LINEAR,
  generateMipmap: true,
  strict: true
})

Use updateTexture for dynamic image/video frames and setTextureParameters for an already-bound texture.

Framebuffer

API

createFramebuffer

Creates a texture-backed offscreen target with an optional depth renderbuffer. Use withFramebuffer to restore framebuffer and viewport state reliably.

  • context – Target WebGL2 rendering context
  • options – Framebuffer configuration
    • width – Framebuffer width in pixels
    • height – Framebuffer height in pixels
    • internalFormat – Color texture internal format (default: RGBA)
    • format – Color texture format (default: RGBA)
    • type – Color texture data type (default: UNSIGNED_BYTE)
    • depth – Create a depth renderbuffer (default: true)
    • minFilter – Texture minification filter (default: LINEAR)
    • magFilter – Texture magnification filter (default: LINEAR)
    • strict – Throw error if creation fails (default: false)
const target = createFramebuffer(context, { width: 1024, height: 1024, depth: true, strict: true })!

withFramebuffer(context, target, () => {
  clear(context, { color: [0, 0, 0, 0], depth: 1 })
  renderScene()
})

deleteFramebuffer(context, target)

withFramebuffer

Binds a framebuffer for a scoped render pass and always restores the previous framebuffer and viewport.

  • context – Target WebGL2 rendering context
  • resource – Framebuffer resource or null for the default framebuffer
  • renderCallback – Callback executed while the framebuffer is active

deleteFramebuffer

Deletes the framebuffer, color texture, and optional depth renderbuffer.

  • context – Target WebGL2 rendering context
  • resource – Framebuffer resource to delete

Vertex Array

API

createVertexArray

Creates a WebGL2 vertex array and captures attribute and index buffer bindings.

  • context – Target WebGL2 rendering context
  • options – Vertex array configuration
    • attributes – Array of vertex attribute descriptors
      • location – Shader attribute location
      • size – Number of components per vertex
      • type – Component data type
      • buffer – Source array buffer
      • stride – Byte distance between vertices (default: 0)
      • offset – First component byte offset (default: 0)
      • normalize – Normalize fixed-point values (default: false)
      • integer – Use integer attribute binding (default: false)
      • divisor – Instancing divisor (default: 0)
    • indexBuffer – Optional element array buffer
    • strict – Throw error if creation fails (default: false)
const vertexArray = createVertexArray(context, {
  attributes: [{
    location: 0,
    size: 3,
    type: context.FLOAT,
    buffer: positionBuffer
  }],
  indexBuffer,
  strict: true
})

withVertexArray

Binds a vertex array for a scoped operation and always restores the previous binding.

  • context – Target WebGL2 rendering context
  • vertexArray – Vertex array object to bind
  • renderCallback – Callback executed while the vertex array is active

deleteVertexArray

Deletes a vertex array when it is no longer needed.

  • context – Target WebGL2 rendering context
  • vertexArray – Vertex array object to delete

Render

API

clear

Clears color and, when provided, depth and stencil attachments.

  • context – Target WebGL2 rendering context
  • options – Clear attachment values
    • color – RGBA clear color (default: [0, 0, 0, 0])
    • depth – Optional depth clear value
    • stencil – Optional stencil clear value
clear(context, { color: [0.02, 0.03, 0.05, 1], depth: 1 })

setViewport

Sets the viewport to the drawing buffer or explicit dimensions.

  • context – Target WebGL2 rendering context
  • width – Viewport width (default: drawing buffer width)
  • height – Viewport height (default: drawing buffer height)
setViewport(context)
setViewport(context, 1920, 1080)

draw

Dispatches array/indexed drawing and uses WebGL2 instanced drawing when instances is greater than one.

  • context – Target WebGL2 rendering context
  • options – Draw configuration
    • mode – Primitive drawing mode
    • count – Number of vertices or indices
    • first – First vertex for non-indexed drawing (default: 0)
    • type – Optional index type for indexed drawing
    • offset – Index buffer byte offset (default: 0)
    • instances – Number of instances (default: 1)
draw(context, { mode: context.TRIANGLES, count: 36, type: context.UNSIGNED_SHORT })
draw(context, { mode: context.TRIANGLES, count: 6, instances: 100 })

createRenderLoop

Creates a controllable animation-frame render loop with clamped delta time.

  • renderCallback – Callback invoked for every animation frame
    • time – Current high-resolution timestamp in milliseconds
    • delta – Time since the previous frame in seconds, clamped to 0.1
    • frame – Number of frames rendered since the loop started
const renderLoop = createRenderLoop(({ time, delta, frame }) => {
  updateScene(delta)
  renderScene(time, frame)
})

renderLoop.start()
renderLoop.stop()

Error Handling

Resource creators use a consistent two-mode contract:

  • Default mode returns null/false when an operation cannot complete.
  • strict: true throws a typed or native error with operation details.

GPU resources remain caller-owned. Delete buffers, shaders, programs, textures and framebuffers when no longer needed.

Compatibility

  • WebGPU is used when available and selected; WebGL2 is the portable rendering fallback.
  • TypeScript declarations are bundled with the package.
  • ESM and CommonJS builds are provided.
  • Bun is the package, test, benchmark and build workflow runtime; rendering requires a browser graphics context.

Development

bun install
bun run check
bun run test
bun run coverage
bun run build
bun run pack

Tests run with bun:test; DOM and canvas globals are registered through very-happy-dom. Builds are produced with tsdown for ESM and CommonJS consumers.

Release Checklist

  1. Run bun run check, bun run test, bun run coverage, and bun run pack.
  2. Confirm package.json and changelog.md contain the target version.
  3. Commit, tag the release as v0.8.1, and publish the GitHub release.
  4. GitHub Actions re-runs Bun tests, TypeScript checks, and the tsdown build before publishing with npm provenance.

License

MIT © 2026 Selçuk Çukur. See license.md.

About

A modular WebGL toolkit — type‑safe, lightweight helpers for every rendering need.

Topics

Resources

Contributing

Security policy

Stars

5 stars

Watchers

3 watching

Forks

Releases

Contributors

Languages