use vello_common::encode::EncodedImage;
use vello_common::kurbo::{Affine, Rect, Vec2};
use vello_common::paint::{ImageSource, Tint, TintMode};
use vello_common::strip::Strip;
use frust_gpu::TierCaps;
use glifo::atlas::PendingBitmapUpload;
use glifo::{AtlasCommandRecorder, GlyphAtlas, PendingClearRect};
use crate::EngineError;
use crate::cache::images::{ATLAS_FORMAT_BYTES, AtlasRegion, ResidentImage};
use crate::diag::{EngineSpan, FrameTimestamps};
use super::GpuEncodedPaint;
use super::config::GpuConfig;
use super::paint_texture::GpuEncodedImage;
use super::strips::{GpuStrip, PaintType, StripDraw, pack_paint_descriptor};
pub const ATLAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
pub const ATLAS_USAGES: wgpu::TextureUsages = wgpu::TextureUsages::TEXTURE_BINDING
.union(wgpu::TextureUsages::COPY_DST)
.union(wgpu::TextureUsages::COPY_SRC)
.union(wgpu::TextureUsages::RENDER_ATTACHMENT);
pub const MAX_ATLAS_INDEX: u32 = 0xFF;
#[must_use]
pub fn atlas_texture_descriptor(
width: u32,
height: u32,
layers: u32,
) -> wgpu::TextureDescriptor<'static> {
wgpu::TextureDescriptor {
label: Some("frust-engine image atlas array"),
size: wgpu::Extent3d {
width: width.max(1),
height: height.max(1),
depth_or_array_layers: layers.max(2),
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: ATLAS_FORMAT,
usage: ATLAS_USAGES,
view_formats: &[],
}
}
#[must_use]
pub fn atlas_view_descriptor() -> wgpu::TextureViewDescriptor<'static> {
wgpu::TextureViewDescriptor {
label: Some("frust-engine image atlas array view"),
format: None,
dimension: Some(wgpu::TextureViewDimension::D2Array),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
usage: None,
}
}
#[must_use]
pub fn atlas_layer_view_descriptor(layer: u32) -> wgpu::TextureViewDescriptor<'static> {
wgpu::TextureViewDescriptor {
label: Some("frust-engine image atlas layer target"),
format: None,
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: layer,
array_layer_count: Some(1),
usage: None,
}
}
#[derive(Debug)]
pub struct AtlasArray {
texture: wgpu::Texture,
view: wgpu::TextureView,
width: u32,
height: u32,
layers: u32,
generation: u64,
}
impl AtlasArray {
#[must_use]
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
Self::with_layers(device, width, height, 1)
}
#[must_use]
pub fn with_layers(device: &wgpu::Device, width: u32, height: u32, layers: u32) -> Self {
let descriptor = atlas_texture_descriptor(width, height, layers);
let texture = device.create_texture(&descriptor);
let view = texture.create_view(&atlas_view_descriptor());
Self {
texture,
view,
width: descriptor.size.width,
height: descriptor.size.height,
layers: descriptor.size.depth_or_array_layers,
generation: 0,
}
}
#[must_use]
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
#[must_use]
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
#[must_use]
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn layers(&self) -> u32 {
self.layers
}
#[must_use]
pub fn layer_view(&self, layer: u32) -> Option<wgpu::TextureView> {
(layer < self.layers).then(|| {
self.texture
.create_view(&atlas_layer_view_descriptor(layer))
})
}
#[must_use]
pub fn generation(&self) -> u64 {
self.generation
}
pub fn ensure_layers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layers: u32,
) -> bool {
if layers <= self.layers || layers > MAX_ATLAS_INDEX + 1 {
return false;
}
let grown = Self::with_layers(device, self.width, self.height, layers);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("frust-engine image atlas growth"),
});
encoder.copy_texture_to_texture(
self.texture.as_image_copy(),
grown.texture.as_image_copy(),
wgpu::Extent3d {
width: self.width,
height: self.height,
depth_or_array_layers: self.layers,
},
);
queue.submit(std::iter::once(encoder.finish()));
let generation = self.generation.saturating_add(1);
*self = grown;
self.generation = generation;
true
}
pub fn write_region(&self, queue: &wgpu::Queue, region: AtlasRegion, pixels: &[u8]) -> bool {
if !self.contains(region) || pixels.len() != region.byte_len() {
return false;
}
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: region.offset[0],
y: region.offset[1],
z: region.layer,
},
aspect: wgpu::TextureAspect::All,
},
pixels,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(region.bytes_per_row()),
rows_per_image: Some(region.size[1]),
},
wgpu::Extent3d {
width: region.size[0],
height: region.size[1],
depth_or_array_layers: 1,
},
);
true
}
pub fn clear_region(&self, queue: &wgpu::Queue, region: AtlasRegion) -> bool {
if !self.contains(region) {
return false;
}
let zeros = vec![0_u8; region.byte_len()];
self.write_region(queue, region, &zeros)
}
#[must_use]
pub fn contains(&self, region: AtlasRegion) -> bool {
!region.is_empty()
&& region.layer < self.layers
&& region.offset[0].saturating_add(region.size[0]) <= self.width
&& region.offset[1].saturating_add(region.size[1]) <= self.height
}
}
#[must_use]
pub fn region_byte_len(region: AtlasRegion) -> usize {
(region.size[0] as usize)
.saturating_mul(region.size[1] as usize)
.saturating_mul(ATLAS_FORMAT_BYTES as usize)
}
#[must_use]
pub fn natural_to_dest(transform: Affine, natural: (u32, u32), dest: Rect) -> Option<Affine> {
let natural_w = f64::from(natural.0);
let natural_h = f64::from(natural.1);
if natural_w <= 0.0 || natural_h <= 0.0 {
return None;
}
Some(
transform
* Affine::translate((dest.x0, dest.y0))
* Affine::scale_non_uniform(dest.width() / natural_w, dest.height() / natural_h),
)
}
#[must_use]
pub fn x_y_advances(transform: Affine) -> (Vec2, Vec2) {
let c = transform.as_coeffs();
(Vec2::new(c[0], c[1]), Vec2::new(c[2], c[3]))
}
#[must_use]
pub const fn pack_image_size(width: u16, height: u16) -> u32 {
((width as u32) << 16) | (height as u32)
}
#[must_use]
pub const fn pack_image_offset(x: u16, y: u16) -> u32 {
((x as u32) << 16) | (y as u32)
}
#[must_use]
pub fn pack_image_params(
quality: u32,
extend_x: u32,
extend_y: u32,
atlas_index: u32,
is_external: bool,
) -> u32 {
debug_assert!(quality <= 3, "quality must fit two bits");
debug_assert!(extend_x <= 3, "extend_x must fit two bits");
debug_assert!(extend_y <= 3, "extend_y must fit two bits");
debug_assert!(
atlas_index <= MAX_ATLAS_INDEX,
"atlas index {atlas_index} exceeds {MAX_ATLAS_INDEX}",
);
(u32::from(is_external) << 14)
| ((atlas_index & MAX_ATLAS_INDEX) << 6)
| ((extend_y & 0b11) << 4)
| ((extend_x & 0b11) << 2)
| (quality & 0b11)
}
#[must_use]
pub fn pack_tint(tint: Option<Tint>) -> (u32, u32) {
match tint {
Some(tint) => (
tint.color.premultiply().to_rgba8().to_u32(),
tint.mode.as_u32(),
),
None => (u32::MAX, TintMode::Multiply.as_u32()),
}
}
pub const fn extend_mode(extend: peniko::Extend) -> u32 {
match extend {
peniko::Extend::Pad => 0,
peniko::Extend::Repeat => 1,
peniko::Extend::Reflect => 2,
}
}
#[must_use]
pub fn lower_encoded_image(
image: &EncodedImage,
resident: &ResidentImage,
) -> Option<GpuEncodedPaint> {
match &image.source {
ImageSource::OpaqueId { id, .. } if *id == resident.id => {}
_ => return None,
}
let region = resident.region;
let (tint, tint_mode) = pack_tint(image.tint);
Some(GpuEncodedPaint::Image(GpuEncodedImage {
image_params: pack_image_params(
image.sampler.quality as u32,
extend_mode(image.sampler.x_extend),
extend_mode(image.sampler.y_extend),
region.layer,
false,
),
image_size: pack_image_size(truncate_u16(region.size[0]), truncate_u16(region.size[1])),
image_offset: pack_image_offset(
truncate_u16(region.offset[0]),
truncate_u16(region.offset[1]),
),
transform: image.transform.as_coeffs().map(|coeff| coeff as f32),
tint,
tint_mode,
image_padding: resident.padding,
}))
}
const fn truncate_u16(value: u32) -> u16 {
if value > u16::MAX as u32 {
u16::MAX
} else {
value as u16
}
}
const PLACEHOLDER_PAINT_TEX_WIDTH: u32 = 1;
const MIN_ATLAS_INSTANCES: u64 = 256;
pub fn push_solid_strips(
strips: &[Strip],
payload: u32,
depth: u32,
out: &mut Vec<GpuStrip>,
) -> u32 {
let draw = StripDraw {
payload,
paint: pack_paint_descriptor(PaintType::Solid, 0),
depth_index: depth,
};
let before = out.len();
for pair in strips.windows(2) {
let span = GpuStrip::from_strip_pair(&pair[0], &pair[1], draw);
if span.width > 0 {
out.push(span);
}
if let Some(gap) = GpuStrip::gap_fill(&pair[0], &pair[1], draw) {
out.push(gap);
}
}
u32::try_from(out.len().saturating_sub(before)).unwrap_or(u32::MAX)
}
#[derive(Debug, Default)]
pub struct AtlasPageBuffers {
pub instances: Vec<GpuStrip>,
pub alphas: Vec<u8>,
}
impl AtlasPageBuffers {
pub fn clear(&mut self) {
self.instances.clear();
self.alphas.clear();
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.instances.is_empty()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AtlasRenderReport {
pub cleared: u32,
pub uploaded: u32,
pub pages: u32,
pub instances: u32,
pub refused: u32,
}
impl AtlasRenderReport {
#[must_use]
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug)]
struct AtlasResourceTexture {
texture: wgpu::Texture,
view: wgpu::TextureView,
width: u32,
height: u32,
}
impl AtlasResourceTexture {
fn new(device: &wgpu::Device, descriptor: &wgpu::TextureDescriptor<'_>) -> Self {
let texture = device.create_texture(descriptor);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
texture,
view,
width: descriptor.size.width,
height: descriptor.size.height,
}
}
fn copy_target(&self) -> wgpu::TexelCopyTextureInfo<'_> {
wgpu::TexelCopyTextureInfo {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
}
}
fn extent(&self) -> wgpu::Extent3d {
wgpu::Extent3d {
width: self.width,
height: self.height,
depth_or_array_layers: 1,
}
}
}
#[derive(Debug)]
struct AtlasPlaceholders {
layer_input: wgpu::TextureView,
array: wgpu::TextureView,
external: wgpu::TextureView,
paints: wgpu::TextureView,
gradients: wgpu::TextureView,
}
impl AtlasPlaceholders {
fn new(device: &wgpu::Device) -> Self {
Self {
layer_input: placeholder(
device,
"frust-engine atlas pass layer input",
ATLAS_FORMAT,
false,
),
array: placeholder(device, "frust-engine atlas pass array", ATLAS_FORMAT, true),
external: placeholder(
device,
"frust-engine atlas pass external",
ATLAS_FORMAT,
false,
),
paints: placeholder(
device,
"frust-engine atlas pass paints",
super::RESOURCE_TEXTURE_FORMAT,
false,
),
gradients: placeholder(
device,
"frust-engine atlas pass gradients",
ATLAS_FORMAT,
false,
),
}
}
}
fn placeholder(
device: &wgpu::Device,
label: &'static str,
format: wgpu::TextureFormat,
array: bool,
) -> wgpu::TextureView {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: if array { 2 } else { 1 },
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
texture.create_view(&wgpu::TextureViewDescriptor {
label: Some(label),
dimension: Some(if array {
wgpu::TextureViewDimension::D2Array
} else {
wgpu::TextureViewDimension::D2
}),
..Default::default()
})
}
#[derive(Debug)]
pub struct AtlasRenderer {
alphas: AtlasResourceTexture,
config: wgpu::Buffer,
instances: wgpu::Buffer,
instance_capacity: u64,
placeholders: AtlasPlaceholders,
buffers: AtlasPageBuffers,
}
impl AtlasRenderer {
#[must_use]
pub fn new(device: &wgpu::Device, caps: &TierCaps) -> Self {
let dim = caps.resource_texture_dim;
Self {
alphas: AtlasResourceTexture::new(
device,
&super::alpha_texture_descriptor(dim, super::MIN_RESOURCE_TEXTURE_HEIGHT),
),
config: device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frust-engine atlas pass config uniform"),
size: GpuConfig::SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}),
instances: device.create_buffer(&instance_descriptor(
MIN_ATLAS_INSTANCES * size_of::<GpuStrip>() as u64,
)),
instance_capacity: MIN_ATLAS_INSTANCES * size_of::<GpuStrip>() as u64,
placeholders: AtlasPlaceholders::new(device),
buffers: AtlasPageBuffers::default(),
}
}
#[must_use]
pub fn alpha_texture_size(&self) -> (u32, u32) {
(self.alphas.width, self.alphas.height)
}
#[must_use]
pub fn instance_capacity(&self) -> u64 {
self.instance_capacity
}
pub fn clear_rect(
&self,
queue: &wgpu::Queue,
atlas: &AtlasArray,
rect: PendingClearRect,
) -> bool {
atlas.clear_region(queue, clear_rect_region(rect))
}
pub fn upload_pixmap(
&self,
queue: &wgpu::Queue,
atlas: &AtlasArray,
upload: &PendingBitmapUpload,
) -> bool {
let region = slot_region(upload);
if u32::from(upload.pixmap.width()) != region.size[0]
|| u32::from(upload.pixmap.height()) != region.size[1]
{
return false;
}
atlas.write_region(queue, region, upload.pixmap.data_as_u8_slice())
}
#[expect(
clippy::too_many_arguments,
reason = "one page's whole draw call: device/queue, the pipeline, the \
array it draws into, which layer, the page's own instances, \
and the timestamp sink, each owned by a different caller"
)]
pub fn render_page(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
pipeline: &wgpu::RenderPipeline,
atlas: &AtlasArray,
layer: u32,
page: &mut AtlasPageBuffers,
timestamps: FrameTimestamps<'_>,
) -> Result<u32, EngineError> {
if page.is_empty() {
return Ok(0);
}
let Some(target) = atlas.layer_view(layer) else {
return Err(EngineError::AtlasError);
};
let height = super::alpha_texture_height(page.alphas.len(), self.alphas.width)?;
if height > self.alphas.height {
self.alphas = AtlasResourceTexture::new(
device,
&super::alpha_texture_descriptor(self.alphas.width, height),
);
}
let alphas = &self.alphas;
super::with_padded_alphas(&mut page.alphas, alphas.width, alphas.height, |bytes| {
queue.write_texture(
alphas.copy_target(),
bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(super::resource_bytes_per_row(alphas.width)),
rows_per_image: Some(alphas.height),
},
alphas.extent(),
);
});
let config = super::targets::atlas_layer_config(
atlas.size(),
self.alphas.width,
PLACEHOLDER_PAINT_TEX_WIDTH,
);
queue.write_buffer(&self.config, 0, bytemuck::bytes_of(&config));
let bytes: &[u8] = bytemuck::cast_slice(&page.instances);
self.grow_instances(device, bytes.len() as u64);
queue.write_buffer(&self.instances, 0, bytes);
let groups = self.bind_groups(device, pipeline);
let count = u32::try_from(page.instances.len()).unwrap_or(u32::MAX);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("frust-engine atlas replay"),
});
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("frust-engine atlas replay pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &target,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: timestamps.writes(EngineSpan::Prepass),
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(pipeline);
pass.set_vertex_buffer(0, self.instances.slice(..));
for (index, group) in groups.iter().enumerate() {
pass.set_bind_group(index as u32, group, &[]);
}
pass.draw(GpuStrip::vertex_range(), GpuStrip::instance_range(0, count));
}
queue.submit(std::iter::once(encoder.finish()));
Ok(count)
}
#[expect(
clippy::too_many_arguments,
reason = "the whole replay pass's inputs: device/queue, the pipeline, \
the array and the glyph source it drains, the timestamp \
sink, and the caller's own lowering closure, each owned by \
a different part of the renderer"
)]
pub fn render_pending<L>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
pipeline: &wgpu::RenderPipeline,
atlas: &AtlasArray,
glyphs: &mut GlyphAtlas,
timestamps: FrameTimestamps<'_>,
mut lower: L,
) -> AtlasRenderReport
where
L: FnMut(&AtlasCommandRecorder, &mut AtlasPageBuffers) -> bool,
{
let mut report = AtlasRenderReport::default();
for rect in glyphs.drain_pending_clear_rects() {
if self.clear_rect(queue, atlas, rect) {
report.cleared = report.cleared.saturating_add(1);
} else {
report.refused = report.refused.saturating_add(1);
}
}
for upload in glyphs.drain_pending_uploads() {
if self.upload_pixmap(queue, atlas, &upload) {
report.uploaded = report.uploaded.saturating_add(1);
} else {
report.refused = report.refused.saturating_add(1);
}
}
let mut buffers = core::mem::take(&mut self.buffers);
glyphs.replay_pending_atlas_commands(|recorder| {
buffers.clear();
if !lower(recorder, &mut buffers) {
report.refused = report.refused.saturating_add(1);
return;
}
match self.render_page(
device,
queue,
pipeline,
atlas,
recorder.page_index,
&mut buffers,
timestamps,
) {
Ok(0) => {}
Ok(instances) => {
report.pages = report.pages.saturating_add(1);
report.instances = report.instances.saturating_add(instances);
}
Err(_) => report.refused = report.refused.saturating_add(1),
}
});
self.buffers = buffers;
report
}
fn grow_instances(&mut self, device: &wgpu::Device, required: u64) {
if self.instance_capacity >= required {
return;
}
let capacity = required
.checked_next_power_of_two()
.unwrap_or(required)
.max(MIN_ATLAS_INSTANCES * size_of::<GpuStrip>() as u64);
self.instances = device.create_buffer(&instance_descriptor(capacity));
self.instance_capacity = capacity;
}
fn bind_groups(
&self,
device: &wgpu::Device,
pipeline: &wgpu::RenderPipeline,
) -> [wgpu::BindGroup; 4] {
[
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frust-engine atlas pass resources"),
layout: &pipeline.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.alphas.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: self.config.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(
&self.placeholders.layer_input,
),
},
],
}),
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frust-engine atlas pass images"),
layout: &pipeline.get_bind_group_layout(1),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.placeholders.array),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.placeholders.external),
},
],
}),
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frust-engine atlas pass paints"),
layout: &pipeline.get_bind_group_layout(2),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.placeholders.paints),
}],
}),
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frust-engine atlas pass gradients"),
layout: &pipeline.get_bind_group_layout(3),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.placeholders.gradients),
}],
}),
]
}
}
fn instance_descriptor(size: u64) -> wgpu::BufferDescriptor<'static> {
wgpu::BufferDescriptor {
label: Some("frust-engine atlas strip instances"),
size,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}
}
#[must_use]
pub fn clear_rect_region(rect: PendingClearRect) -> AtlasRegion {
AtlasRegion {
layer: rect.page_index,
offset: [u32::from(rect.x), u32::from(rect.y)],
size: [u32::from(rect.width), u32::from(rect.height)],
}
}
#[must_use]
pub fn slot_region(upload: &PendingBitmapUpload) -> AtlasRegion {
let slot = upload.atlas_slot;
AtlasRegion {
layer: slot.page_index,
offset: [u32::from(slot.x), u32::from(slot.y)],
size: [u32::from(slot.width), u32::from(slot.height)],
}
}
#[cfg(test)]
mod tests {
use super::*;
use vello_common::kurbo::Point;
fn region(layer: u32, offset: [u32; 2], size: [u32; 2]) -> AtlasRegion {
AtlasRegion {
layer,
offset,
size,
}
}
#[test]
fn the_atlas_is_a_sampled_copyable_renderable_rgba8_array() {
let descriptor = atlas_texture_descriptor(1024, 1024, 4);
assert_eq!(descriptor.format, ATLAS_FORMAT);
assert_eq!(descriptor.dimension, wgpu::TextureDimension::D2);
assert_eq!(descriptor.size.depth_or_array_layers, 4);
assert_eq!(descriptor.mip_level_count, 1);
for usage in [
wgpu::TextureUsages::TEXTURE_BINDING,
wgpu::TextureUsages::COPY_DST,
wgpu::TextureUsages::COPY_SRC,
wgpu::TextureUsages::RENDER_ATTACHMENT,
] {
assert!(descriptor.usage.contains(usage));
}
}
#[test]
fn a_zero_extent_descriptor_is_raised_to_a_creatable_one() {
let descriptor = atlas_texture_descriptor(0, 0, 0);
assert_eq!(descriptor.size.width, 1);
assert_eq!(descriptor.size.height, 1);
assert_eq!(descriptor.size.depth_or_array_layers, 2);
}
#[test]
fn the_layer_floor_is_two_because_wgpu_hal_gles_ignores_the_view_dimension() {
assert_eq!(
atlas_texture_descriptor(64, 64, 0)
.size
.depth_or_array_layers,
2
);
assert_eq!(
atlas_texture_descriptor(64, 64, 1)
.size
.depth_or_array_layers,
2
);
assert_eq!(
atlas_texture_descriptor(64, 64, 3)
.size
.depth_or_array_layers,
3,
"a request already past the floor is not clamped down to it"
);
}
#[test]
fn the_view_the_shader_binds_is_a_layered_one() {
assert_eq!(
atlas_view_descriptor().dimension,
Some(wgpu::TextureViewDimension::D2Array)
);
}
#[test]
fn image_params_round_trip_through_the_shaders_own_field_widths() {
let packed = pack_image_params(1, 2, 3, 200, false);
assert_eq!(packed & 0b11, 1, "quality");
assert_eq!((packed >> 2) & 0b11, 2, "extend_x");
assert_eq!((packed >> 4) & 0b11, 3, "extend_y");
assert_eq!((packed >> 6) & 0xFF, 200, "atlas index");
assert_eq!((packed >> 14) & 1, 0, "source kind");
assert_eq!(pack_image_params(0, 0, 0, 0, true) >> 14 & 1, 1);
}
#[test]
fn size_and_offset_pack_with_the_first_component_high() {
assert_eq!(pack_image_size(0x1234, 0x5678), 0x1234_5678);
assert_eq!(pack_image_offset(0x00FF, 0xAB00), 0x00FF_AB00);
}
#[test]
fn an_absent_tint_is_the_identity_multiply() {
let (color, mode) = pack_tint(None);
assert_eq!(color, u32::MAX);
assert_eq!(mode, TintMode::Multiply.as_u32());
}
#[test]
fn natural_to_dest_lands_the_natural_corners_on_the_dest_corners() {
let dest = Rect::new(5.0, 6.0, 45.0, 46.0);
let transform = natural_to_dest(Affine::IDENTITY, (2, 2), dest).expect("non-degenerate");
assert_eq!(transform * Point::new(0.0, 0.0), Point::new(5.0, 6.0));
assert_eq!(transform * Point::new(2.0, 2.0), Point::new(45.0, 46.0));
}
#[test]
fn natural_to_dest_composes_the_widgets_own_transform_outermost() {
let dest = Rect::new(0.0, 0.0, 4.0, 4.0);
let transform =
natural_to_dest(Affine::translate((10.0, 20.0)), (2, 2), dest).expect("non-degenerate");
assert_eq!(transform * Point::new(0.0, 0.0), Point::new(10.0, 20.0));
}
#[test]
fn natural_to_dest_refuses_a_degenerate_natural_size() {
let dest = Rect::new(0.0, 0.0, 10.0, 10.0);
assert!(natural_to_dest(Affine::IDENTITY, (0, 4), dest).is_none());
assert!(natural_to_dest(Affine::IDENTITY, (4, 0), dest).is_none());
}
#[test]
fn advances_are_the_linear_part_only() {
let transform = Affine::new([2.0, 3.0, 4.0, 5.0, 100.0, 200.0]);
let (x_advance, y_advance) = x_y_advances(transform);
assert_eq!(x_advance, Vec2::new(2.0, 3.0));
assert_eq!(y_advance, Vec2::new(4.0, 5.0));
}
#[test]
fn a_layer_target_names_exactly_one_layer_as_a_plain_2d_view() {
let descriptor = atlas_layer_view_descriptor(3);
assert_eq!(descriptor.dimension, Some(wgpu::TextureViewDimension::D2));
assert_eq!(descriptor.base_array_layer, 3);
assert_eq!(
descriptor.array_layer_count,
Some(1),
"a colour attachment must name exactly one layer"
);
assert_eq!(descriptor.base_mip_level, 0);
assert_ne!(descriptor.dimension, atlas_view_descriptor().dimension);
}
#[test]
fn a_clear_rect_becomes_its_whole_padded_rectangle() {
let region = clear_rect_region(PendingClearRect {
page_index: 2,
x: 40,
y: 8,
width: 18,
height: 22,
});
assert_eq!(region.layer, 2);
assert_eq!(region.offset, [40, 8]);
assert_eq!(region.size, [18, 22]);
assert_eq!(region.byte_len(), 18 * 22 * ATLAS_FORMAT_BYTES as usize);
}
#[test]
fn a_solid_run_expands_to_alpha_sampled_spans_carrying_one_colour() {
let run = [
Strip::new(4, 0, 0, false),
Strip::new(20, 0, 32, false),
Strip::new(64, 0, 80, false),
];
let mut out = Vec::new();
let pushed = push_solid_strips(&run, 0xDEAD_BEEF, 7, &mut out);
assert_eq!(pushed, 2, "two pairs, each contributing one span");
assert_eq!(out.len(), 2);
for instance in &out {
assert_eq!(instance.payload, 0xDEAD_BEEF, "a solid colour is per draw");
assert_eq!(instance.depth_index, 7);
assert!(!instance.is_rect());
assert_eq!(
instance.paint(),
pack_paint_descriptor(PaintType::Solid, 0),
"a solid instance indexes no encoded-paint record"
);
assert_eq!(
instance.dense_width_or_rect_height, instance.width,
"every column of a replayed glyph samples coverage"
);
}
assert_eq!(out[0].x, 4);
assert_eq!(
out[0].width, 8,
"32 coverage bytes at 4 per column is 8 pixels"
);
assert_eq!(out[0].col_idx_or_rect_frac, 0);
assert_eq!(out[1].x, 20);
assert_eq!(out[1].width, 12);
assert_eq!(
out[1].col_idx_or_rect_frac, 8,
"the second span starts at the column its coverage does"
);
}
#[test]
fn a_winding_gap_between_two_strips_is_filled_solid() {
let run = [
Strip::new(0, 0, 0, false),
Strip::new(32, 0, 16, true),
Strip::new(48, 0, 32, false),
];
let mut out = Vec::new();
let pushed = push_solid_strips(&run, 0x11, 0, &mut out);
assert_eq!(pushed, 3, "two spans plus the gap between them");
let gap = out
.iter()
.find(|instance| instance.dense_width_or_rect_height == 0)
.copied();
let gap = match gap {
Some(gap) => gap,
None => unreachable!("the flagged pair fills its gap"),
};
assert_eq!(gap.x, 4, "the gap starts where the first strip ends");
assert_eq!(gap.width, 28);
assert_eq!(gap.payload, 0x11, "the fill takes the run's own colour");
assert_eq!(gap.col_idx_or_rect_frac, 0, "a gap samples no coverage");
}
#[test]
fn a_run_with_no_pair_in_it_draws_nothing() {
let mut out = Vec::new();
assert_eq!(push_solid_strips(&[], 0, 0, &mut out), 0);
assert_eq!(
push_solid_strips(&[Strip::new(0, 0, 0, false)], 0, 0, &mut out),
0,
"a lone sentinel is not a span"
);
assert!(out.is_empty());
}
#[test]
fn an_emptied_page_keeps_its_capacity_for_the_next_one() {
let mut buffers = AtlasPageBuffers::default();
assert!(buffers.is_empty());
buffers.instances.push(GpuStrip::solid_fill(
0,
0,
8,
StripDraw {
payload: 0,
paint: 0,
depth_index: 0,
},
));
buffers.alphas.extend_from_slice(&[1, 2, 3, 4]);
assert!(!buffers.is_empty());
let instances = buffers.instances.capacity();
let alphas = buffers.alphas.capacity();
buffers.clear();
assert!(buffers.is_empty());
assert!(buffers.alphas.is_empty());
assert_eq!(buffers.instances.capacity(), instances);
assert_eq!(buffers.alphas.capacity(), alphas);
}
#[test]
fn a_report_that_serviced_nothing_reads_as_empty() {
let mut report = AtlasRenderReport::default();
assert!(report.is_empty());
report.refused = 1;
assert!(
!report.is_empty(),
"a refusal is work that happened, not an idle frame"
);
}
#[test]
fn the_stand_in_paint_texture_width_is_reconstructable_by_the_shader() {
assert!(PLACEHOLDER_PAINT_TEX_WIDTH.is_power_of_two());
assert_eq!(
super::super::config::tex_width_bits(PLACEHOLDER_PAINT_TEX_WIDTH),
0
);
}
#[test]
fn a_regions_byte_footprint_and_stride_agree_with_rgba8() {
let populated = region(0, [4, 8], [16, 32]);
assert_eq!(populated.bytes_per_row(), 64);
assert_eq!(populated.byte_len(), 64 * 32);
assert_eq!(region_byte_len(populated), populated.byte_len());
assert!(!populated.is_empty());
assert!(region(0, [0, 0], [0, 4]).is_empty());
}
}