Source code for simvx.core.render_queue
"""Render queue — efficient batch submission for backends.
Backends call SceneTree._collect_render_queue() once per frame to get
optimized batches. No user-facing API; purely internal optimization layer.
Example (backend usage):
batches = scene_tree._collect_render_queue()
for batch in batches:
batch.material.bind()
gpu_draw(batch.mesh, batch.transforms) # Single GPU call for 1000s
"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING
import numpy as np
if TYPE_CHECKING:
from .graphics.material import Material
from .graphics.mesh import Mesh
log = logging.getLogger(__name__)
[docs]
@dataclass
class RenderBatch:
"""Single GPU draw call: mesh + material + transform instances.
Attributes:
mesh: Mesh geometry (vertices, indices)
material: Material properties (colour, blend mode, etc.)
transforms: Nx4x4 array of model matrices (or flat if GPU-instanced)
layer: Sort key (higher = rendered last)
"""
mesh: Mesh
material: Material
transforms: list[np.ndarray] | np.ndarray
layer: int = 0
@property
def instance_count(self) -> int:
"""Number of instances in this batch."""
if isinstance(self.transforms, list):
return len(self.transforms)
return len(self.transforms) // 16 if isinstance(self.transforms, np.ndarray) else 1
[docs]
class RenderQueue:
"""Collects and sorts geometry for efficient rendering.
Internal use only. Created by SceneTree._collect_render_queue().
"""
def __init__(self):
self.batches: dict[tuple, RenderBatch] = {} # (mesh_id, material_id) -> RenderBatch
[docs]
def add_instance(self, mesh: Mesh, material: Material, transform: np.ndarray, layer: int = 0) -> None:
"""Add a mesh instance (called by scene tree traversal)."""
if mesh is None:
return
key = (id(mesh), id(material))
if key not in self.batches:
self.batches[key] = RenderBatch(mesh, material, [], layer)
batch = self.batches[key]
batch.transforms.append(transform)
[docs]
def get_batches(self) -> list[RenderBatch]:
"""Get sorted render batches (opaque, then transparent by depth)."""
return sorted(
self.batches.values(),
key=lambda b: (
0 if b.material.blend == "opaque" else 1, # Opaque first
-b.layer, # Higher layer = later
),
)