DocsAPI reference

cinder.render

cinder.render #

Source: cinder/render/__init__.mojo

Renderer: cameras, meshes, materials, textures, the frame graph and the forward+ pipeline. Everything here talks to the GPU only through cinder.gfx.

cinder.render.camera #

Source: cinder/render/camera.mojo

Cameras and frustum culling. Right-handed, Y up, camera looks down -Z. Projection is reverse-Z with an infinite far plane, so the frustum has five planes. cull_spheres tests four spheres at a time with SIMD.

  • comptime F4 = SIMD[DType.float32, 4] — Four float32 lanes: one coordinate of four spheres in Frustum.cull_spheres_into.

struct Plane #

mojo
struct Plane(ImplicitlyCopyable, RegisterPassable)

normal . p + d >= 0 is the inside half-space.

Fields:

  • normal: Vec3

  • d: Float32

  • from_row from_row(v: Vec4) -> Plane
    Plane from a clip-space row combination, scaled so normal has unit length.

  • distance distance(self, p: Vec3) -> Float32
    Signed distance from p to the plane (positive inside), in the units of p.

struct Frustum #

mojo
struct Frustum(Copyable, Movable)

Left, right, bottom, top, near planes extracted from a view-projection matrix (Gribb/Hartmann). Reverse-Z with infinite far: the near plane is row3 - row2 (clip z <= w); no far plane.

Fields:

  • planes: Array[Plane, 5]

  • __init__ __init__(out self, view_proj: Mat4)

  • contains_sphere contains_sphere(self, centre: Vec3, radius: Float32) -> Bool
    True unless the sphere lies entirely behind one of the five planes. Conservative: a sphere near a frustum edge or corner may pass without intersecting it.

  • contains_point contains_point(self, p: Vec3) -> Bool
    True when p is on the inside of all five planes (near, left, right, bottom, top).

  • cull_spheres_into cull_spheres_into(self, spheres: List[Vec4], mut out: List[Bool])
    Visibility per sphere (xyz centre, w radius), four spheres per SIMD step, written into a list supplied by the caller (cleared first, capacity kept).

  • cull_spheres cull_spheres(self, spheres: List[Vec4]) -> List[Bool]
    Visibility per sphere (xyz centre, w radius); allocates the result list. Use cull_spheres_into in per-frame code to reuse a scratch list.

struct Camera #

mojo
struct Camera(ImplicitlyCopyable, Movable)

A perspective camera. near is the only clip distance (infinite far).

Fields:

  • position: Vec3

  • rotation: Quat

  • fov_y: Float32

  • aspect: Float32

  • near: Float32

  • __init__ __init__( out self, position: Vec3, rotation: Quat, fov_y: Float32 = 1.0471976, aspect: Float32 = 16.0 / 9.0, near: Float32 = 0.05, )

  • look_at look_at( eye: Vec3, target: Vec3, up: Vec3 = Vec3.unit_y(), fov_y: Float32 = 1.0471976, aspect: Float32 = 16.0 / 9.0, near: Float32 = 0.05, ) -> Camera
    A camera at eye looking at target, with its rotation taken from Mat4.look_at. up only orients the camera (the stored up() is re-orthogonalised); fov_y is the vertical field of view in radians, near the near clip distance in metres.

  • forward forward(self) -> Vec3
    World-space view direction: Vec3.forward() (-Z) rotated by rotation, unit length.

  • right right(self) -> Vec3
    World-space right axis: +X rotated by rotation.

  • up up(self) -> Vec3
    World-space up axis: +Y rotated by rotation.

  • view view(self) -> Mat4
    World-to-view matrix (right-handed, looking down -Z), column-major.

  • projection projection(self) -> Mat4
    Reverse-Z perspective with an infinite far plane: depth 1 at near, 0 at infinity.

  • view_projection view_projection(self) -> Mat4
    projection() * view(): world space to clip space.

  • frustum frustum(self) -> Frustum
    The five-plane culling frustum of view_projection() (no heap allocation).

  • set_viewport set_viewport(mut self, width: Int, height: Int)
    Sets aspect to width / height in pixels; ignored when height is not positive.

cinder.render.frame_graph #

Source: cinder/render/frame_graph.mojo

Frame graph: declared passes, transient textures with lifetimes and aliasing, automatic ordering. The graph is rebuilt every frame (cheap: a few passes). compile orders passes by their read/write dependencies, culls passes that contribute nothing to an exported resource, computes each transient's lifetime as a pass-index interval and assigns physical textures so that transients with compatible descriptions and disjoint lifetimes share one allocation. Physical textures persist in a pool across frames and are dropped when their description stops being requested (resize). The renderer executes the compiled order itself (order) and looks up each transient's view with view_of; the graph never calls back into user code (no closures cross the GPU layer). to_graphviz renders the compiled graph for the debug UI.

struct TransientDesc #

mojo
struct TransientDesc(Equatable, ImplicitlyCopyable, Movable)

Size in pixels, format, WGPUTextureUsage bits, sample and mip count of a transient. Transients share one pooled physical texture only when their descriptions are equal.

Fields:

  • width: UInt32
  • height: UInt32
  • format: WGPUTextureFormat
  • usage: UInt64
  • sample_count: UInt32
  • mip_levels: UInt32

struct FrameGraph #

mojo
struct FrameGraph(Movable)

One frame's passes and resources, compiled into an execution order and pooled textures. Per frame: reset, declare resources (create, import_view, create_external) and passes (add_pass, read, write), export what must survive, compile, then walk order and look up views with view_of. Physical textures are owned by the graph and freed by release (or when a compile no longer requests them); declaration and compile do not allocate in steady state.

