simvx.graphics.render2d.item_builder

What the collection pass is (design §2.2, §2.3, §2.5)

The pass walks the tree once in the same order the live _draw_recursive visits it (depth-first, parent-before-children, siblings partitioned into below / self / above z-bands, CanvasLayers last by layer, the YSort y-order policy) and, per node, emits one or more Items into an ItemList:

  • seq – a monotonic counter assigned in emission order, so a stable (layer, seq) sort of the produced list reproduces walk order exactly (design §2.2: the walk folds per-sibling-group z into seq; the global sort is not a z lexsort – P0 gate 9). z is recorded as data only.

  • layer – the CanvasLayer band (world content defaults to 0; a CanvasLayer(layer=N) shifts its whole subtree to band N, §5.5).

  • zabsolute_z_index of the emitting node (data, never a sort input).

  • clip_scope – the active :class:ClipScopeTable scope, opened/closed around clipped subtrees and the synthesised Control per-child wrap (§2.5).

  • transform – a per-item LOCAL transform row = the node’s world_transform, not composed with any camera (the view lives in a UBO, §2.3). Camera-free verts + a local transform row is the keystone that lets a later camera pan touch one UBO row instead of N item rows (Decision B).

  • pipeline / blend / texture / flags / geometry – taken from the captured draw op.

The transitional op-adapter bridge (replaced in P3a)

Real nodes (Sprite2D, the shape primitives, Text2D’s family) still draw through the immediate-mode Draw2D op API in their on_draw bodies. Rewriting every node to emit Items natively is P3a work. To stay isolated and make real nodes produce Items today, this builder runs each node’s on_draw against a lightweight recording renderer (:class:_OpRecorder, the _DrawRecorder / DrawLog pattern) that captures the ops a node would draw – with an identity transform so the captured geometry is camera-free and parent-free – then converts each captured op into one Item. The op’s kind maps to a

class:

PipelineKind, its blend to a :class:BlendMode, its tex_id to a texture slot, and its verts/indices are stowed in the :class:GeometryStore behind a handle the Item references. This adapter is the seam P3a replaces with native per-node item emission; the surrounding walk/sort/clip/transform machinery stays.

Deferred (explicitly out of this increment):

  • P1.3 collapses the six live _draw_recursive variants into THIS one walk (this builder reproduces their order so the collapse is a drop-in); today the live walkers are untouched.

  • P1.4 adds dirty/retention on top of :class:GeometryStore (per-item reuse, frame-level skip). Here the store is a plain handle list rebuilt each call.

The collection seam: walk a Node tree and emit render Items (design §2.2).

The collection pass of the build-once 2D pipeline (design P1.2): it walks a scene tree and produces an :class:~simvx.graphics.render2d.item_list.ItemList. The

class:

RenderItemCache drives it each frame on the live render path.

Module Contents

Classes

Geometry

Captured vert/index arrays an Item’s geometry handle resolves to.

GeometryStore

A handle-indexed store of captured :class:Geometry (design §2.1).

NodeEntry

The retained per-node record an incremental patch (P2) locates items by.

CollectResult

The product of one :meth:ItemBuilder.collect (design §2.2).

ItemBuilder

Walks a Node tree and emits render Items into an :class:ItemList.

Functions

affine_row

Return node’s world_transform as a camera-free LOCAL affine row.

hdr_flag

Resolve a node’s hdr override to its HDR-lane flag (N1, 2D-in-HDR).

build_item_list

Collect root into a :class:CollectResult (one-shot convenience).

Data

API

simvx.graphics.render2d.item_builder.__all__

