observeRenderer()
The observeRenderer() method connects 3Lens to your three.js renderer, enabling automatic performance monitoring, frame statistics collection, and GPU timing.
Signature
observeRenderer(renderer: THREE.WebGLRenderer | THREE.Renderer): voidParameters
| Parameter | Type | Description |
|---|---|---|
renderer | WebGLRenderer | Renderer | Any three.js renderer (WebGL or WebGPU) |
Basic Usage
import { createProbe } from '@3lens/core';
import * as THREE from 'three';
const probe = createProbe({ appName: 'My App' });
const renderer = new THREE.WebGLRenderer();
// Connect probe to renderer
probe.observeRenderer(renderer);2
3
4
5
6
7
8
Automatic Backend Detection
3Lens automatically detects whether you're using WebGL or WebGPU:
WebGL Renderer
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
probe.observeRenderer(renderer);
// Check the detected backend
console.log(probe.getRendererKind()); // 'webgl'
console.log(probe.isWebGL()); // true
console.log(probe.isWebGPU()); // false2
3
4
5
6
7
8
9
10
11
WebGPU Renderer
const renderer = new THREE.WebGPURenderer();
await renderer.init();
probe.observeRenderer(renderer);
// Check the detected backend
console.log(probe.getRendererKind()); // 'webgpu'
console.log(probe.isWebGL()); // false
console.log(probe.isWebGPU()); // true2
3
4
5
6
7
8
9
Detection Method
WebGPU renderers are identified by the isWebGPURenderer property on the renderer object, following three.js conventions.
What Gets Tracked
Once observeRenderer() is called, the probe automatically collects:
Frame Statistics
| Metric | Description |
|---|---|
drawCalls | Number of draw calls per frame |
triangles | Total triangles rendered |
points | Point primitives rendered |
lines | Line primitives rendered |
geometryCount | Active geometries |
textureCount | Active textures |
programCount | Active shader programs |
Memory Statistics
| Metric | Description |
|---|---|
textureMemory | Estimated texture GPU memory |
geometryMemory | Estimated geometry GPU memory |
totalGpuMemory | Combined GPU memory estimate |
Performance Metrics
| Metric | Description |
|---|---|
fps | Frames per second |
frameTime | Time per frame (ms) |
cpuTime | JavaScript/CPU time (ms) |
gpuTime | GPU execution time (ms) * |
* GPU timing requires browser support and may not be available in all environments.
GPU Timing
WebGL GPU Timing
Uses EXT_disjoint_timer_query extension when available:
const probe = createProbe({
appName: 'My App',
sampling: {
gpuTiming: true, // Enable GPU timing (default)
},
});
probe.observeRenderer(renderer);
// Check GPU timing availability
probe.onFrameStats((stats) => {
if (stats.performance?.gpuTime !== undefined) {
console.log(`GPU time: ${stats.performance.gpuTime.toFixed(2)}ms`);
}
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
Browser Support
GPU timing may be:
- Disabled by browser privacy settings
- Unavailable in certain contexts (cross-origin iframes)
- Deliberately throttled or randomized for fingerprint prevention
WebGPU Timestamps
For WebGPU renderers, uses native timestamp queries:
const renderer = new THREE.WebGPURenderer();
await renderer.init();
probe.observeRenderer(renderer);
// WebGPU timing is more reliable than WebGL
probe.onFrameStats((stats) => {
console.log(`GPU time: ${stats.performance?.gpuTime}ms`);
});2
3
4
5
6
7
8
9
Accessing Renderer Resources
After observation, query renderer-tracked resources:
Textures
const textures = probe.getTextures();
for (const tex of textures) {
console.log(`${tex.name}: ${tex.width}x${tex.height}, ${tex.format}`);
console.log(` Memory: ${(tex.memoryBytes / 1024 / 1024).toFixed(2)} MB`);
}2
3
4
5
6
Geometries
const geometries = probe.getGeometries();
for (const geo of geometries) {
console.log(`${geo.name}: ${geo.vertexCount} vertices, ${geo.triangleCount} triangles`);
}2
3
4
5
Materials
const materials = probe.getMaterials();
for (const mat of materials) {
console.log(`${mat.name}: ${mat.type}`);
}2
3
4
5
Three.js Version Detection
The renderer observation also captures the three.js version:
probe.observeRenderer(renderer);
console.log(`three.js version: ${probe.threeVersion}`);
// Output: "three.js version: 0.160.0"2
3
4
Custom Renderer Adapters
For advanced use cases, you can create custom adapters:
import { createProbe, RendererAdapter } from '@3lens/core';
// Custom adapter implementation
const customAdapter: RendererAdapter = {
kind: 'webgl',
observeFrame(callback) {
// Custom frame observation logic
},
getTextures() {
return [/* custom texture enumeration */];
},
getGeometries() {
return [/* custom geometry enumeration */];
},
getMaterials() {
return [/* custom material enumeration */];
},
dispose() {
// Cleanup
},
};
probe.attachRendererAdapter(customAdapter);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Multiple Renderers
While 3Lens supports observing one renderer at a time, you can switch between renderers:
// Start with WebGL
const webglRenderer = new THREE.WebGLRenderer();
probe.observeRenderer(webglRenderer);
// Later, switch to WebGPU
const webgpuRenderer = new THREE.WebGPURenderer();
await webgpuRenderer.init();
// This automatically disposes the previous adapter
probe.observeRenderer(webgpuRenderer);2
3
4
5
6
7
8
9
10
WARNING
Only one renderer can be observed at a time. Calling observeRenderer() again will replace the previous observation.
Frame Stats Subscription
Subscribe to receive stats on each frame:
probe.observeRenderer(renderer);
const unsubscribe = probe.onFrameStats((stats) => {
// Called every frame (or as configured by sampling)
document.getElementById('fps').textContent =
`FPS: ${stats.performance?.fps?.toFixed(0) ?? '--'}`;
document.getElementById('drawcalls').textContent =
`Draw Calls: ${stats.drawCalls}`;
document.getElementById('triangles').textContent =
`Triangles: ${stats.triangles.toLocaleString()}`;
});
// Later: stop receiving updates
unsubscribe();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sampling Configuration
Control how often stats are collected:
const probe = createProbe({
appName: 'My App',
sampling: {
// Collect every frame (default)
frameStats: 'every-frame',
// Or: collect every N frames
frameStats: 5, // Every 5th frame
// Or: only when requested
frameStats: 'on-demand',
// GPU timing
gpuTiming: true,
},
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Adaptive Sampling
When using 'every-frame', 3Lens employs adaptive sampling that automatically reduces collection frequency when performance is stable:
// Internally, if FPS is stable for 60+ frames,
// sampling rate is reduced (up to 4x) to lower overhead2
Complete Example
import { createProbe } from '@3lens/core';
import * as THREE from 'three';
// Setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Create probe
const probe = createProbe({
appName: 'Renderer Demo',
sampling: {
frameStats: 'every-frame',
gpuTiming: true,
},
rules: {
maxDrawCalls: 1000,
maxTriangles: 500000,
},
});
// Observe renderer
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Log renderer info
console.log('Backend:', probe.getRendererKind());
console.log('three.js:', probe.threeVersion);
// Monitor performance
probe.onFrameStats((stats) => {
// Check for rule violations
if (stats.violations?.length) {
for (const v of stats.violations) {
console.warn(`⚠️ ${v.ruleId}: ${v.message}`);
}
}
// Log GPU timing when available
if (stats.performance?.gpuTime !== undefined) {
if (stats.performance.gpuTime > 16) {
console.warn('GPU frame time exceeds 16ms target');
}
}
});
// Add content
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);
// Animation loop
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
probe.dispose();
renderer.dispose();
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Troubleshooting
No Frame Stats Received
- Ensure
observeRenderer()was called - Check that
renderer.render()is being called in your animation loop - Verify sampling config isn't set to
'on-demand'
GPU Timing Always Undefined
- GPU timing may be disabled by browser
- Try a different browser or enable WebGL developer features
- WebGPU typically has better timing support
High Overhead
- Reduce sampling frequency:
frameStats: 5 - Disable GPU timing:
gpuTiming: false - Use adaptive sampling (default with
'every-frame')
Related API
| Method | Description |
|---|---|
isWebGL() | Check if WebGL renderer |
isWebGPU() | Check if WebGPU renderer |
getRendererKind() | Get backend type |
getRendererAdapter() | Get raw adapter |
getTextures() | List textures |
getGeometries() | List geometries |
getMaterials() | List materials |
getGpuTimings() | Get GPU timing info |
Related
- observeScene() - Scene observation
- FrameStats Reference - Stats structure
- WebGL Adapter - WebGL details
- WebGPU Adapter - WebGPU details