use std::sync::Arc;
use agg_gui::color::Color;
use agg_gui::draw_ctx::DrawCtx;
use agg_gui::text::shape_glyphs;
use crate::{DrawCommand, LcdArcTextureEntry, WgpuGfxCtx};
impl WgpuGfxCtx {
pub(crate) fn fill_text_impl(&mut self, text: &str, x: f64, y: f64) {
let Some(font) = self.font.clone() else {
return;
};
let ctm = *self.ctm();
let ctm_scale = (ctm.sx * ctm.sx + ctm.shy * ctm.shy).sqrt().max(1e-6);
if self.has_lcd_mask_composite() {
let phys_size = self.font_size * ctm_scale;
let use_lcd = self.lcd_mode && self.text_dst_is_opaque();
let cached = if use_lcd {
agg_gui::lcd_coverage::rasterize_text_lcd_cached(&font, text, phys_size)
} else {
agg_gui::lcd_coverage::rasterize_text_gray_cached(&font, text, phys_size)
};
let mut col = self.fill_color;
col.a *= self.global_alpha as f32;
let dst_x = x - cached.baseline_x_in_mask / ctm_scale;
let dst_y = y - cached.baseline_y_in_mask / ctm_scale;
self.draw_lcd_mask_arc_impl(
&cached.pixels,
cached.width,
cached.height,
col,
dst_x,
dst_y,
);
return;
}
let shaped = shape_glyphs(&font, text, self.font_size);
let font_size = self.font_size;
let width_scale = agg_gui::font_settings::current_width();
let italic_shear = agg_gui::font_settings::current_faux_italic() / 3.0;
let hint_y = agg_gui::font_settings::hinting_enabled();
let interval_px = agg_gui::font_settings::current_interval() * font_size;
let tess_size = font_size * ctm_scale;
let inv_scale = 1.0 / ctm_scale;
let mut all_verts: Vec<[f32; 2]> = Vec::new();
let mut all_idx: Vec<u32> = Vec::new();
let mut pen_x = x;
for glyph in &shaped {
let gx = pen_x + glyph.x_offset;
let gy_raw = y + glyph.y_offset;
let gy = if hint_y {
(gy_raw + 0.5).floor()
} else {
gy_raw
};
let render_font = glyph.fallback_font.as_deref().unwrap_or(&font);
if let Some(cached) =
self.glyph_cache
.get_or_insert(render_font, glyph.glyph_id, tess_size)
{
let base = all_verts.len() as u32;
for &[vx, vy] in &cached.verts {
let vx_f64 = vx as f64 * inv_scale;
let vy_f64 = vy as f64 * inv_scale;
let (mut px, mut py) = (
gx + vx_f64 * width_scale + vy_f64 * italic_shear,
gy + vy_f64,
);
ctm.transform(&mut px, &mut py);
all_verts.push([px as f32, py as f32]);
}
all_idx.extend(cached.indices.iter().map(|&i| i + base));
}
pen_x += glyph.x_advance + interval_px;
}
if !all_verts.is_empty() {
self.commands.push(DrawCommand::Solid {
verts: all_verts,
indices: all_idx,
color: self.fill_color,
global_alpha: self.global_alpha as f32,
clip: self.current_clip(),
});
}
}
pub(crate) fn draw_lcd_mask_slice_impl(
&mut self,
mask: &[u8],
mask_w: u32,
mask_h: u32,
src_color: Color,
dst_x: f64,
dst_y: f64,
) {
if mask.is_empty() || mask_w == 0 || mask_h == 0 {
return;
}
if mask.len() < (mask_w as usize) * (mask_h as usize) * 3 {
return;
}
let (texture, view) = upload_lcd_texture(&self.device, &self.queue, mask, mask_w, mask_h);
self.push_lcd_mask_command(texture, view, mask_w, mask_h, src_color, dst_x, dst_y);
}
pub(crate) fn draw_lcd_mask_arc_impl(
&mut self,
mask: &Arc<Vec<u8>>,
mask_w: u32,
mask_h: u32,
src_color: Color,
dst_x: f64,
dst_y: f64,
) {
if mask.is_empty() || mask_w == 0 || mask_h == 0 {
return;
}
if mask.len() < (mask_w as usize) * (mask_h as usize) * 3 {
return;
}
let (texture, view) = self.lcd_arc_get_or_upload(mask, 0, mask_w, mask_h);
self.push_lcd_mask_command(texture, view, mask_w, mask_h, src_color, dst_x, dst_y);
}
pub(crate) fn draw_lcd_backbuffer_arc_impl(
&mut self,
color: &Arc<Vec<u8>>,
alpha: &Arc<Vec<u8>>,
content_version: u64,
w: u32,
h: u32,
dst_x: f64,
dst_y: f64,
dst_w: f64,
dst_h: f64,
) {
if w == 0 || h == 0 || color.is_empty() || alpha.is_empty() {
return;
}
let needed = (w as usize) * (h as usize) * 3;
if color.len() < needed || alpha.len() < needed {
return;
}
let (color_tex, color_view) = self.lcd_arc_get_or_upload(color, content_version, w, h);
let (alpha_tex, alpha_view) = self.lcd_arc_get_or_upload(alpha, content_version, w, h);
let ctm = *self.ctm();
let far_x = dst_x + dst_w;
let far_y = dst_y + dst_h;
let bl_x = (dst_x * ctm.sx + dst_y * ctm.shx + ctm.tx).round();
let bl_y = (dst_x * ctm.shy + dst_y * ctm.sy + ctm.ty).round();
let tr_x = (far_x * ctm.sx + far_y * ctm.shx + ctm.tx).round();
let tr_y = (far_x * ctm.shy + far_y * ctm.sy + ctm.ty).round();
let verts: [f32; 16] = [
bl_x as f32,
bl_y as f32,
0.0,
1.0,
tr_x as f32,
bl_y as f32,
1.0,
1.0,
tr_x as f32,
tr_y as f32,
1.0,
0.0,
bl_x as f32,
tr_y as f32,
0.0,
0.0,
];
self.commands.push(DrawCommand::LcbMask {
verts,
color_tex,
color_view,
alpha_tex,
alpha_view,
clip: self.current_clip(),
global_alpha: self.global_alpha as f32,
flatten: !self.text_dst_is_opaque(),
});
}
fn push_lcd_mask_command(
&mut self,
texture: Arc<wgpu::Texture>,
view: wgpu::TextureView,
mask_w: u32,
mask_h: u32,
src_color: Color,
dst_x: f64,
dst_y: f64,
) {
let ctm = *self.ctm();
let bl_x = (dst_x * ctm.sx + dst_y * ctm.shx + ctm.tx).round();
let bl_y = (dst_x * ctm.shy + dst_y * ctm.sy + ctm.ty).round();
let tr_x = bl_x + mask_w as f64;
let tr_y = bl_y + mask_h as f64;
let verts: [f32; 16] = [
bl_x as f32,
bl_y as f32,
0.0,
0.0,
tr_x as f32,
bl_y as f32,
1.0,
0.0,
tr_x as f32,
tr_y as f32,
1.0,
1.0,
bl_x as f32,
tr_y as f32,
0.0,
1.0,
];
let a = (src_color.a as f64 * self.global_alpha) as f32;
let color = Color::rgba(src_color.r, src_color.g, src_color.b, a);
self.commands.push(DrawCommand::LcdMask {
verts,
texture,
view,
color,
clip: self.current_clip(),
flatten: !self.text_dst_is_opaque(),
});
}
pub(crate) fn lcd_arc_get_or_upload(
&mut self,
data: &Arc<Vec<u8>>,
version: u64,
w: u32,
h: u32,
) -> (Arc<wgpu::Texture>, wgpu::TextureView) {
let key = data.as_ref().as_ptr() as usize;
let arc_identity_matches = version == 0
&& self
.lcd_arc_texture_cache
.get(&key)
.and_then(|e| e.weak.upgrade())
.is_some_and(|a| Arc::ptr_eq(&a, data));
let meta = self.lcd_arc_texture_cache.get(&key).map(|e| LcdEntryMeta {
version: e.version,
w: e.w,
h: e.h,
});
let action = lcd_cache_decide(meta, version, w, h, arc_identity_matches);
let result = match action {
LcdCacheAction::Reuse => {
let e = self
.lcd_arc_texture_cache
.get_mut(&key)
.expect("Reuse implies a present entry");
e.weak = Arc::downgrade(data);
(Arc::clone(&e.texture), e.view.clone())
}
LcdCacheAction::Rewrite => {
let (texture, view) = {
let e = self
.lcd_arc_texture_cache
.get(&key)
.expect("Rewrite implies a present entry");
(Arc::clone(&e.texture), e.view.clone())
};
write_lcd_plane(&self.queue, &texture, data.as_slice(), w, h);
let e = self
.lcd_arc_texture_cache
.get_mut(&key)
.expect("Rewrite implies a present entry");
e.weak = Arc::downgrade(data);
e.version = version;
(texture, view)
}
LcdCacheAction::Replace => {
let (texture, view) =
upload_lcd_texture(&self.device, &self.queue, data.as_slice(), w, h);
self.lcd_arc_texture_cache.insert(
key,
LcdArcTextureEntry {
weak: Arc::downgrade(data),
texture: Arc::clone(&texture),
view: view.clone(),
w,
h,
version,
},
);
(texture, view)
}
};
let dead_keys: Vec<usize> = self
.lcd_arc_texture_cache
.iter()
.filter(|(k, e)| **k != key && e.weak.strong_count() == 0)
.map(|(k, _)| *k)
.collect();
for k in dead_keys {
self.lcd_arc_texture_cache.remove(&k);
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LcdCacheAction {
Reuse,
Rewrite,
Replace,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LcdEntryMeta {
pub(crate) version: u64,
pub(crate) w: u32,
pub(crate) h: u32,
}
pub(crate) fn lcd_cache_decide(
entry: Option<LcdEntryMeta>,
version: u64,
w: u32,
h: u32,
arc_identity_matches: bool,
) -> LcdCacheAction {
match entry {
None => LcdCacheAction::Replace,
Some(e) if e.w != w || e.h != h => LcdCacheAction::Replace,
Some(e) => {
if version != 0 {
if e.version == version {
LcdCacheAction::Reuse
} else {
LcdCacheAction::Rewrite
}
} else if arc_identity_matches {
LcdCacheAction::Reuse
} else {
LcdCacheAction::Rewrite
}
}
}
}
fn rgb_to_rgba(rgb: &[u8], w: u32, h: u32) -> Vec<u8> {
let n = (w as usize) * (h as usize);
let mut out = Vec::with_capacity(n * 4);
for i in 0..n {
out.push(rgb[i * 3]);
out.push(rgb[i * 3 + 1]);
out.push(rgb[i * 3 + 2]);
out.push(255);
}
out
}
fn upload_lcd_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
rgb: &[u8],
w: u32,
h: u32,
) -> (Arc<wgpu::Texture>, wgpu::TextureView) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
write_lcd_plane(queue, &texture, rgb, w, h);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(Arc::new(texture), view)
}
fn write_lcd_plane(queue: &wgpu::Queue, texture: &wgpu::Texture, rgb: &[u8], w: u32, h: u32) {
let rgba = rgb_to_rgba(rgb, w, h);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(w * 4),
rows_per_image: Some(h),
},
wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
);
}