Content drawn inside a scroll rendered smaller than identical content outside it: the scroll viewport draws its child into a sub-canvas sized to the viewport rect, and Canvas::viewport_layout / viewport_logical — the viewports that geom_px and font_px resolve fluid Lengths against — returned the canvas's own size. Inside a 312 px sub-canvas on a 360 px surface every fluid geometry (icon sizes, row heights, paddings) and font size resolved against 312 instead of 360, shrinking the scroll content by the width ratio, ~13 % in a phone-sized window with 24 px margins. First observed in Eydos Settings' Wi-Fi list, where the connected network row sits outside the scroll and the rest inside: the same full-fan signal icon measured 21x18 px outside and 18x16 px inside. Add a layout_viewport field to SoftwareCanvas and GlesCanvas, None on root canvases and set by sub_canvas to the parent's effective layout viewport, so nested sub-canvases keep propagating the root surface size. viewport_layout and viewport_logical consult the inherited value before falling back to the canvas size. Root canvases are untouched, and the GLES clip layers, which reuse sub_canvas, get the same correction.
821 lines
36 KiB
Rust
821 lines
36 KiB
Rust
// 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;
|
||
|
||
use fontdue::{ Font, LineMetrics, Metrics };
|
||
use glow::HasContext;
|
||
|
||
use crate::theme::{ FontRegistry, FontStyle };
|
||
|
||
use super::helpers::{ alloc_fbo_tex, bytemuck_cast_slice, compile_program, compile_program_with_attribs };
|
||
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, GLYPH_BATCH_VERT_SRC, GLYPH_BATCH_FRAG_SRC,
|
||
LINEAR_GRADIENT_FRAG_SRC, RADIAL_GRADIENT_FRAG_SRC,
|
||
RECT_FRAG_SRC,
|
||
CLIP_COMPOSITE_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 };
|
||
|
||
impl GlesCanvas
|
||
{
|
||
/// Build a GPU canvas of `width × height` physical px on an already-current
|
||
/// EGL/GLES context. Compiles every shader program, looks up its uniforms,
|
||
/// uploads the shared quad geometry and the glyph atlas (format chosen per ES
|
||
/// profile), then allocates the persistent shadow FBO and makes it the active
|
||
/// draw target — every draw writes into the FBO, and [`Self::present`] is the
|
||
/// only call that blits it to the default framebuffer. `dpi_scale` and
|
||
/// `global_alpha` start at 1.0; the default font comes from the process-wide
|
||
/// cached handle. Panics if the FBO is incomplete or a program fails to link.
|
||
pub fn new( gl: Arc<glow::Context>, version: GlesVersion, width: u32, height: u32 ) -> Self
|
||
{
|
||
let font_handle = crate::system_fonts::default_handle();
|
||
let font = font_handle.font.clone();
|
||
let font_bytes = font_handle.bytes.clone();
|
||
let font_face = font_handle.face;
|
||
|
||
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 glyph_batch_program = compile_program_with_attribs( &gl, GLYPH_BATCH_VERT_SRC, GLYPH_BATCH_FRAG_SRC, &[ ( 0, "a_pos" ), ( 1, "a_uv" ) ] );
|
||
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_glyph_uv_offset, u_glyph_uv_scale,
|
||
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( glyph_program, "u_uv_offset" ).unwrap(),
|
||
gl.get_uniform_location( glyph_program, "u_uv_scale" ).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 ( u_glyph_batch_color, u_glyph_batch_opacity, u_glyph_batch_sampler ) = unsafe
|
||
{(
|
||
gl.get_uniform_location( glyph_batch_program, "u_color" ).unwrap(),
|
||
gl.get_uniform_location( glyph_batch_program, "u_opacity" ).unwrap(),
|
||
gl.get_uniform_location( glyph_batch_program, "u_sampler" ).unwrap(),
|
||
)};
|
||
|
||
let clip_composite_program = compile_program( &gl, VERT_SRC, CLIP_COMPOSITE_FRAG_SRC );
|
||
let ( u_clip_mvp, u_clip_layer, u_clip_mask, u_clip_canvas, u_clip_bbox ) = unsafe
|
||
{(
|
||
gl.get_uniform_location( clip_composite_program, "u_mvp" ).unwrap(),
|
||
gl.get_uniform_location( clip_composite_program, "u_layer" ).unwrap(),
|
||
gl.get_uniform_location( clip_composite_program, "u_mask" ).unwrap(),
|
||
gl.get_uniform_location( clip_composite_program, "u_canvas" ).unwrap(),
|
||
gl.get_uniform_location( clip_composite_program, "u_bbox" ).unwrap(),
|
||
)};
|
||
|
||
let ( glyph_batch_vao, glyph_batch_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.enable_vertex_attrib_array( 0 );
|
||
gl.vertex_attrib_pointer_f32( 0, 2, glow::FLOAT, false, 16, 0 );
|
||
gl.enable_vertex_attrib_array( 1 );
|
||
gl.vertex_attrib_pointer_f32( 1, 2, glow::FLOAT, false, 16, 8 );
|
||
gl.bind_vertex_array( None );
|
||
( vao, vbo )
|
||
};
|
||
|
||
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 )
|
||
};
|
||
|
||
// Atlas format picked per ES profile: GL_R8 / GL_RED on ES3
|
||
// (GL_LUMINANCE is deprecated in ES3 core and some mobile drivers
|
||
// — Mali, certain Adreno — return GL_INVALID_ENUM for it), legacy
|
||
// GL_LUMINANCE on ES2 where the modern single-channel formats do
|
||
// not exist. The glyph fragment shader samples `.r` and works for
|
||
// both: GL_RED puts the byte in .r directly, GL_LUMINANCE
|
||
// replicates it into .r=.g=.b, so reading `.r` gives the same
|
||
// coverage value either way.
|
||
let ( atlas_internal, atlas_format ) = match version
|
||
{
|
||
GlesVersion::V3 => ( glow::R8 as i32, glow::RED ),
|
||
GlesVersion::V2 => ( glow::LUMINANCE as i32, glow::LUMINANCE ),
|
||
};
|
||
let atlas_texture = unsafe
|
||
{
|
||
let tex = gl.create_texture().unwrap();
|
||
gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
|
||
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 1 );
|
||
gl.tex_image_2d(
|
||
glow::TEXTURE_2D, 0, atlas_internal,
|
||
super::ATLAS_SIZE as i32, super::ATLAS_SIZE as i32, 0,
|
||
atlas_format, glow::UNSIGNED_BYTE,
|
||
glow::PixelUnpackData::Slice( None ),
|
||
);
|
||
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32 );
|
||
gl.bind_texture( glow::TEXTURE_2D, None );
|
||
tex
|
||
};
|
||
|
||
Self
|
||
{
|
||
gl,
|
||
version,
|
||
font,
|
||
font_bytes,
|
||
font_face,
|
||
font_registry: None,
|
||
dpi_scale: 1.0,
|
||
layout_viewport: None,
|
||
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_glyph_uv_offset,
|
||
u_glyph_uv_scale,
|
||
glyph_batch_program,
|
||
u_glyph_batch_color,
|
||
u_glyph_batch_opacity,
|
||
u_glyph_batch_sampler,
|
||
glyph_batch_vao,
|
||
glyph_batch_vbo,
|
||
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,
|
||
atlas_texture,
|
||
atlas_format,
|
||
atlas_cursor_x: 0,
|
||
atlas_cursor_y: 0,
|
||
atlas_row_height: 0,
|
||
glyph_cache: HashMap::new(),
|
||
image_cache: HashMap::new(),
|
||
gradient_lut_cache: HashMap::new(),
|
||
clip_scissor: None,
|
||
clip_layer: None,
|
||
clip_layer_active: false,
|
||
clip_mask_tex: None,
|
||
clip_bbox: crate::types::Rect::default(),
|
||
clip_composite_program,
|
||
u_clip_mvp,
|
||
u_clip_layer,
|
||
u_clip_mask,
|
||
u_clip_canvas,
|
||
u_clip_bbox,
|
||
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 )
|
||
};
|
||
|
||
let atlas_format = self.atlas_format;
|
||
let atlas_internal = match self.version
|
||
{
|
||
GlesVersion::V3 => glow::R8 as i32,
|
||
GlesVersion::V2 => glow::LUMINANCE as i32,
|
||
};
|
||
let atlas_texture = unsafe
|
||
{
|
||
let tex = gl.create_texture().unwrap();
|
||
gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
|
||
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 1 );
|
||
gl.tex_image_2d(
|
||
glow::TEXTURE_2D, 0, atlas_internal,
|
||
super::ATLAS_SIZE as i32, super::ATLAS_SIZE as i32, 0,
|
||
atlas_format, glow::UNSIGNED_BYTE,
|
||
glow::PixelUnpackData::Slice( None ),
|
||
);
|
||
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32 );
|
||
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32 );
|
||
gl.bind_texture( glow::TEXTURE_2D, None );
|
||
tex
|
||
};
|
||
|
||
GlesCanvas
|
||
{
|
||
gl,
|
||
version: self.version,
|
||
font: Arc::clone( &self.font ),
|
||
font_bytes: Arc::clone( &self.font_bytes ),
|
||
font_face: self.font_face,
|
||
font_registry: self.font_registry.as_ref().map( Arc::clone ),
|
||
dpi_scale: self.dpi_scale,
|
||
layout_viewport: Some( self.layout_viewport.unwrap_or(
|
||
( self.width as f32, self.height as f32 ) ) ),
|
||
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_glyph_uv_offset: self.u_glyph_uv_offset,
|
||
u_glyph_uv_scale: self.u_glyph_uv_scale,
|
||
glyph_batch_program: self.glyph_batch_program,
|
||
u_glyph_batch_color: self.u_glyph_batch_color,
|
||
u_glyph_batch_opacity: self.u_glyph_batch_opacity,
|
||
u_glyph_batch_sampler: self.u_glyph_batch_sampler,
|
||
glyph_batch_vao: self.glyph_batch_vao,
|
||
glyph_batch_vbo: self.glyph_batch_vbo,
|
||
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,
|
||
atlas_texture,
|
||
atlas_format,
|
||
atlas_cursor_x: 0,
|
||
atlas_cursor_y: 0,
|
||
atlas_row_height: 0,
|
||
glyph_cache: HashMap::new(),
|
||
image_cache: HashMap::new(),
|
||
gradient_lut_cache: HashMap::new(),
|
||
clip_scissor: None,
|
||
clip_layer: None,
|
||
clip_layer_active: false,
|
||
clip_mask_tex: None,
|
||
clip_bbox: crate::types::Rect::default(),
|
||
clip_composite_program: self.clip_composite_program,
|
||
u_clip_mvp: self.u_clip_mvp,
|
||
u_clip_layer: self.u_clip_layer,
|
||
u_clip_mask: self.u_clip_mask,
|
||
u_clip_canvas: self.u_clip_canvas,
|
||
u_clip_bbox: self.u_clip_bbox,
|
||
fbo,
|
||
fbo_tex,
|
||
aux_a: None,
|
||
aux_b: None,
|
||
}
|
||
}
|
||
|
||
/// `( width, height )` of the FBO in physical px.
|
||
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 );
|
||
}
|
||
}
|
||
}
|
||
|
||
/// DPI scale factor applied to font sizes before rasterisation.
|
||
pub fn dpi_scale( &self ) -> f32 { self.dpi_scale }
|
||
|
||
/// Set the DPI scale factor applied to font sizes.
|
||
pub fn set_dpi_scale( &mut self, s: f32 ) { self.dpi_scale = s; }
|
||
|
||
/// Global alpha multiplier applied to every draw (0.0 transparent, 1.0 opaque).
|
||
pub fn global_alpha( &self ) -> f32 { self.global_alpha }
|
||
|
||
/// Set the global alpha multiplier applied to every draw.
|
||
pub fn set_global_alpha( &mut self, a: f32 ) { self.global_alpha = a; }
|
||
|
||
/// The canvas default font, used when no specific face is resolved.
|
||
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 ) )
|
||
}
|
||
|
||
/// Glyph metrics for `ch` at `size` logical px, resolved through the fallback
|
||
/// chain and pre-scaled by `dpi_scale`.
|
||
pub fn font_metrics( &self, ch: char, size: f32 ) -> Metrics
|
||
{
|
||
self.font_for_char( ch ).metrics( ch, size * self.dpi_scale )
|
||
}
|
||
|
||
/// Horizontal line metrics of the default font at `size` px (`None` if the
|
||
/// font lacks them). Not pre-scaled by `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,
|
||
);
|
||
// The clip layer was sized for the old dimensions — free it so the next
|
||
// path clip re-allocates at the new size; any in-flight clip is dropped.
|
||
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 );
|
||
}
|
||
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 );
|
||
}
|
||
self.clip_layer_active = false;
|
||
// 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();
|
||
}
|
||
}
|