use glow::HasContext;
use crate::theme::{ gradient_lut, BlendMode, InsetShadow, LinearGradient, RadialGradient, Shadow };
use crate::types::{ Color, Corners, PathCmd, Rect };
use super::helpers::ortho_rect;
use super::GlesCanvas;
impl GlesCanvas
{
fn rect_culled( &self, rect: Rect, margin: f32 ) -> bool
{
let Some( clip ) = self.clip_scissor else { return false };
let r_x0 = rect.x - margin;
let r_y0 = rect.y - margin;
let r_x1 = rect.x + rect.width + margin;
let r_y1 = rect.y + rect.height + margin;
let c_x1 = clip.x + clip.width;
let c_y1 = clip.y + clip.height;
r_x1 <= clip.x || c_x1 <= r_x0 || r_y1 <= clip.y || c_y1 <= r_y0
}
pub fn fill_path( &mut self, cmds: &[ PathCmd ], color: Color )
{
self.rasterise_path( cmds, color, None );
}
pub fn stroke_path( &mut self, cmds: &[ PathCmd ], color: Color, width: f32 )
{
self.rasterise_path( cmds, color, Some( width ) );
}
fn rasterise_path( &mut self, cmds: &[ PathCmd ], color: Color, stroke: Option<f32> )
{
let Some( path ) = crate::render::helpers::build_ts_path( cmds ) else { return };
let bounds = path.bounds();
let pad = stroke.map_or( 1.0, |w| w * 0.5 + 1.0 );
let x0 = ( bounds.left() - pad ).floor();
let y0 = ( bounds.top() - pad ).floor();
let x1 = ( bounds.right() + pad ).ceil();
let y1 = ( bounds.bottom() + pad ).ceil();
let pw = ( x1 - x0 ).max( 1.0 ) as u32;
let ph = ( y1 - y0 ).max( 1.0 ) as u32;
if pw > 8192 || ph > 8192 { return; }
let Some( mut pixmap ) = tiny_skia::Pixmap::new( pw, ph ) else { return };
let mut paint = tiny_skia::Paint::default();
paint.set_color( Color::rgba( color.r, color.g, color.b, color.a * self.global_alpha ).to_tiny_skia() );
paint.anti_alias = true;
let tf = tiny_skia::Transform::from_translate( -x0, -y0 );
match stroke
{
Some( w ) =>
{
let s = tiny_skia::Stroke { width: w, ..tiny_skia::Stroke::default() };
pixmap.stroke_path( &path, &paint, &s, tf, None );
}
None => { pixmap.fill_path( &path, &paint, tiny_skia::FillRule::Winding, tf, None ); }
}
let mut rgba = pixmap.data().to_vec();
for px in rgba.chunks_exact_mut( 4 )
{
let a = px[ 3 ] as u32;
if a > 0 && a < 255
{
px[ 0 ] = ( px[ 0 ] as u32 * 255 / a ).min( 255 ) as u8;
px[ 1 ] = ( px[ 1 ] as u32 * 255 / a ).min( 255 ) as u8;
px[ 2 ] = ( px[ 2 ] as u32 * 255 / a ).min( 255 ) as u8;
}
}
let dest = Rect { x: x0, y: y0, width: pw as f32, height: ph as f32 };
let tex = super::helpers::upload_rgba_texture( &self.gl, self.version, &rgba, pw as i32, ph as i32 );
self.draw_external_texture( tex, dest, 1.0 );
unsafe { self.gl.delete_texture( tex ); }
}
pub fn fill_rect( &mut self, rect: Rect, color: Color, corners: Corners )
{
if self.rect_culled( rect, 1.0 ) { return; }
self.activate_target();
let pad = if corners.is_zero() { 0.0_f32 } else { 1.0 };
let expanded = rect.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
let alpha = color.a * self.global_alpha;
unsafe
{
self.gl.use_program( Some( self.rect_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_rect_mvp ), false, &mvp );
self.gl.uniform_4_f32( Some( &self.u_rect_color ), color.r, color.g, color.b, alpha );
self.gl.uniform_2_f32( Some( &self.u_rect_size ), rect.width, rect.height );
self.gl.uniform_4_f32_slice( Some( &self.u_rect_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_rect_stroke ), 0.0 );
self.gl.uniform_1_f32( Some( &self.u_rect_pad ), pad );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
}
}
fn ensure_lut_texture( &mut self, lut_bytes: &[u8] ) -> glow::Texture
{
use std::hash::{ Hash, Hasher };
let mut h = std::collections::hash_map::DefaultHasher::new();
lut_bytes.hash( &mut h );
let key = h.finish();
if let Some( &tex ) = self.gradient_lut_cache.get( &key )
{
return tex;
}
unsafe
{
let tex = self.gl.create_texture().expect( "gradient LUT texture" );
self.gl.active_texture( glow::TEXTURE0 );
self.gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
self.gl.tex_image_2d(
glow::TEXTURE_2D,
0,
glow::RGBA as i32,
gradient_lut::LUT_SAMPLES as i32,
1,
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice( Some( lut_bytes ) ),
);
self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32 );
self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32 );
self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32 );
self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32 );
self.gl.bind_texture( glow::TEXTURE_2D, None );
self.gradient_lut_cache.insert( key, tex );
tex
}
}
pub fn fill_linear_gradient_rect( &mut self, rect: Rect, g: &LinearGradient, corners: Corners )
{
if self.rect_culled( rect, 1.0 ) { return; }
let lut_bytes = gradient_lut::build_lut_bytes( &g.stops, g.space );
let tex = self.ensure_lut_texture( &lut_bytes );
let theta = g.angle_deg.to_radians();
let dir_x = theta.sin();
let dir_y = -theta.cos();
let line_length = ( rect.width * dir_x ).abs() + ( rect.height * dir_y ).abs();
let line_length = if line_length.abs() < 1e-3 { 1e-3 } else { line_length };
self.activate_target();
let pad = 1.0_f32;
let expanded = rect.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
unsafe
{
self.gl.active_texture( glow::TEXTURE0 );
self.gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
self.gl.use_program( Some( self.linear_gradient_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_lingrad_mvp ), false, &mvp );
self.gl.uniform_1_i32( Some( &self.u_lingrad_lut ), 0 );
self.gl.uniform_2_f32( Some( &self.u_lingrad_dir ), dir_x, dir_y );
self.gl.uniform_2_f32( Some( &self.u_lingrad_size ), rect.width, rect.height );
self.gl.uniform_1_f32( Some( &self.u_lingrad_line_length ), line_length );
self.gl.uniform_4_f32_slice( Some( &self.u_lingrad_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_lingrad_pad ), pad );
self.gl.uniform_1_f32( Some( &self.u_lingrad_lut_domain_min ), gradient_lut::LUT_DOMAIN.0 );
self.gl.uniform_1_f32( Some( &self.u_lingrad_lut_domain_span ), gradient_lut::LUT_DOMAIN.1 - gradient_lut::LUT_DOMAIN.0 );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
self.gl.bind_texture( glow::TEXTURE_2D, None );
}
}
pub fn fill_radial_gradient_rect( &mut self, rect: Rect, g: &RadialGradient, corners: Corners )
{
if self.rect_culled( rect, 1.0 ) { return; }
let lut_bytes = gradient_lut::build_lut_bytes( &g.stops, g.space );
let tex = self.ensure_lut_texture( &lut_bytes );
self.activate_target();
let pad = 1.0_f32;
let expanded = rect.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
unsafe
{
self.gl.active_texture( glow::TEXTURE0 );
self.gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
self.gl.use_program( Some( self.radial_gradient_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_radgrad_mvp ), false, &mvp );
self.gl.uniform_1_i32( Some( &self.u_radgrad_lut ), 0 );
self.gl.uniform_2_f32( Some( &self.u_radgrad_center ), g.center[0], g.center[1] );
self.gl.uniform_1_f32( Some( &self.u_radgrad_radius_frac ), g.radius );
self.gl.uniform_2_f32( Some( &self.u_radgrad_size ), rect.width, rect.height );
self.gl.uniform_4_f32_slice( Some( &self.u_radgrad_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_radgrad_pad ), pad );
self.gl.uniform_1_f32( Some( &self.u_radgrad_lut_domain_min ), gradient_lut::LUT_DOMAIN.0 );
self.gl.uniform_1_f32( Some( &self.u_radgrad_lut_domain_span ), gradient_lut::LUT_DOMAIN.1 - gradient_lut::LUT_DOMAIN.0 );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
self.gl.bind_texture( glow::TEXTURE_2D, None );
}
}
pub fn fill_shadow_outer( &mut self, target: Rect, shadow: &Shadow, corners: Corners )
{
let blur_margin = shadow.blur.max( 0.0 );
let spread_margin = shadow.spread.max( 0.0 );
let margin = blur_margin + spread_margin + 1.0;
let culled_rect = Rect
{
x: target.x + shadow.offset[0],
y: target.y + shadow.offset[1],
width: target.width,
height: target.height,
};
if self.rect_culled( culled_rect, margin ) { return; }
let quad = Rect
{
x: target.x + shadow.offset[0] - margin,
y: target.y + shadow.offset[1] - margin,
width: target.width + 2.0 * margin,
height: target.height + 2.0 * margin,
};
let sigma = shadow.sigma().max( 0.5 );
let alpha = shadow.color.a * self.global_alpha;
self.activate_target();
let mvp = ortho_rect( self.width, self.height, quad );
unsafe
{
self.gl.use_program( Some( self.shadow_outer_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_shadow_mvp ), false, &mvp );
self.gl.uniform_2_f32( Some( &self.u_shadow_size ), target.width, target.height );
self.gl.uniform_2_f32( Some( &self.u_shadow_padding ), margin, margin );
self.gl.uniform_4_f32_slice( Some( &self.u_shadow_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_shadow_spread ), shadow.spread );
self.gl.uniform_1_f32( Some( &self.u_shadow_sigma ), sigma );
self.gl.uniform_4_f32( Some( &self.u_shadow_color ),
shadow.color.r, shadow.color.g, shadow.color.b, alpha );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
}
}
pub fn fill_shadow_inset( &mut self, target: Rect, shadow: &InsetShadow, corners: Corners )
{
if self.rect_culled( target, 1.0 ) { return; }
let sigma = shadow.sigma().max( 0.5 );
let alpha = shadow.color.a * self.global_alpha;
if matches!( shadow.blend, BlendMode::Overlay )
{
let pad = 1.0_f32;
let snap_rect = target.expand( pad );
self.snapshot_fbo_region_tight( snap_rect );
self.activate_target();
let expanded = target.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
let aux_tex = self.aux_a.expect( "snapshotted" ).1;
unsafe
{
self.gl.use_program( Some( self.shadow_inset_overlay_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_inset_ov_mvp ), false, &mvp );
self.gl.uniform_2_f32( Some( &self.u_inset_ov_size ), target.width, target.height );
self.gl.uniform_2_f32( Some( &self.u_inset_ov_padding ), pad, pad );
self.gl.uniform_4_f32_slice( Some( &self.u_inset_ov_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_inset_ov_spread ), shadow.spread );
self.gl.uniform_1_f32( Some( &self.u_inset_ov_sigma ), sigma );
self.gl.uniform_2_f32( Some( &self.u_inset_ov_offset ), shadow.offset[0], shadow.offset[1] );
self.gl.uniform_4_f32( Some( &self.u_inset_ov_color ),
shadow.color.r, shadow.color.g, shadow.color.b, alpha );
self.gl.uniform_2_f32( Some( &self.u_inset_ov_canvas_size ), self.width as f32, self.height as f32 );
self.gl.active_texture( glow::TEXTURE0 );
self.gl.bind_texture( glow::TEXTURE_2D, Some( aux_tex ) );
self.gl.uniform_1_i32( Some( &self.u_inset_ov_snapshot ), 0 );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
self.gl.bind_texture( glow::TEXTURE_2D, None );
}
return;
}
self.activate_target();
let pad = 1.0_f32;
let expanded = target.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
unsafe
{
match shadow.blend
{
BlendMode::Normal => { }
BlendMode::PlusLighter => self.gl.blend_func( glow::ONE, glow::ONE ),
BlendMode::Multiply => self.gl.blend_func_separate
(
glow::DST_COLOR, glow::ZERO,
glow::DST_ALPHA, glow::ZERO,
),
BlendMode::Screen => self.gl.blend_func( glow::ONE_MINUS_DST_COLOR, glow::ONE ),
BlendMode::Overlay => unreachable!( "Overlay handled above via snapshot" ),
}
self.gl.use_program( Some( self.shadow_inset_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_inset_mvp ), false, &mvp );
self.gl.uniform_2_f32( Some( &self.u_inset_size ), target.width, target.height );
self.gl.uniform_2_f32( Some( &self.u_inset_padding ), pad, pad );
self.gl.uniform_4_f32_slice( Some( &self.u_inset_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_inset_spread ), shadow.spread );
self.gl.uniform_1_f32( Some( &self.u_inset_sigma ), sigma );
self.gl.uniform_2_f32( Some( &self.u_inset_offset ), shadow.offset[0], shadow.offset[1] );
self.gl.uniform_4_f32( Some( &self.u_inset_color ),
shadow.color.r, shadow.color.g, shadow.color.b, alpha );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
if !matches!( shadow.blend, BlendMode::Normal )
{
self.apply_composite();
}
}
}
pub fn stroke_rect( &mut self, rect: Rect, color: Color, width: f32, corners: Corners )
{
let half = width * 0.5;
if self.rect_culled( rect, half + 1.0 ) { return; }
self.activate_target();
let pad = half + 1.0;
let expanded = rect.expand( pad );
let mvp = ortho_rect( self.width, self.height, expanded );
let alpha = color.a * self.global_alpha;
unsafe
{
self.gl.use_program( Some( self.rect_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_rect_mvp ), false, &mvp );
self.gl.uniform_4_f32( Some( &self.u_rect_color ), color.r, color.g, color.b, alpha );
self.gl.uniform_2_f32( Some( &self.u_rect_size ), rect.width, rect.height );
self.gl.uniform_4_f32_slice( Some( &self.u_rect_radii ), &corners.to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_rect_stroke ), width );
self.gl.uniform_1_f32( Some( &self.u_rect_pad ), pad );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
}
}
pub fn draw_line( &mut self, x0: f32, y0: f32, x1: f32, y1: f32, color: Color, width: f32 )
{
let dx = x1 - x0;
let dy = y1 - y0;
let len = ( dx * dx + dy * dy ).sqrt();
if len < 0.1 { return; }
let min_x = x0.min( x1 );
let min_y = y0.min( y1 );
if dy.abs() < 0.1
{
self.fill_rect( Rect { x: min_x, y: min_y - width / 2.0, width: dx.abs(), height: width }, color, Corners::ZERO );
} else if dx.abs() < 0.1 {
self.fill_rect( Rect { x: min_x - width / 2.0, y: min_y, width, height: dy.abs() }, color, Corners::ZERO );
} else {
self.fill_rotated_line( x0, y0, dx, dy, len, color, width );
}
}
fn fill_rotated_line( &mut self, x0: f32, y0: f32, dx: f32, dy: f32, len: f32, color: Color, width: f32 )
{
let half = width / 2.0 + 1.0;
let bbox = Rect
{
x: x0.min( x0 + dx ) - half,
y: y0.min( y0 + dy ) - half,
width: dx.abs() + 2.0 * half,
height: dy.abs() + 2.0 * half,
};
if self.rect_culled( bbox, 1.0 ) { return; }
self.activate_target();
let pad = 1.0_f32;
let cos = dx / len;
let sin = dy / len;
let len_p = len + 2.0 * pad;
let wid_p = width + 2.0 * pad;
let lx = -pad;
let ly = -pad - width / 2.0;
let sx0 = x0 + lx * cos - ly * sin;
let sy0 = y0 + lx * sin + ly * cos;
let w = self.width as f32;
let h = self.height as f32;
let mvp = [
2.0 * len_p * cos / w, -2.0 * len_p * sin / h, 0.0, 0.0,
-2.0 * wid_p * sin / w, -2.0 * wid_p * cos / h, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
2.0 * sx0 / w - 1.0, 1.0 - 2.0 * sy0 / h, 0.0, 1.0,
];
let alpha = color.a * self.global_alpha;
unsafe
{
self.gl.use_program( Some( self.rect_program ) );
self.gl.uniform_matrix_4_f32_slice( Some( &self.u_rect_mvp ), false, &mvp );
self.gl.uniform_4_f32( Some( &self.u_rect_color ), color.r, color.g, color.b, alpha );
self.gl.uniform_2_f32( Some( &self.u_rect_size ), len, width );
self.gl.uniform_4_f32_slice( Some( &self.u_rect_radii ), &Corners::all( width / 2.0 ).to_uniform() );
self.gl.uniform_1_f32( Some( &self.u_rect_stroke ), 0.0 );
self.gl.uniform_1_f32( Some( &self.u_rect_pad ), pad );
self.gl.bind_vertex_array( Some( self.quad_vao ) );
self.gl.draw_arrays( glow::TRIANGLES, 0, 6 );
self.gl.bind_vertex_array( None );
}
}
}