simvx.graphics.render2d.item_builder¶
What the collection pass is¶
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 (the walk folds per-sibling-group z intoseq; the global sort is not a z lexsort).zis recorded as data only.layer– the CanvasLayer band (world content defaults to 0; aCanvasLayer(layer=N)shifts its whole subtree to bandN).z–absolute_z_indexof the emitting node (data, never a sort input).clip_scope– the active :class:ClipScopeTablescope, opened/closed around clipped subtrees and the synthesised Control per-child wrap.transform– a per-item LOCAL transform row = the node’sworld_transform, not composed with any camera (the view lives in a UBO). 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.pipeline/blend/texture/flags/geometry– taken from the captured draw op.
The transitional op-adapter bridge¶
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 ongoing 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, itsblendto a :class:BlendMode, itstex_idto a texture slot, and its verts/indices are stowed in the :class:GeometryStorebehind a handle the Item references. This adapter is the seam native per-node item emission replaces; the surrounding walk/sort/clip/transform machinery stays.
The collection seam: walk a Node tree and emit render Items.
The collection pass of the build-once 2D pipeline: it walks a scene
tree and produces an :class:~simvx.graphics.render2d.item_list.ItemList. The
- class:
RenderItemCachedrives it each frame on the live render path.
Module Contents¶
Classes¶
Captured vert/index arrays an Item’s |
|
A handle-indexed store of captured :class: |
|
The retained per-node record an incremental patch locates items by. |
|
The product of one :meth: |
|
Walks a Node tree and emits render Items into an :class: |
Functions¶
Return |
|
Resolve a node’s |
|
Collect |
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¶
Return
node’sworld_transformas a camera-free LOCAL affine row.The compact
(a, b, c, d, tx, ty)row draw2d’s_xfuses (x' = a*x + b*y + tx). Camera-free by construction:world_transformcomposes only the node’s own + ancestor 2D transforms, never a Camera2D (the view lives in a per-frame UBO), so a camera pan rewrites one UBO row, not N item rows. Nodes without a 2D transform (a plainNode, aCanvasLayer) record identity; aControlis aNode2Dand records a real row, whose translation carries its anchor/margin rect origin. Shared by the collection walk and the transform-only patch so both produce identical rows.
- class simvx.graphics.render2d.item_builder.Geometry¶
Bases:
typing.NamedTupleCaptured vert/index arrays an Item’s
geometryhandle resolves to.The unit of the retained geometry store.
vertsare 8-float tuples(x, y, u, v, r, g, b, a)in the node’s LOCAL space (camera-free, parent-free);indicesisNonefor line lists. Every handle is freshly appended.- verts: list[tuple]¶
None
- indices: list[int] | None¶
None
- class simvx.graphics.render2d.item_builder.GeometryStore¶
A handle-indexed store of captured :class:
Geometry.A plain growable list:
- Meth:
addappends and returns the handle an Item records in itsgeometrycolumn; :meth:getresolves it.
Initialization
- __slots__¶
(‘_geoms’,)
- __len__() int¶
- add(verts: list[tuple], indices: list[int] | None) int¶
- get(handle: int) simvx.graphics.render2d.item_builder.Geometry¶
- set(handle: int, verts: list[tuple], indices: list[int] | None) None¶
Overwrite the geometry behind an existing handle (item-level patch).
Lets a render-dirty node re-capture its
on_drawand replace its geometry slice in place, keeping the handle stable so the Item column and every consumer keying by handle stay valid (item granularity).
- class simvx.graphics.render2d.item_builder.NodeEntry¶
Bases:
typing.NamedTupleThe retained per-node record an incremental patch 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 (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:ItemListrow indices the node’s ops produced. geometry The geometry handles (parallel torows) the node’s ops produced. A render-dirty change re-captures the node’son_drawand overwrites these in place. canvas_affine The affine baked into this node’s captured geometry: the enclosing CanvasLayer’s, composed with the Control per-child wrap translation when the node sits under one (Nonefor plain world content). An in-place re-capture re-bakes with this so both survive an item-level patch. screen_space Whether the walk emitted this node inside a screen-pinned scope (aCanvasLayerwithfollow_viewport=False). The flag comes from the enclosing scope, not from the node’s own draw calls, so an in-place re-capture must re-apply it or the item would fall back under the camera. node The emitting node itself.node_indexis keyed byid(node), which answers “is this node retained?” but cannot answer “which nodes are retained?”; a consumer that wants the second question – the freeze detector sampling one clean node per frame – would otherwise have to re-walk the whole tree to find a single node it already has a record for. The reference is dropped by the next collect, and a structural change forces one, so it extends no node’s life beyond a frame.- transform_id: int¶
None
- rows: list[int]¶
None
- geometry: list[int]¶
None
- canvas_affine: simvx.graphics.render2d.item_builder._AffineRow | None¶
None
- screen_space: bool¶
False
- node: Any¶
None
- class simvx.graphics.render2d.item_builder.CollectResult¶
Bases:
typing.NamedTupleThe product of one :meth:
ItemBuilder.collect.Bundles the three artefacts the later sort/batch/submit consume: the SoA
- Class:
ItemList, the :class:ClipScopeTableitsclip_scopeindices point into, the :class:GeometryStoreitsgeometryhandles resolve against, and the per-item LOCAL transform rows itstransformindices select.node_indexmaps each emitting node (byid) to its- Class:
NodeEntry, the substrate for in-place per-item / transform-only patching.
- items: simvx.graphics.render2d.item_list.ItemList¶
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¶
Walks a Node tree and emits render Items into an :class:
ItemList.Reproduces the live
_draw_recursivevisit order (so a stable(layer, seq)sort of the result equals walk order) 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, overlay_draws: tuple = ()) simvx.graphics.render2d.item_builder.CollectResult¶
Walk
rootthen the open overlay set, returning the :class:CollectResult.layeris the starting CanvasLayer band (0 = world content). ACanvasLayerchild shifts its subtree’s band; nested CanvasLayers are absolute (the layer does not add).overlay_drawsis the ordered(kind, control, entry)sequence fromiter_overlay_draws(empty when no overlay is open -> byte-identical to a plaincollect(root)). Each overlay is walked in SCREEN SPACE (parity with the immediate path under a panned Camera2D) and into the overlay sort tier, so overlays land strictly on top regardless of CanvasLayer band.
- simvx.graphics.render2d.item_builder.hdr_flag(node: Any) simvx.graphics.render2d.item_list.ItemFlags¶
Resolve a node’s
hdroverride to its HDR-lane flag (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 togglinghdrtakes 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¶
Collect
rootinto 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.