Fields:

  • resources: List[_Resource]

  • passes: List[_Pass]

  • order: List[Int] — Pass indices in execution order (set by compile).

  • physicals: List[_Physical]

  • compiled: Bool

  • culled_count: Int

  • __init__ __init__(out self)

  • reset reset(mut self)
    Starts a new frame's declaration. Physical textures stay pooled; pass records keep their read/write list capacity for reuse.

  • create create(mut self, name: String, desc: TransientDesc) -> Int
    Declares a transient texture and returns its resource index (valid until reset). The physical texture is assigned by compile, possibly shared with other transients.

  • import_view import_view( mut self, name: String, view: TextureViewHandle, desc: TransientDesc ) -> Int
    A view the caller keeps alive (swapchain, persistent target). Never aliased.

  • create_external create_external(mut self, name: String) -> Int
    A buffer-backed resource (no texture): passes writing it are ordered before readers and kept alive by them, but no physical texture is allocated.

  • export export(mut self, resource: Int)
    Marks a resource as needed outside the graph; passes feeding it are kept.

  • add_pass add_pass(mut self, name: String) -> Int
    Declares a pass and returns its index; reuses a spare pass record when there is one.

  • read read(mut self, pass_: Int, resource: Int)
    Declares that pass_ reads resource (ordered after the resource's producer).

  • write write(mut self, pass_: Int, resource: Int)
    Declares that pass_ writes resource; writers of a resource keep declaration order.

  • compile compile(mut self, mut res: Resources) raises
    Orders, culls, computes lifetimes and assigns physical textures.

  • release release(mut self, mut res: Resources) raises
    Destroys every pooled physical texture and its view; raises on a Resources error.

  • view_of view_of(self, resource: Int) raises -> TextureViewHandle
    View of resource after compile: the imported view or its physical texture's view. Raises for an external (buffer-backed) resource and for one with no physical texture (unused, culled or not compiled).

  • texture_of texture_of(self, resource: Int) raises -> TextureHandle
    Physical texture behind a transient after compile. Raises for imported and external resources and for transients without a physical texture.

  • find find(self, name: String) -> Int
    Index of the resource with this name, or -1.

  • physical_count physical_count(self) -> Int
    Pooled physical textures after the last compile (FrameStats.transient_textures).

  • is_culled is_culled(self, pass_: Int) -> Bool
    True when the last compile culled the pass (it feeds no exported resource).

  • lifetime lifetime(self, resource: Int) -> Tuple[Int, Int]
    (first, last) execution-order indices using the resource; (-1, -1) if no kept pass does.

  • to_graphviz to_graphviz(self) -> String
    DOT text: passes as boxes in execution order, resources as ellipses, edges for reads/writes.

cinder.render.lights #

Source: cinder/render/lights.mojo

Lights and the clustered-culling configuration shared with shaders/common.wgsl.

  • comptime CLUSTER_X = 16 — Cluster grid columns across the screen; mirrors CLUSTER_X in common.wgsl.
  • comptime CLUSTER_Y = 9 — Cluster grid rows across the screen; mirrors CLUSTER_Y in common.wgsl.
  • comptime CLUSTER_Z = 24 — Logarithmic depth slices of the cluster grid; mirrors CLUSTER_Z in common.wgsl.
  • comptime CLUSTER_COUNT = CLUSTER_X * CLUSTER_Y * CLUSTER_Z — Total clusters (16 x 9 x 24); sizes the cluster_grid count buffer (one u32 each).
  • comptime CLUSTER_MAX_LIGHTS = 32 — Light indices stored per cluster; lights beyond this are dropped by clusters.wgsl.
  • comptime LIGHT_BYTES = 64 — Bytes per packed light: the size of Light in common.wgsl.
  • comptime MAX_LIGHTS = 1024 — Most local lights the renderer uploads per frame; extra lights are dropped.

struct LightKind #

mojo
struct LightKind(Equatable, ImplicitlyCopyable, RegisterPassable)

Light type tag; values mirror LIGHT_* in common.wgsl (0 directional, 1 point, 2 spot).

Fields:

  • value: UInt32

  • directional directional() -> LightKind
    Sun-like light at infinity; only its direction matters.

  • point point() -> LightKind
    Omnidirectional light at a position, attenuated to zero at range.

  • spot spot() -> LightKind
    Cone light at a position along direction, attenuated to zero at range.

struct Light #

mojo
struct Light(ImplicitlyCopyable, Movable)

A light. Directional: direction (travel direction), color * intensity in lux. Point and spot: position, range (metres), intensity in watts, spot cone angles in radians.

Fields:

  • kind: LightKind

  • position: Vec3

  • direction: Vec3

  • color: Vec3

  • intensity: Float32

  • range: Float32

  • inner_angle: Float32

  • outer_angle: Float32

  • cast_shadows: Bool

  • __init__ __init__( out self, kind: LightKind, position: Vec3 = Vec3.zero(), direction: Vec3 = Vec3(0.0, -1.0, 0.0), color: Vec3 = Vec3.one(), intensity: Float32 = 1.0, range: Float32 = 10.0, inner_angle: Float32 = 0.4, outer_angle: Float32 = 0.6, cast_shadows: Bool = False, )

  • sun sun( direction: Vec3, color: Vec3 = Vec3.one(), lux: Float32 = 3.0, cast_shadows: Bool = True, ) -> Light
    Directional light travelling along direction (normalised), with lux illuminance. Position, range and cone angles are zero; shadows default to on (cascaded shadow maps).

  • point_light point_light( position: Vec3, color: Vec3, watts: Float32, range: Float32, cast_shadows: Bool = False, ) -> Light
    Point light at position (metres) emitting watts, reaching zero at range metres.

  • spot_light spot_light( position: Vec3, direction: Vec3, color: Vec3, watts: Float32, range: Float32, inner: Float32, outer: Float32, cast_shadows: Bool = False, ) -> Light
    Spot light along direction (normalised) emitting watts with a range in metres. inner and outer are cone half-angles in radians from the axis: full intensity inside inner, fading to zero at outer.

  • bounding_sphere bounding_sphere(self) -> Vec4
    Influence sphere: xyz position, w range in metres (meaningless for directional lights).

  • pack pack(self, mut w: ByteWriter, shadow_view: Int)
    64 bytes matching Light in common.wgsl.

def pack_lights_into #

mojo
def pack_lights_into( mut w: ByteWriter, lights: List[Light], shadow_views: List[Int] )

Appends the GPU light array (at least one zeroed entry) to w.

def pack_lights #

mojo
def pack_lights(lights: List[Light], shadow_views: List[Int]) -> List[UInt8]

pack_lights_into into a new byte list (allocates; at least one zeroed 64-byte entry). shadow_views[i] is light i's first shadow-view index (-1 or missing: no shadow).

struct ClusterParams #

mojo
struct ClusterParams(ImplicitlyCopyable, Movable)

Logarithmic z slicing: slice = log2(z) * scale + bias between near and far.

Fields:

  • scale: Float32

  • bias: Float32

  • far: Float32

  • compute compute(near: Float32, far: Float32) -> ClusterParams
    Slice parameters mapping near to slice 0 and far to CLUSTER_Z (metres).

  • slice_of slice_of(self, view_z: Float32) -> Int
    Z slice for a positive view-space distance in metres, clamped to [0, CLUSTER_Z - 1]. Mirrors cluster_z_slice in common.wgsl.

cinder.render.material #

Source: cinder/render/material.mojo

glTF 2.0 metallic-roughness materials and their GPU bind groups. Uniform layout (96 bytes, 16-byte aligned fields, mirrored by shaders/material.wgsl): 0 base_color: vec4 16 emissive.rgb, emissive_strength 32 metallic, roughness, normal_scale, occlusion_strength 48 alpha_cutoff, alpha_mode (0 opaque, 1 mask, 2 blend), double_sided, texture_flags 64 uv_scale.xy, uv_offset.xy (KHR_texture_transform, applied to every map) 80 uv_rotation, texcoord_bits (3 bits per map: base, mr, normal, occlusion, emissive), pad, pad

  • comptime MATERIAL_UNIFORM_SIZE = 96 — Bytes in the material uniform (group 2, binding 0); see the module docstring for the layout.

struct AlphaMode #

mojo
struct AlphaMode(Equatable, ImplicitlyCopyable, RegisterPassable)

How alpha is used; packed into the material uniform and mirrored in WGSL.

Fields:

  • value: UInt32

  • opaque opaque() -> AlphaMode
    Alpha is ignored; the surface is fully opaque.

  • mask mask() -> AlphaMode
    Alpha-tested: fragments with alpha below alpha_cutoff are discarded.

  • blend blend() -> AlphaMode
    Alpha-blended; drawn after opaque batches, sorted back to front.

  • comptime TEX_BASE_COLOR = 0 — Slot of the base colour map in Material.textures, texcoords and texture_flags.

  • comptime TEX_METALLIC_ROUGHNESS = 1 — Slot of the metallic-roughness map in Material.textures, texcoords and texture_flags.

  • comptime TEX_NORMAL = 2 — Slot of the normal map in Material.textures, texcoords and texture_flags.

  • comptime TEX_OCCLUSION = 3 — Slot of the occlusion map in Material.textures, texcoords and texture_flags.

  • comptime TEX_EMISSIVE = 4 — Slot of the emissive map in Material.textures, texcoords and texture_flags.

struct Material #

mojo
struct Material(Copyable, Movable)

CPU description. Texture views are null when a map is absent (defaults are bound instead).

Fields:

  • name: String

  • base_color: Vec4

  • metallic: Float32

  • roughness: Float32

  • emissive: Vec3

  • emissive_strength: Float32

  • normal_scale: Float32

  • occlusion_strength: Float32

  • alpha_mode: AlphaMode

  • alpha_cutoff: Float32

  • double_sided: Bool

  • uv_scale: Vec2

  • uv_offset: Vec2

  • uv_rotation: Float32

  • textures: Array[TextureViewHandle, 5]

  • texcoords: Array[UInt8, 5]

  • sampler: SamplerHandle

  • __init__ __init__(out self, name: String = String("material"))

  • texture_flags texture_flags(self) -> UInt32
    Bit i set when map slot i (TEX_*) has a texture view.

  • texcoord_bits texcoord_bits(self) -> UInt32
    Texture coordinate set per map, 3 bits per TEX_* slot (the shader takes uv1 for 1).

  • pack_uniform pack_uniform(self) -> List[UInt8]
    The 96-byte uniform laid out as in the module docstring, in a new list (allocates).

def material_bind_group_layout #

mojo
def material_bind_group_layout( mut res: Resources, ) raises -> BindGroupLayoutHandle

Creates the group-2 material layout matching shaders/material.wgsl; raises on a GPU error. Binding 0 is the uniform (vertex and fragment), 1 the sampler, 2..6 the maps in TEX_* order.

struct GpuMaterial #

mojo
struct GpuMaterial(ImplicitlyCopyable, Movable)

Uniform buffer and bind group of an uploaded material. alpha_mode and double_sided are copied from the Material to select its pipeline.

Fields:

  • uniform: BufferHandle
  • bind_group: BindGroupHandle
  • alpha_mode: AlphaMode
  • double_sided: Bool

def upload_material #

mojo
def upload_material( mut res: Resources, layout: BindGroupLayoutHandle, defaults: DefaultTextures, material: Material, ) raises -> GpuMaterial

Creates the material's uniform buffer and group-2 bind group; raises on a GPU error. Absent maps bind the defaults (white base colour, metallic-roughness and occlusion, flat normal, black emissive) and a null sampler binds sampler_linear_repeat. The texture views stay owned by the caller; the returned buffer and bind group are new allocations.

def update_material #

mojo
def update_material( mut res: Resources, gpu: GpuMaterial, material: Material ) raises

Re-uploads the parameters (textures are fixed per bind group).

cinder.render.mesh #

Source: cinder/render/mesh.mojo

Mesh data on the CPU, GPU meshes, the engine vertex format and procedural primitives. Vertex stream (56 bytes): position f32x3 @0, normal f32x3 @12, tangent f32x4 @24 (w = handedness), uv0 f32x2 @40, uv1 f32x2 @48. Skinning attributes live in a second stream (SkinVertex, 24 bytes): joints u16x4 @0, weights f32x4 @8. Indices are u32.

  • comptime VERTEX_STRIDE = 56 — Bytes per vertex in the static stream (slot 0); layout in the module docstring.
  • comptime SKIN_VERTEX_STRIDE = 24 — Bytes per vertex in the skinning stream (slot 1): u16x4 joints, f32x4 weights.
  • comptime PI = Float32(3.14159265358979) — Pi as Float32, for the procedural primitives.

def vertex_layout #

mojo
def vertex_layout() -> VertexLayout

Slot 0: the static vertex stream. Locations 0..4.

def skin_vertex_layout #

mojo
def skin_vertex_layout() -> VertexLayout

Slot 1: joints and weights. Locations 5 and 6.

struct Bounds #

mojo
struct Bounds(ImplicitlyCopyable, Movable)

Axis-aligned bounding box in mesh space (metres).

Fields:

  • min: Vec3

  • max: Vec3

  • centre centre(self) -> Vec3
    Midpoint of the box.

  • radius radius(self) -> Float32
    Half the box diagonal: the radius of the bounding sphere around centre().

  • sphere sphere(self) -> Vec4
    Bounding sphere: xyz centre, w radius (the input of Frustum.cull_spheres_into).

  • empty empty() -> Bounds
    Inverted box (min +3.4e38, max -3.4e38); the first include collapses it to a point.

  • include include(mut self, p: Vec3)
    Grows the box to contain p.

struct MorphTarget #

mojo
struct MorphTarget(Copyable, Movable)

Per-vertex position and normal deltas of one morph target (normals may be empty).

Fields:

  • positions: List[Vec3]
  • normals: List[Vec3]

struct MeshLod #

mojo
struct MeshLod(ImplicitlyCopyable, Movable)

An index range of the combined index buffer and the simplification error it carries.

Fields:

  • first_index: UInt32
  • index_count: UInt32
  • error: Float32

struct MeshData #

mojo
struct MeshData(Copyable, Movable)

CPU-side triangle mesh. Parallel per-vertex arrays; tangents, uv1, joints and weights may be empty and are filled with defaults at upload.

Fields:

  • positions: List[Vec3]

  • normals: List[Vec3]

  • tangents: List[Vec4]

  • uv0: List[Vec2]

  • uv1: List[Vec2]

  • joints: List[SIMD[DType.uint16, 4]]

  • weights: List[Vec4]

  • indices: List[UInt32]

  • morph_targets: List[MorphTarget]

  • __init__ __init__(out self)

  • vertex_count vertex_count(self) -> Int
    Number of vertices (len(positions)).

  • triangle_count triangle_count(self) -> Int
    Number of triangles (len(indices) // 3).

  • has_skin has_skin(self) -> Bool
    True when every vertex has joint indices and weights (joints and weights both match positions, non-empty), which is what pack_skin_vertices reads.

  • bounds bounds(self) -> Bounds
    Bounds of positions; a zero box at the origin when the mesh has no vertices.

  • validate validate(self) raises
    Raises when the arrays are inconsistent. Checks that normals and uv0 match the vertex count, that tangents and uv1 are empty or match it, that joints and weights are either both empty or both match it (a skin needs one of each per vertex), that the index count is a multiple of 3 and that every index is in range. Morph targets are not checked (missing deltas pack as zero).

  • compute_normals compute_normals(mut self)
    Area-weighted face normals accumulated per vertex.

  • compute_tangents compute_tangents(mut self, lib: NativePtr) raises
    MikkTSpace tangents through the shim (requires normals and uv0).

  • pack_vertices pack_vertices(self) -> List[UInt8]
    The 56-byte-per-vertex static stream as a new byte list (allocates). Missing tangents become (1, 0, 0, 1) and missing uv1 repeats uv0. Expects normals and uv0 for every vertex (see validate).

  • pack_skin_vertices pack_skin_vertices(self) -> List[UInt8]
    The 24-byte-per-vertex skin stream: little-endian u16x4 joints, then f32x4 weights. Allocates; expects joints and weights for every vertex.

  • pack_indices pack_indices(self) -> List[UInt8]
    indices as little-endian u32 bytes in a new list (allocates).

  • pack_morph_deltas pack_morph_deltas(self) -> List[UInt8]
    Per target, per vertex: position delta (vec4) then normal delta (vec4), as common.wgsl reads.

  • build_lods build_lods( self, lib: NativePtr, levels: Int = 4 ) raises -> List[List[UInt32]]
    LOD index lists (level 0 is the original, vertex-cache optimised) via meshoptimizer. Each level targets half the triangles of the previous one with a relative error bound of 2%; a level that cannot be simplified further ends the chain.

struct GpuMesh #

mojo
struct GpuMesh(Copyable, Movable)

Buffers for one mesh. skin_vertices is null when the mesh is not skinned, morph_buffer when it has no morph targets. lods[0] is the full-detail index range. index_count is the full-detail index count (lods[0].index_count, 0 without LODs), the same for upload_mesh_lods and upload_mesh_bytes; the index buffer holds every LOD range, so its total length is the sum over lods.

Fields:

  • vertices: BufferHandle

  • skin_vertices: BufferHandle

  • indices: BufferHandle

  • index_count: UInt32

  • vertex_count: UInt32

  • bounds: Bounds

  • lods: List[MeshLod]

  • morph_buffer: BufferHandle

  • morph_target_count: UInt32

  • __init__ __init__( out self, vertices: BufferHandle, skin_vertices: BufferHandle, indices: BufferHandle, index_count: UInt32, vertex_count: UInt32, bounds: Bounds, var lods: List[MeshLod], morph_buffer: BufferHandle, morph_target_count: UInt32, )

  • select_lod select_lod( self, projected_diameter_px: Float32, screen_height_px: Float32 ) -> Int
    Smaller on screen -> coarser LOD. Level 1 below 1/4 of the screen height, level 2 below 1/8, level 3 below 1/16 (resolution independent).

def upload_mesh #

mojo
def upload_mesh( mut res: Resources, label: String, data: MeshData ) raises -> GpuMesh

Uploads with a single LOD.

def upload_mesh_lods #

mojo
def upload_mesh_lods( mut res: Resources, label: String, data: MeshData, lod_indices: List[List[UInt32]], ) raises -> GpuMesh

Uploads vertex streams plus one combined index buffer holding every LOD range.

def upload_mesh_bytes #

mojo
def upload_mesh_bytes( mut res: Resources, label: String, vertex_count: UInt32, vertex_bytes: List[UInt8], skin_bytes: List[UInt8], index_bytes: List[UInt8], var lods: List[MeshLod], bounds: Bounds, morph_target_count: UInt32, morph_bytes: List[UInt8], ) raises -> GpuMesh

Uploads streams that are already in GPU layout (cooked meshes).

def destroy_mesh #

mojo
def destroy_mesh(mut res: Resources, mesh: GpuMesh) raises

Destroys the mesh's vertex and index buffers, and its skin and morph buffers when present. The handles in mesh are left as they are; raises on a Resources error.

def cube #

mojo
def cube(half: Float32 = 0.5) -> MeshData

24-vertex cube with per-face normals, CCW winding seen from outside.

def uv_sphere #

mojo
def uv_sphere( radius: Float32 = 0.5, rings: Int = 16, segments: Int = 32 ) -> MeshData

UV sphere centred at the origin, poles on +Y and -Y; radius in metres. (rings + 1) * (segments + 1) vertices (the seam is duplicated), counter-clockwise seen from outside. u runs around the sphere from +X towards +Z, v from the top pole (0) to the bottom (1); tangents point along increasing u.

def plane #

mojo
def plane(half: Float32 = 0.5, subdivisions: Int = 1) -> MeshData

An XZ plane facing +Y.

cinder.render.renderer #

Source: cinder/render/renderer.mojo

The forward+ renderer: shadows, depth prepass, GTAO, clustered lights, PBR, sky, bloom, tonemap. Renderer.render(scene, target) builds the frame graph for the current settings, compiles it (ordering, transient aliasing), fills the GPU buffers from the scene and records every pass into one command buffer. CPU work per frame: frustum culling (SIMD), LOD selection, batching by (mesh, material, skinning), cascade fitting, light packing. Nothing here waits on the GPU.

  • comptime FRAME_UNIFORM_SIZE = 720 — Bytes in the frame uniform: the size of Frame in common.wgsl (checked each frame).
  • comptime PASS_UNIFORM_STRIDE = 256 — Bytes per view-projection slot in pass_uniforms, the dynamic-offset alignment.
  • comptime MAX_PASS_VIEWS = 128 — Slots in pass_uniforms: four cascades, the atlas views, then the camera.
  • comptime INSTANCE_BYTES = 144 — Bytes per instance in the instances buffer: model, normal matrix, u32x4 params.
  • comptime MORPH_WEIGHTS_BYTES = 32 — Bytes of morph weights bound per morphed instance: eight f32 weights.
  • comptime POST_UNIFORM_STRIDE = 256 — Bytes per slot in post_uniforms (and per morph-weight slot), the dynamic-offset alignment.
  • comptime MAX_CACHED_POST_GROUPS = 64 — Post bind groups kept across frames before the cache is rebuilt.
  • comptime MAX_POST_PASSES = 32 — Slots in post_uniforms per frame.
  • comptime INITIAL_MORPH_SLOTS = 32 — Initial slots in the morph-weight buffer; it doubles when a frame has more morphed instances.
  • comptime BLOOM_MIPS = 6 — Levels in the bloom chain; level 0 is half the output resolution.
  • comptime ENV_SIZE = 256 — Face size in pixels of the baked environment cube (RGBA16F, full mip chain).
  • comptime PREFILTER_SIZE = 128 — Face size in pixels of the prefiltered specular cube.
  • comptime PREFILTER_MIPS = 6 — Mips of the prefiltered cube; mip m holds roughness m / (PREFILTER_MIPS - 1).
  • comptime IRRADIANCE_SIZE = 32 — Face size in pixels of the diffuse irradiance cube.
  • comptime BRDF_LUT_SIZE = 256 — Size in pixels of the split-sum BRDF lookup texture.
  • comptime SHADER_DIR = "cinder/render/shaders" — Directory of the renderer's WGSL files, relative to the working directory.

struct Tonemapper #

mojo
struct Tonemapper(Equatable, ImplicitlyCopyable, RegisterPassable)

Tone curve of the tonemap pass; values mirror post.settings.z in post.wgsl.

Fields:

  • value: UInt32

  • agx agx() -> Tonemapper
    AgX (the default).

  • aces aces() -> Tonemapper
    ACES filmic curve.

  • none none() -> Tonemapper
    No curve: the exposed colour is clamped to [0, 1]; also used for debug views.

struct DebugMode #

mojo
struct DebugMode(Equatable, ImplicitlyCopyable, RegisterPassable)

Mirrors DEBUG_* in common.wgsl.

Fields:

  • value: UInt32

  • none none() -> DebugMode
    Normal lit output.

  • normals normals() -> DebugMode
    World-space normals mapped to colour (n * 0.5 + 0.5).

  • cascades cascades() -> DebugMode
    Colour per sun shadow cascade, shaded by the sun angle.

  • clusters clusters() -> DebugMode
    Heat map of the light count in each fragment's cluster (full scale at 16 lights).

  • overdraw overdraw() -> DebugMode
    Overdraw count: forward pipelines blend additively with the depth test off, so every rasterised fragment (hidden or not, opaque or blended) adds 0.1 grey; brightness is the fragment count / 10 (white at 10 or more) and empty sky is black.

  • lod lod() -> DebugMode
    Colour per instance from its LOD index (bits 8..15 of the instance flags), skinned and morphed instances included.

  • albedo albedo() -> DebugMode
    Unlit base colour (material factor times the base colour map).

  • ao ao() -> DebugMode
    The ambient occlusion term as greyscale.

struct RenderSettings #

mojo
struct RenderSettings(ImplicitlyCopyable, Movable)

Quality knobs, read at the start of every render (change them between frames). cascade_resolution, atlas_size and atlas_tile are read at construction only.

Fields:

  • msaa: UInt32 — 1 or 4.

  • bloom: Bool

  • bloom_strength: Float32

  • bloom_threshold: Float32

  • tonemapper: Tonemapper

  • exposure: Float32

  • fxaa: Bool

  • ao: Bool

  • shadows: Bool

  • shadow_distance: Float32

  • cascade_resolution: Int

  • atlas_size: Int

  • atlas_tile: Int

  • ibl_intensity: Float32

  • debug: DebugMode

  • wireframe: Bool

  • cull: Bool

  • __init__ __init__(out self)

struct Environment #

mojo
struct Environment(ImplicitlyCopyable, Movable)

Procedural sky by default; equirect (an RGBA16F 2D texture view) replaces it when set.

Fields:

  • zenith: Vec3

  • ground: Vec3

  • sun_intensity: Float32

  • equirect: TextureViewHandle

  • __init__ __init__(out self)

struct RenderInstance #

mojo
struct RenderInstance(Copyable, Movable)

One drawn object: mesh and material indices into the renderer's registries.

Fields:

  • mesh: Int

  • material: Int

  • transform: Mat4

  • joint_offset: Int — Index of the first joint matrix in RenderScene.joints (skinned meshes), else -1.

  • morph_weights: List[Float32]

  • cast_shadows: Bool

  • __init__ __init__( out self, mesh: Int, material: Int, transform: Mat4, joint_offset: Int = -1, cast_shadows: Bool = True, )

struct RenderScene #

mojo
struct RenderScene(Movable)

Everything one render call draws; owned by the caller and only read by the renderer.

Fields:

  • camera: Camera

  • lights: List[Light]

  • instances: List[RenderInstance]

  • joints: List[Mat4] — Skinning matrices (joint world * inverse bind), referenced by instances.

  • environment: Environment

  • __init__ __init__(out self, camera: Camera)

struct FrameStats #

mojo
struct FrameStats(ImplicitlyCopyable, Movable)

Counters of the last render, in Renderer.stats.

Fields:

  • instances_submitted: Int
  • instances_drawn: Int
  • draw_calls: Int — Forward (lit) draws, opaque and blended.
  • prepass_draw_calls: Int
  • shadow_draw_calls: Int
  • lights: Int
  • shadow_views: Int
  • passes: Int
  • transient_textures: Int

struct Renderer #

mojo
struct Renderer(Movable)

The forward+ renderer; render draws a RenderScene into a target view. Owns the pipelines, persistent GPU buffers and textures, the mesh and material registries, the frame graph, the shader library and per-frame scratch (steady-state frames do not allocate). Holds raw pointers to Gfx and Resources, which must outlive it.

Fields:

  • gfx: Pointer[Gfx, MutUntrackedOrigin]

  • res: Pointer[Resources, MutUntrackedOrigin]

  • lib: NativePtr

  • shaders: ShaderManager

  • library: ShaderLibrary

  • settings: RenderSettings

  • defaults: DefaultTextures

  • graph: FrameGraph

  • stats: FrameStats

  • frame_index: Int

  • graphviz: String — DOT text of the last compiled frame graph; refreshed by graph_dot() on demand.

  • morph_groups: List[BindGroupHandle] — Object groups binding a morphed mesh's delta buffer, one per morph buffer in use; batches of meshes without morph targets use object_group.

  • morph_group_keys: List[SIMD[DType.int64, 2]] — (index, generation) of the morph buffer each entry of morph_groups binds.

  • post_group_keys: List[SIMD[DType.int64, 4]] — (input index, input generation, bloom index, bloom generation) per cached post group.

  • alloc_marks: Bool — Print the Mojo allocation counter after each render phase (needs the counter library).

  • gtao_groups: List[BindGroupHandle]

  • gtao_group_keys: List[SIMD[DType.int64, 4]]

  • meshes: List[GpuMesh]

  • materials: List[GpuMaterial]

  • material_params: List[Material]

  • forward_shader: ShaderHandle

  • depth_shader: ShaderHandle

  • clusters_shader: ShaderHandle

  • sky_shader: ShaderHandle

  • ibl_shader: ShaderHandle

  • post_shader: ShaderHandle

  • gtao_shader: ShaderHandle

  • gtao_msaa_shader: ShaderHandle

  • frame_layout: BindGroupLayoutHandle

  • object_layout: BindGroupLayoutHandle

  • material_layout: BindGroupLayoutHandle

  • pass_layout: BindGroupLayoutHandle

  • cluster_out_layout: BindGroupLayoutHandle

  • gtao_layout: BindGroupLayoutHandle

  • gtao_msaa_layout: BindGroupLayoutHandle

  • ibl_layout: BindGroupLayoutHandle

  • post_layout: BindGroupLayoutHandle

  • scene_pipeline_layout: PipelineLayoutHandle

  • cluster_pipeline_layout: PipelineLayoutHandle

  • gtao_pipeline_layout: PipelineLayoutHandle

  • gtao_msaa_pipeline_layout: PipelineLayoutHandle

  • ibl_pipeline_layout: PipelineLayoutHandle

  • post_pipeline_layout: PipelineLayoutHandle

  • frame_uniform: BufferHandle

  • pass_uniforms: BufferHandle

  • post_uniforms: BufferHandle

  • lights_buffer: BufferHandle

  • shadow_views_buffer: BufferHandle

  • cluster_grid: BufferHandle

  • cluster_indices: BufferHandle

  • instances_buffer: BufferHandle

  • instances_capacity: Int

  • joints_buffer: BufferHandle

  • joints_capacity: Int

  • morph_weights_buffer: BufferHandle

  • morph_slot_capacity: Int — Morph-weight slots (POST_UNIFORM_STRIDE bytes each) in morph_weights_buffer.

  • empty_morph_buffer: BufferHandle

  • cascade_texture: TextureHandle

  • cascade_array_view: TextureViewHandle

  • cascade_layer_views: List[TextureViewHandle]

  • atlas_texture: TextureHandle

  • atlas_view: TextureViewHandle

  • env_texture: TextureHandle

  • env_cube_view: TextureViewHandle

  • prefiltered_texture: TextureHandle

  • prefiltered_view: TextureViewHandle

  • irradiance_texture: TextureHandle

  • irradiance_view: TextureViewHandle

  • brdf_texture: TextureHandle

  • brdf_view: TextureViewHandle

  • shadow_sampler: SamplerHandle

  • env_sampler: SamplerHandle

  • post_sampler: SamplerHandle

  • nearest_sampler: SamplerHandle

  • env_baked: Bool

  • env_key: String

  • frame_group: BindGroupHandle

  • frame_group_ao_view: TextureViewHandle

  • shadow_frame_group: BindGroupHandle

  • cluster_frame_group: BindGroupHandle — Frame group variant for the cluster pass: the cluster buffers it writes are replaced by a dummy.

  • dummy_cluster_buffer: BufferHandle — Frame group variant for shadow passes: the shadow maps being written are replaced by dummies.

  • dummy_depth_array: TextureHandle

  • dummy_depth_2d: TextureHandle

  • object_group: BindGroupHandle

  • object_group_key: String

  • pass_group: BindGroupHandle

  • cluster_out_group: BindGroupHandle

  • pipelines: List[_CachedPipeline]

  • sky_pipeline: RenderPipelineHandle

  • sky_pipeline_msaa: UInt32

  • cluster_pipeline: ComputePipelineHandle

  • gtao_pipeline: ComputePipelineHandle

  • gtao_blur_pipeline: ComputePipelineHandle

  • gtao_msaa_pipeline: ComputePipelineHandle

  • gtao_msaa_blur_pipeline: ComputePipelineHandle

  • ibl_env_pipeline: ComputePipelineHandle

  • ibl_prefilter_pipeline: ComputePipelineHandle

  • ibl_irradiance_pipeline: ComputePipelineHandle

  • ibl_brdf_pipeline: ComputePipelineHandle

  • bloom_down_pipeline: RenderPipelineHandle

  • bloom_up_pipeline: RenderPipelineHandle

  • tonemap_pipeline: RenderPipelineHandle

  • fxaa_pipeline: RenderPipelineHandle

  • blit_pipeline: RenderPipelineHandle

  • post_target_format: WGPUTextureFormat

  • atlas: ShadowAtlas

  • batches: List[_Batch]

  • blend_batches: List[_Batch]

  • instance_bytes: ByteWriter

  • post_groups: List[BindGroupHandle]

  • cluster_params: ClusterParams

  • __init__ __init__( out self, gfx: Pointer[Gfx, MutUntrackedOrigin], res: Pointer[Resources, MutUntrackedOrigin], lib: NativePtr, settings: RenderSettings, target_format: WGPUTextureFormat, ) raises

  • add_mesh add_mesh( mut self, label: String, data: MeshData, lods: Bool = True ) raises -> Int
    Uploads data and returns its index in meshes; raises if validation or upload fails. With lods and more than 64 triangles, up to four LODs are built with meshoptimizer; otherwise the mesh has one. Always appends (removed slots are only reused by add_cooked_mesh).

  • add_cooked_mesh add_cooked_mesh( mut self, label: String, cooked: CookedMesh ) raises -> Int
    Registers a cooked mesh (streams already in GPU layout). Reuses a removed slot if any.

  • replace_cooked_mesh replace_cooked_mesh( mut self, index: Int, label: String, cooked: CookedMesh ) raises
    Hot reload: swaps the GPU buffers behind index; instances keep their mesh index.

  • remove_mesh remove_mesh(mut self, index: Int) raises
    Frees the GPU buffers; the slot is reused by the next add_cooked_mesh. Instances that still reference it are the caller's error (rendering them raises on the invalid handle).

  • add_material add_material(mut self, material: Material) raises -> Int
    Uploads material and returns its index in materials; raises on a GPU error. A copy of the parameters is kept in material_params; texture views stay the caller's.

  • replace_material replace_material(mut self, index: Int, material: Material) raises
    Hot reload: uploads material into slot index (instances keep their material index) and frees the uniform buffer and bind group of the version it replaces. The new version is uploaded before anything is freed, so a failed upload raises and leaves the slot as it was. Replacing a removed slot makes it live again. material_params[index] becomes a copy of material. Texture views and samplers are the caller's: the old ones are not destroyed (destroy them after this call if nothing else uses them). Raises when index is out of range.

  • remove_material remove_material(mut self, index: Int) raises
    Frees the uniform buffer and bind group of slot index and leaves the slot empty (null handles); removing an empty slot does nothing. Indices stay stable: add_material always appends, so a removed slot is never handed out again (only replace_material on the same index revives it). render raises if a visible instance still uses a removed material. Texture views and samplers are the caller's, and material_params[index] keeps the last parameters. Raises when index is out of range.

  • material_is_live material_is_live(self, index: Int) -> Bool
    True when slot index exists and has not been removed.

  • mesh_lod_count mesh_lod_count(self, mesh: Int) -> Int
    Number of LOD index ranges of mesh mesh; level 0 is full detail.

  • reload_shaders reload_shaders(mut self) raises -> Int
    Hot reload: recompiles changed programs and rebuilds every pipeline on success. Pipelines keep their handles; a failed compile keeps the last good module and pipelines.

  • render render( mut self, scene: RenderScene, target: TextureViewHandle, width: Int, height: Int, ) raises
    Renders scene into target (format given at construction). Raises GfxError on device loss.

  • graph_dot graph_dot(mut self) -> String
    DOT text of the last frame's graph (built on demand: debug UI, docs).

  • release release(mut self) raises
    Frees the frame graph's pooled textures (everything else dies with Resources).

cinder.render.shader_library #

Source: cinder/render/shader_library.mojo

Shader programs assembled from WGSL files with #include "file.wgsl" lines, with hot reload. WGSL has no preprocessor, so includes are resolved here (relative to the shader directory, once per program, in order of first appearance). Error locations reported by naga refer to the assembled text; ShaderLibrary maps them back to the file and line they came from before raising.

struct AssembledShader #

mojo
struct AssembledShader(Copyable, Movable)

WGSL text with every include spliced in, mapped back to its source files. chunks locate each assembled line in its file; files lists every file read (directory/name), in order of first inclusion.

Fields:

  • text: String

  • chunks: List[_Chunk]

  • files: List[String]

  • __init__ __init__(out self)

  • locate locate(self, assembled_line: Int) -> Tuple[String, Int]
    File and file line for a line of the assembled text.

def assemble #

mojo
def assemble(directory: String, entry: String) raises -> AssembledShader

Reads entry and splices every #include "x" (each file at most once) into one text.

struct ShaderLibrary #

mojo
struct ShaderLibrary(Movable)

Loads directory/<entry>.wgsl programs with includes; check_for_changes hot reloads.

Fields:

  • gfx: Pointer[Gfx, MutUntrackedOrigin]

  • res: Pointer[Resources, MutUntrackedOrigin]

  • shaders: Pointer[ShaderManager, MutUntrackedOrigin]

  • directory: String

  • programs: List[_Program]

  • diagnostics: List[LogRecord]

  • reloaded: List[ShaderHandle] — Handles whose module changed in the last check_for_changes (pipelines must be rebuilt).

  • __init__ __init__( out self, gfx: Pointer[Gfx, MutUntrackedOrigin], res: Pointer[Resources, MutUntrackedOrigin], shaders: Pointer[ShaderManager, MutUntrackedOrigin], directory: String, )

  • compile compile(mut self, entry: String) raises -> WGPUShaderModule
    Assembles and compiles without registering (used for one-off modules and by load).

  • load load(mut self, entry: String) raises -> ShaderHandle
    Assembles, compiles and registers entry (a file in directory) for hot reload. Returns the handle Resources holds the module under. Raises on a missing include or a compile error, with locations mapped to the source files.

  • check_for_changes check_for_changes(mut self) -> Int
    Recompiles programs whose files changed. Returns how many modules were replaced; failures keep the last good module and add an error diagnostic naming file:line:column.

  • take_diagnostics take_diagnostics(mut self) -> List[LogRecord]
    Returns and clears the reload records from check_for_changes (info or error).

cinder.render.shadows #

Source: cinder/render/shadows.mojo

Cascaded shadow maps for the sun and an atlas for spot and point lights (CPU-side maths). Cascade fitting: each cascade covers a slice of the camera frustum (practical split scheme, lambda 0.75). The orthographic projection is fitted to the slice's bounding sphere so it has a constant size while the camera rotates, and its origin is snapped to whole shadow texels in light space so edges do not shimmer when the camera moves.

  • comptime CASCADE_COUNT = 4 — Sun shadow cascades; matches the four-entry cascade arrays of Frame in common.wgsl.
  • comptime SHADOW_VIEW_BYTES = 96 — Bytes per packed ShadowView: the size of ShadowView in common.wgsl.

struct Cascade #

mojo
struct Cascade(ImplicitlyCopyable, Movable)

One fitted sun cascade: an orthographic reverse-Z light view-projection (world to clip).

Fields:

  • view_proj: Mat4
  • split_end: Float32 — View-space distance where this cascade ends.
  • texel_world: Float32 — World-space size of one shadow texel in this cascade.

def cascade_splits_into #

mojo
def cascade_splits_into( mut out: Array[Float32, CASCADE_COUNT], near: Float32, shadow_distance: Float32, lambda_: Float32 = 0.75, )

End distances of the four cascades (practical split: blend of log and uniform) into a stack array (no allocation).

def cascade_splits #

mojo
def cascade_splits( near: Float32, shadow_distance: Float32, lambda_: Float32 = 0.75 ) -> List[Float32]

End distances of the four cascades (practical split: blend of log and uniform).

def fit_cascade #

mojo
def fit_cascade( camera: Camera, light_dir: Vec3, z0: Float32, z1: Float32, resolution: Int ) -> Cascade

Orthographic light view-projection for one slice with texel snapping. light_dir is the direction light travels. Depth range is the sphere diameter plus a generous margin behind the slice so casters outside the camera frustum still cast.

def fit_cascades_into #

mojo
def fit_cascades_into( mut out: List[Cascade], camera: Camera, light_dir: Vec3, shadow_distance: Float32, resolution: Int, )

fit_cascades into a list supplied by the caller (cleared first, capacity kept).

def fit_cascades #

mojo
def fit_cascades( camera: Camera, light_dir: Vec3, shadow_distance: Float32, resolution: Int ) -> List[Cascade]

fit_cascades_into into a new list (allocates); per-frame code uses the _into variant.

struct ShadowView #

mojo
struct ShadowView(ImplicitlyCopyable, Movable)

One atlas tile: a light-space view-projection and its uv rectangle (matches common.wgsl).

Fields:

  • view_proj: Mat4

  • uv_origin_x: Float32

  • uv_origin_y: Float32

  • uv_size: Float32

  • texel_uv: Float32

  • near: Float32

  • far: Float32

  • pack pack(self, mut w: ByteWriter)
    Appends the 96-byte GPU form (ShadowView in common.wgsl). view_proj, then the uv rectangle (origin x, origin y, size, size), then the texel size in uv, near, far and a zero pad.

struct ShadowAtlas #

mojo
struct ShadowAtlas(Movable)

Fixed grid of square tiles in one depth texture. Spot lights take one tile, point lights six.

Fields:

  • size: Int

  • tile_size: Int

  • tiles_per_row: Int

  • next_tile: Int

  • views: List[ShadowView]

  • __init__ __init__(out self, size: Int = 4096, tile_size: Int = 512)

  • capacity capacity(self) -> Int
    Number of tiles in the atlas (tiles_per_row squared).

  • reset reset(mut self)
    Frees every tile and clears views (capacity kept); called at the start of each frame.

  • tile_pixel_origin tile_pixel_origin(self, view_index: Int) -> Tuple[Int, Int]
    Top-left pixel of the tile used by shadow view view_index (views fill tiles in order).

  • allocate allocate(mut self, light: Light) -> Int
    Adds the light's shadow views; returns the first view index or -1 when the atlas is full.

  • pack_views_into pack_views_into(self, mut w: ByteWriter)
    Appends the GPU shadow-view array (at least one zeroed entry) to w.

  • pack_views pack_views(self) -> List[UInt8]
    pack_views_into into a new byte list (allocates; at least one zeroed entry).

cinder.render.textures #

Source: cinder/render/textures.mojo

GPU textures from CPU images: CPU box-filter mip chains, sRGB or linear formats, defaults.

struct GpuTexture #

mojo
struct GpuTexture(ImplicitlyCopyable, Movable)

An uploaded 2D texture and a view of all its mips; width and height are mip 0 in pixels. The texture and view are owned by whoever uploaded them and freed through Resources.

Fields:

  • texture: TextureHandle
  • view: TextureViewHandle
  • width: UInt32
  • height: UInt32
  • mip_levels: UInt32

def mip_count #

mojo
def mip_count(width: Int, height: Int) -> Int

Levels in a full mip chain down to 1x1 (floor(log2(max(width, height))) + 1).

def downsample #

mojo
def downsample(img: Image) -> Image

2x2 box filter (edge pixels of odd sizes are clamped), in the image's own encoding.

def upload_image #

mojo
def upload_image( mut res: Resources, label: String, img: Image, srgb: Bool, mips: Bool = True ) raises -> GpuTexture

Uploads an RGBA8 image as a 2D texture with a CPU box-filtered mip chain. srgb selects RGBA8UnormSrgb (colour data) over RGBA8Unorm (linear data); mips = False uploads level 0 only. The caller owns the result; raises on a GPU error.

def solid_image #

mojo
def solid_image(r: UInt8, g: UInt8, b: UInt8, a: UInt8) -> Image

1x1 RGBA8 image of one colour (used for the default textures).

struct DefaultTextures #

mojo
struct DefaultTextures(ImplicitlyCopyable, Movable)

1x1 fallbacks: white (base colour, occlusion, metallic-roughness), flat normal, black emissive.

Fields:

  • white: GpuTexture

  • normal: GpuTexture

  • black: GpuTexture

  • sampler_linear_repeat: SamplerHandle

  • sampler_nearest_clamp: SamplerHandle

  • __init__ __init__(out self, mut res: Resources) raises

View as MarkdownSource: docs/api/render.md