# stats-gl

> [![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

Latest version **4.2.3** (published 2026-07-10) · MIT license · 0 weekly downloads

## Install

```sh
npm install stats-gl
pnpm add stats-gl
yarn add stats-gl
bun add stats-gl
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 4.2.3 |
| Published | 2026-07-10 |
| First published | 2023-07-18 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 4.3 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 279 |
| Author | Renaud ROHLINGER |
| Maintainers | onirenaud |

## Links

- npm: https://www.npmjs.com/package/stats-gl
- Repository: https://github.com/RenaudRohlinger/stats-gl
- Issues: https://github.com/RenaudRohlinger/stats-gl/issues
- npm.io page: https://npm.io/package/stats-gl

## Recent versions

- 4.2.3 (latest) — 2026-07-10
- 4.2.2 — 2026-07-03
- 4.2.1 — 2026-06-30
- 4.2.0 — 2026-06-30
- 4.1.0 — 2026-02-24
- 4.0.2 — 2026-01-31
- 4.0.1 — 2026-01-31
- 4.0.0 — 2026-01-31
- 3.8.0 — 2025-11-28
- 3.7.0 — 2025-11-11
- 3.6.0 — 2024-11-10
- 3.5.0 — 2024-11-10
- 3.4.0 — 2024-11-10
- 3.3.0 — 2024-11-05
- 3.2.0 — 2024-11-05
- … 31 more at https://npm.io/package/stats-gl/versions

## README

# stats-gl
[![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/stats-gl)

> **For AI/LLM users:** See [llms.txt](./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://stats.renaudrohlinger.com/)


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

```bash
npm install stats-gl
```

## Quick Start

### Three.js (WebGL or WebGPU)

```js
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

```js
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

```js
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();
```

#### Multiple passes per frame (render + compute)

`getTimestampWrites('render')` and `getTimestampWrites('compute')` allocate independent
timestamp pairs. Each call consumes one pair from the per-frame budget; counters reset
on the next `stats.begin()`. Render-pass durations sum into the **GPU** panel,
compute-pass durations into the **CPT** panel.

```js
const stats = new Stats({ trackGPU: true, trackCPT: true });
stats.init(device);

function animate() {
  stats.begin();

  const encoder = device.createCommandEncoder();

  const compute = encoder.beginComputePass({
    timestampWrites: stats.getTimestampWrites('compute')
  });
  // ... dispatchWorkgroups ...
  compute.end();

  const shadowPass = encoder.beginRenderPass({
    colorAttachments: [...],
    timestampWrites: stats.getTimestampWrites('render')
  });
  // ... shadow draw calls ...
  shadowPass.end();

  const mainPass = encoder.beginRenderPass({
    colorAttachments: [...],
    timestampWrites: stats.getTimestampWrites('render')
  });
  // ... main draw calls ...
  mainPass.end();

  stats.end(encoder);
  device.queue.submit([encoder.finish()]);
  stats.update();
  requestAnimationFrame(animate);
}
```

### React Three Fiber

A `<StatsGl />` component is available through [@react-three/drei](https://github.com/pmndrs/drei):

```jsx
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](https://cientos.tresjs.org/api/debug-performance/stats-gl):

```vue
<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 compute-pass timing (WebGPU). Adds the **CPT** panel. |
| `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 |
| `maxTimestampPairs` | number | `2048` | Max begin/end timestamp pairs per frame on the native WebGPU path |
| `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):**
```js
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:**
```js
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

```js
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

```js
// 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

```js
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:**
```js
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:**
```js
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):

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

## Custom Panels

Add custom metrics panels:

```js
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.

```js
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('render');   // Allocate a render-pass timestamp pair
stats.getTimestampWrites('compute');  // Allocate a compute-pass timestamp pair
stats.dispose();                      // Clean up resources
```

### Named Exports

```js
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

- [@onirenaud](https://twitter.com/onirenaud)
- [@utsuboco](https://twitter.com/utsuboco)

---
_Source: https://npm.io/package/stats-gl · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
