New Hugo site in website/ with prosperon.dev theme (blue/gold/castle aesthetic), docs sidebar navigation, and content pages. Rewrote all doc files to align with the actual codebase: compositor+film2d rendering, use() modules (no global prosperon object), Pit language, script+JSON entity model. Added entities.md, front matter to all 70+ API docs, and updated API index for current module architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
171 lines
4.7 KiB
Markdown
171 lines
4.7 KiB
Markdown
---
|
|
title: "Compositor"
|
|
type: docs
|
|
---
|
|
|
|
# The Compositor
|
|
|
|
The compositor is Prosperon's rendering orchestrator. You describe your scene — planes, layers, effects — and the compositor figures out what render passes are needed and executes them.
|
|
|
|
## The Basic Idea
|
|
|
|
You don't issue draw calls. You create sprites and set their properties. The compositor does the rest:
|
|
|
|
1. Queries all registered drawables
|
|
2. Organizes them by plane and layer
|
|
3. Applies effects to tagged groups
|
|
4. Composites everything to the screen
|
|
|
|
Think of it like a GBA: you declare "sprite here, sprite there," get handles back, and poke their positions. The hardware (in this case, the compositor + GPU backend) handles the actual rendering.
|
|
|
|
## Scene Configuration
|
|
|
|
A scene is described as a config object passed to the compositor:
|
|
|
|
```javascript
|
|
var compositor = use('compositor')
|
|
|
|
var plan = compositor.compile({
|
|
clear: {r: 0, g: 0, b: 0, a: 1},
|
|
planes: [
|
|
{
|
|
name: 'background',
|
|
plane: 'background',
|
|
resolution: {width: 320, height: 240},
|
|
camera: bg_camera,
|
|
presentation: 'integer_scale'
|
|
},
|
|
{
|
|
name: 'game',
|
|
plane: 'default',
|
|
resolution: {width: 320, height: 240},
|
|
camera: game_camera,
|
|
layer_sort: {'5': 'y'},
|
|
presentation: 'integer_scale'
|
|
},
|
|
{
|
|
name: 'hud',
|
|
plane: 'hud',
|
|
resolution: {width: 1280, height: 720},
|
|
camera: hud_camera,
|
|
presentation: 'stretch'
|
|
}
|
|
]
|
|
})
|
|
```
|
|
|
|
Each plane renders independently at its own resolution and composites onto the screen.
|
|
|
|
## Planes
|
|
|
|
A **plane** is a named rendering group. Sprites belong to a plane via their `plane` property (default: `'default'`). Each plane in the compositor config:
|
|
|
|
- Has its own **resolution** (low-res pixel art, native UI, etc.)
|
|
- Has its own **camera**
|
|
- Has its own **layer sorting** rules
|
|
- Has a **presentation mode** (how it maps to the window)
|
|
|
|
Planes composite in order — later planes draw on top of earlier ones.
|
|
|
|
## Layer Sorting
|
|
|
|
Within a plane, drawables are sorted by `layer` (integer). Within each layer, you can choose a sorting mode:
|
|
|
|
```javascript
|
|
layer_sort: {
|
|
'0': 'explicit', // engine may reorder for batching efficiency
|
|
'5': 'y' // sort by Y position (top-down game depth)
|
|
}
|
|
```
|
|
|
|
Y-sorting is essential for top-down games where objects lower on screen should appear in front.
|
|
|
|
## Effects
|
|
|
|
Effects are applied to **groups** of sprites. Tag sprites with group names, then define effects for those groups:
|
|
|
|
```javascript
|
|
var player = sprite({
|
|
image: "player.png",
|
|
groups: ['glow_objects']
|
|
})
|
|
|
|
var plan = compositor.compile({
|
|
planes: [{
|
|
name: 'game',
|
|
plane: 'default',
|
|
camera: cam,
|
|
resolution: {width: 320, height: 240}
|
|
}],
|
|
group_effects: {
|
|
glow_objects: {
|
|
effects: [
|
|
{type: 'bloom', threshold: 0.5, intensity: 1.5, blur_passes: 2}
|
|
]
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
### Available Effects
|
|
|
|
**Bloom** — extracts bright areas, blurs them, composites back:
|
|
```javascript
|
|
{type: 'bloom', threshold: 0.8, intensity: 1.0, blur_passes: 2}
|
|
```
|
|
|
|
**Mask** — uses sprites in a mask group as a stencil:
|
|
```javascript
|
|
{type: 'mask', mask_group: 'mask_shapes', channel: 'alpha', invert: false}
|
|
```
|
|
|
|
Sprites in the `mask_group` are not drawn directly — they only serve as the mask shape.
|
|
|
|
## Presentation Modes
|
|
|
|
Each plane specifies how its render target maps to the window:
|
|
|
|
| Mode | Behavior |
|
|
|------|----------|
|
|
| `stretch` | Fill the window exactly (may distort aspect ratio) |
|
|
| `letterbox` | Fit inside window, preserving aspect ratio, with black bars |
|
|
| `integer_scale` | Scale by whole numbers only for pixel-perfect rendering |
|
|
|
|
`integer_scale` automatically uses nearest-neighbor filtering for crisp pixels.
|
|
|
|
## Execution
|
|
|
|
The compositor produces a **plan** — a list of render passes. The plan is executed by the GPU backend:
|
|
|
|
```javascript
|
|
var result = compositor.execute(plan)
|
|
// result.commands is sent to the GPU backend
|
|
```
|
|
|
|
In practice, `core.start({render})` handles this for you. Your render callback returns the compositor result and the engine executes it.
|
|
|
|
## Manual Drawables
|
|
|
|
You can inject drawables that aren't registered with film2d by adding them directly to a plane config:
|
|
|
|
```javascript
|
|
planes: [{
|
|
name: 'game',
|
|
plane: 'default',
|
|
camera: cam,
|
|
drawables: [
|
|
{type: 'sprite', image: tex, pos: {x: 0, y: 0}, width: 32, height: 32, layer: 0}
|
|
]
|
|
}]
|
|
```
|
|
|
|
These are merged with the registered drawables for that plane.
|
|
|
|
## Render Targets
|
|
|
|
The compositor automatically manages intermediate render targets for effects. You don't need to create or manage GPU textures — the compositor allocates and reuses them as needed.
|
|
|
|
## Debug
|
|
|
|
Pass `debug: 'graph'` to `core.start` config to log the compiled render plan as JSON. Pass `debug: 'cmd'` to log the final command list sent to the GPU.
|