Skip to main content

Overview

Helios is designed for production-grade video rendering. This guide covers performance optimization strategies for both preview and render workflows.

Rendering performance

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.

Preview performance

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

Shadow DOM performance

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:
  1. Open chrome://inspect
  2. Click “inspect” on your composition
  3. 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):
CompositionModeResolutionFPSReal-time Factor
DOM (CSS)DOM1080p300.5x (slower)
Canvas (2D)Canvas1080p302-3x (faster)
Three.js (simple)Canvas1080p301-2x
Three.js (complex)Canvas1080p300.5-1x
Pixi.jsCanvas1080p602-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)

Performance checklist

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

Build docs developers (and LLMs) love