Asi rio-webgpu-tiles

WebGPU tile rendering for Rio Terminal via wgpu and sugarloaf. Extends

install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/rio-webgpu-tiles" ~/.claude/skills/plurigrid-asi-rio-webgpu-tiles-91a66e && rm -rf "$T"
manifest: skills/rio-webgpu-tiles/SKILL.md
source content

Rio WebGPU Tiles

GPU-accelerated tile rendering in Rio Terminal using wgpu and the sugarloaf brush architecture.

Architecture Overview

OSC 1337 Sequence → rio-backend (parse) → RioEvent::InsertTile → rioterm (frontend) → sugarloaf TileBrush → wgpu render

Core Files

FilePurpose
sugarloaf/src/components/tiles/mod.rs
TileBrush, TileWorldState, GPU buffers
sugarloaf/src/sugarloaf.rs
Main renderer integration, public API
rio-backend/src/ansi/tile_protocol.rs
OSC 1337
Tile=
parsing
rio-backend/src/performer/handler.rs
OSC dispatch to handler
rio-backend/src/event/mod.rs
InsertTile
event variant
frontends/rioterm/src/application.rs
Frontend event handling

Protocol: OSC 1337 Tile Extension

# Format: ESC ] 1337 ; Tile = key:value,key:value,... BEL
printf '\033]1337;Tile=shader:plasma,x:50,y:50,w:200,h:150\007'

Parameters

KeyTypeDescription
shader
string
plasma
,
clock
,
noise
, or custom ID
x
,
y
f32Position in pixels
w
,
h
f32Size in pixels
id
u64Tile ID (0 = auto-assign)
kind
string
persistent
(default) or
transient
r
,
g
,
b
,
a
f32Custom color/data (0.0-1.0)
time_offset
f32Animation time offset

Tile Lifecycle

Persistent Tiles

Remain until explicitly removed by ID:

let id = sugarloaf.create_persistent_tile(scene);
// ... later
sugarloaf.remove_persistent_tile(id);

Transient Tiles

Cleared at

begin_frame()
, must be re-pushed each frame:

sugarloaf.push_transient_tile(scene);

TileWorldState

Manages CPU state with high-precision time (f64), converted to modular f32 for GPU:

pub struct TileWorldState {
    persistent: HashMap<TileId, TileScene>,
    transient: Vec<TileScene>,
    world_time_seconds: f64,
    next_id: TileId,
}

impl TileWorldState {
    pub fn begin_frame(&mut self, dt_seconds: f64) {
        self.world_time_seconds += dt_seconds;
        self.transient.clear();
    }
}

Writing WGSL Shaders

Uniform structure available in shaders:

struct Uniforms {
    position: vec2<f32>,  // NDC position
    size: vec2<f32>,      // NDC size
    time: f32,            // Animation time (modular)
    custom: vec4<f32>,    // r,g,b,a from protocol
}
@group(0) @binding(0) var<uniform> u: Uniforms;

Vertex Shader Pattern

@vertex
fn vs_main(@location(0) pos: vec2<f32>, @location(1) uv: vec2<f32>) -> VertexOutput {
    var out: VertexOutput;
    let screen_pos = u.position + pos * u.size;
    out.position = vec4<f32>(screen_pos, 0.0, 1.0);
    out.uv = uv;
    return out;
}

Fragment Shader Pattern

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let t = u.time;
    // Your shader logic using in.uv, t, u.custom
    return vec4<f32>(color, 1.0);
}

Demo Scripts

# Plasma effect
printf '\033]1337;Tile=shader:plasma,x:50,y:50,w:200,h:150,r:0.5,g:0.2,b:0.8\007'

# Clock display
printf '\033]1337;Tile=shader:clock,x:300,y:50,w:250,h:80\007'

# Remove by ID
printf '\033]1337;Tile=remove:42\007'

Integration Checklist

  1. Add TileBrush to sugarloaf - Register in brush list, call in render loop
  2. Parse OSC 1337 in rio-backend - Extend performer handler
  3. Create RioEvent variant -
    InsertTile(TileSpec)
  4. Handle in frontend - Convert TileSpec → TileScene, call sugarloaf API
  5. Manage state - Call
    begin_frame(dt)
    before each render

Shader Examples

See

reference/shaders.md
for complete plasma, clock, and noise shader implementations.