1use densa_text::{AtlasUpdate, GlyphUpload};
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum AtlasError {
11 TooLarge {
13 width: u32,
15 height: u32,
17 limit: u32,
19 },
20 BadWrite {
22 rect: (u32, u32, u32, u32),
24 bytes: usize,
26 atlas: (u32, u32),
28 },
29 UnsupportedUpdate,
31}
32
33impl fmt::Display for AtlasError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::TooLarge {
37 width,
38 height,
39 limit,
40 } => write!(
41 f,
42 "glyph atlas of {width}x{height} exceeds the device's texture limit {limit}"
43 ),
44 Self::BadWrite { rect, bytes, atlas } => write!(
45 f,
46 "glyph write {rect:?} with {bytes} bytes does not fit the {atlas:?} atlas"
47 ),
48 Self::UnsupportedUpdate => f.write_str("unsupported glyph atlas update"),
49 }
50 }
51}
52
53impl std::error::Error for AtlasError {}
54
55#[derive(Debug)]
57pub(crate) struct GpuAtlas {
58 texture: wgpu::Texture,
59 version: u64,
61}
62
63impl GpuAtlas {
64 pub(crate) fn new(device: &wgpu::Device) -> Self {
66 Self {
67 texture: create_texture(device, 1, 1),
68 version: 0,
69 }
70 }
71
72 pub(crate) fn version(&self) -> u64 {
75 self.version
76 }
77
78 pub(crate) fn size(&self) -> (u32, u32) {
79 (self.texture.width(), self.texture.height())
80 }
81
82 pub(crate) fn view(&self) -> wgpu::TextureView {
83 self.texture
84 .create_view(&wgpu::TextureViewDescriptor::default())
85 }
86
87 pub(crate) fn apply(
89 &mut self,
90 device: &wgpu::Device,
91 queue: &wgpu::Queue,
92 updates: impl IntoIterator<Item = AtlasUpdate>,
93 ) -> Result<usize, AtlasError> {
94 let mut writes = 0;
95 for update in updates {
96 match update {
97 AtlasUpdate::Resize { width, height } => {
98 self.grow(device, queue, width, height)?;
99 }
100 AtlasUpdate::Write(upload) => {
101 self.write(queue, &upload)?;
102 writes += 1;
103 }
104 _ => return Err(AtlasError::UnsupportedUpdate),
105 }
106 }
107 Ok(writes)
108 }
109
110 fn grow(
114 &mut self,
115 device: &wgpu::Device,
116 queue: &wgpu::Queue,
117 width: u32,
118 height: u32,
119 ) -> Result<(), AtlasError> {
120 let current = self.size();
121 let (width, height) = (width.max(current.0), height.max(current.1));
122 if (width, height) == current {
123 return Ok(());
124 }
125 let limit = device.limits().max_texture_dimension_2d;
126 if width > limit || height > limit {
127 return Err(AtlasError::TooLarge {
128 width,
129 height,
130 limit,
131 });
132 }
133
134 let grown = create_texture(device, width, height);
135 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
136 label: Some("densa atlas grow"),
137 });
138 encoder.copy_texture_to_texture(
139 self.texture.as_image_copy(),
140 grown.as_image_copy(),
141 self.texture.size(),
142 );
143 queue.submit([encoder.finish()]);
146 self.texture = grown;
147 self.version += 1;
148 Ok(())
149 }
150
151 fn write(&self, queue: &wgpu::Queue, upload: &GlyphUpload) -> Result<(), AtlasError> {
152 let (width, height) = self.size();
153 let inside = upload
154 .x
155 .checked_add(upload.width)
156 .is_some_and(|r| r <= width)
157 && upload
158 .y
159 .checked_add(upload.height)
160 .is_some_and(|b| b <= height);
161 let complete =
162 upload.coverage.len() as u64 == u64::from(upload.width) * u64::from(upload.height);
163 if !inside || !complete {
164 return Err(AtlasError::BadWrite {
165 rect: (upload.x, upload.y, upload.width, upload.height),
166 bytes: upload.coverage.len(),
167 atlas: (width, height),
168 });
169 }
170 if upload.width == 0 || upload.height == 0 {
171 return Ok(());
172 }
173 queue.write_texture(
174 wgpu::TexelCopyTextureInfo {
175 texture: &self.texture,
176 mip_level: 0,
177 origin: wgpu::Origin3d {
178 x: upload.x,
179 y: upload.y,
180 z: 0,
181 },
182 aspect: wgpu::TextureAspect::All,
183 },
184 &upload.coverage,
185 wgpu::TexelCopyBufferLayout {
186 offset: 0,
187 bytes_per_row: Some(upload.width),
188 rows_per_image: Some(upload.height),
189 },
190 wgpu::Extent3d {
191 width: upload.width,
192 height: upload.height,
193 depth_or_array_layers: 1,
194 },
195 );
196 Ok(())
197 }
198}
199
200fn create_texture(device: &wgpu::Device, width: u32, height: u32) -> wgpu::Texture {
202 device.create_texture(&wgpu::TextureDescriptor {
203 label: Some("densa glyph atlas"),
204 size: wgpu::Extent3d {
205 width,
206 height,
207 depth_or_array_layers: 1,
208 },
209 mip_level_count: 1,
210 sample_count: 1,
211 dimension: wgpu::TextureDimension::D2,
212 format: wgpu::TextureFormat::R8Unorm,
213 usage: wgpu::TextureUsages::TEXTURE_BINDING
214 | wgpu::TextureUsages::COPY_DST
215 | wgpu::TextureUsages::COPY_SRC,
216 view_formats: &[],
217 })
218}