rustic-render crate handles all drawing. It builds on wgpu for GPU access and implements a batched sprite pipeline that flushes to the GPU in texture-grouped draw calls. Text is rendered via glyphon. A post-processing pass sits between the scene framebuffer and the swap chain.
Sparrow XML atlas format
Character and UI sprites are packed into Sparrow-format texture atlases. Each atlas is a PNG image paired with an XML file. The XML lists every frame as a<SubTexture> element:
example.xml
SubTexture attributes
Frame name. Trailing digits identify the frame index within an animation sequence.
Left edge of the source rectangle in the atlas texture, in pixels.
Top edge of the source rectangle in the atlas texture, in pixels.
Width of the source rectangle in pixels.
Height of the source rectangle in pixels.
Horizontal offset from the frame origin to the source rect, in pixels. Negative means the sprite is trimmed on the left.
Vertical offset from the frame origin to the source rect, in pixels.
Logical width of the full (untrimmed) frame. Defaults to
width when absent.Logical height of the full (untrimmed) frame. Defaults to
height when absent.When
true, the source rect is stored rotated 90° clockwise in the atlas. The renderer un-rotates it at draw time.SpriteAtlas
crates/rustic-render/src/sprites.rs
Animation registration
Animations are registered by prefix — matching how HaxeFlixel’saddByPrefix works:
add_by_prefix strips the prefix from each matching frame name, parses the remaining digits as a frame index, sorts by index, and stores the resulting SpriteFrame slice under anim_name. Frames without trailing digits are treated as index 0.
Animation name derivation
When character JSON files list animation prefixes, the engine derives the animation name by passing the prefix string toadd_by_prefix. The trailing digit stripping happens inside the method — you never need to strip digits manually.
Querying frames
get_frame wraps frame_index with modulo so you can pass a raw accumulated counter without clamping.
SpriteFrame
Each registered frame carries the data needed by the GPU renderer:crates/rustic-render/src/sprites.rs
AnimationController
crates/rustic-render/src/sprites.rs
get_frame:
play() only resets if the animation name changes or the previous animation finished. force_play() always resets to frame 0.
GPU backend (GpuState)
crates/rustic-render/src/gpu.rs
GpuState owns the wgpu device, queue, surface, and sprite pipeline. It pre-allocates vertex and index buffers for up to 4096 sprites per batch:
Initialization
game_w × game_h). The renderer letterboxes the output within the window, preserving aspect ratio.
Projection
An orthographic projection maps(0, 0)–(game_w, game_h) to clip space with Y going down (screen convention):
Sprite vertex format
crates/rustic-render/src/gpu.rs
Drawing sprites
frameX, frameY) are applied during drawing to position the visible sprite correctly within its logical bounding box.
Texture loading
image crate, converted to RGBA8, and alpha-premultiplied before upload to eliminate white fringing on transparent edges. The pipeline uses PREMULTIPLIED_ALPHA_BLENDING.
Multi-batch frame rendering
For a scene with multiple texture layers (background sprites, character sprites, notes, HUD) use the multi-batch API:draw_batch() call issues one draw call and clears the vertex buffer, so you can use more than 4096 sprites per frame by splitting across batches.
Text rendering
Text is rendered viaglyphon through the TextSystem embedded in GpuState:
end_frame(), on top of all sprite batches.
Post-processing
APostProcessor sits between the offscreen render target and the swap chain surface. Enable it before begin_frame():
end_frame(), PostProcessor::apply() samples the offscreen texture and writes to the surface, applying any active effects (such as a chromatic aberration or vignette shader).