Renderer Auto-Detection
3Lens automatically detects whether you're using a WebGL or WebGPU renderer and creates the appropriate adapter. This guide explains how detection works and how to handle different renderer types.
Overview
When you call probe.observeRenderer(renderer), 3Lens automatically:
- Detects the renderer type (WebGL or WebGPU)
- Creates the appropriate adapter
- Configures GPU timing based on availability
- Starts collecting metrics
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'My App' });
// Automatic detection - works for both renderer types
probe.observeRenderer(renderer);2
3
4
5
6
Detection Mechanism
WebGPU Detection
WebGPU renderers are identified by the isWebGPURenderer property:
function isWebGPURenderer(renderer: unknown): renderer is WebGPURenderer {
return (
typeof renderer === 'object' &&
renderer !== null &&
'isWebGPURenderer' in renderer &&
(renderer as WebGPURenderer).isWebGPURenderer === true
);
}2
3
4
5
6
7
8
This follows the Three.js convention where WebGPURenderer has:
interface WebGPURenderer {
readonly isWebGPURenderer: true;
// ... other properties
}2
3
4
WebGL Detection
If the renderer is not a WebGPU renderer, it's assumed to be a WebGL renderer. WebGL renderers include:
THREE.WebGLRendererTHREE.WebGL1Renderer(legacy)- Custom renderers extending
WebGLRenderer
function isWebGLRenderer(renderer: unknown): renderer is THREE.WebGLRenderer {
return (
typeof renderer === 'object' &&
renderer !== null &&
!isWebGPURenderer(renderer) &&
'render' in renderer &&
'getContext' in renderer
);
}2
3
4
5
6
7
8
9
API Reference
isWebGPURenderer
Type guard for WebGPU renderer detection.
function isWebGPURenderer(renderer: unknown): renderer is WebGPURendererParameters:
| Parameter | Type | Description |
|---|---|---|
renderer | unknown | Any renderer object to check |
Returns: boolean - true if renderer is a WebGPURenderer
Example:
import { isWebGPURenderer, createWebGLAdapter, createWebGPUAdapter } from '@3lens/core';
function createAdapter(renderer: THREE.Renderer) {
if (isWebGPURenderer(renderer)) {
return createWebGPUAdapter(renderer);
} else {
return createWebGLAdapter(renderer as THREE.WebGLRenderer);
}
}2
3
4
5
6
7
8
9
observeRenderer
The main method that performs automatic detection.
probe.observeRenderer(renderer: THREE.Renderer): voidDetection Flow:
┌─────────────────────────────────────────────────────────────┐
│ observeRenderer(renderer) │
├─────────────────────────────────────────────────────────────┤
│ │
│ renderer ──────┬──────────────────────────────────────> │
│ │ │
│ ▼ │
│ isWebGPURenderer? │
│ │ │
│ ┌───────┴───────┐ │
│ ▼ ▼ │
│ Yes No │
│ │ │ │
│ ▼ ▼ │
│ createWebGPUAdapter createWebGLAdapter │
│ │ │ │
│ └───────┬───────┘ │
│ ▼ │
│ setRendererAdapter(adapter) │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Usage Examples
Automatic Detection (Recommended)
import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu';
import { createProbe } from '@3lens/core';
const probe = createProbe({ appName: 'My App' });
// Works with WebGL
const webglRenderer = new THREE.WebGLRenderer();
probe.observeRenderer(webglRenderer);
// Also works with WebGPU
const webgpuRenderer = new WebGPURenderer();
await webgpuRenderer.init();
probe.observeRenderer(webgpuRenderer);2
3
4
5
6
7
8
9
10
11
12
13
14
Manual Detection and Adapter Creation
For advanced use cases where you need explicit control:
import {
isWebGPURenderer,
createWebGLAdapter,
createWebGPUAdapter
} from '@3lens/core';
function setupProbe(renderer: THREE.Renderer) {
const probe = createProbe({ appName: 'My App' });
if (isWebGPURenderer(renderer)) {
// Custom WebGPU adapter options
const adapter = createWebGPUAdapter(renderer);
probe.setRendererAdapter(adapter);
console.log('Using WebGPU adapter');
} else {
// Custom WebGL adapter options
const adapter = createWebGLAdapter(renderer as THREE.WebGLRenderer, {
gpuTiming: true,
resourceScanInterval: 1000,
});
probe.setRendererAdapter(adapter);
console.log('Using WebGL adapter');
}
return probe;
}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
Runtime Renderer Switching
Handle dynamic renderer changes:
class AdaptiveRenderer {
private probe: DevtoolProbe;
private currentRenderer: THREE.Renderer | null = null;
constructor() {
this.probe = createProbe({ appName: 'Adaptive App' });
}
setRenderer(renderer: THREE.Renderer) {
// Clean up previous renderer observation
if (this.currentRenderer) {
// Note: observeRenderer handles cleanup internally
}
this.currentRenderer = renderer;
this.probe.observeRenderer(renderer);
// Log renderer type for debugging
if (isWebGPURenderer(renderer)) {
console.log('Switched to WebGPU renderer');
} else {
console.log('Switched to WebGL renderer');
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Fallback Pattern
WebGPU with WebGL fallback:
import * as THREE from 'three';
import { createProbe, isWebGPURenderer } from '@3lens/core';
async function createRendererWithFallback() {
const probe = createProbe({ appName: 'My App' });
let renderer: THREE.Renderer;
// Try WebGPU first
if (navigator.gpu) {
try {
const { WebGPURenderer } = await import('three/webgpu');
renderer = new WebGPURenderer();
await (renderer as any).init();
console.log('Using WebGPU');
} catch (e) {
console.warn('WebGPU initialization failed, falling back to WebGL');
renderer = new THREE.WebGLRenderer();
}
} else {
console.log('WebGPU not available, using WebGL');
renderer = new THREE.WebGLRenderer();
}
// Automatic detection handles both cases
probe.observeRenderer(renderer);
return { renderer, probe };
}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
Conditional Feature Usage
Use renderer-specific features conditionally:
import { isWebGPURenderer, getWebGPUCapabilities } from '@3lens/core';
function setupAdvancedFeatures(renderer: THREE.Renderer, probe: DevtoolProbe) {
if (isWebGPURenderer(renderer)) {
// WebGPU-specific setup
const caps = getWebGPUCapabilities(renderer);
if (caps.hasTimestampQuery) {
console.log('Per-pass GPU timing available');
}
if (caps.hasShaderF16) {
console.log('Half-precision shaders available');
}
// Enable compute shader monitoring
probe.onFrameStats((stats) => {
if (stats.webgpuExtras?.computePasses) {
console.log(`Compute passes: ${stats.webgpuExtras.computePasses}`);
}
});
} else {
// WebGL-specific setup
const gl = (renderer as THREE.WebGLRenderer).getContext();
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (ext) {
console.log('GPU timing via timer query extension');
} else {
console.log('GPU timing not available');
}
}
}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
Type Definitions
Renderer Types
// Three.js base renderer interface
interface Renderer {
render(scene: Scene, camera: Camera): void;
setSize(width: number, height: number, updateStyle?: boolean): void;
dispose(): void;
}
// WebGL renderer (Three.js standard)
interface WebGLRenderer extends Renderer {
readonly capabilities: WebGLCapabilities;
readonly info: WebGLInfo;
getContext(): WebGLRenderingContext | WebGL2RenderingContext;
}
// WebGPU renderer (Three.js r153+)
interface WebGPURenderer extends Renderer {
readonly isWebGPURenderer: true;
readonly backend?: WebGPUBackend;
readonly info: WebGPURendererInfo;
renderAsync?(scene: Scene, camera: Camera): Promise<void>;
compute?(computeNodes: unknown): Promise<void>;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Detection Results
type DetectedRendererType = 'webgl' | 'webgpu';
interface DetectionResult {
type: DetectedRendererType;
capabilities: {
gpuTiming: boolean;
computeShaders: boolean;
asyncRender: boolean;
timestampQueries: boolean;
};
}2
3
4
5
6
7
8
9
10
11
Best Practices
1. Use Automatic Detection
// ✅ Recommended - simple and handles all cases
probe.observeRenderer(renderer);
// ⚠️ Only use manual creation when you need custom options
const adapter = createWebGLAdapter(renderer, { gpuTiming: false });
probe.setRendererAdapter(adapter);2
3
4
5
6
2. Check Capabilities, Not Renderer Type
// ❌ Avoid - assumes WebGL can't do certain things
if (isWebGPURenderer(renderer)) {
enableAdvancedFeatures();
}
// ✅ Better - check actual capabilities
probe.onFrameStats((stats) => {
if (stats.gpuTimeMs !== undefined) {
showGpuTiming(stats.gpuTimeMs);
}
if (stats.webgpuExtras?.computePasses !== undefined) {
showComputeStats(stats.webgpuExtras.computePasses);
}
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
3. Handle Both Renderers in UI
// ✅ Graceful capability-based UI
function updatePerformanceUI(stats: FrameStats) {
// Always available
ui.setCpuTime(stats.cpuTimeMs);
ui.setDrawCalls(stats.drawCalls);
// Conditionally available
ui.setGpuTime(stats.gpuTimeMs ?? 'N/A');
// WebGPU-specific panel
if (stats.webgpuExtras) {
ui.showWebGPUPanel(true);
ui.setComputePasses(stats.webgpuExtras.computePasses);
ui.setPipelines(stats.webgpuExtras.pipelinesUsed);
} else {
ui.showWebGPUPanel(false);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Troubleshooting
Detection Not Working
Symptom: observeRenderer doesn't create the expected adapter type.
Causes:
- Custom renderer without
isWebGPURendererproperty - Minified code stripping properties
- Renderer not fully initialized
Solution:
// Force adapter type manually
if (myRenderer.backend?.device) {
// It's WebGPU, create adapter directly
probe.setRendererAdapter(createWebGPUAdapter(myRenderer as any));
} else {
probe.setRendererAdapter(createWebGLAdapter(myRenderer as THREE.WebGLRenderer));
}2
3
4
5
6
7
WebGPU Renderer Detected as WebGL
Symptom: WebGPU renderer creates WebGL adapter.
Cause: Checking renderer before initialization completes.
Solution:
const renderer = new WebGPURenderer();
await renderer.init(); // Wait for init!
probe.observeRenderer(renderer);2
3
Related
- WebGL Adapter - WebGL adapter details
- WebGPU Adapter - WebGPU adapter details
- observeRenderer() - Main observation method
- DevtoolProbe - Core probe class