First commit. Version 0.1.0
This commit is contained in:
387
src/gles_render/mod.rs
Normal file
387
src/gles_render/mod.rs
Normal file
@@ -0,0 +1,387 @@
|
||||
// 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.
|
||||
//!
|
||||
//! 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. Disjoint
|
||||
//! regions would want a stencil-buffer path; not implemented today.
|
||||
//!
|
||||
//! # 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, draw_glyph_texture}`.
|
||||
//! * `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,
|
||||
//! `find_font` + `SYSTEM_FONT_CANDIDATES`.
|
||||
//! * `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;
|
||||
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: pre-rasterized bitmap uploaded as a GL texture.
|
||||
pub ( super ) struct GlyphEntry
|
||||
{
|
||||
pub ( super ) texture: glow::Texture,
|
||||
pub ( super ) metrics: fontdue::Metrics,
|
||||
pub ( super ) tex_w: i32,
|
||||
pub ( super ) tex_h: i32,
|
||||
}
|
||||
|
||||
// ─── 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 `helpers::find_font`.
|
||||
/// Kept as a fallback for callers that do not route through the
|
||||
/// theme registry.
|
||||
pub font: Arc<Font>,
|
||||
/// 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,
|
||||
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,
|
||||
|
||||
// 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,
|
||||
|
||||
// Glyph cache: (char, size_key, font_id) → GlyphEntry. The
|
||||
// `font_id` is the address of the `Arc<Font>` used for the
|
||||
// rasterisation, so distinct weights / families of the same
|
||||
// (char, size) keep separate atlas entries.
|
||||
glyph_cache: HashMap<(char, u32, usize), 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.
|
||||
image_cache: HashMap<(u32, u32, u64), (glow::Texture, u32, u32)>,
|
||||
|
||||
// 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>,
|
||||
|
||||
/// 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.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 );
|
||||
}
|
||||
for ( _, entry ) in self.glyph_cache.drain()
|
||||
{
|
||||
self.gl.delete_texture( entry.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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user