simvx.graphics.renderer.view_occlusion

Per-view two-phase Hi-Z occlusion culling for offscreen views (RM-B6).

Extra views (SubViewport / RenderView) render as scene-render units (SRUs) through :meth:SceneAdapter.render_to_target, outside the main pass’s occlusion machinery. Setting use_occlusion=True on the node gives such a view its own two-phase Hi-Z island: a scratch depth prepass + Hi-Z pyramid sized to the VIEW’s target, its own cull compute pass, indirect batches, and visibility ping-pong buffers, driven by the exact Renderer._two_phase_occlusion sequence the main pass uses. During the recording the renderer’s occlusion attributes are swapped to this bundle (the same save / swap / restore isolation render_to_target applies to the submission lists), so the main pass’s own occlusion state is untouched and the default (flag-off) frame is byte-identical.

Scope (documented deviations, both engine decisions carried by RM-B6):

  • Desktop only. The web renderer’s Hi-Z cull is single-phase (TODO.md, “Web two-phase Hi-Z occlusion”) and has no per-view hook yet; the flag is a no-op on web.

  • Pipelined (P2) render mode defers per-view occlusion exactly like reflection-probe capture (render_packet.py, “State intentionally NOT snapshotted”): the view renders un-culled and the owning manager logs a one-time warning. Immediate (synchronous) mode is the supported path.

Module Contents

Classes

ViewOcclusion

One offscreen view’s private two-phase Hi-Z occlusion state.

Functions

view_occlusion_for

Return (creating on demand) the node’s occlusion bundle, or None.

Data

API

simvx.graphics.renderer.view_occlusion.log

‘getLogger(…)’

simvx.graphics.renderer.view_occlusion.__all__

[‘ViewOcclusion’, ‘view_occlusion_for’]

class simvx.graphics.renderer.view_occlusion.ViewOcclusion(engine: Any)[source]

One offscreen view’s private two-phase Hi-Z occlusion state.

Owns the view-sized scratch :class:~.depth_prepass.DepthPrepass +

Class:

~.hiz_pass.HiZPass pair (created eagerly, the sizes differ from the main pass’s) and harvests the size-agnostic pieces (cull compute pass, indirect batches, visibility ping-pong buffers) from the renderer’s own lazy _ensure_* helpers on the first recorded frame. Recreated whole when the view’s target size or the renderer’s instance capacity changes (both rare: resizes are debounced, capacity growth happens at a frame boundary after a device-idle reallocation).

Initialization

record(cmd: Any, renderer: Any, width: int, height: int) None[source]

Record this view’s two-phase occlusion cull into cmd.

Must run while the SRU’s state is bound (instances / viewport / slice base installed by render_to_target) and OUTSIDE any render pass (compute is illegal inside one), i.e. between the SRU’s transform upload and target.begin_pass. Leaves the scene renderer’s per-frame occlusion plans pointing at THIS view’s culled batches so the SRU’s render_scene_content consumes them; :meth:finish must be called after the SRU render to restore the main pass’s plan state.

finish(renderer: Any) None[source]

Restore the main pass’s plan state after the SRU render consumed ours.

read_drawn(*, wait: bool = False) int[source]

Sum instance_count over this view’s culled indirect commands.

Mirrors SceneContentRenderer.read_occlusion_telemetry for the view’s own batches. Pass wait=True (test / end-of-run use) to vkDeviceWaitIdle first so the phase-2 dispatch has executed.

destroy() None[source]

Release all GPU resources (view reaped, resized, or app shutdown).

simvx.graphics.renderer.view_occlusion.view_occlusion_for(store: dict[int, simvx.graphics.renderer.view_occlusion.ViewOcclusion], node: Any, engine: Any) simvx.graphics.renderer.view_occlusion.ViewOcclusion | None[source]

Return (creating on demand) the node’s occlusion bundle, or None.

The one gate both offscreen-view managers use: with use_occlusion False (the default) this is a single getattr plus, at most, the destruction of a bundle whose flag was just toggled off, so the common path stays zero-cost. store is the manager’s id(node) -> bundle dict; reaping on node exit stays the manager’s responsibility.