use std::borrow::Cow;
use aetna_core::ir::TextAnchor;
use aetna_core::shader::stock_wgsl;
use aetna_core::text::atlas::{
ATLAS_BYTES_PER_PIXEL, AtlasPage, AtlasRect, GlyphAtlas, RunStyle, ShapedGlyph, ShapedRun,
};
use aetna_core::text::msdf_atlas::{
DEFAULT_BASE_EM, DEFAULT_SPREAD, MSDF_BYTES_PER_PIXEL, MsdfAtlas, MsdfAtlasPage, MsdfGlyphKey,
MsdfRect, MsdfSlot,
};
use aetna_core::tree::{Rect, TextWrap};
use bytemuck::{Pod, Zeroable};
use cosmic_text::fontdb;
use ttf_parser::Face;
use aetna_core::paint::{PhysicalScissor, rgba_f32};
use aetna_core::runtime::TextRecorder;
const INITIAL_INSTANCE_CAPACITY: usize = 256;
const COLOR_INSTANCE_ATTRS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
1 => Float32x4, 2 => Float32x4, 3 => Float32x4, ];
const MSDF_INSTANCE_ATTRS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
1 => Float32x4, 2 => Float32x4, 3 => Float32x4, 4 => Float32x4, ];
const HIGHLIGHT_INSTANCE_ATTRS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
1 => Float32x4, 2 => Float32x4, ];
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
pub(crate) struct ColorGlyphInstance {
pub rect: [f32; 4],
pub uv: [f32; 4],
pub color: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
pub(crate) struct MsdfGlyphInstance {
pub rect: [f32; 4],
pub uv: [f32; 4],
pub color: [f32; 4],
pub params: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
pub(crate) struct HighlightInstance {
pub rect: [f32; 4],
pub color: [f32; 4],
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum TextRunKind {
Color,
Msdf,
Highlight,
}
#[derive(Clone, Copy)]
pub(crate) struct TextRun {
pub kind: TextRunKind,
pub page: u32,
pub scissor: Option<PhysicalScissor>,
pub first: u32,
pub count: u32,
}
struct PageTexture {
texture: wgpu::Texture,
bind_group: wgpu::BindGroup,
}
pub(crate) struct TextPaint {
pub atlas: GlyphAtlas,
pub msdf_atlas: MsdfAtlas,
color_pages: Vec<PageTexture>,
color_instances: Vec<ColorGlyphInstance>,
color_instance_buf: wgpu::Buffer,
color_instance_capacity: usize,
color_pipeline: wgpu::RenderPipeline,
color_page_bind_layout: wgpu::BindGroupLayout,
color_sampler: wgpu::Sampler,
msdf_pages: Vec<PageTexture>,
msdf_instances: Vec<MsdfGlyphInstance>,
msdf_instance_buf: wgpu::Buffer,
msdf_instance_capacity: usize,
msdf_pipeline: wgpu::RenderPipeline,
msdf_page_bind_layout: wgpu::BindGroupLayout,
msdf_sampler: wgpu::Sampler,
highlight_instances: Vec<HighlightInstance>,
highlight_instance_buf: wgpu::Buffer,
highlight_instance_capacity: usize,
highlight_pipeline: wgpu::RenderPipeline,
runs: Vec<TextRun>,
}
impl TextPaint {
pub(crate) fn new(
device: &wgpu::Device,
target_format: wgpu::TextureFormat,
sample_count: u32,
frame_bind_layout: &wgpu::BindGroupLayout,
) -> Self {
let color_page_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("aetna_wgpu::text::color_page_bind_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let color_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("aetna_wgpu::text::color_pipeline_layout"),
bind_group_layouts: &[Some(frame_bind_layout), Some(&color_page_bind_layout)],
immediate_size: 0,
});
let color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("stock::text"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT)),
});
let color_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("aetna_wgpu::text::color_pipeline"),
layout: Some(&color_pipeline_layout),
vertex: wgpu::VertexState {
module: &color_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (2 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ColorGlyphInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &COLOR_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: &color_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: target_format,
blend: Some(premultiplied_blend()),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: triangle_strip(),
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let color_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("aetna_wgpu::text::color_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let color_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::color_instance_buf"),
size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<ColorGlyphInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let msdf_page_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("aetna_wgpu::text::msdf_page_bind_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let msdf_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("aetna_wgpu::text::msdf_pipeline_layout"),
bind_group_layouts: &[Some(frame_bind_layout), Some(&msdf_page_bind_layout)],
immediate_size: 0,
});
let msdf_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("stock::text_msdf"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT_MSDF)),
});
let msdf_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("aetna_wgpu::text::msdf_pipeline"),
layout: Some(&msdf_pipeline_layout),
vertex: wgpu::VertexState {
module: &msdf_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (2 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<MsdfGlyphInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &MSDF_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: &msdf_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: target_format,
blend: Some(premultiplied_blend()),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: triangle_strip(),
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let msdf_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("aetna_wgpu::text::msdf_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let msdf_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::msdf_instance_buf"),
size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<MsdfGlyphInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let highlight_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("aetna_wgpu::text::highlight_pipeline_layout"),
bind_group_layouts: &[Some(frame_bind_layout)],
immediate_size: 0,
});
let highlight_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("stock::text_highlight"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT_HIGHLIGHT)),
});
let highlight_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("aetna_wgpu::text::highlight_pipeline"),
layout: Some(&highlight_pipeline_layout),
vertex: wgpu::VertexState {
module: &highlight_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (2 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<HighlightInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &HIGHLIGHT_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: &highlight_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: target_format,
blend: Some(premultiplied_blend()),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: triangle_strip(),
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let highlight_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::highlight_instance_buf"),
size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<HighlightInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
atlas: GlyphAtlas::new(),
msdf_atlas: MsdfAtlas::new(DEFAULT_BASE_EM, DEFAULT_SPREAD),
color_pages: Vec::new(),
color_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
color_instance_buf,
color_instance_capacity: INITIAL_INSTANCE_CAPACITY,
color_pipeline,
color_page_bind_layout,
color_sampler,
msdf_pages: Vec::new(),
msdf_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
msdf_instance_buf,
msdf_instance_capacity: INITIAL_INSTANCE_CAPACITY,
msdf_pipeline,
msdf_page_bind_layout,
msdf_sampler,
highlight_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
highlight_instance_buf,
highlight_instance_capacity: INITIAL_INSTANCE_CAPACITY,
highlight_pipeline,
runs: Vec::new(),
}
}
pub(crate) fn frame_begin(&mut self) {
self.color_instances.clear();
self.msdf_instances.clear();
self.highlight_instances.clear();
self.runs.clear();
}
#[allow(clippy::too_many_arguments)]
fn record_inner(
&mut self,
rect: Rect,
scissor: Option<PhysicalScissor>,
runs: &[(String, RunStyle)],
size: f32,
line_height: f32,
wrap: TextWrap,
anchor: TextAnchor,
scale_factor: f32,
) -> std::ops::Range<usize> {
let avail = wrap_available_width(rect.w, scale_factor, wrap, anchor);
let runs_ref: Vec<(&str, RunStyle)> = runs
.iter()
.map(|(text, style)| (text.as_str(), style.clone()))
.collect();
let shaped = self.atlas.shape_runs_with_line_height(
&runs_ref,
size,
line_height,
wrap,
anchor,
avail,
);
self.emit_shaped_glyphs(rect, scissor, &shaped, wrap, scale_factor)
}
fn emit_shaped_glyphs(
&mut self,
rect: Rect,
scissor: Option<PhysicalScissor>,
shaped: &ShapedRun,
wrap: TextWrap,
scale_factor: f32,
) -> std::ops::Range<usize> {
let runs_start = self.runs.len();
if shaped.glyphs.is_empty() && shaped.highlights.is_empty() && shaped.decorations.is_empty()
{
return runs_start..runs_start;
}
let logical_line_height = shaped.layout.line_height;
let v_offset = match wrap {
TextWrap::NoWrap => ((rect.h - logical_line_height).max(0.0)) * 0.5,
TextWrap::Wrap => 0.0,
};
let origin_x = rect.x;
let origin_y = rect.y + v_offset;
if !shaped.highlights.is_empty() {
let first = self.highlight_instances.len() as u32;
for h in &shaped.highlights {
self.highlight_instances.push(HighlightInstance {
rect: [origin_x + h.x, origin_y + h.y, h.w, h.h],
color: rgba_f32(h.color),
});
}
let count = self.highlight_instances.len() as u32 - first;
if count > 0 {
self.runs.push(TextRun {
kind: TextRunKind::Highlight,
page: 0,
scissor,
first,
count,
});
}
}
let mut current: Option<(TextRunKind, u32, u32)> = None;
for glyph in &shaped.glyphs {
let font_id = glyph.key.font;
let is_color = self.atlas.is_color_font(font_id);
if is_color {
self.atlas.ensure_color_glyph(glyph.key);
let Some(slot) = self.atlas.slot(glyph.key) else {
continue;
};
if slot.rect.w == 0 || slot.rect.h == 0 {
continue;
}
let page = slot.page;
let next_kind = TextRunKind::Color;
self.maybe_close_run(&mut current, next_kind, page, scissor);
self.push_color_glyph(glyph, slot, origin_x, origin_y, scale_factor);
} else {
let mkey = MsdfGlyphKey {
font: font_id,
glyph_id: glyph.key.glyph_id,
};
let Some(slot) = self.ensure_msdf(mkey, font_id, glyph.key.weight) else {
continue;
};
let page = slot.page;
let next_kind = TextRunKind::Msdf;
self.maybe_close_run(&mut current, next_kind, page, scissor);
self.push_msdf_glyph(glyph, slot, origin_x, origin_y);
}
}
if let Some((kind, page, first)) = current {
let count = self.instance_count_after(kind, first);
if count > 0 {
self.runs.push(TextRun {
kind,
page,
scissor,
first,
count,
});
}
}
if !shaped.decorations.is_empty() {
let first = self.highlight_instances.len() as u32;
for d in &shaped.decorations {
self.highlight_instances.push(HighlightInstance {
rect: [origin_x + d.x, origin_y + d.y, d.w, d.h],
color: rgba_f32(d.color),
});
}
let count = self.highlight_instances.len() as u32 - first;
if count > 0 {
self.runs.push(TextRun {
kind: TextRunKind::Highlight,
page: 0,
scissor,
first,
count,
});
}
}
runs_start..self.runs.len()
}
fn maybe_close_run(
&mut self,
current: &mut Option<(TextRunKind, u32, u32)>,
next_kind: TextRunKind,
next_page: u32,
scissor: Option<PhysicalScissor>,
) {
let new_start = match next_kind {
TextRunKind::Color => self.color_instances.len() as u32,
TextRunKind::Msdf => self.msdf_instances.len() as u32,
TextRunKind::Highlight => self.highlight_instances.len() as u32,
};
let needs_close = match current {
Some((kind, page, _)) => !same_kind(*kind, next_kind) || *page != next_page,
None => false,
};
if needs_close {
let (kind, page, first) = current.take().unwrap();
let count = self.instance_count_after(kind, first);
if count > 0 {
self.runs.push(TextRun {
kind,
page,
scissor,
first,
count,
});
}
}
if current.is_none() {
*current = Some((next_kind, next_page, new_start));
}
}
fn instance_count_after(&self, kind: TextRunKind, first: u32) -> u32 {
let len = match kind {
TextRunKind::Color => self.color_instances.len() as u32,
TextRunKind::Msdf => self.msdf_instances.len() as u32,
TextRunKind::Highlight => self.highlight_instances.len() as u32,
};
len.saturating_sub(first)
}
fn push_color_glyph(
&mut self,
glyph: &ShapedGlyph,
slot: aetna_core::text::atlas::GlyphSlot,
origin_x: f32,
origin_y: f32,
scale_factor: f32,
) {
let bx = origin_x + glyph.x + slot.offset.0 as f32 / scale_factor;
let by = origin_y + glyph.y - slot.offset.1 as f32 / scale_factor;
let bw = slot.rect.w as f32 / scale_factor;
let bh = slot.rect.h as f32 / scale_factor;
let atlas_page = self
.atlas
.page(slot.page)
.expect("shaped glyph references missing colour atlas page");
let page_w = atlas_page.width as f32;
let page_h = atlas_page.height as f32;
let uv = [
slot.rect.x as f32 / page_w,
slot.rect.y as f32 / page_h,
slot.rect.w as f32 / page_w,
slot.rect.h as f32 / page_h,
];
let inst_color = if slot.is_color {
[1.0, 1.0, 1.0, 1.0]
} else {
rgba_f32(glyph.color)
};
self.color_instances.push(ColorGlyphInstance {
rect: [bx, by, bw, bh],
uv,
color: inst_color,
});
}
fn push_msdf_glyph(
&mut self,
glyph: &ShapedGlyph,
slot: MsdfSlot,
origin_x: f32,
origin_y: f32,
) {
let logical_em = glyph.key.size();
let base_em = self.msdf_atlas.base_em() as f32;
let scale = logical_em / base_em;
let bx = origin_x + glyph.x + slot.bearing_x * scale;
let by = origin_y + glyph.y + slot.bearing_y * scale;
let bw = slot.rect.w as f32 * scale;
let bh = slot.rect.h as f32 * scale;
let atlas_page = self
.msdf_atlas
.page(slot.page)
.expect("shaped glyph references missing MSDF atlas page");
let page_w = atlas_page.width as f32;
let page_h = atlas_page.height as f32;
let uv = [
slot.rect.x as f32 / page_w,
slot.rect.y as f32 / page_h,
slot.rect.w as f32 / page_w,
slot.rect.h as f32 / page_h,
];
let color = rgba_f32(glyph.color);
self.msdf_instances.push(MsdfGlyphInstance {
rect: [bx, by, bw, bh],
uv,
color,
params: [slot.spread, 0.0, 0.0, 0.0],
});
}
fn ensure_msdf(
&mut self,
key: MsdfGlyphKey,
font_id: fontdb::ID,
weight: fontdb::Weight,
) -> Option<MsdfSlot> {
if let Some(slot) = self.msdf_atlas.slot(key) {
return Some(slot);
}
let font = self.atlas.font_system_mut().get_font(font_id, weight)?;
let face_index = self.atlas.font_system().db().face(font_id)?.index;
let face = Face::parse(font.data(), face_index).ok()?;
self.msdf_atlas.ensure(key, &face)
}
pub(crate) fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let color_dirty = self.atlas.take_dirty();
while self.color_pages.len() < self.atlas.pages().len() {
let i = self.color_pages.len();
let page = &self.atlas.pages()[i];
self.color_pages.push(create_color_page(
device,
&self.color_page_bind_layout,
&self.color_sampler,
page.width,
page.height,
));
}
for (page_idx, rect) in color_dirty {
let page = &self.atlas.pages()[page_idx];
upload_color_region(queue, &self.color_pages[page_idx].texture, page, rect);
}
let msdf_dirty = self.msdf_atlas.take_dirty();
while self.msdf_pages.len() < self.msdf_atlas.pages().len() {
let i = self.msdf_pages.len();
let page = &self.msdf_atlas.pages()[i];
self.msdf_pages.push(create_msdf_page(
device,
&self.msdf_page_bind_layout,
&self.msdf_sampler,
page.width,
page.height,
));
}
for (page_idx, rect) in msdf_dirty {
let page = &self.msdf_atlas.pages()[page_idx];
upload_msdf_region(queue, &self.msdf_pages[page_idx].texture, page, rect);
}
if self.color_instances.len() > self.color_instance_capacity {
let new_cap = self.color_instances.len().next_power_of_two();
self.color_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::color_instance_buf (resized)"),
size: (new_cap * std::mem::size_of::<ColorGlyphInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.color_instance_capacity = new_cap;
}
if !self.color_instances.is_empty() {
queue.write_buffer(
&self.color_instance_buf,
0,
bytemuck::cast_slice(&self.color_instances),
);
}
if self.msdf_instances.len() > self.msdf_instance_capacity {
let new_cap = self.msdf_instances.len().next_power_of_two();
self.msdf_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::msdf_instance_buf (resized)"),
size: (new_cap * std::mem::size_of::<MsdfGlyphInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.msdf_instance_capacity = new_cap;
}
if !self.msdf_instances.is_empty() {
queue.write_buffer(
&self.msdf_instance_buf,
0,
bytemuck::cast_slice(&self.msdf_instances),
);
}
if self.highlight_instances.len() > self.highlight_instance_capacity {
let new_cap = self.highlight_instances.len().next_power_of_two();
self.highlight_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aetna_wgpu::text::highlight_instance_buf (resized)"),
size: (new_cap * std::mem::size_of::<HighlightInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.highlight_instance_capacity = new_cap;
}
if !self.highlight_instances.is_empty() {
queue.write_buffer(
&self.highlight_instance_buf,
0,
bytemuck::cast_slice(&self.highlight_instances),
);
}
}
pub(crate) fn run(&self, index: usize) -> TextRun {
self.runs[index]
}
pub(crate) fn pipeline_for(&self, kind: TextRunKind) -> &wgpu::RenderPipeline {
match kind {
TextRunKind::Color => &self.color_pipeline,
TextRunKind::Msdf => &self.msdf_pipeline,
TextRunKind::Highlight => &self.highlight_pipeline,
}
}
pub(crate) fn instance_buf_for(&self, kind: TextRunKind) -> &wgpu::Buffer {
match kind {
TextRunKind::Color => &self.color_instance_buf,
TextRunKind::Msdf => &self.msdf_instance_buf,
TextRunKind::Highlight => &self.highlight_instance_buf,
}
}
pub(crate) fn page_bind_group(&self, kind: TextRunKind, page: u32) -> &wgpu::BindGroup {
match kind {
TextRunKind::Color => &self.color_pages[page as usize].bind_group,
TextRunKind::Msdf => &self.msdf_pages[page as usize].bind_group,
TextRunKind::Highlight => unreachable!("highlight runs carry no page binding"),
}
}
}
fn same_kind(a: TextRunKind, b: TextRunKind) -> bool {
a == b
}
fn premultiplied_blend() -> wgpu::BlendState {
wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}
}
fn triangle_strip() -> wgpu::PrimitiveState {
wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
}
}
fn create_color_page(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
width: u32,
height: u32,
) -> PageTexture {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("aetna_wgpu::text::color_page"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("aetna_wgpu::text::color_page_bg"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
PageTexture {
texture,
bind_group,
}
}
fn create_msdf_page(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
width: u32,
height: u32,
) -> PageTexture {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("aetna_wgpu::text::msdf_page"),
size: wgpu::Extent3d {
width,
height,
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: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("aetna_wgpu::text::msdf_page_bg"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
PageTexture {
texture,
bind_group,
}
}
impl TextRecorder for TextPaint {
fn record(
&mut self,
rect: Rect,
scissor: Option<PhysicalScissor>,
style: &RunStyle,
text: &str,
size: f32,
line_height: f32,
wrap: TextWrap,
anchor: TextAnchor,
scale_factor: f32,
) -> std::ops::Range<usize> {
self.record_inner(
rect,
scissor,
&[(text.to_string(), style.clone())],
size,
line_height,
wrap,
anchor,
scale_factor,
)
}
fn record_runs(
&mut self,
rect: Rect,
scissor: Option<PhysicalScissor>,
runs: &[(String, RunStyle)],
size: f32,
line_height: f32,
wrap: TextWrap,
anchor: TextAnchor,
scale_factor: f32,
) -> std::ops::Range<usize> {
self.record_inner(
rect,
scissor,
runs,
size,
line_height,
wrap,
anchor,
scale_factor,
)
}
}
fn wrap_available_width(
rect_w: f32,
_scale_factor: f32,
wrap: TextWrap,
anchor: TextAnchor,
) -> Option<f32> {
match (wrap, anchor) {
(TextWrap::Wrap, _) => Some(rect_w),
(TextWrap::NoWrap, TextAnchor::Start) => None,
(TextWrap::NoWrap, TextAnchor::Middle | TextAnchor::End) => Some(rect_w),
}
}
fn upload_color_region(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
page: &AtlasPage,
rect: AtlasRect,
) {
if rect.w == 0 || rect.h == 0 {
return;
}
let bpp = ATLAS_BYTES_PER_PIXEL as usize;
let row_bytes = rect.w as usize * bpp;
let mut bytes = Vec::with_capacity(row_bytes * rect.h as usize);
for row in 0..rect.h {
let y = rect.y + row;
let start = (y as usize * page.width as usize + rect.x as usize) * bpp;
let end = start + row_bytes;
bytes.extend_from_slice(&page.pixels[start..end]);
}
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: rect.x,
y: rect.y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
&bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(rect.w * ATLAS_BYTES_PER_PIXEL),
rows_per_image: Some(rect.h),
},
wgpu::Extent3d {
width: rect.w,
height: rect.h,
depth_or_array_layers: 1,
},
);
}
fn upload_msdf_region(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
page: &MsdfAtlasPage,
rect: MsdfRect,
) {
if rect.w == 0 || rect.h == 0 {
return;
}
let bpp = MSDF_BYTES_PER_PIXEL as usize;
let row_bytes = rect.w as usize * bpp;
let mut bytes = Vec::with_capacity(row_bytes * rect.h as usize);
for row in 0..rect.h {
let y = rect.y + row;
let start = (y as usize * page.width as usize + rect.x as usize) * bpp;
let end = start + row_bytes;
bytes.extend_from_slice(&page.pixels[start..end]);
}
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: rect.x,
y: rect.y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
&bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(rect.w * MSDF_BYTES_PER_PIXEL),
rows_per_image: Some(rect.h),
},
wgpu::Extent3d {
width: rect.w,
height: rect.h,
depth_or_array_layers: 1,
},
);
}