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

stats-gl

v4.0.2

Published

[![Version](https://img.shields.io/npm/v/stats-gl?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/stats-gl) [![Version](https://img.shields.io/npm/dw/stats-gl?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/st

Readme

stats-gl

Version Version

For AI/LLM users: See llms.txt for a condensed API reference.

WebGL/WebGPU Performance Monitor with real-time FPS, CPU, and GPU timing. Supports Three.js, native WebGL2/WebGPU, Web Workers, and texture preview panels.

Live Demo

https://github.com/RenaudRohlinger/stats-gl/assets/15867665/3fdafff4-1357-4872-9baf-0629dbaf9d8c

Note: To support GPU monitoring on Safari you need to enable Timer Queries under WebKit Feature Flags > WebGL Timer Queries

Installation

npm install stats-gl

Quick Start

Three.js (WebGL or WebGPU)

import Stats from 'stats-gl';
import * as THREE from 'three';

const stats = new Stats({ trackGPU: true });
document.body.appendChild(stats.dom);

const renderer = new THREE.WebGLRenderer(); // or WebGPURenderer
stats.init(renderer);

function animate() {
  renderer.render(scene, camera); // or renderAsync for WebGPU
  stats.update();
}
renderer.setAnimationLoop(animate);

Native WebGL2

import Stats from 'stats-gl';

const stats = new Stats({ trackGPU: true });
const canvas = document.querySelector('#canvas');
stats.init(canvas);
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  // ... your WebGL draw calls ...
  stats.end();
  stats.update();
  requestAnimationFrame(animate);
}
animate();

Native WebGPU

import Stats from 'stats-gl';

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice({ requiredFeatures: ['timestamp-query'] });
const context = canvas.getContext('webgpu');

const stats = new Stats({ trackGPU: true });
stats.init(device); // Pass the GPUDevice
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [...],
    timestampWrites: stats.getTimestampWrites() // Enable GPU timing
  });
  // ... your draw calls ...
  pass.end();

  stats.end(encoder); // Pass encoder to resolve timestamps
  device.queue.submit([encoder.finish()]);

  stats.update();
  requestAnimationFrame(animate);
}
animate();

React Three Fiber

A <StatsGl /> component is available through @react-three/drei:

import { Canvas } from '@react-three/fiber'
import { StatsGl } from '@react-three/drei'

const Scene = () => (
  <Canvas>
    <StatsGl />
  </Canvas>
)

Tresjs (Vue)

A <StatsGl /> component is available through cientos:

<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import { StatsGl } from '@tresjs/cientos'
</script>

<template>
  <TresCanvas>
    <StatsGl />
  </TresCanvas>
</template>

Parameters

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | trackFPS | boolean | true | Enable built-in FPS and CPU panels | | trackGPU | boolean | false | Enable GPU timing (requires extension support) | | trackHz | boolean | false | Enable refresh rate detection | | trackCPT | boolean | false | Enable Three.js compute shader timing (WebGPU only) | | logsPerSecond | number | 4 | How often to update text display | | graphsPerSecond | number | 30 | How often to update graphs | | samplesLog | number | 40 | Number of samples for text averaging | | samplesGraph | number | 10 | Number of samples for graph averaging | | precision | number | 2 | Decimal places for CPU/GPU values | | minimal | boolean | false | Minimal mode - click to cycle panels | | horizontal | boolean | true | Horizontal panel layout | | mode | number | 0 | Initial panel (0=FPS, 1=CPU, 2=GPU) |

Web Worker / OffscreenCanvas

stats-gl supports rendering in a Web Worker using OffscreenCanvas. Use StatsProfiler in the worker to collect timing data, and send it to the main thread where Stats displays it.

Worker (offscreen rendering):

import { StatsProfiler } from 'stats-gl';

const profiler = new StatsProfiler({ trackGPU: true });

self.onmessage = async (e) => {
  if (e.data.type === 'init') {
    const canvas = e.data.canvas;
    const gl = canvas.getContext('webgl2');
    await profiler.init(gl);
    requestAnimationFrame(loop);
  }
};

function loop() {
  profiler.begin();
  // ... your rendering code ...
  profiler.end();
  profiler.update();

  // Send timing data to main thread
  self.postMessage({ type: 'stats', ...profiler.getData() });
  requestAnimationFrame(loop);
}

Main thread:

import Stats from 'stats-gl';

const stats = new Stats({ trackGPU: true });
document.body.appendChild(stats.dom);

const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();

const worker = new Worker('worker.js', { type: 'module' });
worker.postMessage({ type: 'init', canvas: offscreen }, [offscreen]);

worker.onmessage = (e) => {
  if (e.data.type === 'stats') {
    stats.setData(e.data);
  }
};

function loop() {
  stats.update();
  requestAnimationFrame(loop);
}
loop();

StatsProfiler API

StatsProfiler is a headless version of Stats designed for workers:

  • init(canvas | device) - Initialize with WebGL context, OffscreenCanvas, or GPUDevice
  • begin() / end(encoder?) - Wrap your render calls (pass encoder for native WebGPU)
  • getTimestampWrites() - Get timestampWrites config for native WebGPU render pass
  • update() - Process timing data
  • getData() - Returns { fps, cpu, gpu, gpuCompute }
  • captureTexture(source, sourceId) - Capture texture to ImageBitmap for transfer

Stats.setData()

Use stats.setData(data) to feed external timing data into the Stats UI. When set, update() uses this data instead of internal timing.

Texture Preview Panels

Display render target previews alongside performance metrics. Supports both WebGL and WebGPU.

Three.js Usage

const stats = new Stats({ trackGPU: true });
stats.init(renderer);

