Overview
Helios is designed for production-grade video rendering. This guide covers performance optimization strategies for both preview and render workflows.
Choose the right rendering path
Helios supports two rendering modes:
Canvas mode (faster)
- Direct WebCodecs encoding
- GPU-accelerated
- Best for: Three.js, Pixi.js, Canvas API
DOM mode (versatile)
- Screenshots HTML/CSS/SVG
- Works with any web content
- Best for: Text, layouts, complex CSS
import { Renderer } from '@helios-project/renderer';
// Canvas mode (default)
const renderer = new Renderer({ mode: 'canvas' });
// DOM mode
const renderer = new Renderer({ mode: 'dom' });
Enable hardware acceleration
GPU acceleration is enabled by default, but you can verify:
const diagnostics = await renderer.diagnose();
console.log('Hardware Encoders:', diagnostics.browser.codecs);
console.log('FFmpeg HW Accel:', diagnostics.ffmpeg.hwaccels);
Supported hardware acceleration:
- H.264 - Most common, best compatibility
- VP8/VP9 - WebM format
- AV1 - Newer, better compression (slower)
Bitrate and quality
Higher bitrate = better quality but larger files:
const renderer = new Renderer({
mode: 'canvas',
ffmpegConfig: {
videoBitrate: '5M', // 5 Mbps (high quality)
// '2M' = medium quality
// '10M' = very high quality
}
});
Codec selection
H.264 provides the best speed/quality balance:
const renderer = new Renderer({
mode: 'canvas',
ffmpegConfig: {
videoCodec: 'libx264',
preset: 'medium', // slow/medium/fast/ultrafast
crf: 23, // 0-51 (lower = better quality)
}
});
Preset recommendations:
ultrafast - Quick tests (low quality)
medium - Production (balanced)
slow - Final export (best quality)
Frame rate optimization
Lower frame rates render faster:
const helios = new Helios({
duration: 10,
fps: 30 // Use 30 instead of 60 if motion blur isn't critical
});
Frame rate guidelines:
- 24 fps - Cinematic look
- 30 fps - Standard web video
- 60 fps - Smooth motion, gaming
Resolution optimization
Render at target resolution, not higher:
const helios = new Helios({
width: 1920,
height: 1080,
duration: 10,
fps: 30
});
canvas.width = 1920;
canvas.height = 1080;
Avoid rendering at 4K and downscaling to 1080p. Render at 1080p directly for 4x faster encodes.
Disable autoplay animations
Helios controls timing, so disable auto-playing animations:
/* Good: Helios controls timing */
.box {
animation: fadeIn 1s forwards paused;
}
/* Bad: Animation plays independently */
.box {
animation: fadeIn 1s forwards;
}
Use autoSyncAnimations: true to automatically pause animations:
const helios = new Helios({
duration: 10,
fps: 30,
autoSyncAnimations: true // Pauses all WAAPI animations
});
Use requestAnimationFrame efficiently
Helios uses RafTicker by default, which is optimized for browser rendering:
import { Helios, RafTicker } from '@helios-project/core';
const helios = new Helios({
duration: 10,
fps: 30,
ticker: new RafTicker() // Default
});
For Node.js or testing, use TimeoutTicker:
import { TimeoutTicker } from '@helios-project/core';
const helios = new Helios({
ticker: new TimeoutTicker()
});
Debounce expensive operations
Avoid recalculating on every frame if not needed:
let cachedValue: number;
let lastFrame = -1;
helios.subscribe((state) => {
// Only recalculate every 10 frames
if (state.currentFrame % 10 === 0 || state.currentFrame !== lastFrame) {
cachedValue = expensiveCalculation(state);
lastFrame = state.currentFrame;
}
applyValue(cachedValue);
});
Optimize asset loading
Preload assets before starting playback:
const helios = new Helios({ duration: 10, fps: 30 });
// Wait for all assets to load
await helios.waitUntilStable();
// Now start playback
helios.play();
The waitUntilStable() method waits for:
- Fonts (
document.fonts.ready)
- Images (
img.decode())
- Audio/video metadata
DomDriver automatically discovers shadow roots, but deep nesting impacts performance:
// Good: Flat structure
<div>
<my-component></my-component>
</div>
// Slower: Deep shadow DOM nesting
<level-1>
<level-2>
<level-3>
<level-4>...</level-4>
</level-3>
</level-2>
</level-1>
Memory optimization
Dispose resources
Always clean up when done:
const helios = new Helios({ duration: 10, fps: 30 });
// Use composition
helios.play();
// Clean up when done
helios.dispose();
For Three.js, dispose geometries and materials:
scene.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.geometry.dispose();
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose());
} else {
obj.material.dispose();
}
}
});
renderer.dispose();
Reuse objects
Avoid creating new objects every frame:
// Bad: Creates garbage
helios.subscribe(() => {
const vec = new THREE.Vector3(x, y, z);
cube.position.copy(vec);
});
// Good: Reuse
const vec = new THREE.Vector3();
helios.subscribe(() => {
vec.set(x, y, z);
cube.position.copy(vec);
});
Limit subscriber count
Each subscriber adds overhead:
// Bad: Multiple subscribers
helios.subscribe(() => updateBox());
helios.subscribe(() => updateCircle());
helios.subscribe(() => updateText());
// Good: Single subscriber
helios.subscribe(() => {
updateBox();
updateCircle();
updateText();
});
Distributed rendering
For large projects, use distributed rendering:
# Generate job spec
helios render composition.html --emit-job job.json
# Run distributed job
helios job run job.json --chunks 4
This splits rendering across multiple workers for 4x speedup.
Chunk size optimization
More chunks = more parallelism, but more overhead:
# Short video (< 30s): 2-4 chunks
helios job run job.json --chunks 4
# Medium video (30s - 2m): 8-16 chunks
helios job run job.json --chunks 16
# Long video (> 2m): 32+ chunks
helios job run job.json --chunks 32
Rule of thumb: 1 chunk per 10-15 seconds of video
Profiling and debugging
Measure render time
const start = Date.now();
await renderer.render('composition.html', 'output.mp4');
const duration = Date.now() - start;
console.log(`Rendered in ${duration}ms`);
Monitor frame budget
Each frame has a time budget. For 30fps, that’s 33.3ms per frame:
let slowFrames = 0;
helios.subscribe((state) => {
const frameStart = performance.now();
// Your rendering code
renderer.render(scene, camera);
const frameTime = performance.now() - frameStart;
const budget = 1000 / state.fps;
if (frameTime > budget) {
console.warn(`Frame ${state.currentFrame} over budget: ${frameTime.toFixed(2)}ms`);
slowFrames++;
}
});
console.log(`Slow frames: ${slowFrames}`);
Diagnose bottlenecks
Use Chrome DevTools to profile:
# Run with remote debugging
helios render composition.html --no-headless
Then:
- Open
chrome://inspect
- Click “inspect” on your composition
- Use Performance tab to record
Check diagnostics
const report = await Helios.diagnose();
console.log('WebCodecs:', report.webCodecs);
console.log('WebGL:', report.webgl);
console.log('WebGL2:', report.webgl2);
console.log('H.264:', report.videoCodecs.h264);
console.log('VP9:', report.videoCodecs.vp9);
console.log('User Agent:', report.userAgent);
Benchmarking examples
Typical render speeds on modern hardware (M1 MacBook Pro):
| Composition | Mode | Resolution | FPS | Real-time Factor |
|---|
| DOM (CSS) | DOM | 1080p | 30 | 0.5x (slower) |
| Canvas (2D) | Canvas | 1080p | 30 | 2-3x (faster) |
| Three.js (simple) | Canvas | 1080p | 30 | 1-2x |
| Three.js (complex) | Canvas | 1080p | 30 | 0.5-1x |
| Pixi.js | Canvas | 1080p | 60 | 2-4x |
Real-time factor:
1x = Renders as fast as playback (10s video in 10s)
2x = Renders twice as fast (10s video in 5s)
0.5x = Renders half speed (10s video in 20s)
Rendering:
- ✅ Use canvas mode for canvas-heavy compositions
- ✅ Enable GPU acceleration
- ✅ Choose appropriate bitrate (2-5 Mbps for 1080p)
- ✅ Use H.264 codec for best compatibility
- ✅ Render at target resolution (don’t downscale)
- ✅ Use distributed rendering for videos > 30s
Preview:
- ✅ Set
autoSyncAnimations: true
- ✅ Preload assets with
waitUntilStable()
- ✅ Use single subscriber when possible
- ✅ Avoid per-frame allocations
- ✅ Reuse Three.js geometries and materials
Memory:
- ✅ Call
helios.dispose() when done
- ✅ Dispose Three.js resources
- ✅ Reuse objects instead of creating new ones
- ✅ Clear intervals and event listeners
Next steps