Source code for simvx.graphics.renderer._base

"""RendererBackend protocol — defines the contract any rendering backend must satisfy.

The default ``Renderer`` (Vulkan, forward) is the reference implementation. Other
backends (WebGPU, streaming) implement the same protocol so SceneAdapter, App,
and testing infrastructure work identically across backends.
"""

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

import numpy as np

if TYPE_CHECKING:
    from .._types import MeshHandle

log = logging.getLogger(__name__)

__all__ = ["RendererBackend"]

[docs] class RendererBackend(ABC): """Base class for rendering pipelines (forward, deferred, webgpu, etc.). Backends implement these methods. SceneAdapter calls the submission methods each frame; Engine calls the lifecycle methods. The numpy dtype boundary (VERTEX_DTYPE, MATERIAL_DTYPE, LIGHT_DTYPE, TRANSFORM_DTYPE) is the contract. """ # -- Frame lifecycle --
[docs] @abstractmethod def begin_frame(self) -> None: """Reset per-frame state, prepare for new submissions."""
[docs] @abstractmethod def pre_render(self, cmd: Any) -> None: """Offscreen passes (shadows, SSAO) — after submissions, before main pass."""
[docs] @abstractmethod def render(self, cmd: Any) -> None: """Record draw commands into *cmd* during the main render pass."""
[docs] @abstractmethod def resize(self, width: int, height: int) -> None: """Handle framebuffer resize."""
[docs] @abstractmethod def destroy(self) -> None: """Release all GPU resources."""
# -- Scene submissions (called by SceneAdapter each frame) --
[docs] @abstractmethod def submit_instance( self, mesh_handle: MeshHandle, transform: np.ndarray, material_id: int, viewport_id: int = 0, ) -> None: """Submit a single mesh instance for rendering."""
[docs] @abstractmethod def submit_multimesh( self, mesh_handle: MeshHandle, transforms: np.ndarray, material_id: int = 0, material_ids: np.ndarray | None = None, viewport_id: int = 0, count: int = 0, ) -> None: """Submit multiple instances of the same mesh."""
[docs] @abstractmethod def submit_skinned_instance( self, mesh_handle: MeshHandle, transform: np.ndarray, material_id: int, joint_matrices: np.ndarray, ) -> None: """Submit a skinned mesh with joint matrices."""
[docs] @abstractmethod def set_materials(self, materials: np.ndarray) -> None: """Upload material array (MATERIAL_DTYPE) to GPU."""
[docs] @abstractmethod def set_lights(self, lights: np.ndarray) -> None: """Upload light array (LIGHT_DTYPE) to GPU."""
[docs] @abstractmethod def submit_text( self, text: str, x: float, y: float, size: float, colour: tuple[float, float, float, float], **kwargs: Any, ) -> None: """Submit 2D text for rendering."""
[docs] @abstractmethod def submit_particles(self, particle_data: np.ndarray) -> None: """Submit CPU particle data for rendering."""
[docs] @abstractmethod def submit_light2d(self, **kwargs: Any) -> None: """Submit a 2D light source."""
# -- Resource management --
[docs] @abstractmethod def register_mesh(self, vertices: np.ndarray, indices: np.ndarray) -> MeshHandle: """Register mesh data on GPU, return a handle for submissions."""
[docs] @abstractmethod def upload_texture_pixels(self, pixels: np.ndarray, width: int, height: int) -> int: """Upload RGBA pixel data to GPU, return bindless texture index. Matches the ``_TextureRegistrar`` protocol consumed by ``TextureManager``. """
# -- Frame capture (for headless testing) --
[docs] @abstractmethod def capture_frame(self) -> np.ndarray: """Capture the last rendered frame as (H, W, 4) uint8 RGBA numpy array."""
# -- Viewport -- # viewport_manager: ViewportManager — expected as an attribute, not enforced by ABC