Files
ltk/src/gles_render/mod.rs
Pedro M. de Echanove Pasquin 640db23de2
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
doc: silence rustdoc intra-doc warnings (private-item links and palette/surface ambiguities)
`cargo doc --no-deps` was emitting eight warnings about intra-doc links resolving to private items or ambiguous fn/module names. None reflect a behaviour bug; they were just noise that cluttered the docs build output.
Five of the warnings came from doc comments that cross-referenced items not in the rendered public API. `widget::scroll::Scroll` (struct), `Scroll::horizontal`, `Scroll::both`, `event_loop::text_editing` (module) and `text_shaping` (module) are all `pub` in their own modules, but the `widget` and `event_loop` parents are private to the crate root, so rustdoc treats them as private when resolving links from items that ARE on the public surface (`ScrollAxis`, the `scroll` constructor, `font_bytes`, the `text_edit` module docs). The fix is to drop the link syntax for those references: keep the identifier in backticks (so it still renders as code) but remove the surrounding `[...]` so rustdoc doesn't try to resolve it. Where the cross-reference had no semantic load beyond "see this module", the prose is rephrased to name the module without trying to link to it (e.g. "the `event_loop::text_editing` private module", "see the `text_shaping` private module").
The remaining three warnings were `palette` / `surface` linking ambiguously between a module of that name and a function of the same name within `crate::theme`. Adding `()` after the identifier inside the brackets (`palette` → `palette()`, `surface` → `surface()`) disambiguates to the function, which is the intent in all three sites — the surrounding text talks about "per-slot shorthand accessors", which is what the functions are.
After this `cargo doc --no-deps` runs clean with no warnings.
2026-05-19 22:41:42 +02:00

424 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 13
//! 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}`.
//! * `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: 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 `helpers::find_font`.
/// 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,
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.
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 );
}
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 );
}
}
}
}