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

@ascii-fx/gpu

v0.8.0

Published

WebGPU renderer for ASCII FX: exact structural-v1 compute matching and one-draw atlas compositing, with a CPU fallback.

Readme

@ascii-fx/gpu

The realtime renderer: the exact structural matcher running as WebGPU compute, and an exact CPU implementation behind the identical API for everywhere else.

pnpm add @ascii-fx/gpu
import { createAsciiRenderer } from '@ascii-fx/gpu'
import { loadProfile } from '@ascii-fx/core'

const ascii = await createAsciiRenderer({
  canvas,
  profile: await loadProfile('/fonts/default.asciip'),
  columns: 160,
  color: 'full',
})

ascii.setSource(video)
ascii.start()

~2.6 ms/frame on an M3 Pro at 160×42 — indistinguishable from drawing the source alone, because matching and compositing overlap on the GPU.

The fallback is not a downgrade

backend: 'auto' picks WebGPU when there's an adapter and the CPU matcher when there isn't, and both produce identical output — same glyph ids, same colours, same flags, enforced by a browser conformance suite across colour modes, palettes, alpha modes, uneven reductions, temporal reuse, and dirty-region rematches.

What it will never do is quietly swap in a cheaper, worse matcher to hold a frame rate. Approximate matchers exist in @ascii-fx/core, and you get one only by naming it. A slow frame is a slow frame; it isn't silently a different picture.

What runs without a GPU

Spec §12's fallback, both halves of it. Matching goes to a pool of workers — one per core less one, capped at 8 — each running core's reduceBand/matchBand over a slice of cell rows. That is the same code a whole-frame matchFrame runs, so the assembled cells are byte-identical and the pool is a scheduling change, not an algorithm. Painting goes to a WebGL2 fullscreen draw of the glyph field against the atlas — the compositor shader from the WebGPU backend, ported to GLSL ES 3.00 — instead of building a full-resolution RGBA buffer on the CPU and blitting it. Pointer interactions come along with it, as the real shader rather than the Canvas2D path's cell-granular approximation.

await createAsciiRenderer({ canvas, profile, backend: 'cpu', workers: false }) // match on the main thread
await createAsciiRenderer({ canvas, profile, backend: 'cpu', workers: 4 }) // pin the pool size
await createAsciiRenderer({ canvas, profile, backend: 'cpu', compositor: 'canvas2d' }) // paint on Canvas2D

At 160×42 on an M3 Pro, taking those one at a time: 42.0 ms/frame on the main thread with Canvas2D, 31.9 with the worker pool, 2.8 with the WebGL2 composite as well — the same number the WebGPU backend posts, and the scene-only floor is 2.7.

A live source pays one frame of latency for the matcher: the renderer presents a frame while the next one matches. The first frame, static sources, and captureFrame() are matched inline and pay none. chromatic carries frame-to-frame hysteresis and stays on the main thread, though it is painted by the same WebGL2 composite.

Device loss

A GPU device can vanish — driver reset, tab backgrounded too long, laptop switching GPUs. The renderer rebuilds on a fresh device by itself. For the case it can't recover from, onDeviceLost fires and you remount the canvas: a canvas is bound to its first context type for good, so one that has held a 'webgpu' context can never be given a 2D one, and starting over needs a new element. @ascii-fx/react does this for you via canvasKey.

Interactions

Pointer-driven effects run in the composite stage, after matching, so they cost nothing extra per glyph:

ascii.setInteraction({ type: 'reveal', radius: 0.2, feather: 0.08, intensity: 1 })
ascii.pointer.set(0.5, 0.5)

reveal, displace, wave, push, color, glyph-scale, glyph-rotate, original-mix, and resolution.

Capability probe

import { getAsciiSupport } from '@ascii-fx/gpu'

const { webgpu, recommendedBackend, limitations } = await getAsciiSupport()

Safe to call anywhere, including Node during SSR — it reports rather than throws.

License

MIT