simvx.graphics.renderer.render_packet¶
Field provenance¶
Every field mirrors a per-frame attribute the GPU frame reads, each cleared in
Renderer.begin_frame (forward.py ~605-616) and rebuilt by the scene adapter:
instances
Renderer._instances (forward.py ~64): list of
(MeshHandle, transform, material_id, viewport_id). Consumed by
_upload_transforms, the shadow pass, occlusion cull, and the forward
draw. The 4x4 transform arrays are copied so the main thread may rebuild
next frame without touching the in-flight packet.
skinned_instances
Renderer._skinned_instances (forward.py ~176):
(MeshHandle, transform, material_id, joint_matrices). Both numpy arrays
per entry are copied.
shader_material_submissions
Renderer._shader_material_submissions (forward.py ~120):
(MeshHandle, transform, material_id, shader_material, texture_slots).
The transform and the resolved texture-slot mapping are copied;
shader_material is a shared, frame-stable object referenced.
particle_submissions
Renderer._particle_submissions (forward.py ~109): (data, count).
data is copied.
gpu_particle_submissions
Renderer._gpu_particle_submissions (forward.py ~113):
(emitter_id, emitter_config). emitter_config is a per-frame dict;
shallow-copied so a downstream mutation of the dict object is isolated.
materials
Renderer._materials (forward.py ~105, set via set_materials). Copied.
lights
Renderer._lights (forward.py ~106, set via set_lights). Copied.
viewports
Snapshot of Renderer.viewport_manager.viewports (scene_adapter ~785).
Each entry is a :class:ViewportSnapshot carrying the viewport id plus an
owned copy of the camera view/proj matrices, rect, and the (shared, not
copied) render-target handle.
structure_version
tree._structure_version at extract time. The velocity and occlusion
passes guard prev<->cur pairing on this; the render thread must see the
value that matched the snapshotted instance ordering, not a later one.
frame_index
Monotonic producer frame counter, for ordering / telemetry / debugging.
item_view
Published, immutable 2D item-pipeline view: the frozen SoA
item columns the render thread reads instead of the live game-thread item
store. None when the scene drew no 2D this frame. A clean frame
republishes the SAME view object (identity-equal, zero-byte) – the
double-buffer reuse contract, achieved via an immutable frozen view rather
than a per-frame re-copy.
Subsystem submission buffers (packetised)¶
These mirror per-frame submission lists that live inside lazily-created
subsystem passes. Each is rebuilt every frame on the MAIN thread (during
adapter.submit_scene) and read during recording, so the render thread must
read an OWNED copy rather than the live list the main thread is clearing.
tilemap_layers
Owned snapshot of Renderer._tilemap_pass._submissions
(tilemap_pass.py ~42, populated by submit_layer from
SceneAdapter._submit_tilemaps). Each entry is
(tile_data: ndarray, tileset_texture_id: int, tile_size: tuple, colours: ndarray | None); the tile_data structured array (and the
optional colours tint array, when present) are copied (the main thread
reuses TileMap layer buffers across frames).
light2d_lights / light2d_occluders
Owned snapshots of Renderer._light2d_pass._lights (list of per-light
dicts, light2d_pass.py ~65) and ._occluders (list of polygon
vertex-lists, ~66). Each dict / polygon is shallow-copied so a downstream
mutation of the per-frame entry is isolated from the packet.
(Text2D no longer has a dedicated overlay geometry buffer to snapshot: after
the unified text path draws through the ``item_view``, which the
``item_view`` packet field already carries, so there is no separate text
snapshot here.)
Scene-render-unit (SRU) plans¶
subviewport_srus
Ordered list of :class:SubViewportSRU plans, one per live SubViewport,
captured on the MAIN thread by :meth:SubViewportManager.build_srus (which
reuses the topological order_subviewports so producers precede
consumers). Each plan owns the SubViewport’s submitted opaque/skinned
instances (transform copies), its camera view/proj matrices, its published 2D
item view, target identity, and the update-mode decision, so the render thread
can record the offscreen SRU WITHOUT walking the live tree. Empty when no
SubViewport is present or in the synchronous path (which still walks the tree
live, byte-identically).
State intentionally NOT snapshotted (and why)¶
_main_base/ arena slice info: reservation is a GPU-SSBO concern owned entirely by the render thread (reserve_main_slice+_upload_transformsrun there from the packet’sinstances). The main thread never reserves.Per-frame HDR / post flags (
_hdr_rendered): recomputed by the render thread insidepre_render/renderfrom the snapshotted lists; they are outputs of recording, not inputs to it.Reflection-probe capture (
ReflectionProbePass.update_probes): DEFERRED. The probe path is deeply GPU-stateful (per-probe source cubemaps, six face renders, an IBL compute convolution with record-time descriptor mutation, and cube-array copies with many intra-cmd barriers). Packetising it faithfully would require snapshotting six face instance-lists per probe plus replaying the convolution/copy state machine on the render thread, which is out of scope here. Probe-bearing scenes keep the safe skip + one-time warning in pipelined mode (app.py_warn_pipelined_unpacketised/pre_render_fn).RenderView capture (
RenderViewManager): DEFERRED on the same precedent. A RenderView re-renders the live MAIN tree through a second camera; packetising it faithfully would mean snapshotting a full extra main-scene instance list per view each frame. In pipelined mode the manager is simply not invoked (frame_loop pre_render takes the packet-replay branch): the published slot stays valid but stale, covered by the same one-time warning. Immediate (synchronous) mode is the supported path.
Note: the 2D HUD/overlay (immediate-mode + Text2D) is carried by the published
item_view (installed via install_packet), so the render thread never
touches the live game-thread item store. The subsystem buffers (tilemap /
2D-light / 3D-overlay text) and SubViewport SRUs documented above are now
packet-owned AND consumed by
the render thread: Renderer.install_packet binds them onto the per-frame
override attributes the pass read-sites consult, and the pipelined pre_render
replays the SubViewport SRUs from the plan via
SceneAdapter.render_sru_from_plan (no live-tree walk). Only reflection-probe
and RenderView capture remain deferred in pipelined mode (see above). The synchronous path
installs no packet, so every read-site falls back to the live state and stays
byte-identical.
CPU-side per-frame render snapshot for the pipelined render thread.
A :class:RenderPacket is an owned, immutable-by-convention snapshot of the
renderer’s per-frame submission state, taken on the MAIN thread right after
SceneAdapter.submit_scene populates that state. In pipelined render mode the
packet is handed to the render thread, which reconstructs the renderer’s
per-frame lists from it and then records + submits the GPU frame, while the main
thread is free to simulate the next frame and rebuild the renderer’s (now empty)
lists without mutating the in-flight packet.
The packet, its :func:extract_render_packet builder, and the
- class:
RenderPacketRingdouble-buffer are defined and unit tested. The default (synchronous) path never constructs a packet, so it stays byte-identical.
Module Contents¶
Classes¶
Owned snapshot of one viewport’s camera + rect for a single frame. |
|
Owned plan to record one SubViewport offscreen without walking the tree. |
|
Owned snapshot of the renderer’s per-frame submission state. |
|
Bounded double-buffered handoff between the main and render threads. |
Functions¶
Snapshot the renderer’s current per-frame state into an owned RenderPacket. |
Data¶
API¶
- simvx.graphics.renderer.render_packet.__all__¶
[‘RenderPacket’, ‘RenderPacketRing’, ‘SubViewportSRU’, ‘ViewportSnapshot’, ‘extract_render_packet’]
- class simvx.graphics.renderer.render_packet.ViewportSnapshot¶
Owned snapshot of one viewport’s camera + rect for a single frame.
Matrices are copied so the render thread reads a stable view/proj even after the main thread rebuilds the live
Viewportnext frame (TAA jitter and the motion-blur matrix update both mutatecamera_projin place during recording, which must happen on the render thread’s owned copy).- vp_id: int¶
None
- x: int¶
None
- y: int¶
None
- width: int¶
None
- height: int¶
None
- camera_view: numpy.ndarray¶
None
- camera_proj: numpy.ndarray¶
None
- render_target: Any | None¶
None
- class simvx.graphics.renderer.render_packet.SubViewportSRU¶
Owned plan to record one SubViewport offscreen without walking the tree.
Captured on the MAIN thread by :meth:
SubViewportManager.build_srusfrom a SubViewport’s already-submitted offscreen scene. The render thread replays it: reserve a transform-SSBO slice forinstances+skinned_instances, write those transforms, record the draws into the SubViewport’srenderertarget withcamera_view/camera_proj, then overlaydraw2d_ops. The producer-before-consumer ordering is encoded by this list’s position in :attr:RenderPacket.subviewport_srus(topological sort), so a consumer SRU appears after the producer it samples.Fields
sru_id Stable identity (
id(node)) keying the frustum-visibility cache so SRUs never collide. Mirrorsrender_to_target(sru_id=...). renderer The SubViewport’s :class:SubViewportRenderer(shared GPU target, not copied: it owns the offscreen image whose bindless slot the main scene samples). The render thread records into it; the main thread does not mutate it concurrently (create/resize happen during extract, before the plan is built). width / height The SRU’s offscreen extent at capture time (drives viewport + the SSBO transform write). clear_colour Per-frame clear colour (transparent_bgdecides RGBA), copied. camera_view / camera_proj Owned copies of the SRU camera’s view + projection matrices (Nonefor a 2D-only SubViewport, which uses the screen-size path). screen_size(width, height)float screen size override the 2D path needs. instances / skinned_instances Owned snapshots of the offscreen scene’s submitted opaque / skinned instances (same shape as :attr:RenderPacket.instances/skinned_instances; transform + joint arrays copied). billboard_submissions Owned snapshot of the subtree’s Sprite3D / Text3D billboard rows so the render thread renders them into the offscreen target (parity with the synchronousrender_to_targetpath). Empty when the subtree has none. item_view The published, immutable 2D item-pipeline view for THIS SubViewport target: each SubViewport SRU snapshots the published item view for its target.Nonewhen the subtree drew no 2D this frame.- sru_id: int¶
None
- renderer: Any¶
None
- width: int¶
None
- height: int¶
None
- clear_colour: tuple[float, float, float, float]¶
None
- camera_view: numpy.ndarray | None¶
None
- camera_proj: numpy.ndarray | None¶
None
- screen_size: tuple[float, float]¶
None
- instances: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, int]]¶
None
- skinned_instances: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, numpy.ndarray]]¶
None
- multimesh_blocks: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, numpy.ndarray | None, int, int, int]]¶
‘field(…)’
- billboard_submissions: list[numpy.ndarray]¶
‘field(…)’
- item_view: simvx.graphics.render2d.publish.PublishedItemView | None¶
None
- item_camera: tuple[float, float, float, float, float, float]¶
(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
- class simvx.graphics.renderer.render_packet.RenderPacket¶
Owned snapshot of the renderer’s per-frame submission state.
Construct via :func:
extract_render_packet. All mutable numpy data is copied at extract time, so the main thread may rebuild the renderer’s per-frame lists for the next frame while this packet is in flight on the render thread.- frame_index: int¶
None
- structure_version: int¶
None
- instances: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, int]]¶
None
- skinned_instances: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, numpy.ndarray]]¶
None
- shader_material_submissions: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, Any, dict[str, int]]]¶
None
- particle_submissions: list[tuple[numpy.ndarray, int]]¶
None
- gpu_particle_submissions: list[tuple[int, dict]]¶
None
- materials: numpy.ndarray¶
None
- lights: numpy.ndarray¶
None
- viewports: list[simvx.graphics.renderer.render_packet.ViewportSnapshot]¶
‘field(…)’
- tilemap_layers: list[tuple[numpy.ndarray, int, tuple[float, float], numpy.ndarray | None]]¶
‘field(…)’
- billboard_submissions: list[numpy.ndarray]¶
‘field(…)’
- light2d_lights: list[dict]¶
‘field(…)’
- light2d_occluders: list[list[tuple[float, float]]]¶
‘field(…)’
- camera2d_affine: tuple[tuple[float, ...], tuple[float, float]] | None¶
None
- subviewport_srus: list[simvx.graphics.renderer.render_packet.SubViewportSRU]¶
‘field(…)’
- multimesh_blocks: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int, numpy.ndarray | None, int, int, int]]¶
‘field(…)’
- item_view: simvx.graphics.render2d.publish.PublishedItemView | None¶
None
- item_camera: tuple¶
(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
- simvx.graphics.renderer.render_packet.extract_render_packet(renderer: simvx.graphics.renderer.forward.Renderer, tree: simvx.core.SceneTree, *, frame_index: int = 0, sub_viewports: Any = None, item_view: simvx.graphics.render2d.publish.PublishedItemView | None = None, item_camera: tuple = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)) simvx.graphics.renderer.render_packet.RenderPacket¶
Snapshot the renderer’s current per-frame state into an owned RenderPacket.
Call on the MAIN thread immediately after
adapter.submit_scene(tree), beforeRenderer.begin_frameclears the lists for the next frame. Numpy arrays are copied (ownership transferred to the packet) so the producer can safely rebuild the live lists while this packet is consumed by the render thread.Args: renderer: The forward renderer whose per-frame lists to snapshot. tree: The scene tree (read for
_structure_versionand SubViewport discovery whensub_viewportsis supplied). frame_index: Monotonic producer frame counter stamped on the packet. sub_viewports: Optional :class:SubViewportManager. When supplied the packet captures ordered SubViewport SRU plans (so the render thread can record them without walking the tree).None(the default and the unit-test path) yields an emptysubviewport_srus. item_view: Optional published, immutable 2D item-pipeline view to attach. The caller obtains it from :meth:ItemPublisher.publishat this same sync point (inside_frame_state_lock).None(the unit-test path) leavesRenderPacket.item_viewNone(the scene drew no 2D).
- class simvx.graphics.renderer.render_packet.RenderPacketRing(capacity: int = 2)¶
Bounded double-buffered handoff between the main and render threads.
The ring IS the double-buffer: a fixed
capacity(default 2) of packet slots with backpressure.submitblocks the producer (main thread) once it is one frame ahead, bounding latency to +1 frame.acquireblocks the consumer (render thread) until a packet is available. The render thread callsreleaseafter it has finished the GPU frame for a packet, freeing the slot so the producer may run ahead again.Shutdown is cooperative:
closewakes any blocked thread. After close,submitraises andacquiredrains remaining packets then returnsNoneso the consumer loop exits cleanly.Initialization
- property capacity: int¶
- property closed: bool¶
- submit(packet: simvx.graphics.renderer.render_packet.RenderPacket) None¶
Producer: enqueue a packet, blocking while the ring is full.
Raises
RuntimeErrorif the ring has been closed.
- acquire(timeout: float | None = None) simvx.graphics.renderer.render_packet.RenderPacket | None¶
Consumer: dequeue the next packet, blocking until one is available.
Returns
Noneif the ring is closed and drained (consumer should exit), or iftimeoutelapses with no packet.
- release() None¶
Consumer: signal that the most recently acquired packet’s GPU frame is done.
Frees one in-flight slot so the producer may run ahead again.
- close() None¶
Mark the ring closed and wake all blocked threads for clean shutdown.
- pending() int¶
Number of packets queued but not yet acquired (diagnostics/tests).