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

gpudump

v0.1.0

Published

Embeddable WebGL instrumentation: always-on frame metrics + on-demand frame captures, as one versioned data model.

Readme

gpudump

Backbone library to gather metrics about WebGL 1/2 (WebGPU will be added later) apps.

I was using stuff like Spector.js / gamestats.js for the longest time, but for serious projects I want more insight into how my game is being rendered. Lately I started making web games with my own rendering solutions, and I wanted a unified way to gather insight about different parts of my render pipeline.

But instead of making "Spector.js 2", I opted for this library — gpudump — that lets you easily gather enough information to make your own domain-specific gamestats / Spector.js / MCP for debugging rendering.

So: no UI, no transport, no opinions about your engine. It patches getContext, watches the GL calls that go through it, and hands you plain data.

Two tiers

Metrics — always on, basically free. Every frame you get draw calls, triangles, state switches, upload bytes, CPU/GPU time and a memory snapshot, split by your own markers. This is the "rendering inspector" case, without hand-maintaining drawCalls++ all over your renderer.

Captures — armed on demand. One frame (or one callback) recorded as a full command stream, plus the GL state, a resource table with shader sources, and optional images of what the framebuffer looked like along the way. This is the "Spector.js" case.

Both tiers are the same versioned data model — see src/core/types.ts, that file is the contract. Everything downstream (your HUD, your capture viewer, your agent bridge) is written against it and nothing else.

Quick start

import gpudump from "gpudump";

gpudump.install();              // before you create any context

Then per frame, if you want the numbers attributed to something meaningful:

gpudump.pushMarker("shadows");
drawShadows();
gpudump.popMarker();

gpudump.pushMarker("world");
drawWorld();
gpudump.popMarker();

And read them wherever your HUD lives:

const [frame] = gpudump.fetchMetrics();
frame.counters.drawCalls;       // frame totals
frame.markers.children;         // same counters, per marker, as a tree
frame.gpuMs;                    // null if timer queries aren't available

Frames are detected from rAF automatically. If your loop isn't rAF-driven, call frameStart() / frameEnd() yourself — the first explicit call turns auto-detection off.

Capturing a frame:

const capture = await gpudump.snapshotNextFrame({ visuals: { passes: true } });
// capture.commands, capture.resources, capture.frames, capture.blobs

or a specific piece of code, wherever it runs:

const { capture, result } = await gpudump.snapshot(() => rebuildAtlas());

Name your objects so captures are readable — label() is the only reason an inspector can say "atlas" instead of "texture #7":

gpudump.label(texture, "atlas");

API

| | | |---|---| | install(options?) / uninstall() | patch / restore this realm | | frameStart() / frameEnd() | manual frame boundaries (optional) | | pushMarker(name) / popMarker(name?) | nestable scopes; name on pop is just mismatch checking | | fetchMetrics(amount?, realm?) | newest-first frames, deep-copied | | changeMetricsBufferSize(frames) | ring depth, default 240 (~4s at 60fps) | | label(obj, name) | name a texture/buffer/program/framebuffer | | fetchResources(realm?) | per-resource byte accounting, on demand | | snapshotNextFrame(options?) | capture the next frame boundary | | snapshot(fn, options?) | capture whatever fn does; returns its value too | | getRealms() | main thread + connected workers | | serializeCapture / deserializeCapture | captures over the wire, binary blobs intact |

SnapshotOptions is where the cost lives: maxCommands (default 15k), stacks, visuals (images after draws / at pass switches / on markers / at frame end, with a byte budget), and resources (shader source on by default, buffer/texture contents off). resources.texturePreviews is the cheap one — a thumbnail per 2D texture, downscaled on the GPU once each, and it stands on its own without visuals.

Workers

Call install() in the worker too. It finds the main realm on its own, and from the main thread getRealms() lists it — pass one to fetchMetrics, fetchResources or snapshotNextFrame to reach across. Worker metrics land about one frame behind, and fetchResources(realm) returns a promise for a remote realm, so await it either way.

Caveats

  • install() has to run before your contexts are created. It patches getContext; a context that already exists was never seen being made.
  • GPU timings need EXT_disjoint_timer_query_webgl2 and resolve a few frames late — gpuMs is null where that isn't available, and gpuMsFrameLag tells you how late.
  • Byte totals are estimates: mips and formats are counted, driver padding isn't. Counts are exact.
  • Not for production. There's no fast path that makes patched GL free.

MIT.