npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@latkit/gpu

v0.7.1

Published

What every Latkit renderer shares: WebGPU devices, presentation, the frame loop, the attach lifecycle, channels, and colormap textures.

Readme

@latkit/gpu

What every Latkit renderer shares: Core WebGPU devices and the pool they are leased from, canvas presentation, the frame loop, the attach lifecycle, the channels a renderer binds values and series to, the colormap lookup texture, and controller events.

@latkit/gpu handles the environmental part of requesting a device and then returns the platform GPUDevice directly. Applications rarely import it beyond the device pool; a renderer is built on the rest. All exports come from the single @latkit/gpu entrypoint.

Install

npm install @latkit/gpu

Request a device

import { requestDevice } from '@latkit/gpu';

const device = await requestDevice();

try {
  console.log(device.limits);

  void device.lost.then((info) => {
    console.error('GPU device lost:', info.reason, info.message);
  });
} finally {
  device.destroy();
}

requestDevice() requests Core WebGPU and leaves the adapter power preference to the browser. Pass powerPreference only when the application has a specific reason to override that choice:

const device = await requestDevice({
  powerPreference: 'high-performance',
});

Handle availability

import { GpuUnavailableError, requestDevice } from '@latkit/gpu';

try {
  const device = await requestDevice();

  try {
    // Create renderers that borrow device.
  } finally {
    device.destroy();
  }
} catch (error) {
  if (error instanceof GpuUnavailableError) {
    console.error(`WebGPU unavailable at ${error.stage}:`, error.message);
  } else {
    throw error;
  }
}

Only API absence, a null adapter, and device-request rejection use GpuUnavailableError. Other platform and programming failures retain their original identity.

Share one device

Every Latkit controller leases its device from devices, the realm-wide pool: one device per page, requested by the first acquire and destroyed with the last release. Leases count the borrowers, concurrent acquisitions coalesce into one request, and a device the platform reports lost is retired so the next acquisition requests a replacement. createDevicePool() makes a private pool with the same rules and forwards requestDevice() options; hand it to a controller through its devices option.

import { createDevicePool, devices } from '@latkit/gpu';

const lease = await devices.acquire();
try {
  // Borrow lease.device alongside the controllers on this page.
} finally {
  lease.release(); // the device outlives this lease only while another one holds it
}

const network = createNetwork({ devices: createDevicePool({ powerPreference: 'low-power' }) });

Configure presentation

Renderer implementations can configure either an HTMLCanvasElement or an OffscreenCanvas through the same primitive:

import { createPresentation } from '@latkit/gpu';

const presentation = createPresentation(device, canvas);
presentation.resize(800, 450);

try {
  const texture = presentation.context.getCurrentTexture();
  // Encode rendering commands for texture.
} finally {
  presentation.destroy();
}

Presentation owns its context configuration and backing-size changes. It preserves aspect ratio when fitting oversized requests to the device limit, restores the original canvas size when destroyed, and never destroys its borrowed device. presentation.observe() reports device-pixel size and pixel ratio now and on every change of an HTML canvas (an OffscreenCanvas reports once) while leaving scheduling and resize policy to the renderer, or to createFrameLoop() below:

const stop = presentation.observe((width, height, pixelRatio) => {
  presentation.resize(width, height);
});
// ...
stop();

Drive frames

createFrameLoop() schedules one canvas's frames: wakes coalesce into one animation frame, a resize re-renders before the next paint, and the backing store grows in steps of 64 device pixels while a resize is in flight and snaps exact once the size holds for three frames. render receives the same Frame every call (read it, never keep it) and returns true to be called again next frame:

import { createFrameLoop, createPresentation } from '@latkit/gpu';

const presentation = createPresentation(device, canvas);
const loop = createFrameLoop(presentation, ({ now, width, height, backingScale, settled }) => {
  // Draw the frame at width x height CSS pixels into presentation.context.getCurrentTexture().
  return animating(now); // true keeps frames coming; false waits for the next wake
});

loop.wake(); // after any change that should be drawn
loop.pause(); // while the view is hidden; resume() schedules a frame
loop.destroy(); // for good, and stop observing the canvas

Every size report after the synchronous first one renders a frame, woken or not, and that includes the observer's initial notification: be ready to draw the current state once the loop exists. A canvas without area skips its frame until a resize gives it one. A render that pauses or destroys the loop stops it, and wakes while paused are dropped: resume() schedules the next frame.

