Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Environment Processing Unit (EPU)

The Environment Processing Unit (EPU) is ZX’s environment system. You can drive it from a packed 128-byte procedural configuration (8 x 128-bit instructions) or from six imported cube-face textures, then use the immediate-mode EPU API to:

  • Render the environment background
  • Drive ambient + reflection lighting for lit materials (computed on the GPU)

For canonical ABI docs, see nethercore/include/zx.rs. For the opcode catalog/spec, see nethercore-design/specs/epu-feature-catalog.md.


FFI

epu_set

Select the current EPU source from a procedural config (no background draw).

To switch environments in the same frame, call epu_set(...), epu_textures(...), or epu_asset(...) before the draws that should use that source.

/// Select the current EPU source from a procedural config.
///
/// config_ptr points to 16 u64 values (128 bytes):
/// 8 instructions x (hi u64, lo u64)
fn epu_set(config_ptr: *const u64);

epu_textures

Select the current EPU source from six already-loaded 2D textures interpreted as cubemap faces.

Face order is fixed: px, nx, py, ny, pz, nz.

/// Select the current EPU source from six texture handles.
fn epu_textures(px: u32, nx: u32, py: u32, ny: u32, pz: u32, nz: u32);

epu_asset

Select the current EPU source from a packed six-face environment asset in the ROM data pack.

/// Select the current EPU source from a packed EPU environment asset.
fn epu_asset(id_ptr: *const u8, id_len: u32);

draw_epu

Draw the environment background for the current viewport/pass.

/// Draw the EPU background for the current viewport/pass.
fn draw_epu();

Call draw_epu() after your 3D geometry so the environment only fills background pixels.

Notes:

  • For split-screen, set viewport(...) and call draw_epu() per viewport.
  • The EPU compute pass runs automatically before rendering.
  • Ambient lighting is computed and applied entirely on the GPU; there is no CPU ambient query.
  • epu_set(...), epu_textures(...), and epu_asset(...) all select the current EPU source.
  • draw_epu() draws the currently selected source.

Configuration Layout

Each environment is exactly 8 x 128-bit instructions (128 bytes total). In memory, that is 16 u64 values laid out as 8 [hi, lo] pairs.

SlotsKindAuthoring Model
0-7MixedThe runtime evaluates all 8 instructions sequentially in authored order. Bounds opcodes (0x01..0x07) establish or reshape region weights; feature opcodes (0x08+) consume the current regions. A common cadence is BOUNDS -> FEATURES -> BOUNDS -> FEATURES, but it is not required.

Instruction Bit Layout (128-bit)

Each instruction is packed as two u64 values:

High Word (bits 127..64)

bits 127..123: opcode     (5)  - Which algorithm to run
bits 122..120: region     (3)  - Bitfield: SKY=0b100, WALLS=0b010, FLOOR=0b001
bits 119..117: blend      (3)  - How to combine layer output (8 modes)
bits 116..112: meta5      (5)  - (domain_id<<3)|variant_id; use 0 when unused
bits 111..88:  color_a    (24) - RGB24 primary color
bits 87..64:   color_b    (24) - RGB24 secondary color

Low Word (bits 63..0)

bits 63..56:   intensity  (8)  - Layer brightness
bits 55..48:   param_a    (8)  - Opcode-specific
bits 47..40:   param_b    (8)  - Opcode-specific
bits 39..32:   param_c    (8)  - Opcode-specific
bits 31..24:   param_d    (8)  - Opcode-specific
bits 23..8:    direction  (16) - Octahedral-encoded direction (u8,u8)
bits 7..4:     alpha_a    (4)  - color_a alpha (0=transparent, 15=opaque)
bits 3..0:     alpha_b    (4)  - color_b alpha (0=transparent, 15=opaque)

Determinism (No Host Time)

The EPU has no host-managed time input. Any temporal variation (scrolling, pulsing, drifting, twinkling, etc.) must be driven explicitly by the game by changing instruction parameters as part of deterministic simulation.

In practice this often means incrementing an opcode-specific motion or modulation parameter each frame and re-calling epu_set(...) with the updated config. Many animated variants use param_d for this, but not all do; some variants use param_d as seed or waveform selection rather than smooth motion.


Opcode Map (current shaders)

This is the opcode number. Some opcodes use meta5 for domain/variant selection; when unused, set meta5 = 0.

CodeNameNotes
0x00NOPDisable layer
0x01RAMPBounds gradient
0x02SECTORBounds modifier
0x03SILHOUETTEBounds modifier
0x04SPLITBounds
0x05CELLBounds
0x06PATCHESBounds
0x07APERTUREBounds
0x08DECALFeature
0x09GRIDFeature
0x0ASCATTERFeature
0x0BFLOWFeature
0x0CTRACEFeature
0x0DVEILFeature
0x0EATMOSPHEREFeature
0x0FPLANEFeature
0x10CELESTIALFeature
0x11PORTALFeature
0x12LOBEFeature
0x13BANDFeature
0x14MOTTLEFeature
0x15ADVECTFeature
0x16SURFACEFeature

For full per-opcode packing/algorithm details, see:

  • nethercore-design/specs/epu-feature-catalog.md
  • nethercore/nethercore-zx/shaders/epu/

Region Mask (3-bit bitfield)

Regions are combinable using bitwise OR:

ValueBinaryNameMeaning
70b111ALLApply to sky + walls + floor
40b100SKYSky/ceiling only
20b010WALLSWall/horizon belt only
10b001FLOORFloor/ground only
60b110SKY_WALLSSky + walls
50b101SKY_FLOORSky + floor
30b011WALLS_FLOORWalls + floor
00b000NONELayer disabled

The region mask is consumed by feature opcodes: their contribution is multiplied by region_weight(current_regions, mask).

current_regions comes from the most recent bounds opcode; every bounds opcode outputs updated RegionWeights for subsequent layers. Bounds opcodes do not use the region mask.


Blend Modes (3-bit, 8 modes)

ValueNameFormula
0ADDdst + src * a
1MULTIPLYdst * mix(1, src, a)
2MAXmax(dst, src * a)
3LERPmix(dst, src, a)
4SCREEN1 - (1-dst)*(1-src*a)
5HSV_MODHSV shift dst by src
6MINmin(dst, src * a)
7OVERLAYPhotoshop-style overlay

meta5

The 5-bit meta5 field (hi bits 116..112) is interpreted as:

  • meta5 = (domain_id << 3) | variant_id
  • domain_id = (meta5 >> 3) & 0b11
  • variant_id = meta5 & 0b111

Quick Start

The easiest reference implementation is the EPU showcase presets:

  • nethercore/examples/3-inspectors/epu-showcase/src/presets.rs
  • nethercore/examples/3-inspectors/epu-showcase/src/constants.rs
// 8 x [hi, lo]
static ENV: [[u64; 2]; 8] = [
    [0, 0], [0, 0], [0, 0], [0, 0],
    [0, 0], [0, 0], [0, 0], [0, 0],
];

fn render() {
    unsafe {
        epu_set(ENV.as_ptr().cast());
        // ... draw scene geometry
        draw_epu();
    }
}

See Also