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] def transforms_as_matrices(self) -> list[np.ndarray]: """Get transforms as list of 4x4 numpy arrays.""" if isinstance(self.transforms, list): return self.transforms matrices = [] for i in range(self.instance_count): mat_data = self.transforms[i * 16 : (i + 1) * 16] matrices.append(mat_data.reshape(4, 4)) return matrices
[docs] def transforms_as_bytes(self) -> bytes: """Get transforms as GPU-ready bytes (for instancing buffer).""" if isinstance(self.transforms, np.ndarray): return self.transforms.astype(np.float32).tobytes() # Convert list of mat4 to bytes data = np.array([list(m) for m in self.transforms], dtype=np.float32) return data.tobytes()
[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 ), )