Skip to main content

One post tagged with "Game Development"

View All Tags

(3/n) Game Engine 101

· 6 min read
Sheetalsingh
Software Engineer @ Glean

Related Articles:

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

AspectWeb BrowserGame Engine
Rendering ModeRetained ModeImmediate Mode
State ManagementComplex DOM/CSSOMMinimal/None
Memory UsageHigh (object graphs)Low (direct rendering)
Update OverheadDiffing + incrementalFull redraw
Typical FPS30-60 FPS120+ FPS
Latency16-33ms8ms 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:

  1. DOM Parsing: Create button element
  2. CSS Processing: Apply styles, compute layout
  3. Render Tree: Add to render tree
  4. Layout: Calculate position and size
  5. Paint: Create paint layers
  6. Composite: GPU compositing
  7. 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:

  1. Direct GPU Call: Draw rectangle
  2. Direct GPU Call: Draw text
  3. 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-change for 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 /