First commit. Version 0.1.0
This commit is contained in:
657
src/gles_render/setup.rs
Normal file
657
src/gles_render/setup.rs
Normal file
@@ -0,0 +1,657 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! Construction + accessors for [`GlesCanvas`].
|
||||
//!
|
||||
//! The bulk of `GlesCanvas::new` is one big shader-compile +
|
||||
//! uniform-lookup block: each of the shader programs gets compiled
|
||||
//! from [`super::shaders`], its uniform locations are pulled out via
|
||||
//! `get_uniform_location`, and both end up as `Copy` handles on the
|
||||
//! struct so sub-canvases can share them for free. `sub_canvas` is
|
||||
//! the same struct-literal again with the `new`-only bootstrap
|
||||
//! elided (programs, VAO, default font all come from the parent).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{ Arc, OnceLock };
|
||||
|
||||
use fontdue::{ Font, FontSettings, LineMetrics, Metrics };
|
||||
use glow::HasContext;
|
||||
|
||||
use crate::theme::{ FontRegistry, FontStyle };
|
||||
|
||||
use super::helpers::{ alloc_fbo_tex, bytemuck_cast_slice, compile_program, load_default_font_bytes };
|
||||
use super::shaders::
|
||||
{
|
||||
BACKDROP_BLUR_H_FRAG_SRC, BACKDROP_COMPOSITE_FRAG_SRC,
|
||||
BACKDROP_FAST_BLUR_H_FRAG_SRC, BACKDROP_FAST_COMPOSITE_FRAG_SRC,
|
||||
BLIT_FRAG_SRC, BLIT_VERT_SRC,
|
||||
GLYPH_FRAG_SRC,
|
||||
LINEAR_GRADIENT_FRAG_SRC, RADIAL_GRADIENT_FRAG_SRC,
|
||||
RECT_FRAG_SRC,
|
||||
SHADOW_INSET_FRAG_SRC, SHADOW_INSET_OVERLAY_FRAG_SRC, SHADOW_OUTER_FRAG_SRC,
|
||||
SUB_BLIT_FRAG_SRC,
|
||||
TEX_FRAG_SRC, VERT_SRC,
|
||||
};
|
||||
use super::{ GlesCanvas, GlesVersion };
|
||||
|
||||
/// Process-wide cache of the GLES path's default font. Avoids
|
||||
/// re-reading + re-parsing the small Sora face on every surface
|
||||
/// bring-up. The fallback chain (Noto Sans / CJK / Devanagari / …)
|
||||
/// is owned by the crate-private system-fonts module and loaded
|
||||
/// lazily per codepoint, not per canvas.
|
||||
static DEFAULT_FONT_GLES: OnceLock<Arc<Font>> = OnceLock::new();
|
||||
|
||||
fn default_font_gles() -> Arc<Font>
|
||||
{
|
||||
Arc::clone( DEFAULT_FONT_GLES.get_or_init( ||
|
||||
{
|
||||
let bytes = load_default_font_bytes();
|
||||
let font = Font::from_bytes( bytes.as_slice(), FontSettings::default() )
|
||||
.expect( "bad font" );
|
||||
Arc::new( font )
|
||||
} ) )
|
||||
}
|
||||
|
||||
impl GlesCanvas
|
||||
{
|
||||
pub fn new( gl: Arc<glow::Context>, version: GlesVersion, width: u32, height: u32 ) -> Self
|
||||
{
|
||||
let font = default_font_gles();
|
||||
|
||||
let rect_program = compile_program( &gl, VERT_SRC, RECT_FRAG_SRC );
|
||||
let tex_program = compile_program( &gl, VERT_SRC, TEX_FRAG_SRC );
|
||||
let glyph_program = compile_program( &gl, VERT_SRC, GLYPH_FRAG_SRC );
|
||||
let blit_program = compile_program( &gl, BLIT_VERT_SRC, BLIT_FRAG_SRC );
|
||||
let sub_blit_program = compile_program( &gl, VERT_SRC, SUB_BLIT_FRAG_SRC );
|
||||
let linear_gradient_program = compile_program( &gl, VERT_SRC, LINEAR_GRADIENT_FRAG_SRC );
|
||||
let radial_gradient_program = compile_program( &gl, VERT_SRC, RADIAL_GRADIENT_FRAG_SRC );
|
||||
let shadow_outer_program = compile_program( &gl, VERT_SRC, SHADOW_OUTER_FRAG_SRC );
|
||||
let shadow_inset_program = compile_program( &gl, VERT_SRC, SHADOW_INSET_FRAG_SRC );
|
||||
let shadow_inset_overlay_program = compile_program( &gl, VERT_SRC, SHADOW_INSET_OVERLAY_FRAG_SRC );
|
||||
let backdrop_blur_h_program = compile_program( &gl, BLIT_VERT_SRC, BACKDROP_BLUR_H_FRAG_SRC );
|
||||
let backdrop_composite_program = compile_program( &gl, VERT_SRC, BACKDROP_COMPOSITE_FRAG_SRC );
|
||||
let backdrop_fast_blur_h_program = compile_program( &gl, BLIT_VERT_SRC, BACKDROP_FAST_BLUR_H_FRAG_SRC );
|
||||
let backdrop_fast_composite_program = compile_program( &gl, VERT_SRC, BACKDROP_FAST_COMPOSITE_FRAG_SRC );
|
||||
|
||||
// SAFETY: every program in scope has just been linked successfully by
|
||||
// `compile_program` (which panics on link failure), so `get_uniform_location`
|
||||
// on these programs is well-defined. Each name argument is a `'static`
|
||||
// ASCII literal — `glow` will not invoke UB on a malformed C string.
|
||||
// `get_uniform_location` does not mutate the GL state machine, so this
|
||||
// block has no interaction with whatever bindings precede it.
|
||||
let (
|
||||
u_rect_mvp, u_rect_color, u_rect_size, u_rect_radii, u_rect_stroke, u_rect_pad,
|
||||
u_tex_mvp, u_tex_opacity, u_tex_sampler,
|
||||
u_glyph_mvp, u_glyph_color, u_glyph_opacity, u_glyph_sampler,
|
||||
u_blit_sampler,
|
||||
u_subblit_mvp, u_subblit_sampler, u_subblit_opacity, u_subblit_fade_bottom, u_subblit_height_px,
|
||||
u_lingrad_mvp, u_lingrad_lut, u_lingrad_dir, u_lingrad_size, u_lingrad_line_length,
|
||||
u_lingrad_radii, u_lingrad_pad, u_lingrad_lut_domain_min, u_lingrad_lut_domain_span,
|
||||
u_radgrad_mvp, u_radgrad_lut, u_radgrad_center, u_radgrad_radius_frac, u_radgrad_size,
|
||||
u_radgrad_radii, u_radgrad_pad, u_radgrad_lut_domain_min, u_radgrad_lut_domain_span,
|
||||
u_shadow_mvp, u_shadow_size, u_shadow_padding, u_shadow_radii, u_shadow_spread, u_shadow_sigma, u_shadow_color,
|
||||
u_inset_mvp, u_inset_size, u_inset_padding, u_inset_radii, u_inset_spread, u_inset_sigma, u_inset_offset, u_inset_color,
|
||||
u_inset_ov_mvp, u_inset_ov_size, u_inset_ov_padding, u_inset_ov_radii,
|
||||
u_inset_ov_spread, u_inset_ov_sigma, u_inset_ov_offset, u_inset_ov_color,
|
||||
u_inset_ov_snapshot, u_inset_ov_canvas_size,
|
||||
u_bd_h_source, u_bd_h_texel, u_bd_h_canvas_size, u_bd_h_sigma,
|
||||
u_bd_c_mvp, u_bd_c_source, u_bd_c_canvas_size, u_bd_c_texel, u_bd_c_sigma,
|
||||
u_bd_c_size, u_bd_c_padding, u_bd_c_radii, u_bd_c_tint,
|
||||
u_bd_fh_source, u_bd_fh_texel, u_bd_fh_canvas_size, u_bd_fh_sigma,
|
||||
u_bd_fc_mvp, u_bd_fc_source, u_bd_fc_canvas_size, u_bd_fc_texel, u_bd_fc_sigma,
|
||||
u_bd_fc_size, u_bd_fc_padding, u_bd_fc_radii, u_bd_fc_tint,
|
||||
) = unsafe
|
||||
{(
|
||||
gl.get_uniform_location( rect_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( rect_program, "u_color" ).unwrap(),
|
||||
gl.get_uniform_location( rect_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( rect_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( rect_program, "u_stroke" ).unwrap(),
|
||||
gl.get_uniform_location( rect_program, "u_pad" ).unwrap(),
|
||||
gl.get_uniform_location( tex_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( tex_program, "u_opacity" ).unwrap(),
|
||||
gl.get_uniform_location( tex_program, "u_sampler" ).unwrap(),
|
||||
gl.get_uniform_location( glyph_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( glyph_program, "u_color" ).unwrap(),
|
||||
gl.get_uniform_location( glyph_program, "u_opacity" ).unwrap(),
|
||||
gl.get_uniform_location( glyph_program, "u_sampler" ).unwrap(),
|
||||
gl.get_uniform_location( blit_program, "u_sampler" ).unwrap(),
|
||||
gl.get_uniform_location( sub_blit_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( sub_blit_program, "u_sampler" ).unwrap(),
|
||||
gl.get_uniform_location( sub_blit_program, "u_opacity" ).unwrap(),
|
||||
gl.get_uniform_location( sub_blit_program, "u_fade_bottom_px" ).unwrap(),
|
||||
gl.get_uniform_location( sub_blit_program, "u_height_px" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_lut" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_dir" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_line_length" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_pad" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_lut_domain_min" ).unwrap(),
|
||||
gl.get_uniform_location( linear_gradient_program, "u_lut_domain_span" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_lut" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_center" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_radius_frac" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_pad" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_lut_domain_min" ).unwrap(),
|
||||
gl.get_uniform_location( radial_gradient_program, "u_lut_domain_span" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_padding" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_spread" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_outer_program, "u_color" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_padding" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_spread" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_offset" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_program, "u_color" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_padding" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_spread" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_offset" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_color" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_snapshot" ).unwrap(),
|
||||
gl.get_uniform_location( shadow_inset_overlay_program, "u_canvas_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_blur_h_program, "u_source" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_blur_h_program, "u_texel" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_blur_h_program, "u_canvas_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_blur_h_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_source" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_canvas_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_texel" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_padding" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_composite_program, "u_tint" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_blur_h_program, "u_source" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_blur_h_program, "u_texel" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_blur_h_program, "u_canvas_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_blur_h_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_mvp" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_source" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_canvas_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_texel" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_sigma" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_size" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_padding" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_radii" ).unwrap(),
|
||||
gl.get_uniform_location( backdrop_fast_composite_program, "u_tint" ).unwrap(),
|
||||
)};
|
||||
|
||||
let quad_vertices: [f32; 12] = [
|
||||
0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||
];
|
||||
// SAFETY: the GL context is current (caller contract). We allocate a
|
||||
// VAO + VBO, upload `quad_vertices` (24 bytes, statically known size,
|
||||
// matches `STATIC_DRAW` semantics), and configure attribute 0 to read
|
||||
// 2-float vertices from the VBO at offset 0 with stride 8. The vertex
|
||||
// attrib pointer is bound to `vbo` because `vbo` is the current
|
||||
// `ARRAY_BUFFER` binding when `vertex_attrib_pointer_f32` runs. We
|
||||
// unbind the VAO at the end so we don't strand a binding the rest of
|
||||
// `new` might inherit; the VBO remains attached to the VAO and is not
|
||||
// touched again until `Drop`.
|
||||
let ( quad_vao, quad_vbo ) = unsafe
|
||||
{
|
||||
let vao = gl.create_vertex_array().unwrap();
|
||||
let vbo = gl.create_buffer().unwrap();
|
||||
gl.bind_vertex_array( Some( vao ) );
|
||||
gl.bind_buffer( glow::ARRAY_BUFFER, Some( vbo ) );
|
||||
gl.buffer_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
bytemuck_cast_slice( &quad_vertices ),
|
||||
glow::STATIC_DRAW,
|
||||
);
|
||||
gl.enable_vertex_attrib_array( 0 );
|
||||
gl.vertex_attrib_pointer_f32( 0, 2, glow::FLOAT, false, 8, 0 );
|
||||
gl.bind_vertex_array( None );
|
||||
( vao, vbo )
|
||||
};
|
||||
|
||||
// Build the persistent FBO (shadow canvas) and bind it as the active
|
||||
// draw target. From here on, every draw method writes into the FBO;
|
||||
// `present` is the only call that switches to the default framebuffer.
|
||||
//
|
||||
// SAFETY: the GL context is current. `create_framebuffer` allocates a
|
||||
// fresh FBO name. `alloc_fbo_tex` (an `unsafe fn`) requires the same
|
||||
// invariant and returns a colour-renderable RGBA texture sized to the
|
||||
// caller's width × height — the call is sound because both invariants
|
||||
// are established. The bind + framebuffer_texture_2d pair attach the
|
||||
// texture to COLOR_ATTACHMENT0; `check_framebuffer_status` is the
|
||||
// canonical post-condition check and will assert before we return a
|
||||
// handle to a broken FBO. The trailing GL state (`BLEND`, `blend_func`,
|
||||
// `viewport`, `clear`) is the canvas-wide default state every draw
|
||||
// method assumes — see the comment block beside `blend_func` for why
|
||||
// `(ONE, ONE_MINUS_SRC_ALPHA)` is the correct pair for premultiplied
|
||||
// shaders.
|
||||
let ( fbo, fbo_tex ) = unsafe
|
||||
{
|
||||
let fbo = gl.create_framebuffer().expect( "create_framebuffer" );
|
||||
let fbo_tex = alloc_fbo_tex( &gl, version, width, height );
|
||||
gl.bind_framebuffer( glow::FRAMEBUFFER, Some( fbo ) );
|
||||
gl.framebuffer_texture_2d(
|
||||
glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0,
|
||||
glow::TEXTURE_2D, Some( fbo_tex ), 0,
|
||||
);
|
||||
let status = gl.check_framebuffer_status( glow::FRAMEBUFFER );
|
||||
assert_eq!( status, glow::FRAMEBUFFER_COMPLETE, "FBO incomplete: 0x{status:x}" );
|
||||
|
||||
gl.enable( glow::BLEND );
|
||||
// Premultiplied-alpha "over" composite: `result = src + dst * (1 - src.a)`
|
||||
// applied uniformly to both colour and alpha. All shaders in this
|
||||
// pipeline emit premul colour (see the note above `RECT_FRAG_SRC`),
|
||||
// so `(ONE, ONE_MINUS_SRC_ALPHA)` is correct for both channels.
|
||||
// Premultiplied inputs are also a requirement for the plus-lighter
|
||||
// and overlay blend modes.
|
||||
gl.blend_func( glow::ONE, glow::ONE_MINUS_SRC_ALPHA );
|
||||
gl.viewport( 0, 0, width as i32, height as i32 );
|
||||
// Clear the FBO once at startup so the first frame is not garbage.
|
||||
gl.clear_color( 0.0, 0.0, 0.0, 0.0 );
|
||||
gl.clear( glow::COLOR_BUFFER_BIT );
|
||||
( fbo, fbo_tex )
|
||||
};
|
||||
|
||||
Self
|
||||
{
|
||||
gl,
|
||||
version,
|
||||
font,
|
||||
font_registry: None,
|
||||
dpi_scale: 1.0,
|
||||
global_alpha: 1.0,
|
||||
width,
|
||||
height,
|
||||
rect_program,
|
||||
tex_program,
|
||||
glyph_program,
|
||||
blit_program,
|
||||
sub_blit_program,
|
||||
linear_gradient_program,
|
||||
radial_gradient_program,
|
||||
shadow_outer_program,
|
||||
shadow_inset_program,
|
||||
shadow_inset_overlay_program,
|
||||
quad_vao,
|
||||
_quad_vbo: quad_vbo,
|
||||
u_rect_mvp,
|
||||
u_rect_color,
|
||||
u_rect_size,
|
||||
u_rect_radii,
|
||||
u_rect_stroke,
|
||||
u_rect_pad,
|
||||
u_tex_mvp,
|
||||
u_tex_opacity,
|
||||
u_tex_sampler,
|
||||
u_glyph_mvp,
|
||||
u_glyph_color,
|
||||
u_glyph_opacity,
|
||||
u_glyph_sampler,
|
||||
u_blit_sampler,
|
||||
u_subblit_mvp,
|
||||
u_subblit_sampler,
|
||||
u_subblit_opacity,
|
||||
u_subblit_fade_bottom,
|
||||
u_subblit_height_px,
|
||||
u_lingrad_mvp,
|
||||
u_lingrad_lut,
|
||||
u_lingrad_dir,
|
||||
u_lingrad_size,
|
||||
u_lingrad_line_length,
|
||||
u_lingrad_radii,
|
||||
u_lingrad_pad,
|
||||
u_lingrad_lut_domain_min,
|
||||
u_lingrad_lut_domain_span,
|
||||
u_radgrad_mvp,
|
||||
u_radgrad_lut,
|
||||
u_radgrad_center,
|
||||
u_radgrad_radius_frac,
|
||||
u_radgrad_size,
|
||||
u_radgrad_radii,
|
||||
u_radgrad_pad,
|
||||
u_radgrad_lut_domain_min,
|
||||
u_radgrad_lut_domain_span,
|
||||
u_shadow_mvp,
|
||||
u_shadow_size,
|
||||
u_shadow_padding,
|
||||
u_shadow_radii,
|
||||
u_shadow_spread,
|
||||
u_shadow_sigma,
|
||||
u_shadow_color,
|
||||
u_inset_mvp,
|
||||
u_inset_size,
|
||||
u_inset_padding,
|
||||
u_inset_radii,
|
||||
u_inset_spread,
|
||||
u_inset_sigma,
|
||||
u_inset_offset,
|
||||
u_inset_color,
|
||||
u_inset_ov_mvp,
|
||||
u_inset_ov_size,
|
||||
u_inset_ov_padding,
|
||||
u_inset_ov_radii,
|
||||
u_inset_ov_spread,
|
||||
u_inset_ov_sigma,
|
||||
u_inset_ov_offset,
|
||||
u_inset_ov_color,
|
||||
u_inset_ov_snapshot,
|
||||
u_inset_ov_canvas_size,
|
||||
backdrop_blur_h_program,
|
||||
u_bd_h_source,
|
||||
u_bd_h_texel,
|
||||
u_bd_h_canvas_size,
|
||||
u_bd_h_sigma,
|
||||
backdrop_composite_program,
|
||||
u_bd_c_mvp,
|
||||
u_bd_c_source,
|
||||
u_bd_c_canvas_size,
|
||||
u_bd_c_texel,
|
||||
u_bd_c_sigma,
|
||||
u_bd_c_size,
|
||||
u_bd_c_padding,
|
||||
u_bd_c_radii,
|
||||
u_bd_c_tint,
|
||||
backdrop_fast_blur_h_program,
|
||||
u_bd_fh_source,
|
||||
u_bd_fh_texel,
|
||||
u_bd_fh_canvas_size,
|
||||
u_bd_fh_sigma,
|
||||
backdrop_fast_composite_program,
|
||||
u_bd_fc_mvp,
|
||||
u_bd_fc_source,
|
||||
u_bd_fc_canvas_size,
|
||||
u_bd_fc_texel,
|
||||
u_bd_fc_sigma,
|
||||
u_bd_fc_size,
|
||||
u_bd_fc_padding,
|
||||
u_bd_fc_radii,
|
||||
u_bd_fc_tint,
|
||||
glyph_cache: HashMap::new(),
|
||||
image_cache: HashMap::new(),
|
||||
gradient_lut_cache: HashMap::new(),
|
||||
clip_scissor: None,
|
||||
fbo,
|
||||
fbo_tex,
|
||||
aux_a: None,
|
||||
aux_b: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a sub-canvas: a separate render target sharing this canvas's GL
|
||||
/// context, font, shader programs, geometry, and uniform locations, but
|
||||
/// with its own FBO + color texture sized to `width × height`. Used to
|
||||
/// render content into an off-screen target that can then be composited
|
||||
/// back via [`Self::blit`].
|
||||
///
|
||||
/// The returned canvas inherits the parent's `dpi_scale` and `global_alpha`
|
||||
/// (so glyphs render at the same pixel size). Its glyph cache starts empty
|
||||
/// — re-rasterising on first use is the cost of not sharing GL textures
|
||||
/// across canvases.
|
||||
pub fn sub_canvas( &self, width: u32, height: u32 ) -> GlesCanvas
|
||||
{
|
||||
let gl = Arc::clone( &self.gl );
|
||||
// SAFETY: `self.gl` is the same context held by `self`; if `self` was
|
||||
// constructed soundly its context is current on this thread. We
|
||||
// allocate a new FBO + colour texture and attach them, then assert
|
||||
// completeness. We deliberately leave `gl` with the new FBO bound
|
||||
// instead of restoring the parent's binding — every draw method goes
|
||||
// through `activate_target`, which re-binds the canvas's own FBO
|
||||
// before issuing any draw call, so the transient binding cannot be
|
||||
// observed by other code on this thread.
|
||||
let ( fbo, fbo_tex ) = unsafe
|
||||
{
|
||||
let fbo = gl.create_framebuffer().expect( "create_framebuffer" );
|
||||
let fbo_tex = alloc_fbo_tex( &gl, self.version, width, height );
|
||||
gl.bind_framebuffer( glow::FRAMEBUFFER, Some( fbo ) );
|
||||
gl.framebuffer_texture_2d(
|
||||
glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0,
|
||||
glow::TEXTURE_2D, Some( fbo_tex ), 0,
|
||||
);
|
||||
let status = gl.check_framebuffer_status( glow::FRAMEBUFFER );
|
||||
assert_eq!( status, glow::FRAMEBUFFER_COMPLETE, "sub-FBO incomplete: 0x{status:x}" );
|
||||
( fbo, fbo_tex )
|
||||
};
|
||||
|
||||
GlesCanvas
|
||||
{
|
||||
gl,
|
||||
version: self.version,
|
||||
font: Arc::clone( &self.font ),
|
||||
font_registry: self.font_registry.as_ref().map( Arc::clone ),
|
||||
dpi_scale: self.dpi_scale,
|
||||
global_alpha: self.global_alpha,
|
||||
width,
|
||||
height,
|
||||
rect_program: self.rect_program,
|
||||
tex_program: self.tex_program,
|
||||
glyph_program: self.glyph_program,
|
||||
blit_program: self.blit_program,
|
||||
sub_blit_program: self.sub_blit_program,
|
||||
linear_gradient_program: self.linear_gradient_program,
|
||||
radial_gradient_program: self.radial_gradient_program,
|
||||
shadow_outer_program: self.shadow_outer_program,
|
||||
shadow_inset_program: self.shadow_inset_program,
|
||||
shadow_inset_overlay_program: self.shadow_inset_overlay_program,
|
||||
backdrop_blur_h_program: self.backdrop_blur_h_program,
|
||||
backdrop_composite_program: self.backdrop_composite_program,
|
||||
backdrop_fast_blur_h_program: self.backdrop_fast_blur_h_program,
|
||||
backdrop_fast_composite_program: self.backdrop_fast_composite_program,
|
||||
quad_vao: self.quad_vao,
|
||||
_quad_vbo: self._quad_vbo,
|
||||
u_rect_mvp: self.u_rect_mvp,
|
||||
u_rect_color: self.u_rect_color,
|
||||
u_rect_size: self.u_rect_size,
|
||||
u_rect_radii: self.u_rect_radii,
|
||||
u_rect_stroke: self.u_rect_stroke,
|
||||
u_rect_pad: self.u_rect_pad,
|
||||
u_tex_mvp: self.u_tex_mvp,
|
||||
u_tex_opacity: self.u_tex_opacity,
|
||||
u_tex_sampler: self.u_tex_sampler,
|
||||
u_glyph_mvp: self.u_glyph_mvp,
|
||||
u_glyph_color: self.u_glyph_color,
|
||||
u_glyph_opacity: self.u_glyph_opacity,
|
||||
u_glyph_sampler: self.u_glyph_sampler,
|
||||
u_blit_sampler: self.u_blit_sampler,
|
||||
u_subblit_mvp: self.u_subblit_mvp,
|
||||
u_subblit_sampler: self.u_subblit_sampler,
|
||||
u_subblit_opacity: self.u_subblit_opacity,
|
||||
u_subblit_fade_bottom: self.u_subblit_fade_bottom,
|
||||
u_subblit_height_px: self.u_subblit_height_px,
|
||||
u_lingrad_mvp: self.u_lingrad_mvp,
|
||||
u_lingrad_lut: self.u_lingrad_lut,
|
||||
u_lingrad_dir: self.u_lingrad_dir,
|
||||
u_lingrad_size: self.u_lingrad_size,
|
||||
u_lingrad_line_length: self.u_lingrad_line_length,
|
||||
u_lingrad_radii: self.u_lingrad_radii,
|
||||
u_lingrad_pad: self.u_lingrad_pad,
|
||||
u_lingrad_lut_domain_min: self.u_lingrad_lut_domain_min,
|
||||
u_lingrad_lut_domain_span: self.u_lingrad_lut_domain_span,
|
||||
u_radgrad_mvp: self.u_radgrad_mvp,
|
||||
u_radgrad_lut: self.u_radgrad_lut,
|
||||
u_radgrad_center: self.u_radgrad_center,
|
||||
u_radgrad_radius_frac: self.u_radgrad_radius_frac,
|
||||
u_radgrad_size: self.u_radgrad_size,
|
||||
u_radgrad_radii: self.u_radgrad_radii,
|
||||
u_radgrad_pad: self.u_radgrad_pad,
|
||||
u_radgrad_lut_domain_min: self.u_radgrad_lut_domain_min,
|
||||
u_radgrad_lut_domain_span: self.u_radgrad_lut_domain_span,
|
||||
u_shadow_mvp: self.u_shadow_mvp,
|
||||
u_shadow_size: self.u_shadow_size,
|
||||
u_shadow_padding: self.u_shadow_padding,
|
||||
u_shadow_radii: self.u_shadow_radii,
|
||||
u_shadow_spread: self.u_shadow_spread,
|
||||
u_shadow_sigma: self.u_shadow_sigma,
|
||||
u_shadow_color: self.u_shadow_color,
|
||||
u_inset_mvp: self.u_inset_mvp,
|
||||
u_inset_size: self.u_inset_size,
|
||||
u_inset_padding: self.u_inset_padding,
|
||||
u_inset_radii: self.u_inset_radii,
|
||||
u_inset_spread: self.u_inset_spread,
|
||||
u_inset_sigma: self.u_inset_sigma,
|
||||
u_inset_offset: self.u_inset_offset,
|
||||
u_inset_color: self.u_inset_color,
|
||||
u_inset_ov_mvp: self.u_inset_ov_mvp,
|
||||
u_inset_ov_size: self.u_inset_ov_size,
|
||||
u_inset_ov_padding: self.u_inset_ov_padding,
|
||||
u_inset_ov_radii: self.u_inset_ov_radii,
|
||||
u_inset_ov_spread: self.u_inset_ov_spread,
|
||||
u_inset_ov_sigma: self.u_inset_ov_sigma,
|
||||
u_inset_ov_offset: self.u_inset_ov_offset,
|
||||
u_inset_ov_color: self.u_inset_ov_color,
|
||||
u_inset_ov_snapshot: self.u_inset_ov_snapshot,
|
||||
u_inset_ov_canvas_size: self.u_inset_ov_canvas_size,
|
||||
u_bd_h_source: self.u_bd_h_source,
|
||||
u_bd_h_texel: self.u_bd_h_texel,
|
||||
u_bd_h_canvas_size: self.u_bd_h_canvas_size,
|
||||
u_bd_h_sigma: self.u_bd_h_sigma,
|
||||
u_bd_c_mvp: self.u_bd_c_mvp,
|
||||
u_bd_c_source: self.u_bd_c_source,
|
||||
u_bd_c_canvas_size: self.u_bd_c_canvas_size,
|
||||
u_bd_c_texel: self.u_bd_c_texel,
|
||||
u_bd_c_sigma: self.u_bd_c_sigma,
|
||||
u_bd_c_size: self.u_bd_c_size,
|
||||
u_bd_c_padding: self.u_bd_c_padding,
|
||||
u_bd_c_radii: self.u_bd_c_radii,
|
||||
u_bd_c_tint: self.u_bd_c_tint,
|
||||
u_bd_fh_source: self.u_bd_fh_source,
|
||||
u_bd_fh_texel: self.u_bd_fh_texel,
|
||||
u_bd_fh_canvas_size: self.u_bd_fh_canvas_size,
|
||||
u_bd_fh_sigma: self.u_bd_fh_sigma,
|
||||
u_bd_fc_mvp: self.u_bd_fc_mvp,
|
||||
u_bd_fc_source: self.u_bd_fc_source,
|
||||
u_bd_fc_canvas_size: self.u_bd_fc_canvas_size,
|
||||
u_bd_fc_texel: self.u_bd_fc_texel,
|
||||
u_bd_fc_sigma: self.u_bd_fc_sigma,
|
||||
u_bd_fc_size: self.u_bd_fc_size,
|
||||
u_bd_fc_padding: self.u_bd_fc_padding,
|
||||
u_bd_fc_radii: self.u_bd_fc_radii,
|
||||
u_bd_fc_tint: self.u_bd_fc_tint,
|
||||
glyph_cache: HashMap::new(),
|
||||
image_cache: HashMap::new(),
|
||||
gradient_lut_cache: HashMap::new(),
|
||||
clip_scissor: None,
|
||||
fbo,
|
||||
fbo_tex,
|
||||
aux_a: None,
|
||||
aux_b: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size( &self ) -> ( u32, u32 ) { ( self.width, self.height ) }
|
||||
|
||||
/// Discard all cached gradient LUT textures. Call after a theme change
|
||||
/// so stale LUTs for old palette colours are freed and rebuilt fresh.
|
||||
pub fn clear_gradient_cache( &mut self )
|
||||
{
|
||||
// SAFETY: GL context is current (canvas invariant). Each texture in
|
||||
// `gradient_lut_cache` was created through this same context in
|
||||
// `gradient.rs::ensure_lut`, so deleting them through the same context
|
||||
// is well-defined. `drain` consumes the entries so the same name is
|
||||
// never deleted twice.
|
||||
unsafe
|
||||
{
|
||||
for ( _, tex ) in self.gradient_lut_cache.drain()
|
||||
{
|
||||
self.gl.delete_texture( tex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dpi_scale( &self ) -> f32 { self.dpi_scale }
|
||||
|
||||
pub fn set_dpi_scale( &mut self, s: f32 ) { self.dpi_scale = s; }
|
||||
|
||||
pub fn global_alpha( &self ) -> f32 { self.global_alpha }
|
||||
|
||||
pub fn set_global_alpha( &mut self, a: f32 ) { self.global_alpha = a; }
|
||||
|
||||
pub fn font( &self ) -> &Font { &self.font }
|
||||
|
||||
/// Install a theme font registry so [`Self::font_for`] can resolve
|
||||
/// family+weight+style triples declared by the theme's `fonts` block.
|
||||
pub fn set_font_registry( &mut self, registry: Arc<FontRegistry> )
|
||||
{
|
||||
self.font_registry = Some( registry );
|
||||
}
|
||||
|
||||
/// Resolve a specific font from the theme registry, falling back to the
|
||||
/// canvas' default [`Self::font`] when no registry is installed or the
|
||||
/// triple cannot be satisfied.
|
||||
pub fn font_for( &self, family: &str, weight: u16, style: FontStyle ) -> Arc<Font>
|
||||
{
|
||||
self.font_registry
|
||||
.as_ref()
|
||||
.and_then( |r| r.resolve( family, weight, style ) )
|
||||
.unwrap_or_else( || Arc::clone( &self.font ) )
|
||||
}
|
||||
|
||||
/// Pick the right font for `ch`. Tries the primary [`Self::font`]
|
||||
/// first; on a miss, delegates to the crate-private system-fonts
|
||||
/// fallback chain (lazy load of the relevant Noto pack). Falls
|
||||
/// back to the primary (which paints a `.notdef` box) when no
|
||||
/// installed fallback covers the codepoint.
|
||||
pub fn font_for_char( &self, ch: char ) -> Arc<Font>
|
||||
{
|
||||
if self.font.lookup_glyph_index( ch ) != 0
|
||||
{
|
||||
return Arc::clone( &self.font );
|
||||
}
|
||||
crate::system_fonts::lookup( ch ).unwrap_or_else( || Arc::clone( &self.font ) )
|
||||
}
|
||||
|
||||
pub fn font_metrics( &self, ch: char, size: f32 ) -> Metrics
|
||||
{
|
||||
self.font_for_char( ch ).metrics( ch, size * self.dpi_scale )
|
||||
}
|
||||
|
||||
pub fn font_line_metrics( &self, size: f32 ) -> Option<LineMetrics>
|
||||
{
|
||||
self.font.horizontal_line_metrics( size )
|
||||
}
|
||||
|
||||
/// Resize the FBO and viewport. The previous color attachment is freed and
|
||||
/// a fresh one of the new size is attached — frame-N pixels are dropped, so
|
||||
/// the caller should expect to do a full redraw immediately after a resize.
|
||||
pub fn resize( &mut self, width: u32, height: u32 )
|
||||
{
|
||||
if width == self.width && height == self.height { return; }
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
// SAFETY: GL context is current (canvas invariant). Sequence:
|
||||
// release the old colour attachment (created via this context in
|
||||
// `new`/`sub_canvas`/last `resize`), allocate a fresh one of the
|
||||
// new size, swap it in for `COLOR_ATTACHMENT0` of the existing FBO,
|
||||
// resize the viewport to match, and clear so the first frame after
|
||||
// resize is well-defined RGBA. `self.fbo` is unchanged and remains
|
||||
// valid; only its colour attachment is replaced.
|
||||
unsafe
|
||||
{
|
||||
self.gl.delete_texture( self.fbo_tex );
|
||||
self.fbo_tex = alloc_fbo_tex( &self.gl, self.version, width, height );
|
||||
self.gl.bind_framebuffer( glow::FRAMEBUFFER, Some( self.fbo ) );
|
||||
self.gl.framebuffer_texture_2d(
|
||||
glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0,
|
||||
glow::TEXTURE_2D, Some( self.fbo_tex ), 0,
|
||||
);
|
||||
self.gl.viewport( 0, 0, width as i32, height as i32 );
|
||||
self.gl.clear_color( 0.0, 0.0, 0.0, 0.0 );
|
||||
self.gl.clear( glow::COLOR_BUFFER_BIT );
|
||||
}
|
||||
// Auxiliary textures were sized for the old dimensions — drop them so
|
||||
// the next effect that needs them re-allocates at the new size.
|
||||
self.invalidate_aux();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user