Source code for picogl.renderer.initializable

"""
One-shot lifecycle helpers for renderer resources.

``Initializable`` — GPU/setup work runs once until the object is discarded
(e.g. framebuffer creation, renderer ``_do_initialize``).

``Bindable`` — OpenGL *binding* is sticky within a pass: repeated ``bind()``
is a no-op until ``unbind()``. Use for pass-scoped helpers such as
:class:`~picogl.backend.modern.core.pipeline.shader_pipeline.ShaderPipeline`
and :class:`~picogl.backend.geometry.legacy_mesh_binding.LegacyClientMeshBinding`.

Do **not** mix ``Bindable`` into types that rely on nested context managers
or state restore (``ShaderProgram.__enter__``, VAO ``with``, ``GLFramebuffer.bound()``).
Those need stack/restore semantics, not a single sticky flag.

Also do **not** subclass ``Bindable`` from :class:`~picogl.buffers.base.VertexBase`,
:class:`~picogl.gpu.buffers.vertex.legacy.VertexBufferGroup`, VAO/VBO types, or
:class:`~picogl.protocols.drawable_buffer.DrawableBuffer` implementations.
They use stack-based bind/unbind (every ``with`` calls bind then unbind); sticky
``_bound`` would disagree with actual gl state after nested ``with vbo`` scopes.
For pass-scoped VAO binding without ``with`` churn, use a separate wrapper
(``StickyVAOBinding``) only if profiling warrants it — not inheritance on
``VertexBase``.
"""


[docs] class Initializable: """Enforces one-time initialization with optional lazy semantics."""
[docs] __slots__ = ("_initialized",)
def __init__(self):
[docs] self._initialized = False
[docs] def initialize(self) -> None: if self._initialized: return self._do_initialize() self._initialized = True
[docs] def _do_initialize(self) -> None: """Subclass must implement actual initialization.""" raise NotImplementedError
[docs] def ensure_initialized(self) -> None: """Call before any operation that requires initialization.""" if not self._initialized: self.initialize()
[docs] def require_initialized(self) -> None: """Strict check (no auto-init).""" if not self._initialized: raise RuntimeError(f"{self.__class__.__name__} not initialized")
[docs] class Bindable: """Enforces one-time binding with optional lazy semantics. Not for VAO/VBO/``VertexBase`` types; see module docstring. """
[docs] __slots__ = ("_bound",)
def __init__(self):
[docs] self._bound = False
[docs] def bind(self) -> None: if self._bound: return self._do_binding() self._bound = True
[docs] def _do_binding(self) -> None: raise NotImplementedError
[docs] def ensure_bound(self) -> None: if not self._bound: self.bind()
[docs] def require_bound(self) -> None: if not self._bound: raise RuntimeError(f"{self.__class__.__name__} not bound")
[docs] def unbind(self) -> None: if not self._bound: return self._do_unbinding() self._bound = False
[docs] def _do_unbinding(self) -> None: raise NotImplementedError
[docs] def __enter__(self): self.ensure_bound() return self
[docs] def __exit__(self, exc_type, exc_val, exc_tb): self.unbind()