🎨 Limn Engine Rendering Pipeline — From CSS Background to Dynamic Components
How Limn Engine Draws Your Game, Layer by Layer
Ever wondered what happens when Limn Engine renders a frame? Behind the simple display.start() lies a carefully designed rendering pipeline that layers everything from CSS backgrounds to dynamic game objects.
This article breaks down every layer of Limn Engine's rendering process — from the bottom (CSS background) to the top (dynamic components).
The Complete Render Order
Limn Engine renders in this exact order, from back to front:
┌─────────────────────────────────────────────────────────────────┐
│ LAYER 0: CSS Background │
│ ↓ │
│ LAYER 1: display.backgroundColor / lgradient / rgradient │
│ ↓ │
│ LAYER 2: fake.bgComm (Background Component) │
│ ↓ │
│ LAYER 3: tileComm (TileMap Layers) │
│ ↓ │
│ LAYER 4: commp (Fake Canvas Components) │
│ ↓ │
│ LAYER 5: comm (Main Display Components) │
│ ↓ │
│ LAYER 6: UI with .fixed() (HUD on top of everything) │
└─────────────────────────────────────────────────────────────────┘
Layer 0: CSS Background (Bottom Layer)
This is the lowest layer. It's the actual CSS background of the canvas element.
display.canvas.style.backgroundColor = "black";
Why it matters: If all other layers fail or are transparent, this is what shows. It's your safety net.
Limn Engine wraps this with convenience methods:
display.backgroundColor("#0d0d2a"); // Solid color
display.lgradient("top", "blue", "black"); // Linear gradient
display.rgradient("white", "navy"); // Radial gradient
When to use: For simple backgrounds or as a fallback. This layer is fast because it's pure CSS.
Layer 1: Display Background Methods
These are CSS-based backgrounds applied to the canvas.
| Method | Purpose |
|---|---|
display.backgroundColor(color) |
Solid CSS color |
display.lgradient(dir, c1, c2) |
Linear gradient |
display.rgradient(c1, c2) |
Radial gradient |
// Solid background
display.backgroundColor("lightblue");
// Gradient sky
display.lgradient("top", "royalblue", "darkblue");
What's happening: These set this.canvas.style.background to the appropriate CSS value. The browser handles the rendering.
When to use: When you need a simple, fast background without using the canvas drawing context.
Layer 2: fake.bgComm (Background Component)
This is the first canvas-drawn layer. fake.bgComm is a special Component that renders behind everything on the fake canvas.
fake.bgComm = new Component(1600, 1200, null, 0, 0, "image");
fake.bgComm.setImage("sky.png");
In the Render Loop
// Inside ani()
if(display.once){
fake.context.save();
fake.context.translate(-fake.camera.x, -fake.camera.y);
// RENDER BACKGROUND FIRST
if(fake.bgComm){
fake.bgComm.update(fake.context);
}
// THEN render tiles
tileComm.forEach(...);
// THEN render other fake objects
commp.forEach(...);
fake.context.restore();
}
What's happening: This renders once (or when fake.refresh() is called) to the offscreen fake canvas. It then gets composited with other layers.
When to use: For skyboxes, parallax backgrounds, or any large static background image.
Layer 3: tileComm (TileMap Layers)
Tilemaps render on top of the background but behind everything else.
display.tile = [
new Component(64, 64, "#2d6a2d", 0, 0), // grass
new Component(64, 64, "#654321", 0, 0) // dirt
];
display.map = [
[1,1,1,1,1],
[1,0,2,0,1],
[1,1,1,1,1]
];
display.tileMap();
display.tileFace.show();
In the Render Loop
// Render tiles AFTER background
tileComm.forEach(component => {
component.x.update(fake.context);
});
What's happening: Tiles are rendered as Components on the fake canvas. They can have colors, images, and even physics.
When to use: For ground, walls, platforms, and any grid-based level elements.
Layer 4: commp (Fake Canvas Components)
These are static Components on the fake canvas that render AFTER tiles.
fake.add(tree, 0); // Adds a tree on top of tiles
fake.add(decoration, 0); // Adds decorations
In the Render Loop
// Render static components AFTER tiles
commp.forEach(component => {
if (component.scene == fake.scene) {
component.x.bUpdate(fake.context);
}
});
What's happening: These are "fake" Components — they exist on the offscreen canvas and render once. They're perfect for decorations, static obstacles, and anything that doesn't move.
When to use: For trees, rocks, decorations, and any static element that sits on top of the ground.
Layer 5: comm (Main Display Components)
These are dynamic Components on the main display that render EVERY frame.
const player = new Component(40, 40, "blue", 100, 100);
display.add(player);
const enemy = new Component(35, 35, "red", 400, 300);
display.add(enemy);
In the Render Loop
comm.forEach(component => {
if (component.scene == display.scene) {
if (component.x.angularMovement) {
component.x.moveAngle();
} else {
component.x.move();
}
if (TCJSgameGameArea.crashWith(component.x)) {
component.x.update(display.context);
}
}
});
What's happening: These Components are drawn every frame on the main display. They move, animate, and interact with the player.
When to use: For the player, enemies, bullets, and any moving object.
Layer 6: UI with .fixed() (Top Layer)
UI elements that stay locked to the screen position, even when the camera moves.
const healthBar = new Component(200, 20, "green", 20, 20);
const scoreText = new Tctxt("24px", "Arial", "white", 20, 50);
display.add(healthBar);
display.add(scoreText);
function update() {
healthBar.fixed(); // Stays at screen position (20,20)
scoreText.fixed(); // Stays at screen position (20,50)
}
What's happening: .fixed() adds the current camera offset to the Component's anchor position, so it appears at the same screen coordinates regardless of camera movement.
When to use: For all UI — health bars, score displays, menus, and dialogue boxes.
The Complete Render Pipeline Visualization
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 6: UI with .fixed() [TOP] │ │
│ │ (Health bars, score, menus) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 5: comm (Main Display) │ │
│ │ (Player, enemies, bullets - dynamic) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 4: commp (Fake Canvas) │ │
│ │ (Trees, decorations - static) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 3: tileComm (TileMap) │ │
│ │ (Ground, walls, platforms) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 2: fake.bgComm (Background Component) │ │
│ │ (Sky, clouds, mountains) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 1: CSS Background Methods │ │
│ │ (backgroundColor, lgradient, rgradient) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LAYER 0: CSS Background (canvas.style.background) │ │
│ │ (Fallback - bottom layer) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Performance Impact of Each Layer
| Layer | Renders Once | Renders Every Frame | Performance Impact |
|---|---|---|---|
| CSS Background | ✅ Browser handles | Minimal | Zero |
| display.backgroundColor/gradient | ✅ Browser handles | Minimal | Zero |
| fake.bgComm | ✅ Once (cached) | ✅ Pasted as image | Low |
| tileComm | ✅ Once (cached) | ✅ Pasted as image | Low |
| commp | ✅ Once (cached) | ✅ Pasted as image | Low |
| comm | ❌ No (dynamic) | ✅ Every frame | Medium |
| UI with .fixed() | ❌ No (dynamic) | ✅ Every frame | Medium |
💡 Performance Secret: The fake canvas cache means Layers 2-4 render ONCE and then just get pasted as an image. This is why Limn can run at 60 FPS on a Toshiba with 4GB RAM.
How to Use This Knowledge
For Better Performance
| If you have... | Use... | Why |
|---|---|---|
| Static backgrounds | fake.bgComm |
Renders once, cached forever |
| Static decorations | fake.add() |
Renders once, cached forever |
| Moving objects | display.add() |
Renders every frame (required) |
| UI elements | .fixed() |
Stays on screen, camera-proof |
For Better Organization
// Clear separation of layers
// 1. Background
fake.bgComm = new Component(1600, 1200, null, 0, 0, "image");
fake.bgComm.setImage("sky.png");
// 2. Tilemap
const tilemap = new TileMap(display, level, tiles, 1600, 1200);
tilemap.show();
// 3. Static decorations
fake.add(tree);
fake.add(rock);
// 4. Dynamic objects
display.add(player);
display.add(enemy);
// 5. UI
const scoreText = new Tctxt(...);
display.add(scoreText);
The One-Line Summary
"Limn Engine renders from CSS background up to dynamic objects, with three layers cached on the fake canvas for maximum performance and three layers drawn every frame for maximum flexibility."
Draw your game into existence — one layer at a time. 🎨
Top comments (0)