[‘CollectResult’, ‘Geometry’, ‘GeometryStore’, ‘ItemBuilder’, ‘NodeEntry’, ‘affine_row’, ‘build_item…

simvx.graphics.render2d.item_builder.affine_row(node: Any) simvx.graphics.render2d.item_builder._AffineRow[source]

Return node’s world_transform as a camera-free LOCAL affine row.

The compact (a, b, c, d, tx, ty) row draw2d’s _xf uses (x' = a*x + b*y + tx). Camera-free by construction: world_transform composes only the node’s own + ancestor 2D transforms, never a Camera2D (the view lives in a per-frame UBO, design §2.3), so a camera pan rewrites one UBO row, not N item rows (Decision B). Nodes without a 2D transform (plain Node, Control, CanvasLayer) record identity. Shared by the collection walk and the P2 transform-only patch so both produce identical rows.

class simvx.graphics.render2d.item_builder.Geometry[source]

Bases: typing.NamedTuple

Captured vert/index arrays an Item’s geometry handle resolves to.

The unit of the retained geometry store. verts are 8-float tuples (x, y, u, v, r, g, b, a) in the node’s LOCAL space (camera-free, parent-free); indices is None for line lists. P1.4 layers dirty/reuse on top, keying handles by item_id so a clean item keeps its slice; here every handle is freshly appended.

verts: list[tuple]

None

indices: list[int] | None

None

class simvx.graphics.render2d.item_builder.GeometryStore[source]

A handle-indexed store of captured :class:Geometry (design §2.1).

The seed of the retained geometry store. Today it is a plain growable list:

Meth:

add appends and returns the handle an Item records in its geometry column; :meth:get resolves it. P1.4 adds dirty tracking, item_id keying and arena slices on top of this surface.

Initialization

__slots__

(‘_geoms’,)

__len__() int[source]
add(verts: list[tuple], indices: list[int] | None) int[source]
get(handle: int) simvx.graphics.render2d.item_builder.Geometry[source]
set(handle: int, verts: list[tuple], indices: list[int] | None) None[source]

Overwrite the geometry behind an existing handle (P2 item-level patch).

Lets a render-dirty node re-capture its on_draw and replace its geometry slice in place, keeping the handle stable so the Item column and every consumer keying by handle stay valid (design §2.7 item granularity).

class simvx.graphics.render2d.item_builder.NodeEntry[source]

Bases: typing.NamedTuple

The retained per-node record an incremental patch (P2) locates items by.

Built during collection so a later per-item / transform-only update finds the exact rows + transform slot + geometry handles a single node produced, without a full re-walk (design §2.7 item / transform-only granularity).

Attributes

transform_id The node’s row index in :attr:CollectResult.transforms (its LOCAL affine). A transform-only change rewrites just this row. rows The physical :class:ItemList row indices the node’s ops produced. geometry The geometry handles (parallel to rows) the node’s ops produced. A render-dirty change re-captures the node’s on_draw and overwrites these in place. canvas_affine The enclosing CanvasLayer affine baked into this node’s captured geometry (None for world content). A P2 in-place re-capture re-bakes with this so the canvas offset/rotation/scale_val survives an item-level patch (design §5.5, P4).

transform_id: int

None

rows: list[int]

None

geometry: list[int]

None

canvas_affine: simvx.graphics.render2d.item_builder._AffineRow | None

None

class simvx.graphics.render2d.item_builder.CollectResult[source]

Bases: typing.NamedTuple

The product of one :meth:ItemBuilder.collect (design §2.2).

Bundles the three artefacts the later sort/batch/submit consume: the SoA

Class:

ItemList, the :class:ClipScopeTable its clip_scope indices point into, the :class:GeometryStore its geometry handles resolve against, and the per-item LOCAL transform rows its transform indices select. node_index maps each emitting node (by id) to its

Class:

NodeEntry, the substrate for P2’s in-place per-item / transform-only patching (design §2.7).

items: simvx.graphics.render2d.item_list.ItemList

None

clips: simvx.graphics.render2d.clip_scope.ClipScopeTable

None

geometry: simvx.graphics.render2d.item_builder.GeometryStore

None

transforms: list[simvx.graphics.render2d.item_builder._AffineRow]

None

node_index: dict[int, simvx.graphics.render2d.item_builder.NodeEntry]

None

class simvx.graphics.render2d.item_builder.ItemBuilder[source]

Walks a Node tree and emits render Items into an :class:ItemList.

Reproduces the live _draw_recursive visit order (so a stable (layer, seq) sort of the result equals walk order, design §2.2) while keeping geometry camera-free and recording per-item LOCAL transforms + nested clip scopes. See the module docstring for the op-adapter bridge.

One builder is single-use per :meth:collect; construct a fresh one (or just call :func:build_item_list) per collection.

Initialization

__slots__

(‘_items’, ‘_clips’, ‘_geom’, ‘_transforms’, ‘_seq’, ‘_node_index’, ‘_canvas_affine’, ‘_screen_space…

collect(root: Any, *, layer: int = 0) simvx.graphics.render2d.item_builder.CollectResult[source]

Walk root and return the produced :class:CollectResult.

layer is the starting CanvasLayer band (0 = world content). A CanvasLayer child shifts its subtree’s band; nested CanvasLayers are absolute (the layer does not add), matching §5.5.

simvx.graphics.render2d.item_builder.hdr_flag(node: Any) simvx.graphics.render2d.item_list.ItemFlags[source]

Resolve a node’s hdr override to its HDR-lane flag (N1, 2D-in-HDR).

hdr=True -> :attr:ItemFlags.HDR_OPT_IN (force the HDR lane), hdr=False -> :attr:ItemFlags.HDR_OPT_OUT (force the LDR lane), None/absent ->

Attr:

ItemFlags.NONE (by role: world->HDR, screen->LDR). Read per node and applied to all its items; the submit lane logic (submit.item_in_hdr_lane) combines this with the screen-space role. Resolved fresh in both the build walk and the in-place re-capture patch, so toggling hdr takes effect next frame without a full re-collect.

simvx.graphics.render2d.item_builder.build_item_list(root: Any, *, layer: int = 0) simvx.graphics.render2d.item_builder.CollectResult[source]

Collect root into a :class:CollectResult (one-shot convenience).

Equivalent to ItemBuilder().collect(root, layer=layer). This is the on-demand entry point for tests and the (default-OFF) flagged path; it does not touch the live render path.