// Create a texture panel
const panel = stats.addTexturePanel('GBuffer');

// Set texture source (WebGLRenderTarget or WebGPU RenderTarget)
const renderTarget = new THREE.WebGLRenderTarget(width, height);
stats.setTexture('GBuffer', renderTarget);

// In render loop - textures update automatically
function animate() {
  renderer.setRenderTarget(renderTarget);
  renderer.render(scene, camera);
  renderer.setRenderTarget(null);
  renderer.render(scene, camera);
  stats.update();
}

Worker Texture Transfer

// Worker - capture and transfer texture
const bitmap = await profiler.captureTexture(renderTarget, 'gbuffer');
self.postMessage(
  { type: 'texture', name: 'GBuffer', bitmap, width, height },
  [bitmap]
);

// Main thread - receive and display
worker.onmessage = (e) => {
  if (e.data.type === 'texture') {
    stats.setTextureBitmap(e.data.name, e.data.bitmap, e.data.width, e.data.height);
  }
};

Texture Panel API

  • stats.addTexturePanel(name) - Create a new texture preview panel
  • stats.setTexture(name, source) - Set Three.js RenderTarget source
  • stats.setTextureWebGL(name, framebuffer, width, height) - Set raw WebGL framebuffer
  • stats.setTextureBitmap(name, bitmap, width?, height?) - Set ImageBitmap (for workers)
  • stats.removeTexturePanel(name) - Remove a texture panel

TSL Node Capture (WebGPU)

Capture any Three.js TSL node for live preview. Works with MRT, post-processing, and custom shaders.

Main Thread Usage

import Stats from 'stats-gl';
import { statsGL } from 'stats-gl/addons/StatsGLNode.js';
import { addMethodChaining } from 'three/tsl';

// Enable .toStatsGL() method on TSL nodes
addMethodChaining('toStatsGL', statsGL);

const stats = new Stats({ trackGPU: true });
stats.init(renderer);
document.body.appendChild(stats.dom);

// In your PostProcessing setup:
const scenePass = pass(scene, camera);
scenePass.setMRT(mrt({
  output,
  normal: directionToColor(normalView),
  diffuse: diffuseColor
}));

// Register nodes for capture - panels are created automatically
scenePass.getTextureNode('diffuse').toStatsGL('Diffuse', stats);
scenePass.getTextureNode('normal').toStatsGL('Normal', stats);
scenePass.getLinearDepthNode().toStatsGL('Depth', stats);

Web Worker Usage

The same StatsGLNode.js addon works in Web Workers with OffscreenCanvas:

Worker:

import { StatsProfiler } from 'stats-gl';
import { flushCaptures } from 'stats-gl/addons/StatsGLNode.js';

const profiler = new StatsProfiler({ trackGPU: true });
await profiler.init(renderer);

// Register nodes (no stats instance needed in worker)
diffuseNode.toStatsGL('Diffuse');
normalNode.toStatsGL('Normal');
depthNode.toStatsGL('Depth');

async function render() {
  profiler.begin();
  postProcessing.render();
  profiler.end();
  profiler.update();

  // Send stats to main thread
  self.postMessage({ type: 'stats', data: profiler.getData() });

  // Capture and transfer TSL nodes as ImageBitmaps
  const captures = await flushCaptures(renderer);
  for (const { name, bitmap } of captures) {
    self.postMessage({ type: 'texture', name, bitmap }, [bitmap]);
  }
}

Main Thread:

import Stats from 'stats-gl';

const stats = new Stats({ trackGPU: true });
document.body.appendChild(stats.dom);

// Create panels for worker captures
stats.addTexturePanel('Diffuse');
stats.addTexturePanel('Normal');
stats.addTexturePanel('Depth');

worker.onmessage = (e) => {
  if (e.data.type === 'stats') stats.setData(e.data.data);
  if (e.data.type === 'texture') {
    stats.setTextureBitmap(e.data.name, e.data.bitmap);
  }
};

Transform Callback

Use a callback to transform the node before capture (e.g., linearize depth):

depthNode.toStatsGL('Depth', stats, (node) => linearizeDepth(node));

Custom Panels

Add custom metrics panels:

const customPanel = stats.addPanel(new Stats.Panel('COUNT', '#ff0', '#220'));

function animate() {
  // Update with value and max
  customPanel.update(currentValue, maxValue, 2); // 2 decimal places
  customPanel.updateGraph(currentValue, maxValue);
  stats.update();
}

API Reference

Default Export: Stats

Main class with DOM rendering.

import Stats from 'stats-gl';

const stats = new Stats(options);
stats.init(renderer);           // Initialize with Three.js renderer, canvas, or GPUDevice
stats.begin();                  // Start timing (auto-called for Three.js)
stats.end(encoder?);            // End timing (pass encoder for native WebGPU)
stats.update();                 // Update display
stats.setData(data);            // Set external timing data
stats.getTimestampWrites();     // Get timestampWrites for native WebGPU render pass
stats.dispose();                // Clean up resources

Named Exports

import Stats, {
  StatsProfiler,           // Headless profiler for workers
  PanelTexture,            // Texture preview panel class
  TextureCaptureWebGL,     // WebGL texture capture utility
  TextureCaptureWebGPU,    // WebGPU texture capture utility
  StatsGLCapture           // Addon capture helper
} from 'stats-gl';

// TSL Node capture addon (WebGPU only, works in main thread and workers)
import { statsGL, flushCaptures } from 'stats-gl/addons/StatsGLNode.js';

Contributing

Contributions to stats-gl are welcome. Please report any issues or bugs you encounter.

License

This project is licensed under the MIT License.

Maintainers