1use crate::*;
2
3pub(crate) const INITIAL_BUFFER_SIZE: u64 = 4096;
4
5
6const ATLAS_BIND_GROUP_LAYOUT: BindGroupLayoutDescriptor = wgpu::BindGroupLayoutDescriptor {
7 entries: &[
8 BindGroupLayoutEntry {
9 binding: 0,
10 visibility: ShaderStages::VERTEX.union(ShaderStages::FRAGMENT),
11 ty: BindingType::Texture {
12 multisampled: false,
13 view_dimension: TextureViewDimension::D2,
14 sample_type: TextureSampleType::Float { filterable: true },
15 },
16 count: None,
17 },
18 BindGroupLayoutEntry {
19 binding: 1,
20 visibility: ShaderStages::FRAGMENT,
21 ty: BindingType::Sampler(SamplerBindingType::Filtering),
22 count: None,
23 },
24 ],
25 label: Some("atlas bind group layout"),
26};
27
28pub struct TextRendererParams {
30 pub atlas_page_size: AtlasPageSize,
32}
33impl Default for TextRendererParams {
34 fn default() -> Self {
35 let atlas_page_size = AtlasPageSize::DownlevelWrbgl2Max; Self { atlas_page_size }
39 }
40}
41pub enum AtlasPageSize {
43 Flat(u32),
45 CurrentDeviceMax,
47 DownlevelWrbgl2Max,
49 DownlevelMax,
51 WgpuMax,
53}
54impl AtlasPageSize {
55 fn size(self, device: &Device) -> u32 {
56 match self {
57 AtlasPageSize::Flat(i) => i,
58 AtlasPageSize::DownlevelWrbgl2Max => Limits::downlevel_defaults().max_texture_dimension_2d,
59 AtlasPageSize::DownlevelMax => Limits::downlevel_webgl2_defaults().max_texture_dimension_2d,
60 AtlasPageSize::WgpuMax => Limits::default().max_texture_dimension_2d,
61 AtlasPageSize::CurrentDeviceMax => device.limits().max_texture_dimension_2d,
62 }
63 }
64}
65
66fn create_vertex_buffer(device: &Device, size: u64) -> Buffer {
67 device.create_buffer(&BufferDescriptor {
68 label: Some("shared vertex buffer"),
69 size,
70 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
71 mapped_at_creation: false,
72 })
73}
74
75impl ContextlessTextRenderer {
76 pub fn new_with_params(
77 device: &Device,
78 _queue: &Queue,
79 format: TextureFormat,
80 depth_stencil: Option<DepthStencilState>,
81 params: TextRendererParams,
82 ) -> Self {
83 let _srgb = format.is_srgb();
84 let atlas_size = params.atlas_page_size.size(device);
87
88 let mask_texture = device.create_texture(&TextureDescriptor {
89 label: Some("atlas"),
90 size: Extent3d {
91 width: atlas_size,
92 height: atlas_size,
93 depth_or_array_layers: 1,
94 },
95 mip_level_count: 1,
96 sample_count: 1,
97 dimension: TextureDimension::D2,
98 format: TextureFormat::R8Unorm,
99 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
100 view_formats: &[],
101 });
102 let mask_texture_view = mask_texture.create_view(&TextureViewDescriptor::default());
103
104
105 let sampler = device.create_sampler(&SamplerDescriptor {
106 label: Some("sampler"),
107 min_filter: FilterMode::Nearest,
108 mag_filter: FilterMode::Nearest,
109 mipmap_filter: FilterMode::Nearest,
110 lod_min_clamp: 0f32,
111 lod_max_clamp: 0f32,
112 ..Default::default()
113 });
114
115 let shader = device.create_shader_module(ShaderModuleDescriptor {
116 label: Some("shader"),
117 source: ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
118 });
119
120 let vertex_buffer_layout = wgpu::VertexBufferLayout {
121 array_stride: std::mem::size_of::<Quad>() as wgpu::BufferAddress,
122 step_mode: wgpu::VertexStepMode::Instance,
123 attributes: &wgpu::vertex_attr_array![
124 0 => Sint32x2,
125 1 => Uint32,
126 2 => Uint32,
127 3 => Uint32,
128 4 => Float32,
129 5 => Uint32,
130 6 => Sint16x4,
131 ],
132 };
133
134 let params = Params {
135 screen_resolution_width: 0.0,
136 screen_resolution_height: 0.0,
137 _pad: [0, 0],
138 };
139
140 let params_buffer = device.create_buffer(&BufferDescriptor {
141 label: Some("params"),
142 size: mem::size_of::<Params>() as u64,
143 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
144 mapped_at_creation: false,
145 });
146
147 let params_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
148 entries: &[BindGroupLayoutEntry {
149 binding: 0,
150 visibility: ShaderStages::VERTEX,
151 ty: BindingType::Buffer {
152 ty: BufferBindingType::Uniform,
153 has_dynamic_offset: false,
154 min_binding_size: NonZeroU64::new(mem::size_of::<Params>() as u64),
155 },
156 count: None,
157 }],
158 label: Some("uniforms bind group layout"),
159 });
160
161 let params_bind_group = device.create_bind_group(&BindGroupDescriptor {
162 layout: ¶ms_layout,
163 entries: &[BindGroupEntry {
164 binding: 0,
165 resource: params_buffer.as_entire_binding(),
166 }],
167 label: Some("uniforms bind group"),
168 });
169
170 let atlas_bind_group_layout = device.create_bind_group_layout(&ATLAS_BIND_GROUP_LAYOUT);
171 let mask_bind_group = device.create_bind_group(&BindGroupDescriptor {
172 layout: &atlas_bind_group_layout,
173 entries: &[
174 BindGroupEntry {
175 binding: 0,
176 resource: BindingResource::TextureView(&mask_texture_view),
177 },
178 BindGroupEntry {
179 binding: 1,
180 resource: BindingResource::Sampler(&sampler),
181 },
182 ],
183 label: Some("atlas bind group"),
184 });
185
186 let glyph_cache = LruCache::unbounded_with_hasher(BuildHasherDefault::<FxHasher>::default());
187
188 let mask_atlas_pages = vec![AtlasPage::<GrayImage> {
189 image: GrayImage::from_pixel(atlas_size, atlas_size, Luma([0])),
190 packer: BucketedAtlasAllocator::new(size2(atlas_size as i32, atlas_size as i32)),
191 quads: Vec::<Quad>::with_capacity(300),
192 gpu: Some(GpuAtlasPage {
193 texture: mask_texture,
194 bind_group: mask_bind_group,
195 }),
196 quad_count_before_render: 0,
197 }];
198
199 let color_texture = device.create_texture(&TextureDescriptor {
200 label: Some("atlas"),
201 size: Extent3d {
202 width: atlas_size,
203 height: atlas_size,
204 depth_or_array_layers: 1,
205 },
206 mip_level_count: 1,
207 sample_count: 1,
208 dimension: TextureDimension::D2,
209 format: TextureFormat::Rgba8Unorm,
210 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
211 view_formats: &[],
212 });
213 let color_texture_view = color_texture.create_view(&TextureViewDescriptor::default());
214
215
216 let color_bind_group = device.create_bind_group(&BindGroupDescriptor {
217 layout: &atlas_bind_group_layout,
218 entries: &[
219 BindGroupEntry {
220 binding: 0,
221 resource: BindingResource::TextureView(&color_texture_view),
222 },
223 BindGroupEntry {
224 binding: 1,
225 resource: BindingResource::Sampler(&sampler),
226 },
227 ],
228 label: Some("atlas bind group"),
229 });
230
231 let color_atlas_pages = vec![AtlasPage::<RgbaImage> {
232 image: RgbaImage::from_pixel(atlas_size, atlas_size, Rgba([0, 0, 0, 0])),
233 packer: BucketedAtlasAllocator::new(size2(atlas_size as i32, atlas_size as i32)),
234 quads: Vec::<Quad>::with_capacity(300),
235 gpu: Some(GpuAtlasPage {
236 texture: color_texture,
237 bind_group: color_bind_group,
238 }),
239 quad_count_before_render: 0,
240 }];
241
242 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
243 label: None,
244 bind_group_layouts: &[&atlas_bind_group_layout, ¶ms_layout],
245 push_constant_ranges: &[],
246 });
247
248 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
249 label: Some("textslabs pipeline"),
250 layout: Some(&pipeline_layout),
251 vertex: VertexState {
252 module: &shader,
253 entry_point: Some("vs_main"),
254 buffers: &[vertex_buffer_layout],
255 compilation_options: PipelineCompilationOptions::default(),
256 },
257 fragment: Some(FragmentState {
258 module: &shader,
259 entry_point: Some("fs_main"),
260 targets: &[Some(ColorTargetState {
261 format: TextureFormat::Bgra8UnormSrgb,
263 blend: Some(BlendState::ALPHA_BLENDING),
264 write_mask: ColorWrites::default(),
265 })],
266 compilation_options: PipelineCompilationOptions::default(),
267 }),
268 primitive: PrimitiveState {
269 topology: PrimitiveTopology::TriangleStrip,
270 ..Default::default()
271 },
272 depth_stencil,
273 multisample: MultisampleState::default(),
274 multiview: None,
275 cache: None,
276 });
277
278 let tmp_image = Image::new();
279 let frame = 1;
280
281 let vertex_buffer = create_vertex_buffer(device, INITIAL_BUFFER_SIZE);
282
283 Self {
284 frame,
285 atlas_size,
286 tmp_image,
287 mask_atlas_pages,
288 color_atlas_pages,
289 decorations: Vec::with_capacity(50),
290 pipeline,
291 atlas_bind_group_layout,
292 sampler,
293 params,
294 params_buffer,
295 params_bind_group,
296 glyph_cache,
297 last_frame_evicted: 0,
298 vertex_buffer,
300 needs_gpu_sync: true,
301 }
302 }
303}
304
305impl ContextlessTextRenderer {
306 pub fn gpu_load(&mut self, device: &Device, queue: &Queue) {
307 if !self.needs_gpu_sync {
308 return;
309 }
310
311 let bytes: &[u8] = bytemuck::cast_slice(std::slice::from_ref(&self.params));
312 queue.write_buffer(&self.params_buffer, 0, bytes);
313
314 let total_quads = self.mask_atlas_pages.iter().map(|p| p.quads.len()).sum::<usize>()
316 + self.color_atlas_pages.iter().map(|p| p.quads.len()).sum::<usize>()
317 + self.decorations.len();
318
319 let required_size = (total_quads * std::mem::size_of::<Quad>()) as u64;
320
321 if self.vertex_buffer.size() < required_size {
323 let min_size = u64::max(required_size, INITIAL_BUFFER_SIZE);
324 let growth_size = min_size * 3 / 2;
325 let current_growth = self.vertex_buffer.size() * 3 / 2;
326 let new_size = u64::max(growth_size, current_growth);
327
328 self.vertex_buffer = create_vertex_buffer(device, new_size);
329 }
330
331 let mut buffer_offset = 0u64;
332
333 for page in &self.mask_atlas_pages {
334 if !page.quads.is_empty() {
335 let bytes: &[u8] = bytemuck::cast_slice(&page.quads);
336 queue.write_buffer(&self.vertex_buffer, buffer_offset, bytes);
337 buffer_offset += bytes.len() as u64;
338 }
339 }
340
341 for page in &self.color_atlas_pages {
342 if !page.quads.is_empty() {
343 let bytes: &[u8] = bytemuck::cast_slice(&page.quads);
344 queue.write_buffer(&self.vertex_buffer, buffer_offset, bytes);
345 buffer_offset += bytes.len() as u64;
346 }
347 }
348
349 if !self.decorations.is_empty() {
350 let bytes: &[u8] = bytemuck::cast_slice(&self.decorations);
351 queue.write_buffer(&self.vertex_buffer, buffer_offset, bytes);
352 }
353
354 for page in &mut self.mask_atlas_pages {
356 if page.gpu.is_none() {
357 let texture = device.create_texture(&TextureDescriptor {
358 label: Some("atlas"),
359 size: Extent3d {
360 width: self.atlas_size,
361 height: self.atlas_size,
362 depth_or_array_layers: 1,
363 },
364 mip_level_count: 1,
365 sample_count: 1,
366 dimension: TextureDimension::D2,
367 format: TextureFormat::R8Unorm,
368 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
369 view_formats: &[],
370 });
371 let texture_view = texture.create_view(&TextureViewDescriptor::default());
372
373 let bind_group = device.create_bind_group(&BindGroupDescriptor {
374 layout: &self.atlas_bind_group_layout,
375 entries: &[
376 BindGroupEntry {
377 binding: 0,
378 resource: BindingResource::TextureView(&texture_view),
379 },
380 BindGroupEntry {
381 binding: 1,
382 resource: BindingResource::Sampler(&self.sampler),
383 },
384 ],
385 label: Some("atlas bind group"),
386 });
387
388 page.gpu = Some(GpuAtlasPage {
389 texture,
390 bind_group,
391 })
392 }
393
394 queue.write_texture(
395 ImageCopyTexture {
396 texture: &page.gpu.as_ref().unwrap().texture,
397 mip_level: 0,
398 origin: Origin3d { x: 0, y: 0, z: 0 },
399 aspect: TextureAspect::All,
400 },
401 &page.image.as_raw(),
402 ImageDataLayout {
403 offset: 0,
404 bytes_per_row: Some(page.image.width()),
405 rows_per_image: None,
406 },
407 Extent3d {
408 width: page.image.width(),
409 height: page.image.height(),
410 depth_or_array_layers: 1,
411 },
412 );
413 }
414
415 for page in &mut self.color_atlas_pages {
417 if page.gpu.is_none() {
418 let texture = device.create_texture(&TextureDescriptor {
419 label: Some("atlas"),
420 size: Extent3d {
421 width: self.atlas_size,
422 height: self.atlas_size,
423 depth_or_array_layers: 1,
424 },
425 mip_level_count: 1,
426 sample_count: 1,
427 dimension: TextureDimension::D2,
428 format: TextureFormat::Rgba8Unorm,
429 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
430 view_formats: &[],
431 });
432 let texture_view = texture.create_view(&TextureViewDescriptor::default());
433
434 let bind_group = device.create_bind_group(&BindGroupDescriptor {
435 layout: &self.atlas_bind_group_layout,
436 entries: &[
437 BindGroupEntry {
438 binding: 0,
439 resource: BindingResource::TextureView(&texture_view),
440 },
441 BindGroupEntry {
442 binding: 1,
443 resource: BindingResource::Sampler(&self.sampler),
444 },
445 ],
446 label: Some("atlas bind group"),
447 });
448
449 page.gpu = Some(GpuAtlasPage {
450 texture,
451 bind_group,
452 })
453 }
454
455 queue.write_texture(
456 ImageCopyTexture {
457 texture: &page.gpu.as_ref().unwrap().texture,
458 mip_level: 0,
459 origin: Origin3d { x: 0, y: 0, z: 0 },
460 aspect: TextureAspect::All,
461 },
462 &page.image.as_raw(),
463 ImageDataLayout {
464 offset: 0,
465 bytes_per_row: Some(page.image.width() * 4),
466 rows_per_image: None,
467 },
468 Extent3d {
469 width: page.image.width(),
470 height: page.image.height(),
471 depth_or_array_layers: 1,
472 },
473 );
474 }
475
476 self.needs_gpu_sync = false;
477 }
478}