simvx.graphics.renderer.render_thread¶
Dedicated render thread driving the pipelined GPU frame (opt-in, default OFF).
In pipelined mode (App(render_thread=True) or WorldEnvironment render_mode='pipelined') the MAIN thread simulates frame N+1 while this thread
records + submits the GPU work for frame N. The split:
MAIN THREAD:
glfwPollEvents(window events must stay on main), physics + tick, Draw2D,adapter.submit_scene(building the renderer’s per-frame submission lists), thenextract_render_packetinto a CPU- class:
~.render_packet.RenderPacket, thenring.submit(packet). It issues ZERO GPU calls.
RENDER THREAD (this driver):
ring.acquirea packet,install_packetit onto the renderer’s per-frame attributes (under the renderer’s_frame_state_lock), then run the engine’s existing GPU frame body (wait_and_resetfence,vkAcquireNextImageKHR, record pre_render + the render pass,vkQueueSubmit,vkQueuePresentKHR,sync.advance), thenring.release.
Invariants enforced here (see report):
(a) The main thread issues no GPU calls: all vkCmd* / acquire / submit /
present run on this thread.
(b) This thread is the ONLY writer of the GPU SSBOs (_upload_transforms /
reserve_main_slice run here from the packet) and wait_and_reset
gates reuse, so a single GPU SSBO is safe (no per-frame GPU ring).
(c) The pre_render / render closures read the renderer’s per-frame attributes,
which install_packet has just bound to the PACKET’s owned snapshot; the
_frame_state_lock makes the install + record region mutually exclusive
with the main thread’s begin_frame + submit_scene, so the main thread never
tears those attributes mid-record.
(d) +1 frame latency is bounded by the 2-slot ring’s backpressure.
(e) No deadlock on quit: stop closes the ring (waking a producer blocked on
backpressure and this consumer blocked on acquire) and joins.
Command pool: this thread REUSES the engine’s single command pool / per-frame command buffers. Vulkan requires one pool per recording thread; that holds here because in pipelined mode the render thread is the ONLY thread that records (the main thread issues zero GPU work, invariant (a)). No second pool is created.
Module Contents¶
Classes¶
Consumer thread: installs render packets and records + submits GPU frames. |
Data¶
API¶
- simvx.graphics.renderer.render_thread.log¶
‘getLogger(…)’
- simvx.graphics.renderer.render_thread.__all__¶
[‘RenderThread’]
- class simvx.graphics.renderer.render_thread.RenderThread(engine: simvx.graphics.engine.Engine, renderer: simvx.graphics.renderer.forward.Renderer, ring: simvx.graphics.renderer.render_packet.RenderPacketRing, *, draw_frame: collections.abc.Callable[[], None] | None = None, capture: collections.abc.Callable[[int, Any], None] | None = None)[source]¶
Consumer thread: installs render packets and records + submits GPU frames.
Args: engine: The :class:
Engineowning the swapchain, queues, sync, and the per-frame command buffers. The render thread is the sole GPU caller. renderer: The forward :class:Rendererwhose per-frame attributes a packet is installed onto before recording. ring: The :class:RenderPacketRingthe main thread submits packets to. draw_frame: The engine’s GPU-frame body to invoke per packet. Defaults toengine._draw_frame. It records pre_render + the render pass (via the engine’s pre_render / render callbacks), submits, and presents.Initialization
- wait_for_frame(frame_index: int, timeout: float | None = None) bool[source]¶
Block until the GPU frame for
frame_indexhas been submitted + presented.Used by the headless capture path to sequence
capture_frameafter the render thread has finished drawing the frame. ReturnsTrueonce_frames_donehas passedframe_index,Falseon timeout.Raises the render thread’s captured exception if it crashed before reaching
frame_index: the awaited frame will never arrive, so the caller must learn promptly rather than block forever or silently proceed on a stale frame. The crash handler’snotify_allwakes a waiter that is blocked withtimeout=Noneso this raise happens at once.
- stop(timeout: float | None = 5.0) None[source]¶
Close the ring and join the thread; re-raise any thread exception.
closewakes a producer blocked on backpressure AND this consumer blocked onacquire(invariant (e)). The thread drains remaining packets, then exits. Re-raises any exception the thread captured so a render-thread crash surfaces on the main thread rather than vanishing.