Length::dp no longer collapses to absolute pixels at construction: the design value travels in a new LengthBase::Dp variant and the density multiplication happens when the length is resolved. Previously dp( n ) baked in whatever density() returned at view-build time, so correctness across output changes depended on the view being rebuilt after set_density and in that order; now a density change is picked up by the very next paint with no reconstruction. Length::resolve keeps its signature (process density), and the new Length::resolve_with_density takes an explicit factor. dp becomes const in the bargain. Density also becomes overridable per canvas, the first step towards surface-local responsive state. SoftwareCanvas and GlesCanvas carry a density: Option<f32> analogous to the layout_viewport introduced for sub-canvas fluid resolution: None means "use the process global", Canvas::set_density pins a local factor, and sub-canvases inherit it. All canvas-routed resolution honours it — geom_px / font_px for stock-widget design pixels, and the new Canvas::resolve_geom / resolve_font for explicit Length values, which every widget now uses in place of the raw l.resolve( canvas.viewport_layout(), EM ) pattern (row, column, wrap_grid, spacer, container, separator, button, text, rich_text, text_edit, list_item, vslider, image, and the container draw path). Overlay sizing keeps resolving against the main surface with the global density, which is what it describes. New tests cover explicit-density resolution, resolution-time application, the local-over-global override and sub-canvas inheritance. The GLES image texture cache is now bounded. It was content-keyed but unbounded and never evicted, so a stream of distinct buffers — a photo carousel, video thumbnails — grew GPU memory for the lifetime of the canvas. The cache now tracks an estimated byte total (RGBA8, w × h × 4) against a 32 MiB budget and evicts least-recently-drawn textures on insert; the most recent entry is never evicted, so a single texture larger than the whole budget still draws and simply owns the cache until replaced. Drop-time cleanup is unchanged: drain deletes whatever the map holds. CI gains a Clippy step (workspace, all targets, test-support, -D warnings) sharing the build cache of the test job, with make clippy mirroring the invocation locally and CONTRIBUTING listing it. Run make clippy locally before pushing the first time — the gate has not seen the tree yet and pre-existing lints will fail CI until addressed. docs/backends.md formalises the software/GLES capability matrix that was previously scattered across per-method rustdoc: parity set (fills, strokes, text, images, paths, path clips), graceful degradations on software (flat-fill gradients, no shadows, no backdrop blur, hard bottom edge), GPU-only features (external textures), the shared Oklab-fallback limitation, and the cross-backend blit panic. Linked from README, onboarding and architecture's known-gaps list, which now states the parity gaps explicitly. The dp/density prose in architecture.md, lib.rs and the Length rustdoc is updated for resolution-time semantics and the per-canvas override.
468 lines
20 KiB
Rust
468 lines
20 KiB
Rust
// SPDX-License-Identifier: LGPL-2.1-only
|
||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||
|
||
//! GPU-accelerated rendering backend using EGL + GLES2 / GLES3.
|
||
//!
|
||
//! Mirrors the public surface of the software backend so that
|
||
//! [`crate::core::Canvas`] can route widget draw calls to either backend
|
||
//! by `match self`. The EGL context bootstrap lives in
|
||
//! [`crate::egl_context`] — this module is just the renderer that runs
|
||
//! once a context is current.
|
||
//!
|
||
//! Rect clipping is implemented with `glScissor`. When
|
||
//! [`GlesCanvas::set_clip_rects`] receives multiple rects the
|
||
//! bounding-box union is used as the scissor — coarse, but the
|
||
//! partial-redraw path normally clusters the dirty rects of 1–3
|
||
//! widgets so the union is barely larger than the sum. Arbitrary
|
||
//! path clipping ([`GlesCanvas::set_clip_path`]) captures the clipped
|
||
//! draws into an offscreen layer and composites it back through an
|
||
//! anti-aliased coverage mask, for a smooth clipped edge.
|
||
//!
|
||
//! # Submodule layout
|
||
//!
|
||
//! * `setup` — `GlesCanvas::{new, sub_canvas, resize, set_font_registry,
|
||
//! font_for, dpi/alpha accessors}`.
|
||
//! * `framebuffer` — `GlesCanvas::{blit, present, activate_target,
|
||
//! borrowed_texture, read_rgba_pixels, ensure_aux_*, snapshot_fbo_region,
|
||
//! fill_backdrop, invalidate_aux}` — everything that manipulates the
|
||
//! FBO or auxiliary snapshot textures.
|
||
//! * `clip` — `GlesCanvas::{set_clip_rects, clear_clip, set_scissor,
|
||
//! scissor_pixels, fill, clear, clear_rects_transparent}`.
|
||
//! * `primitives` — `GlesCanvas::{fill_rect, fill_linear_gradient_rect,
|
||
//! fill_radial_gradient_rect, fill_shadow_outer, fill_shadow_inset,
|
||
//! stroke_rect, draw_line}`.
|
||
//! * `text` — `GlesCanvas::{draw_text, measure_text}`.
|
||
//! * `image` — `GlesCanvas::draw_image_data`.
|
||
//! * `shaders` — GLSL ES 1.00 shader sources (const strings).
|
||
//! * `helpers` — free functions: `ortho_rect`, `compile_program`,
|
||
//! `alloc_fbo_tex`, `upload_*_texture`, handle extractors.
|
||
//! System-font resolution lives in `crate::system_fonts`.
|
||
//! * `raii` — `FboBinding` / `ProgramBinding` scoped guards for the
|
||
//! handful of operations that change global GL state for a scope
|
||
//! and must guarantee restoration even on early return / panic
|
||
//! (presently only `present`). The renderer otherwise relies on
|
||
//! `activate_target`'s lazy re-bind at the entry of each draw
|
||
//! method — see the module's own doc for when to use the guards
|
||
//! and when not to.
|
||
|
||
use std::collections::{ HashMap, VecDeque };
|
||
use std::sync::Arc;
|
||
|
||
use fontdue::Font;
|
||
use glow::HasContext;
|
||
|
||
use crate::theme::FontRegistry;
|
||
use crate::types::Rect;
|
||
|
||
pub( crate ) mod shaders;
|
||
pub( crate ) mod helpers;
|
||
pub( crate ) mod setup;
|
||
pub( crate ) mod framebuffer;
|
||
pub( crate ) mod clip;
|
||
pub( crate ) mod primitives;
|
||
pub( crate ) mod text;
|
||
pub( crate ) mod image;
|
||
mod raii;
|
||
|
||
// ─── Public types ────────────────────────────────────────────────────────────
|
||
|
||
/// Which GLES profile the active context is. Stored so per-frame
|
||
/// fast-paths can be selected without re-querying GL.
|
||
#[ derive( Clone, Copy, PartialEq, Eq, Debug ) ]
|
||
pub enum GlesVersion
|
||
{
|
||
V2,
|
||
V3,
|
||
}
|
||
|
||
/// Borrowed view of the texture backing a [`GlesCanvas`].
|
||
///
|
||
/// The texture and framebuffer remain owned by ltk. Consumers may
|
||
/// sample the texture while the canvas is alive, but must not delete
|
||
/// or take ownership of the GL names. A resize can replace both
|
||
/// names, so callers should query this after rendering/resizing, not
|
||
/// cache it indefinitely.
|
||
#[ derive( Clone, Copy, Debug ) ]
|
||
pub struct BorrowedGlesTexture
|
||
{
|
||
/// Native GL texture name for the canvas color attachment.
|
||
pub texture_id: u32,
|
||
/// Native GL framebuffer name that owns `texture_id` as color attachment 0.
|
||
pub framebuffer_id: u32,
|
||
/// Glow texture handle for callers already using glow.
|
||
pub texture: glow::Texture,
|
||
/// Glow framebuffer handle for callers already using glow.
|
||
pub framebuffer: glow::Framebuffer,
|
||
/// Texture width in physical pixels.
|
||
pub width: u32,
|
||
/// Texture height in physical pixels.
|
||
pub height: u32,
|
||
/// The texture contains RGBA pixels with premultiplied alpha.
|
||
pub premultiplied: bool,
|
||
/// Whether consumers should treat the texture as vertically inverted.
|
||
pub y_inverted: bool,
|
||
}
|
||
|
||
/// Cached glyph: rasterized bitmap placed in the shared atlas.
|
||
pub ( super ) struct GlyphEntry
|
||
{
|
||
pub ( super ) metrics: fontdue::Metrics,
|
||
pub ( super ) tex_w: i32,
|
||
pub ( super ) tex_h: i32,
|
||
pub ( super ) atlas_x: u32,
|
||
pub ( super ) atlas_y: u32,
|
||
}
|
||
|
||
/// Cache key for the GLES glyph atlas — the GPU-side mirror of the
|
||
/// software canvas's `GlyphKey`. `glyph_id` is the per-font index
|
||
/// returned by HarfBuzz shaping; `size_bits` is `f32::to_bits` of
|
||
/// `size * dpi_scale`; `font_id` is the address of the
|
||
/// `Arc<fontdue::Font>` used for rasterisation.
|
||
pub( super ) type GlyphAtlasKey = ( u16, u32, usize );
|
||
|
||
pub( super ) const ATLAS_SIZE: u32 = 2048;
|
||
|
||
// ─── GlesCanvas ──────────────────────────────────────────────────────────────
|
||
|
||
/// GPU-accelerated canvas using EGL + GLES2/3.
|
||
///
|
||
/// Renders into a persistent FBO (the "shadow canvas") so widget
|
||
/// pixels survive across frames — this mirrors the software pixmap
|
||
/// model and is what enables the partial-redraw path on the GPU side.
|
||
/// `Self::present` blits the FBO onto the default framebuffer; the
|
||
/// caller is responsible for the `eglSwapBuffers` that follows.
|
||
pub struct GlesCanvas
|
||
{
|
||
pub gl: Arc<glow::Context>,
|
||
pub version: GlesVersion,
|
||
/// Default font loaded from the system via `system_fonts::default_handle`.
|
||
/// Kept as a fallback for callers that do not route through the
|
||
/// theme registry.
|
||
pub font: Arc<Font>,
|
||
/// Raw bytes of the default font. Required by rustybuzz for
|
||
/// HarfBuzz shaping (see the `text_shaping` private module). Kept on the
|
||
/// canvas so the shape pipeline has direct access without a
|
||
/// global lookup.
|
||
pub font_bytes: Arc<Vec<u8>>,
|
||
/// TTC sub-face index for the default font (0 for non-`.ttc` files).
|
||
pub font_face: u32,
|
||
/// Optional theme font registry. Populated by the runtime after
|
||
/// theme load; until then it is `None` and [`Self::font_for`]
|
||
/// falls back to [`Self::font`].
|
||
pub font_registry: Option<Arc<FontRegistry>>,
|
||
pub dpi_scale: f32,
|
||
/// Layout-resolution viewport inherited from the parent canvas.
|
||
/// `None` on a root canvas (resolve against own size); sub-canvases
|
||
/// carry the root surface size so fluid geometry and fonts resolve
|
||
/// the same inside offscreen content (scroll viewports, clip layers)
|
||
/// as outside it.
|
||
pub( crate ) layout_viewport: Option<( f32, f32 )>,
|
||
/// Canvas-local pixel density for `Dp` resolution, when the owning
|
||
/// surface sits on an output whose density differs from the process
|
||
/// [`crate::density`]. `None` falls back to the global. Inherited by
|
||
/// sub-canvases.
|
||
pub( crate ) density: Option<f32>,
|
||
pub global_alpha: f32,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
|
||
// Shader programs. `Program` is a Copy handle; sub-canvases share
|
||
// these with their parent (no reference counting needed since they
|
||
// outlive the process).
|
||
rect_program: glow::Program,
|
||
tex_program: glow::Program,
|
||
glyph_program: glow::Program,
|
||
blit_program: glow::Program,
|
||
sub_blit_program: glow::Program,
|
||
linear_gradient_program: glow::Program,
|
||
radial_gradient_program: glow::Program,
|
||
shadow_outer_program: glow::Program,
|
||
shadow_inset_program: glow::Program,
|
||
|
||
// Shared geometry (a unit quad as two triangles)
|
||
quad_vao: glow::VertexArray,
|
||
_quad_vbo: glow::Buffer,
|
||
|
||
// Uniform locations for rect shader
|
||
u_rect_mvp: glow::UniformLocation,
|
||
u_rect_color: glow::UniformLocation,
|
||
u_rect_size: glow::UniformLocation,
|
||
u_rect_radii: glow::UniformLocation,
|
||
u_rect_stroke: glow::UniformLocation,
|
||
u_rect_pad: glow::UniformLocation,
|
||
|
||
// Uniform locations for texture shader
|
||
u_tex_mvp: glow::UniformLocation,
|
||
u_tex_opacity: glow::UniformLocation,
|
||
u_tex_sampler: glow::UniformLocation,
|
||
|
||
// Uniform locations for glyph shader
|
||
u_glyph_mvp: glow::UniformLocation,
|
||
u_glyph_color: glow::UniformLocation,
|
||
u_glyph_opacity: glow::UniformLocation,
|
||
u_glyph_sampler: glow::UniformLocation,
|
||
u_glyph_uv_offset: glow::UniformLocation,
|
||
u_glyph_uv_scale: glow::UniformLocation,
|
||
|
||
glyph_batch_program: glow::Program,
|
||
u_glyph_batch_color: glow::UniformLocation,
|
||
u_glyph_batch_opacity: glow::UniformLocation,
|
||
u_glyph_batch_sampler: glow::UniformLocation,
|
||
pub ( super ) glyph_batch_vao: glow::VertexArray,
|
||
pub ( super ) glyph_batch_vbo: glow::Buffer,
|
||
|
||
// Uniform location for blit shader
|
||
u_blit_sampler: glow::UniformLocation,
|
||
|
||
// Uniform locations for sub-canvas blit shader
|
||
u_subblit_mvp: glow::UniformLocation,
|
||
u_subblit_sampler: glow::UniformLocation,
|
||
u_subblit_opacity: glow::UniformLocation,
|
||
u_subblit_fade_bottom: glow::UniformLocation,
|
||
u_subblit_height_px: glow::UniformLocation,
|
||
|
||
// Uniform locations for the linear gradient shader
|
||
u_lingrad_mvp: glow::UniformLocation,
|
||
u_lingrad_lut: glow::UniformLocation,
|
||
u_lingrad_dir: glow::UniformLocation,
|
||
u_lingrad_size: glow::UniformLocation,
|
||
u_lingrad_line_length: glow::UniformLocation,
|
||
u_lingrad_radii: glow::UniformLocation,
|
||
u_lingrad_pad: glow::UniformLocation,
|
||
u_lingrad_lut_domain_min: glow::UniformLocation,
|
||
u_lingrad_lut_domain_span: glow::UniformLocation,
|
||
|
||
// Uniform locations for the radial gradient shader
|
||
u_radgrad_mvp: glow::UniformLocation,
|
||
u_radgrad_lut: glow::UniformLocation,
|
||
u_radgrad_center: glow::UniformLocation,
|
||
u_radgrad_radius_frac: glow::UniformLocation,
|
||
u_radgrad_size: glow::UniformLocation,
|
||
u_radgrad_radii: glow::UniformLocation,
|
||
u_radgrad_pad: glow::UniformLocation,
|
||
u_radgrad_lut_domain_min: glow::UniformLocation,
|
||
u_radgrad_lut_domain_span: glow::UniformLocation,
|
||
|
||
// Uniform locations for the outer shadow shader
|
||
u_shadow_mvp: glow::UniformLocation,
|
||
u_shadow_size: glow::UniformLocation,
|
||
u_shadow_padding: glow::UniformLocation,
|
||
u_shadow_radii: glow::UniformLocation,
|
||
u_shadow_spread: glow::UniformLocation,
|
||
u_shadow_sigma: glow::UniformLocation,
|
||
u_shadow_color: glow::UniformLocation,
|
||
|
||
// Uniform locations for the inner (inset) shadow shader
|
||
u_inset_mvp: glow::UniformLocation,
|
||
u_inset_size: glow::UniformLocation,
|
||
u_inset_padding: glow::UniformLocation,
|
||
u_inset_radii: glow::UniformLocation,
|
||
u_inset_spread: glow::UniformLocation,
|
||
u_inset_sigma: glow::UniformLocation,
|
||
u_inset_offset: glow::UniformLocation,
|
||
u_inset_color: glow::UniformLocation,
|
||
|
||
/// Inset-shadow shader variant for `BlendMode::Overlay`. Distinct
|
||
/// from [`Self::shadow_inset_program`] because CSS Overlay cannot
|
||
/// be expressed with fixed-function blend — this shader samples
|
||
/// the just-snapshotted FBO content (via `aux_a`) and
|
||
/// computes the per-channel Overlay formula in-shader, then
|
||
/// outputs premultiplied.
|
||
shadow_inset_overlay_program: glow::Program,
|
||
u_inset_ov_mvp: glow::UniformLocation,
|
||
u_inset_ov_size: glow::UniformLocation,
|
||
u_inset_ov_padding: glow::UniformLocation,
|
||
u_inset_ov_radii: glow::UniformLocation,
|
||
u_inset_ov_spread: glow::UniformLocation,
|
||
u_inset_ov_sigma: glow::UniformLocation,
|
||
u_inset_ov_offset: glow::UniformLocation,
|
||
u_inset_ov_color: glow::UniformLocation,
|
||
u_inset_ov_snapshot: glow::UniformLocation,
|
||
u_inset_ov_canvas_size: glow::UniformLocation,
|
||
|
||
/// Horizontal pass of the separable Gaussian used by
|
||
/// [`Self::fill_backdrop`](framebuffer). Samples
|
||
/// `aux_a` (snapshot of the main FBO) and writes the
|
||
/// horizontally-blurred result into `aux_b`.
|
||
backdrop_blur_h_program: glow::Program,
|
||
u_bd_h_source: glow::UniformLocation,
|
||
u_bd_h_texel: glow::UniformLocation,
|
||
u_bd_h_canvas_size: glow::UniformLocation,
|
||
u_bd_h_sigma: glow::UniformLocation,
|
||
|
||
/// Vertical pass of the separable Gaussian combined with the SDF
|
||
/// clip to the surface shape and optional tint. Reads
|
||
/// `aux_b` (H-blurred) and writes to the main FBO at the
|
||
/// surface rect.
|
||
backdrop_composite_program: glow::Program,
|
||
u_bd_c_mvp: glow::UniformLocation,
|
||
u_bd_c_source: glow::UniformLocation,
|
||
u_bd_c_canvas_size: glow::UniformLocation,
|
||
u_bd_c_texel: glow::UniformLocation,
|
||
u_bd_c_sigma: glow::UniformLocation,
|
||
u_bd_c_size: glow::UniformLocation,
|
||
u_bd_c_padding: glow::UniformLocation,
|
||
u_bd_c_radii: glow::UniformLocation,
|
||
u_bd_c_tint: glow::UniformLocation,
|
||
|
||
/// Fast (low-quality) horizontal Gaussian. Same role as
|
||
/// `backdrop_blur_h_program` but with a 9-tap kernel
|
||
/// (`RADIUS = 4`) instead of 41 taps. Used during animations /
|
||
/// drags via [`crate::render::low_quality_paint`].
|
||
backdrop_fast_blur_h_program: glow::Program,
|
||
u_bd_fh_source: glow::UniformLocation,
|
||
u_bd_fh_texel: glow::UniformLocation,
|
||
u_bd_fh_canvas_size: glow::UniformLocation,
|
||
u_bd_fh_sigma: glow::UniformLocation,
|
||
|
||
/// Fast (low-quality) vertical + SDF + tint composite. 9-tap
|
||
/// kernel counterpart to `backdrop_composite_program`.
|
||
backdrop_fast_composite_program: glow::Program,
|
||
u_bd_fc_mvp: glow::UniformLocation,
|
||
u_bd_fc_source: glow::UniformLocation,
|
||
u_bd_fc_canvas_size: glow::UniformLocation,
|
||
u_bd_fc_texel: glow::UniformLocation,
|
||
u_bd_fc_sigma: glow::UniformLocation,
|
||
u_bd_fc_size: glow::UniformLocation,
|
||
u_bd_fc_padding: glow::UniformLocation,
|
||
u_bd_fc_radii: glow::UniformLocation,
|
||
u_bd_fc_tint: glow::UniformLocation,
|
||
|
||
atlas_texture: glow::Texture,
|
||
/// Upload format of `atlas_texture`. On GLES3 we use the modern
|
||
/// single-channel `GL_RED` over `GL_R8`; on GLES2 we keep the
|
||
/// legacy `GL_LUMINANCE` because `GL_R8` / `GL_RED` did not exist
|
||
/// before ES3. The fragment shader samples `.r` and is identical
|
||
/// for both — `GL_LUMINANCE` replicates the single channel into
|
||
/// `.r=.g=.b`, so `.r` carries the coverage either way.
|
||
pub ( super ) atlas_format: u32,
|
||
atlas_cursor_x: u32,
|
||
atlas_cursor_y: u32,
|
||
atlas_row_height: u32,
|
||
|
||
// (glyph_id, size_key, font_id) → GlyphEntry. `glyph_id` is the
|
||
// per-font glyph index returned by HarfBuzz shaping; the cache
|
||
// therefore persists Arabic / Devanagari / CJK shaped forms
|
||
// independently of the source codepoints.
|
||
glyph_cache: HashMap<GlyphAtlasKey, GlyphEntry>,
|
||
|
||
// Reusable texture cache for images. Keyed by
|
||
// `(width, height, content fingerprint)` rather than the source
|
||
// buffer's heap address — pointer-keying produced ghosting when
|
||
// short-lived `Arc<Vec<u8>>` buffers got dropped and the
|
||
// allocator handed the same address to a different buffer next
|
||
// frame (the cache would happily serve the stale texture).
|
||
// Content-keying tolerates that case at the cost of one
|
||
// `DefaultHasher` pass over the bytes per draw call — fast for
|
||
// any reasonable icon size. Bounded by `image.rs`'s
|
||
// `IMAGE_CACHE_MAX_BYTES` with LRU eviction.
|
||
image_cache: HashMap<(u32, u32, u64), (glow::Texture, u32, u32)>,
|
||
// LRU order of `image_cache` keys, least-recent at the front.
|
||
image_cache_lru: VecDeque<(u32, u32, u64)>,
|
||
// Estimated GPU bytes held by `image_cache` (RGBA8: w × h × 4).
|
||
image_cache_bytes: usize,
|
||
|
||
// Gradient LUT cache: FNV-ish hash of the 512×RGBA8 LUT bytes → texture.
|
||
// Gradients are theme-derived and constant across frames; caching avoids
|
||
// a glTexImage2D round-trip (create + upload + delete) on every draw call.
|
||
// Cleared via `clear_gradient_cache()` on theme changes.
|
||
gradient_lut_cache: HashMap<u64, glow::Texture>,
|
||
|
||
/// Current scissor: `Some(rect)` when a clip is installed
|
||
/// (GL_SCISSOR_TEST is enabled), `None` when cleared.
|
||
clip_scissor: Option<Rect>,
|
||
|
||
/// Anti-aliased path clip (`set_clip_path`). While `clip_layer_active`,
|
||
/// `activate_target` redirects draws to `clip_layer` (a full-canvas
|
||
/// offscreen FBO); ending the clip composites that layer back onto `fbo`
|
||
/// multiplied by `clip_mask_tex` (an anti-aliased coverage texture covering
|
||
/// `clip_bbox`). The layer FBO is allocated lazily on the first path clip.
|
||
clip_layer: Option<( glow::Framebuffer, glow::Texture )>,
|
||
clip_layer_active: bool,
|
||
clip_mask_tex: Option<glow::Texture>,
|
||
clip_bbox: Rect,
|
||
/// Composite program (layer × coverage mask). Shared with sub-canvases.
|
||
clip_composite_program: glow::Program,
|
||
u_clip_mvp: glow::UniformLocation,
|
||
u_clip_layer: glow::UniformLocation,
|
||
u_clip_mask: glow::UniformLocation,
|
||
u_clip_canvas: glow::UniformLocation,
|
||
u_clip_bbox: glow::UniformLocation,
|
||
|
||
/// Persistent shadow framebuffer. All draw methods bind this;
|
||
/// [`Self::present`](framebuffer) is the only call that switches
|
||
/// to the default framebuffer.
|
||
fbo: glow::Framebuffer,
|
||
/// Color attachment for `fbo`. Reallocated on resize.
|
||
fbo_tex: glow::Texture,
|
||
|
||
/// Auxiliary FBO + texture used as a snapshot of `fbo`
|
||
/// for framebuffer-fetch-style effects (CSS `Overlay` blend,
|
||
/// backdrop blur). Lazily allocated on first use; dropped on
|
||
/// resize so the next user re-allocates at the new size.
|
||
aux_a: Option<( glow::Framebuffer, glow::Texture )>,
|
||
/// Second auxiliary FBO, used as ping-pong target for the
|
||
/// separable Gaussian blur in backdrop compositing. Uses LINEAR
|
||
/// filtering for the V-pass bilinear sampling (vs `aux_a`'s
|
||
/// NEAREST).
|
||
aux_b: Option<( glow::Framebuffer, glow::Texture )>,
|
||
}
|
||
|
||
// ─── Drop ────────────────────────────────────────────────────────────────────
|
||
|
||
/// Free this canvas's owned GL resources: FBO, color attachment, aux
|
||
/// FBOs if allocated, and any cached glyph / image textures. Shader
|
||
/// programs and the quad VAO/VBO are shared with sub-canvases and
|
||
/// intentionally NOT deleted here — they leak at process exit, which
|
||
/// is fine for a process-wide GL context.
|
||
impl Drop for GlesCanvas
|
||
{
|
||
fn drop( &mut self )
|
||
{
|
||
// SAFETY: every handle freed below was created through `self.gl` —
|
||
// either in `setup.rs::new` / `sub_canvas` (`fbo`, `fbo_tex`),
|
||
// `framebuffer.rs::ensure_aux_a` / `ensure_aux_b` (`aux_a`, `aux_b`),
|
||
// `text.rs::draw_text` (`glyph_cache`), `image.rs::draw_image_data`
|
||
// (`image_cache`), or `primitives.rs::ensure_lut_texture`
|
||
// (`gradient_lut_cache`). Each container `drain` / `take` is
|
||
// consumed once so no double-free is possible. Caller must keep
|
||
// the GL context current at drop time — this is documented on
|
||
// `core::UiSurface::from_current_gles_loader` and
|
||
// `from_canvas_with_egl_context`.
|
||
unsafe
|
||
{
|
||
self.gl.delete_framebuffer( self.fbo );
|
||
self.gl.delete_texture( self.fbo_tex );
|
||
if let Some( ( fbo, tex ) ) = self.clip_layer.take()
|
||
{
|
||
self.gl.delete_framebuffer( fbo );
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
if let Some( tex ) = self.clip_mask_tex.take()
|
||
{
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
if let Some( ( fbo, tex ) ) = self.aux_a.take()
|
||
{
|
||
self.gl.delete_framebuffer( fbo );
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
if let Some( ( fbo, tex ) ) = self.aux_b.take()
|
||
{
|
||
self.gl.delete_framebuffer( fbo );
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
self.glyph_cache.clear();
|
||
self.gl.delete_texture( self.atlas_texture );
|
||
for ( _, ( tex, _, _ ) ) in self.image_cache.drain()
|
||
{
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
for ( _, tex ) in self.gradient_lut_cache.drain()
|
||
{
|
||
self.gl.delete_texture( tex );
|
||
}
|
||
}
|
||
}
|
||
}
|