Profiler & Debug System
Built-in performance monitoring and event system for debugging GPU operations.
Enabling Events
Enable the event system when initializing the GPU context. You can optionally filter which event types to track.
import { gpu } from "ralph-gpu";
const ctx = await gpu.init(canvas, {
events: {
enabled: true,
types: ['draw', 'compute', 'frame', 'memory'], // Optional filter
historySize: 1000, // Event history buffer size
},
});Event Options
- •
enabled— Enable/disable event emission - •
types— Array of event types to track (omit for all) - •
historySize— Max events to keep in history (default: 1000)
Using the Profiler
The Profiler class provides a high-level API for tracking performance. It automatically subscribes to context events.
import { Profiler } from "ralph-gpu";
// Create profiler attached to context
const profiler = new Profiler(ctx, {
maxFrameHistory: 120,
autoTrackFrames: true,
});
// In your render loop
function animate() {
profiler.tick(); // Track frame timing for FPS
// Profile specific regions
profiler.begin('physics');
updatePhysics();
profiler.end('physics');
profiler.begin('render');
myPass.draw();
profiler.end('render');
// Get stats
const fps = profiler.getFPS();
console.log(`FPS: ${fps.toFixed(1)}`);
requestAnimationFrame(animate);
}
// Cleanup when done
profiler.dispose();Profiler API
// FPS tracking (use with pass.draw() API)
profiler.tick() // Call once per animation frame
profiler.getFPS(sampleCount?) // Get averaged FPS (default: 60 samples)
// Region profiling - measure specific code sections
profiler.begin(name) // Start timing a region
profiler.end(name) // End timing a region
profiler.getRegion(name) // Get stats for a specific region
profiler.getResults() // Get all region stats as Map
// Frame statistics
profiler.getFrameStats() // Get overall frame time statistics
profiler.getLastFrames(count) // Get last N frame profiles
profiler.getAverageFrameTime(frames?) // Average frame interval (for FPS)
profiler.getAverageRenderTime(frames?) // Average GPU work duration
// Control
profiler.setEnabled(enabled) // Enable/disable profiling
profiler.isEnabled() // Check if profiling is enabled
profiler.reset() // Clear all collected data
profiler.dispose() // Cleanup and unsubscribe from eventsprofiler.tick() once per animation frame for accurate FPS tracking. This works with the regular pass.draw() API — no need to change your rendering code.Region Profiling
Use begin() and end() to measure specific code sections.
interface ProfilerRegion {
name: string; // Region name
calls: number; // Total number of calls
totalTime: number; // Total time in ms
minTime: number; // Minimum time in ms
maxTime: number; // Maximum time in ms
averageTime: number; // Average time in ms
lastTime: number; // Most recent time in ms
}
// Example usage
profiler.begin('particles');
particleSystem.update();
particleSystem.draw();
profiler.end('particles');
const stats = profiler.getRegion('particles');
console.log(`Particles: ${stats?.averageTime.toFixed(2)}ms avg`);Best Practices
- • Use descriptive region names
- • Always pair begin/end calls
- • Nest regions for hierarchical timing
Common Regions
- •
physics— Simulation updates - •
render— Drawing passes - •
postprocess— Effects
Frame Statistics
Get aggregated statistics about frame timing.
interface FrameStats {
frameCount: number; // Total frames tracked
totalTime: number; // Total time in ms
minTime: number; // Fastest frame in ms
maxTime: number; // Slowest frame in ms
averageTime: number; // Average frame time in ms
lastTime: number; // Most recent frame time in ms
}
const stats = profiler.getFrameStats();
console.log(`
Frames: ${stats.frameCount}
Avg: ${stats.averageTime.toFixed(2)}ms
Min: ${stats.minTime.toFixed(2)}ms
Max: ${stats.maxTime.toFixed(2)}ms
`);Frame Time vs Render Time
Frame time is the interval between frames (includes vsync wait).Render time is just the GPU work duration. Use getAverageFrameTime() for FPS calculations and getAverageRenderTime() to measure GPU load.
Event Listeners
Subscribe to GPU events directly on the context for custom debugging or visualization.
// Subscribe to specific event type
const unsubscribe = ctx.on('draw', (event) => {
console.log(`Draw: ${event.source}, vertices: ${event.vertexCount}`);
});
// Subscribe to all events
const unsubAll = ctx.onAll((event) => {
console.log(`[${event.type}]`, event);
});
// One-time listener
ctx.once('shader_compile', (event) => {
console.log('Shader compiled:', event.label);
});
// Get event history
const drawEvents = ctx.getEventHistory(['draw']);
const allEvents = ctx.getEventHistory();
// Unsubscribe when done
unsubscribe();
unsubAll();Event Types
Available event types and their data structures.
// Draw event - emitted at start and end of each draw call
interface DrawEvent {
type: "draw";
phase: "start" | "end"; // Distinguish start vs end
source: "pass" | "material" | "particles";
label?: string;
vertexCount?: number;
instanceCount?: number;
topology?: GPUPrimitiveTopology;
target: "screen" | "texture";
targetSize: [number, number];
}
// Compute event - emitted at start and end of each dispatch
interface ComputeEvent {
type: "compute";
phase: "start" | "end"; // Distinguish start vs end
label?: string;
workgroups?: [number, number, number];
workgroupSize?: [number, number, number];
totalInvocations?: number;
}
// Frame event - emitted at frame boundaries
interface FrameEvent {
type: "frame";
phase: "start" | "end";
frameNumber: number;
deltaTime: number; // Time since last frame in ms
time: number; // Total elapsed time in ms
}
// Memory event - buffer/texture allocation
interface MemoryEvent {
type: "memory";
resourceType: "buffer" | "texture" | "sampler";
action: "allocate" | "free" | "resize";
label?: string;
size?: number; // in bytes
}
// Other events: shader_compile, target, pipeline, gpu_timing| Event Type | Description |
|---|---|
draw | Draw call (pass, material, particles) |
compute | Compute shader dispatch |
frame | Frame start/end with timing |
shader_compile | Shader compilation |
memory | Buffer/texture allocate/free/resize |
target | Render target set/clear |
pipeline | Pipeline creation (with cache hit info) |
gpu_timing | GPU timing queries (when available) |
Full Example
A complete React component demonstrating the profiler with live FPS and region stats.
"use client";
import { useEffect, useRef, useState } from "react";
import { gpu, GPUContext, Profiler } from "ralph-gpu";
export default function ProfilerDemo() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [fps, setFps] = useState(0);
const [regions, setRegions] = useState<Map<string, any>>(new Map());
useEffect(() => {
let ctx: GPUContext | null = null;
let profiler: Profiler | null = null;
let animationId: number;
let disposed = false;
async function init() {
if (!canvasRef.current || !gpu.isSupported()) return;
ctx = await gpu.init(canvasRef.current, {
events: { enabled: true, historySize: 100 },
});
if (disposed) {
ctx.dispose();
return;
}
profiler = new Profiler(ctx, { maxFrameHistory: 120 });
// Create some passes to profile
const background = ctx.pass(`
@fragment
fn main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
let uv = pos.xy / globals.resolution;
return vec4f(uv * 0.3, 0.1, 1.0);
}
`);
const effect = ctx.pass(`
@fragment
fn main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
let uv = pos.xy / globals.resolution;
let d = length(uv - 0.5);
let pulse = sin(globals.time * 3.0) * 0.1 + 0.2;
if (d < pulse) {
return vec4f(1.0, 1.0, 1.0, 0.5);
}
discard;
}
`, { blend: 'alpha' });
let lastUpdate = 0;
function frame() {
if (disposed || !profiler) return;
profiler.tick();
profiler.begin('background');
background.draw();
profiler.end('background');
profiler.begin('effect');
effect.draw();
profiler.end('effect');
// Update UI every 100ms
const now = performance.now();
if (now - lastUpdate > 100) {
lastUpdate = now;
setFps(profiler.getFPS());
setRegions(new Map(profiler.getResults()));
}
animationId = requestAnimationFrame(frame);
}
frame();
}
init();
return () => {
disposed = true;
cancelAnimationFrame(animationId);
profiler?.dispose();
ctx?.dispose();
};
}, []);
return (
<div>
<canvas ref={canvasRef} width={800} height={400} />
<div>
<p>FPS: {fps.toFixed(1)}</p>
{Array.from(regions.entries()).map(([name, stats]) => (
<p key={name}>
{name}: {stats.averageTime.toFixed(2)}ms
</p>
))}
</div>
</div>
);
}