Attach a controller

createAttachment() is the attach lifecycle every Latkit controller shares: supersession, joining a repeat attach, and recovery on a replacement device, as the lifecycle guide describes. A renderer supplies what one binding builds and what its release forgets:

import { createAttachment, devices } from '@latkit/gpu';

const attachment = createAttachment({
  devices,
  bind(device, canvas, cleanup) {
    const presentation = createPresentation(device, canvas);
    cleanup(() => presentation.destroy()); // cleanups run in reverse on release
    return presentation;
  },
  release: (presentation) => {}, // before the cleanups
  attached: (bound) => emit('attached', bound),
  lost: (loss) => emit('deviceLost', loss),
});

await attachment.attach(canvas); // false when a newer attach or a detach took over
attachment.detach(canvas); // only while `canvas` is the current one

Bind channels

createChannels() is the channel binder every renderer's setChannel runs on. A renderer hands it its registry (each channel's scope, components, whether it is normalized, and whether it can follow a series), the store its shaders read (reserve and writeWords), and how a channel's record reaches its uniforms: the word its values start at, whether it is bound, and the (value - min) * scale its values map through.

import { createChannels } from '@latkit/gpu';

const channels = createChannels<Channel, 'vertex' | 'edge'>({
  name: 'network',
  structure: 'topology',
  channels: CHANNELS,
  store: () => renderer, // null while detached: the CPU keeps every value, and upload() restores it
  record: (channel, offset, bound, min, scale) => writeUniforms(channel, offset, bound, min, scale),
  shown: () => loop.wake(), // a followed channel shows another frame
  error: (channel, cause) => emit('error', { channel, cause }),
});

channels.load({ vertex: vertexCount, edge: edgeCount }); // a slot per channel
channels.set('vertexColor', values, [0, 1]);
channels.set('vertexHeight', { series, signal: 0 }); // follows the signal; the domain follows its range
channels.seek(t); // every followed channel at the playhead

Every channel owns a slot for as long as a load holds, so binding one is one write and never a relayout. A followed signal's frames stay resident in a window of the store after the slots, shared by every channel following that signal, and the next ones load as the playhead advances or the series appends, so a seek within them rewrites one word per channel.

Colormaps and events

bakeColormap(colormap) samples a Colormap into COLORMAP_LUT_SIZE opaque rgba8 texels, the lookup texture every renderer's shaders map normalized values through. createEmitter() is the typed event dispatcher behind every controller's on: listeners run in order, and one that throws rethrows on a microtask while the rest still run.

Render targets

RenderTarget is a device, format, dimensions, and texture() for the next frame. Presentation implements it for a canvas; createRenderTarget(device, width, height) owns a fixed texture for offscreen composition. Destroy a fixed target after the renderers borrowing it are destroyed. SceneRenderer.prepare(sourceTime, signal) waits for channel samples; draw(outputTimeMs) advances visual animation. These are the shared primitives used by @latkit/video.

Shared glyphs

Diagram and Monitor share the same monospace SDF atlas, rasterizer, texture synchronization, and WGSL coverage function. Layout, anchors, culling, and tick policy belong to each renderer.

import { GlyphAtlas, createGlyphTexture, glyphMetrics, glyphShader } from '@latkit/gpu';

const atlas = new GlyphAtlas('ui-monospace, monospace');
const cell = atlas.cell('A');
const texture = createGlyphTexture(device);
texture.sync(atlas);
// Bind texture.view, and append glyphShader to the renderer's WGSL.
// glyph_coverage(distance) must run in uniform fragment control flow.
const advance = glyphMetrics.advance * 12;

const captured = atlas.snapshot();
const restored = GlyphAtlas.from(captured); // identical pixels and cell indices in another realm
texture.destroy();

Rasterization happens once per grapheme and font generation. Each texture remembers its own revision, so consumers cannot clear each other's pending updates. Only changed rows upload; unchanged frames upload nothing. An atlas is capped at 4 MiB of r8 pixels. Exhaustion and missing glyphs in a sealed snapshot throw explicitly. Snapshots own copied pixels; rasterization can use an injected GlyphRasterizer or the built-in Canvas2D implementation. All exports remain at the package root.

Renderer shade compilers use shaderFailure(label, modules, cause) for one diagnostic format, including shader source locations, while each renderer owns its pipeline and fragment contract.