ltk/gles_render/raii.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! RAII guards for scoped GL bindings.
//!
//! The renderer normally restores GL state lazily: every draw method
//! calls [`super::GlesCanvas::activate_target`] at entry, which
//! re-binds the canvas FBO and reasserts viewport / scissor. So a
//! method that temporarily binds a different FBO (e.g. `aux_a` for a
//! snapshot) does not need to restore the previous binding — the next
//! draw will. That is a deliberate optimisation; introducing a RAII
//! restore at every site would double-bind the canvas FBO on every
//! frame.
//!
//! These guards exist for the *opposite* shape — operations that
//! genuinely change global state for the duration of a scope and
//! must guarantee restoration **even on early return / panic**.
//! Today that fits exactly one site: [`super::GlesCanvas::present`],
//! which binds the default framebuffer (id 0) and the blit program,
//! draws a fullscreen quad, and rebinds the canvas FBO + previous
//! program. Without RAII, an early return between the two binds
//! would leave the GL context with the default FBO and the wrong
//! program active — the next draw on this canvas would render to
//! the wrong target until `activate_target` ran (FBO is corrected
//! by `activate_target`; `use_program` is **not**).
//!
//! When in doubt: do not wrap `bind_framebuffer` / `use_program` in
//! a guard "for safety". The lazy-restore convention is part of the
//! renderer's contract and the guard pays for itself only when the
//! scope can exit through a path that bypasses `activate_target`.
use glow::HasContext;
/// Scoped framebuffer binding. Binds `target` on construction and
/// restores `previous` on Drop. Caller passes `previous` explicitly
/// because every site that needs this guard already knows what it
/// wants to restore (typically `self.fbo`) — querying
/// `GL_FRAMEBUFFER_BINDING` would force a sync round-trip we do not
/// need.
pub( super ) struct FboBinding<'a>
{
gl: &'a glow::Context,
previous: Option<glow::Framebuffer>,
}
impl<'a> FboBinding<'a>
{
/// Bind `target` immediately; remember `previous` to rebind on Drop.
///
/// # Safety
///
/// The GL context behind `gl` must be current on the calling thread
/// for the entire lifetime of the returned guard. Both `target` and
/// `previous` must be names produced by this same context (or
/// `None` for the default framebuffer).
pub( super ) unsafe fn scoped( gl: &'a glow::Context, target: Option<glow::Framebuffer>, previous: Option<glow::Framebuffer> ) -> Self
{
// SAFETY: forwarded from the fn's own `# Safety` contract.
unsafe { gl.bind_framebuffer( glow::FRAMEBUFFER, target ); }
Self { gl, previous }
}
}
impl<'a> Drop for FboBinding<'a>
{
fn drop( &mut self )
{
// SAFETY: the construction-time invariant (current context) is the
// guard's lifetime invariant. Restoring `previous` is a pure
// state-machine mutation.
unsafe { self.gl.bind_framebuffer( glow::FRAMEBUFFER, self.previous ); }
}
}
/// Scoped program binding. Same shape as [`FboBinding`] but for
/// `glUseProgram`. Caller passes the program to restore explicitly
/// (typically the program the next pipeline stage will need, or
/// `None` to leave nothing bound).
pub( super ) struct ProgramBinding<'a>
{
gl: &'a glow::Context,
previous: Option<glow::Program>,
}
impl<'a> ProgramBinding<'a>
{
/// Activate `target` immediately; remember `previous` to restore on Drop.
///
/// # Safety
///
/// Same as [`FboBinding::scoped`].
pub( super ) unsafe fn scoped( gl: &'a glow::Context, target: Option<glow::Program>, previous: Option<glow::Program> ) -> Self
{
// SAFETY: forwarded from the fn's own `# Safety` contract.
unsafe { gl.use_program( target ); }
Self { gl, previous }
}
}
impl<'a> Drop for ProgramBinding<'a>
{
fn drop( &mut self )
{
// SAFETY: see `FboBinding::drop`.
unsafe { self.gl.use_program( self.previous ); }
}
}