(3/n) Game Engine 101
Related Articles:
- From HTML to Pixel: A Deep Dive into Browser Rendering - Learn how modern browsers use GPU acceleration
- From HTML to Pixel without GPU - Explore the pre-GPU era of browser rendering
While web browsers struggle to maintain 60 FPS, modern game engines routinely achieve 120+ FPS for complex UI systems. How do game engines accomplish this, and what can web developers learn from their approach?
The Fundamental Difference: Immediate Mode vs Retained Mode
Web Browsers: Retained Mode Rendering
Web browsers use retained mode rendering - they maintain a persistent representation of the UI:
Web Browser Rendering:
┌─────────────────────────────────────┐
│ DOM Tree (Persistent) │
│ ├── HTML Elements │
│ ├── CSS Styles │
│ └── JavaScript State │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Render Tree (Persistent) │
│ ├── Computed Styles │
│ ├── Layout Information │
│ └── Paint Layers │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ GPU Rendering │
│ ├── Layer Compositing │
│ ├── Rasterization │
│ └── Screen Output │
└─────────────────────────────────────┘
Characteristics:
- Persistent state: DOM/CSSOM trees maintained in memory
- Incremental updates: Only changed elements re-render
- Complex diffing: Browser must determine what changed
- Memory overhead: Large object graphs in memory
Game Engines: Immediate Mode Rendering
Game engines use immediate mode rendering - they redraw everything every frame:
Game Engine Rendering:
┌─────────────────────────────────────┐
│ Frame Start │
│ ├── Clear Screen │
│ ├── Process Input │
│ └── Update Game State │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Immediate Mode UI │
│ ├── Draw Button (x, y, w, h) │
│ ├── Draw Text (x, y, text) │
│ ├── Draw Image (x, y, texture) │
│ └── No State Management │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Direct GPU Commands │
│ ├── Vertex Buffers │
│ ├── Shader Programs │
│ └── Screen Output │
└─────────────────────────────────────┘
Characteristics:
- No persistent state: UI elements don't exist between frames
- Direct rendering: Immediate GPU commands
- No diffing: Everything redrawn every frame
- Minimal memory: No UI object graphs
Performance Comparison: Web vs Game Engine
| Aspect | Web Browser | Game Engine |
|---|---|---|
| Rendering Mode | Retained Mode | Immediate Mode |
| State Management | Complex DOM/CSSOM | Minimal/None |
| Memory Usage | High (object graphs) | Low (direct rendering) |
| Update Overhead | Diffing + incremental | Full redraw |
| Typical FPS | 30-60 FPS | 120+ FPS |
| Latency | 16-33ms | 8ms or less |
How Game Engines Achieve High FPS
1. Direct GPU Communication
Game engines bypass the browser's abstraction layers:
// Game Engine (Direct GPU)
glBegin(GL_QUADS);
glVertex2f(x, y);
glVertex2f(x + width, y);
glVertex2f(x + width, y + height);
glVertex2f(x, y + height);
glEnd();
// Web Browser (Multiple Layers)
DOM → Render Tree → Layout → Paint → Composite → GPU
2. Optimized Rendering Pipeline
Game engines use specialized rendering pipelines:
Game Engine Pipeline:
┌─────────────────────────────────────┐
│ Input Processing │
│ ├── Mouse/Keyboard Events │
│ └── Game State Updates │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ UI Drawing Loop │
│ ├── Clear Frame Buffer │
│ ├── Draw Background │
│ ├── Draw UI Elements │
│ └── Draw Overlays │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ GPU Submission │
│ ├── Batch Commands │
│ ├── Minimize State Changes │
│ └── Direct Memory Access │
└─────────────────────────────────────┘
3. Batched Rendering
Game engines batch similar rendering operations:
// Efficient batching
glBindTexture(GL_TEXTURE_2D, buttonTexture);
for (int i = 0; i < buttonCount; i++) {
// Draw all buttons in one batch
drawButton(buttons[i]);
}
glBindTexture(GL_TEXTURE_2D, 0);
4. Minimal State Changes
Game engines minimize GPU state changes:
// Optimized state management
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw all transparent elements
glDisable(GL_BLEND);
// Draw all opaque elements
Real-World Example: Button Rendering
Web Browser Approach
<button class="game-button" onclick="handleClick()">Start Game</button>
Rendering Steps:
- DOM Parsing: Create button element
- CSS Processing: Apply styles, compute layout
- Render Tree: Add to render tree
- Layout: Calculate position and size
- Paint: Create paint layers
- Composite: GPU compositing
- Event Handling: Attach event listeners
Memory Usage: ~2KB per button (DOM + CSSOM + Event handlers)
Game Engine Approach
// Immediate mode rendering
void renderButton(float x, float y, float width, float height, const char* text) {
// Direct GPU commands
drawRectangle(x, y, width, height, buttonColor);
drawText(x + padding, y + padding, text, textColor);
}
Rendering Steps:
- Direct GPU Call: Draw rectangle
- Direct GPU Call: Draw text
- No State Management: No persistent objects
Memory Usage: ~50 bytes per button (just parameters)
Advanced Game Engine Techniques
1. Command Buffers
Game engines pre-record rendering commands:
// Command buffer approach
struct RenderCommand {
CommandType type;
float x, y, width, height;
Color color;
Texture* texture;
};
std::vector<RenderCommand> commandBuffer;
// Record commands
commandBuffer.push_back({DRAW_RECT, x, y, w, h, color, nullptr});
// Execute all commands in one batch
executeCommandBuffer(commandBuffer);
2. GPU Instancing
Render thousands of similar elements efficiently:
// Instance rendering
glBindBuffer(GL_ARRAY_BUFFER, instanceBuffer);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, instanceCount);
3. Spatial Partitioning
Only render visible UI elements:
// Frustum culling for UI
if (isInViewport(uiElement)) {
renderUIElement(uiElement);
}
Web Technologies Adopting Game Engine Techniques
1. Canvas API
Web developers can use immediate mode rendering:
// Canvas immediate mode
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
function renderFrame() {
// Clear everything
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw UI elements directly
ctx.fillStyle = "#4CAF50";
ctx.fillRect(10, 10, 100, 40);
ctx.fillStyle = "white";
ctx.fillText("Start Game", 20, 35);
requestAnimationFrame(renderFrame);
}
2. WebGL
Direct GPU access in the browser:
// WebGL immediate mode
const gl = canvas.getContext("webgl");
function drawButton(x, y, width, height) {
// Direct GPU commands
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
3. React Canvas
React's experimental canvas renderer:
// React Canvas (experimental)
import { Canvas } from "react-canvas";
function GameUI() {
return (
<Canvas>
<Button x={10} y={10} width={100} height={40}>
Start Game
</Button>
</Canvas>
);
}
Performance Benchmarks
Web Browser (Complex UI)
DOM Elements: 1000
CSS Rules: 500
JavaScript: 50KB
Memory Usage: 50MB
FPS: 30-45
Latency: 22-33ms
Game Engine (Complex UI)
UI Elements: 1000
Rendering Commands: 2000
Memory Usage: 5MB
FPS: 120+
Latency: 8ms
Key Takeaways for Web Developers
1. Consider Canvas for High-Performance UI
- Use Canvas API for game-like interfaces
- Immediate mode rendering for complex animations
- Direct GPU access via WebGL
2. Minimize DOM Complexity
- Reduce DOM tree depth
- Use CSS transforms instead of layout changes
- Batch DOM updates
3. Optimize Rendering Pipeline
- Use
will-changefor GPU layers - Minimize paint operations
- Leverage compositor-only animations
4. Adopt Game Engine Patterns
- Immediate mode for real-time interfaces
- Command batching for similar operations
- Spatial culling for large lists
Future of Web UI Rendering
1. WebGPU
Next-generation GPU API for web:
// WebGPU (future)
const gpu = navigator.gpu;
const device = await gpu.requestAdapter().requestDevice();
// Direct GPU commands
const commandEncoder = device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass();
2. React Server Components
Reducing client-side rendering:
// Server Components
async function GameUI() {
const gameState = await fetchGameState();
return <GameInterface state={gameState} />;
}
3. WebAssembly + Canvas
High-performance UI with WASM:
// WASM + Canvas
const wasmModule = await WebAssembly.instantiateStreaming(
fetch("ui-renderer.wasm")
);
wasmModule.instance.exports.renderUI(canvasContext);
Conclusion
Game engines achieve high FPS through:
- Immediate mode rendering (no persistent state)
- Direct GPU communication (minimal abstraction)
- Optimized batching (efficient command submission)
- Minimal memory overhead (no object graphs)
Web developers can adopt these techniques through:
- Canvas API for immediate mode rendering
- WebGL for direct GPU access
- Performance optimization of existing DOM-based UIs
- Emerging technologies like WebGPU and WASM
The gap between web and game engine performance is narrowing as web technologies evolve to support more efficient rendering patterns!
Curious ?
What if we could bring game engine performance to web applications? Let's explore WebGPU and the future of high-performance web rendering...
Stay tuned /
