use crate::render::adapter::RenderCell;
use crate::render::cell::cellsym_block;
use crate::render::graph::UnifiedTransform;
use crate::render::symbol_map::{Tile, MipUV, get_layered_symbol_map};
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct WgpuSymbolInstance {
pub a1: [f32; 4],
pub a2: [f32; 4],
pub a3: [f32; 4],
pub a4: [f32; 4],
pub color: [f32; 4],
}
unsafe impl bytemuck::Pod for WgpuSymbolInstance {}
unsafe impl bytemuck::Zeroable for WgpuSymbolInstance {}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct WgpuTransformUniforms {
pub tw: [f32; 4],
pub th: [f32; 4],
pub color_filter: [f32; 4],
}
unsafe impl bytemuck::Pod for WgpuTransformUniforms {}
unsafe impl bytemuck::Zeroable for WgpuTransformUniforms {}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct WgpuQuadVertex {
pub position: [f32; 2],
}
unsafe impl bytemuck::Pod for WgpuQuadVertex {}
unsafe impl bytemuck::Zeroable for WgpuQuadVertex {}
pub struct WgpuSymbolRenderer {
canvas_width: f32,
canvas_height: f32,
transform_stack: UnifiedTransform,
instance_buffer: Vec<WgpuSymbolInstance>,
instance_count: usize,
max_instances: usize,
pub ratio_x: f32,
pub ratio_y: f32,
viewport_scale: f32,
render_scale: f32,
}
impl WgpuSymbolRenderer {
pub fn new(canvas_width: u32, canvas_height: u32) -> Self {
let transform_stack = UnifiedTransform::new_with_values(
1.0, 0.0, 0.0, -1.0, 0.0, canvas_height as f32 );
let max_instances = (canvas_width * canvas_height) as usize;
Self {
canvas_width: canvas_width as f32,
canvas_height: canvas_height as f32,
transform_stack,
instance_buffer: Vec::with_capacity(max_instances),
instance_count: 0,
max_instances,
ratio_x: 1.0,
ratio_y: 1.0,
viewport_scale: 1.0,
render_scale: 1.0,
}
}
pub fn generate_instances_from_render_cells(&mut self, render_cells: &[RenderCell], ratio_x: f32, ratio_y: f32) {
self.instance_buffer.clear();
self.instance_count = 0;
self.generate_instances_layered(render_cells, ratio_x, ratio_y);
}
#[inline]
pub(crate) fn select_mip_level(screen_pixel_h: f32, cell_h: u8) -> usize {
let per_unit = screen_pixel_h / cell_h.max(1) as f32;
if per_unit >= 48.0 { 0 }
else if per_unit >= 24.0 { 1 }
else { 2 }
}
fn generate_instances_layered(&mut self, render_cells: &[RenderCell], ratio_x: f32, ratio_y: f32) {
const MOD_BOLD: u16 = 0x0001;
const MOD_DIM: u16 = 0x0002;
const MOD_ITALIC: u16 = 0x0004;
const MOD_UNDERLINED: u16 = 0x0008;
const MOD_REVERSED: u16 = 0x0040;
const MOD_HIDDEN: u16 = 0x0080;
const MOD_CROSSED_OUT: u16 = 0x0100;
const MOD_GLOW: u16 = 0x0400;
const ITALIC_SKEW: f32 = 0.21;
let base_w = crate::render::PIXEL_SYM_WIDTH.get().copied().unwrap_or(32.0);
let base_h = crate::render::PIXEL_SYM_HEIGHT.get().copied().unwrap_or(32.0);
let bg_pua = cellsym_block(0, 160);
let bg_tile = get_layered_symbol_map()
.map(|m| *m.resolve(&bg_pua))
.unwrap_or_default();
let bg_mip = bg_tile.mips[1];
let bg_frame_w = bg_tile.cell_w.max(1) as f32 * base_w;
let bg_frame_h = bg_tile.cell_h.max(1) as f32 * base_h;
let rs = self.render_scale;
for r in render_cells {
let cell_width = r.w as f32 * rs;
let cell_height = r.h as f32 * rs;
let rx = r.x * rs;
let ry = r.y * rs;
let rcx = r.cx * rs;
let rcy = r.cy * rs;
let modifier = r.modifier;
let (mut fg_color, bg_color) = if modifier & MOD_REVERSED != 0 {
let bg = r.bcolor.unwrap_or((0.0, 0.0, 0.0, 0.0));
let fg = r.fcolor;
(bg, Some(fg))
} else {
(r.fcolor, r.bcolor)
};
if modifier & MOD_BOLD != 0 {
fg_color.0 = (fg_color.0 * 1.3).min(1.0);
fg_color.1 = (fg_color.1 * 1.3).min(1.0);
fg_color.2 = (fg_color.2 * 1.3).min(1.0);
}
if modifier & MOD_DIM != 0 { fg_color.3 *= 0.6; }
if modifier & MOD_HIDDEN != 0 { fg_color.3 = 0.0; }
if let Some(b) = bg_color {
let mut bg_transform = self.transform_stack;
bg_transform.translate(rx + rcx - cell_width, ry + rcy - cell_height);
if r.angle != 0.0 { bg_transform.rotate(r.angle); }
bg_transform.translate(-rcx + cell_width, -rcy + cell_height);
let bg_fw = bg_frame_w / ratio_x;
let bg_fh = bg_frame_h / ratio_y;
bg_transform.scale(cell_width / bg_fw / ratio_x, cell_height / bg_fh / ratio_y);
self.draw_layered_instance(&bg_tile, &bg_mip, bg_frame_w, bg_frame_h, &bg_transform, [b.0, b.1, b.2, b.3], false);
}
let tile = r.tile;
let screen_pixel_h = cell_height * self.viewport_scale;
let mip_level = Self::select_mip_level(screen_pixel_h, tile.cell_h);
let mip = tile.mips[mip_level];
let frame_width = tile.cell_w.max(1) as f32 * base_w;
let frame_height = tile.cell_h.max(1) as f32 * base_h;
let is_bold = modifier & MOD_BOLD != 0;
let is_glow = modifier & MOD_GLOW != 0;
if is_glow {
let glow_color = [fg_color.0, fg_color.1, fg_color.2, fg_color.3 * 0.4];
let mut glow_transform = self.transform_stack;
glow_transform.translate(rx + rcx - cell_width, ry + rcy - cell_height);
if r.angle != 0.0 { glow_transform.rotate(r.angle); }
glow_transform.translate(-rcx + cell_width, -rcy + cell_height);
if modifier & MOD_ITALIC != 0 { glow_transform.skew_x(ITALIC_SKEW); }
let fw = frame_width / ratio_x;
let fh = frame_height / ratio_y;
glow_transform.scale(cell_width / fw / ratio_x, cell_height / fh / ratio_y);
self.draw_layered_instance_with_glow(&tile, &mip, frame_width, frame_height, &glow_transform, glow_color, is_bold, 2.5);
}
let mut transform = self.transform_stack;
transform.translate(rx + rcx - cell_width, ry + rcy - cell_height);
if r.angle != 0.0 { transform.rotate(r.angle); }
transform.translate(-rcx + cell_width, -rcy + cell_height);
if modifier & MOD_ITALIC != 0 { transform.skew_x(ITALIC_SKEW); }
let fw = frame_width / ratio_x;
let fh = frame_height / ratio_y;
transform.scale(cell_width / fw / ratio_x, cell_height / fh / ratio_y);
self.draw_layered_instance(&tile, &mip, frame_width, frame_height, &transform, [fg_color.0, fg_color.1, fg_color.2, fg_color.3], is_bold);
if modifier & MOD_UNDERLINED != 0 {
let mut lt = self.transform_stack;
lt.translate(rx + rcx - cell_width, ry + rcy - cell_height + cell_height * 0.9);
if r.angle != 0.0 { lt.rotate(r.angle); }
lt.translate(-rcx + cell_width, -rcy + cell_height);
let bg_fw = bg_frame_w / ratio_x;
let bg_fh = bg_frame_h / ratio_y;
lt.scale(cell_width / bg_fw / ratio_x, (cell_height * 0.08) / bg_fh / ratio_y);
self.draw_layered_instance(&bg_tile, &bg_mip, bg_frame_w, bg_frame_h, <, [fg_color.0, fg_color.1, fg_color.2, fg_color.3], false);
}
if modifier & MOD_CROSSED_OUT != 0 {
let mut lt = self.transform_stack;
lt.translate(rx + rcx - cell_width, ry + rcy - cell_height + cell_height * 0.46);
if r.angle != 0.0 { lt.rotate(r.angle); }
lt.translate(-rcx + cell_width, -rcy + cell_height);
let bg_fw = bg_frame_w / ratio_x;
let bg_fh = bg_frame_h / ratio_y;
lt.scale(cell_width / bg_fw / ratio_x, (cell_height * 0.08) / bg_fh / ratio_y);
self.draw_layered_instance(&bg_tile, &bg_mip, bg_frame_w, bg_frame_h, <, [fg_color.0, fg_color.1, fg_color.2, fg_color.3], false);
}
}
}
fn draw_layered_instance(
&mut self,
tile: &Tile,
mip: &MipUV,
frame_width: f32,
frame_height: f32,
transform: &UnifiedTransform,
color: [f32; 4],
is_bold: bool,
) {
if self.instance_count >= self.max_instances { return; }
if mip.w <= 0.0 || mip.h <= 0.0 { return; }
let origin_x = if is_bold { -1.0_f32 } else { 1.0 };
let origin_y = 1.0_f32;
let half_texel = 0.5 / 2048.0; let uv_left = mip.x + half_texel;
let uv_top = mip.y + half_texel;
let uv_width = mip.w - half_texel * 2.0;
let uv_height = mip.h - half_texel * 2.0;
let instance = WgpuSymbolInstance {
a1: [origin_x, origin_y, uv_left, uv_top],
a2: [
uv_width,
uv_height,
transform.m00 * frame_width,
transform.m10 * frame_width,
],
a3: [
transform.m01 * frame_height,
transform.m11 * frame_height,
transform.m20,
transform.m21,
],
a4: [mip.layer as f32, 0.0, 0.0, 0.0],
color,
};
self.instance_buffer.push(instance);
self.instance_count += 1;
}
fn draw_layered_instance_with_glow(
&mut self,
tile: &Tile,
mip: &MipUV,
frame_width: f32,
frame_height: f32,
transform: &UnifiedTransform,
color: [f32; 4],
is_bold: bool,
glow_scale: f32,
) {
if self.instance_count >= self.max_instances { return; }
if mip.w <= 0.0 || mip.h <= 0.0 { return; }
let origin_x = if is_bold { -1.0_f32 } else { 1.0 };
let origin_y = 1.0_f32;
let half_texel = 0.5 / 2048.0;
let uv_left = mip.x + half_texel;
let uv_top = mip.y + half_texel;
let uv_width_raw = mip.w - half_texel * 2.0;
let uv_height_val = mip.h - half_texel * 2.0;
let gs = glow_scale;
let mat_00_fw = transform.m00 * frame_width;
let mat_10_fw = transform.m10 * frame_width;
let mat_01_fh = transform.m01 * frame_height;
let mat_11_fh = transform.m11 * frame_height;
let cx = 0.5 - 1.0; let cy = 0.5 - 1.0;
let center_x = mat_00_fw * cx + mat_01_fh * cy;
let center_y = mat_10_fw * cx + mat_11_fh * cy;
let tx = transform.m20 - (gs - 1.0) * center_x;
let ty = transform.m21 - (gs - 1.0) * center_y;
let uv_width = -uv_width_raw;
let instance = WgpuSymbolInstance {
a1: [origin_x, origin_y, uv_left, uv_top],
a2: [uv_width, uv_height_val, mat_00_fw * gs, mat_10_fw * gs],
a3: [mat_01_fh * gs, mat_11_fh * gs, tx, ty],
a4: [mip.layer as f32, 0.0, 0.0, 0.0],
color,
};
self.instance_buffer.push(instance);
self.instance_count += 1;
}
pub fn get_base_quad_vertices() -> &'static [WgpuQuadVertex] {
&[
WgpuQuadVertex { position: [0.0, 0.0] }, WgpuQuadVertex { position: [0.0, 1.0] }, WgpuQuadVertex { position: [1.0, 1.0] }, WgpuQuadVertex { position: [1.0, 0.0] }, ]
}
pub fn get_base_quad_indices() -> &'static [u16] {
&[0, 1, 2, 2, 3, 0]
}
pub fn get_instance_buffer(&self) -> &[WgpuSymbolInstance] {
&self.instance_buffer[..self.instance_count]
}
pub fn get_instance_count(&self) -> u32 {
self.instance_count as u32
}
pub fn get_transform_uniforms(&self) -> WgpuTransformUniforms {
WgpuTransformUniforms {
tw: [
self.transform_stack.m00,
self.transform_stack.m10,
self.transform_stack.m20,
self.canvas_width
],
th: [
self.transform_stack.m01,
self.transform_stack.m11,
self.transform_stack.m21,
self.canvas_height
],
color_filter: [1.0, 1.0, 1.0, 1.0],
}
}
pub fn update_canvas_size(&mut self, width: u32, height: u32) {
self.canvas_width = width as f32;
self.canvas_height = height as f32;
self.transform_stack.m21 = height as f32;
}
pub fn set_ratio(&mut self, ratio_x: f32, ratio_y: f32) {
self.ratio_x = ratio_x;
self.ratio_y = ratio_y;
}
pub fn set_viewport_scale(&mut self, scale: f32) {
self.viewport_scale = scale;
}
pub fn set_render_scale(&mut self, scale: f32) {
self.render_scale = scale;
}
pub fn generate_vertices_from_buffer(&self, _buffer: &crate::render::buffer::Buffer) -> Vec<crate::render::adapter::wgpu::pixel::WgpuVertex> {
Vec::new()
}
}
impl WgpuQuadVertex {
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<WgpuQuadVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
],
}
}
}
impl WgpuSymbolInstance {
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<WgpuSymbolInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 1,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (2 * std::mem::size_of::<[f32; 4]>()) as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (3 * std::mem::size_of::<[f32; 4]>()) as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (4 * std::mem::size_of::<[f32; 4]>()) as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mip_level_high_res() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(96.0, 1), 0);
assert_eq!(WgpuSymbolRenderer::select_mip_level(48.0, 1), 0);
assert_eq!(WgpuSymbolRenderer::select_mip_level(200.0, 1), 0);
}
#[test]
fn test_mip_level_mid_res() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(47.0, 1), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(24.0, 1), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(36.0, 1), 1);
}
#[test]
fn test_mip_level_low_res() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(23.0, 1), 2);
assert_eq!(WgpuSymbolRenderer::select_mip_level(16.0, 1), 2);
assert_eq!(WgpuSymbolRenderer::select_mip_level(1.0, 1), 2);
}
#[test]
fn test_mip_level_multi_cell() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(96.0, 2), 0);
assert_eq!(WgpuSymbolRenderer::select_mip_level(48.0, 2), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(24.0, 2), 2);
}
#[test]
fn test_mip_level_zero_cell_h() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(48.0, 0), 0);
assert_eq!(WgpuSymbolRenderer::select_mip_level(24.0, 0), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(10.0, 0), 2);
}
#[test]
fn test_mip_level_boundary_values() {
assert_eq!(WgpuSymbolRenderer::select_mip_level(48.0, 1), 0);
assert_eq!(WgpuSymbolRenderer::select_mip_level(47.999, 1), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(24.0, 1), 1);
assert_eq!(WgpuSymbolRenderer::select_mip_level(23.999, 1), 2);
}
#[test]
fn test_mip_level_return_range() {
for h in [0.1, 1.0, 10.0, 23.0, 24.0, 47.0, 48.0, 100.0, 1000.0] {
for cell_h in [1, 2, 4] {
let level = WgpuSymbolRenderer::select_mip_level(h, cell_h);
assert!(level <= 2, "mip level {} out of range for h={}, cell_h={}", level, h, cell_h);
}
}
}
}