1use bytemuck::{Pod, Zeroable};
2use ravenrs_core::{Brush, Color, ImageQuad, Primitive, Quad, Scene};
3
4use crate::atlas::{GlyphAtlas, GlyphKey, Lookup, SUBPIXEL_BINS};
5use crate::images::ImageCache;
6use crate::paths::PathEngine;
7use crate::timing::GpuTimer;
8
9const INITIAL_ATLAS_SIZE: u32 = 1024;
10
11#[repr(C)]
12#[derive(Clone, Copy, Pod, Zeroable)]
13struct Globals {
14 viewport: [f32; 2],
15 scale: f32,
16 srgb_target: u32,
17}
18
19#[repr(C)]
20#[derive(Clone, Copy, Pod, Zeroable)]
21struct QuadInstance {
22 rect: [f32; 4],
23 radii: [f32; 4],
24 color0: [f32; 4],
25 color1: [f32; 4],
26 gradient: [f32; 4],
27 border_color: [f32; 4],
28 params: [f32; 4],
29}
30
31#[repr(C)]
32#[derive(Clone, Copy, Pod, Zeroable)]
33struct GlyphInstance {
34 rect: [f32; 4],
35 uv: [f32; 4],
36 color: [f32; 4],
37}
38
39#[repr(C)]
40#[derive(Clone, Copy, Pod, Zeroable)]
41struct ImageInstance {
42 rect: [f32; 4],
43 uv: [f32; 4],
44 radii: [f32; 4],
45 params: [f32; 4],
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49enum BatchKind {
50 Quads,
51 Glyphs,
52 Image(u64),
54 Paths(u32),
56}
57
58#[derive(Clone, Copy, Debug)]
59struct Batch {
60 kind: BatchKind,
61 start: u32,
62 end: u32,
63 scissor: [u32; 4],
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct RenderStats {
70 pub quads: usize,
72 pub glyphs: usize,
74 pub draw_calls: usize,
76 pub atlas_glyphs: usize,
78 pub images: usize,
80 pub cached_images: usize,
82 pub paths: usize,
84 pub path_edges: usize,
86 pub breaks_kind: usize,
88 pub breaks_image: usize,
90 pub breaks_clip: usize,
92 pub uploaded_bytes: usize,
94 pub glyphs_rasterized: u64,
96 pub atlas_resets: u64,
98 pub atlas_used: u32,
100 pub atlas_size: u32,
102 pub gpu_time: Option<std::time::Duration>,
105}
106
107struct InstanceBuffer {
109 buffer: wgpu::Buffer,
110 capacity: usize,
111 stride: usize,
112 label: &'static str,
113}
114
115impl RenderStats {
116 pub fn work(&self) -> ravenrs_core::RenderWork {
118 ravenrs_core::RenderWork {
119 breaks_kind: self.breaks_kind as u32,
120 breaks_image: self.breaks_image as u32,
121 breaks_clip: self.breaks_clip as u32,
122 uploaded_bytes: self.uploaded_bytes as u64,
123 glyphs_rasterized: self.glyphs_rasterized,
124 atlas_resets: self.atlas_resets,
125 atlas_used: self.atlas_used,
126 atlas_size: self.atlas_size,
127 cached_images: self.cached_images as u32,
128 gpu: self.gpu_time,
129 }
130 }
131}
132
133impl InstanceBuffer {
134 fn new(device: &wgpu::Device, label: &'static str, stride: usize) -> Self {
135 let capacity = 256;
136 Self { buffer: Self::create(device, label, stride * capacity), capacity, stride, label }
137 }
138
139 fn create(device: &wgpu::Device, label: &'static str, size: usize) -> wgpu::Buffer {
140 device.create_buffer(&wgpu::BufferDescriptor {
141 label: Some(label),
142 size: size as u64,
143 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
144 mapped_at_creation: false,
145 })
146 }
147
148 fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8]) {
149 let needed = bytes.len() / self.stride;
150 if needed > self.capacity {
151 self.capacity = needed.next_power_of_two();
152 self.buffer = Self::create(device, self.label, self.capacity * self.stride);
153 }
154 if !bytes.is_empty() {
155 queue.write_buffer(&self.buffer, 0, bytes);
156 }
157 }
158}
159
160pub struct Renderer {
162 device: wgpu::Device,
163 queue: wgpu::Queue,
164 srgb_target: bool,
165 quad_pipeline: wgpu::RenderPipeline,
166 glyph_pipeline: wgpu::RenderPipeline,
167 image_pipeline: wgpu::RenderPipeline,
168 paths: PathEngine,
169 image_sampler: wgpu::Sampler,
170 image_cache: ImageCache,
171 image_buffer: InstanceBuffer,
172 image_instances: Vec<ImageInstance>,
173 globals_buffer: wgpu::Buffer,
174 globals_bind_group: wgpu::BindGroup,
175 atlas_layout: wgpu::BindGroupLayout,
176 atlas_bind_group: wgpu::BindGroup,
177 sampler: wgpu::Sampler,
178 atlas: GlyphAtlas,
179 quad_buffer: InstanceBuffer,
180 glyph_buffer: InstanceBuffer,
181 quads: Vec<QuadInstance>,
182 glyphs: Vec<GlyphInstance>,
183 batches: Vec<Batch>,
184 clips: Vec<[u32; 4]>,
185 target_size: (u32, u32),
186 breaks: BatchBreaks,
187 atlas_resets: u64,
188 glyphs_rasterized: u64,
190 timer: Option<GpuTimer>,
191}
192
193#[derive(Clone, Copy, Debug, Default)]
195struct BatchBreaks {
196 kind: usize,
197 image: usize,
198 clip: usize,
199}
200
201impl std::fmt::Debug for Renderer {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 f.debug_struct("Renderer")
204 .field("srgb_target", &self.srgb_target)
205 .field("atlas_size", &self.atlas.size)
206 .finish_non_exhaustive()
207 }
208}
209
210fn premultiplied_target(format: wgpu::TextureFormat) -> [Option<wgpu::ColorTargetState>; 1] {
211 [Some(wgpu::ColorTargetState {
212 format,
213 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
214 write_mask: wgpu::ColorWrites::ALL,
215 })]
216}
217
218pub(crate) fn pipeline(
219 device: &wgpu::Device,
220 label: &str,
221 source: &str,
222 layout: &wgpu::PipelineLayout,
223 instance_layout: wgpu::VertexBufferLayout<'_>,
224 format: wgpu::TextureFormat,
225) -> wgpu::RenderPipeline {
226 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
227 label: Some(label),
228 source: wgpu::ShaderSource::Wgsl(source.into()),
229 });
230 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
231 label: Some(label),
232 layout: Some(layout),
233 vertex: wgpu::VertexState {
234 module: &module,
235 entry_point: Some("vs_main"),
236 compilation_options: Default::default(),
237 buffers: &[Some(instance_layout)],
238 },
239 primitive: wgpu::PrimitiveState {
240 topology: wgpu::PrimitiveTopology::TriangleStrip,
241 ..Default::default()
242 },
243 depth_stencil: None,
244 multisample: wgpu::MultisampleState::default(),
245 fragment: Some(wgpu::FragmentState {
246 module: &module,
247 entry_point: Some("fs_main"),
248 compilation_options: Default::default(),
249 targets: &premultiplied_target(format),
250 }),
251 multiview_mask: None,
252 cache: None,
253 })
254}
255
256impl Renderer {
257 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, target_format: wgpu::TextureFormat) -> Self {
259 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
260 label: Some("raven globals"),
261 entries: &[wgpu::BindGroupLayoutEntry {
262 binding: 0,
263 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
264 ty: wgpu::BindingType::Buffer {
265 ty: wgpu::BufferBindingType::Uniform,
266 has_dynamic_offset: false,
267 min_binding_size: None,
268 },
269 count: None,
270 }],
271 });
272 let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
273 label: Some("raven atlas"),
274 entries: &[
275 wgpu::BindGroupLayoutEntry {
276 binding: 0,
277 visibility: wgpu::ShaderStages::FRAGMENT,
278 ty: wgpu::BindingType::Texture {
279 sample_type: wgpu::TextureSampleType::Float { filterable: true },
280 view_dimension: wgpu::TextureViewDimension::D2,
281 multisampled: false,
282 },
283 count: None,
284 },
285 wgpu::BindGroupLayoutEntry {
286 binding: 1,
287 visibility: wgpu::ShaderStages::FRAGMENT,
288 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
289 count: None,
290 },
291 ],
292 });
293 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
294 label: Some("raven globals"),
295 size: std::mem::size_of::<Globals>() as u64,
296 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
297 mapped_at_creation: false,
298 });
299 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
300 label: Some("raven globals"),
301 layout: &globals_layout,
302 entries: &[wgpu::BindGroupEntry { binding: 0, resource: globals_buffer.as_entire_binding() }],
303 });
304 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
305 label: Some("raven atlas sampler"),
306 mag_filter: wgpu::FilterMode::Linear,
307 min_filter: wgpu::FilterMode::Linear,
308 ..Default::default()
309 });
310 let atlas = GlyphAtlas::new(device, INITIAL_ATLAS_SIZE);
311 let atlas_bind_group = Self::atlas_bind_group(device, &atlas_layout, &atlas, &sampler);
312
313 let quad_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
314 label: Some("raven quads"),
315 bind_group_layouts: &[Some(&globals_layout)],
316 immediate_size: 0,
317 });
318 let glyph_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
319 label: Some("raven glyphs"),
320 bind_group_layouts: &[Some(&globals_layout), Some(&atlas_layout)],
321 immediate_size: 0,
322 });
323 let quad_attributes = wgpu::vertex_attr_array![
324 0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4,
325 4 => Float32x4, 5 => Float32x4, 6 => Float32x4
326 ];
327 let glyph_attributes = wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4];
328 let image_attributes =
329 wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4];
330 let image_pipeline = pipeline(
331 device,
332 "raven image pipeline",
333 include_str!("../shaders/image.wgsl"),
334 &glyph_layout,
335 wgpu::VertexBufferLayout {
336 array_stride: std::mem::size_of::<ImageInstance>() as u64,
337 step_mode: wgpu::VertexStepMode::Instance,
338 attributes: &image_attributes,
339 },
340 target_format,
341 );
342 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
343 label: Some("raven image sampler"),
344 mag_filter: wgpu::FilterMode::Linear,
345 min_filter: wgpu::FilterMode::Linear,
346 mipmap_filter: wgpu::MipmapFilterMode::Linear,
347 ..Default::default()
348 });
349 let quad_pipeline = pipeline(
350 device,
351 "raven quad pipeline",
352 include_str!("../shaders/quad.wgsl"),
353 &quad_layout,
354 wgpu::VertexBufferLayout {
355 array_stride: std::mem::size_of::<QuadInstance>() as u64,
356 step_mode: wgpu::VertexStepMode::Instance,
357 attributes: &quad_attributes,
358 },
359 target_format,
360 );
361 let glyph_pipeline = pipeline(
362 device,
363 "raven glyph pipeline",
364 include_str!("../shaders/glyph.wgsl"),
365 &glyph_layout,
366 wgpu::VertexBufferLayout {
367 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
368 step_mode: wgpu::VertexStepMode::Instance,
369 attributes: &glyph_attributes,
370 },
371 target_format,
372 );
373
374 let paths = PathEngine::new(device, &globals_layout, target_format);
375
376 Self {
377 device: device.clone(),
378 queue: queue.clone(),
379 paths,
380 srgb_target: target_format.is_srgb(),
381 quad_pipeline,
382 glyph_pipeline,
383 image_pipeline,
384 image_sampler,
385 image_cache: ImageCache::default(),
386 image_buffer: InstanceBuffer::new(
387 device,
388 "raven image instances",
389 std::mem::size_of::<ImageInstance>(),
390 ),
391 image_instances: Vec::new(),
392 globals_buffer,
393 globals_bind_group,
394 atlas_layout,
395 atlas_bind_group,
396 sampler,
397 atlas,
398 quad_buffer: InstanceBuffer::new(
399 device,
400 "raven quad instances",
401 std::mem::size_of::<QuadInstance>(),
402 ),
403 glyph_buffer: InstanceBuffer::new(
404 device,
405 "raven glyph instances",
406 std::mem::size_of::<GlyphInstance>(),
407 ),
408 quads: Vec::new(),
409 glyphs: Vec::new(),
410 batches: Vec::new(),
411 clips: Vec::new(),
412 target_size: (1, 1),
413 breaks: BatchBreaks::default(),
414 atlas_resets: 0,
415 glyphs_rasterized: 0,
416 timer: None,
417 }
418 }
419
420 pub fn set_gpu_timing(&mut self, on: bool) {
423 match (on, &self.timer) {
424 (true, None) => self.timer = GpuTimer::new(&self.device, &self.queue),
425 (false, Some(_)) => self.timer = None,
426 _ => {}
427 }
428 }
429
430 fn lose_atlas(&mut self, size: u32) {
431 self.glyphs_rasterized += self.atlas.rasterized();
432 self.atlas_resets += 1;
433 self.atlas = GlyphAtlas::new(&self.device, size);
434 self.atlas_bind_group =
435 Self::atlas_bind_group(&self.device, &self.atlas_layout, &self.atlas, &self.sampler);
436 }
437
438 fn atlas_bind_group(
439 device: &wgpu::Device,
440 layout: &wgpu::BindGroupLayout,
441 atlas: &GlyphAtlas,
442 sampler: &wgpu::Sampler,
443 ) -> wgpu::BindGroup {
444 device.create_bind_group(&wgpu::BindGroupDescriptor {
445 label: Some("raven atlas"),
446 layout,
447 entries: &[
448 wgpu::BindGroupEntry {
449 binding: 0,
450 resource: wgpu::BindingResource::TextureView(&atlas.view),
451 },
452 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler) },
453 ],
454 })
455 }
456
457 pub fn render(
460 &mut self,
461 scene: &Scene,
462 target: &wgpu::TextureView,
463 size: (u32, u32),
464 scale: f32,
465 ) -> RenderStats {
466 let _span = tracing::trace_span!("ravenrs::render", primitives = scene.len()).entered();
467 if size.0 == 0 || size.1 == 0 || scale <= 0.0 {
468 return RenderStats::default();
469 }
470 self.target_size = size;
471 self.image_cache.begin_frame();
472 self.prepare(scene, scale);
473
474 let globals = Globals {
475 viewport: [size.0 as f32 / scale, size.1 as f32 / scale],
476 scale,
477 srgb_target: u32::from(self.srgb_target),
478 };
479 self.queue.write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals));
480 self.quad_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.quads));
481 self.glyph_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.glyphs));
482 self.image_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.image_instances));
483
484 let clear = self.color_for_target(scene.clear_color());
485 let mut encoder = self
486 .device
487 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("raven frame") });
488 self.paths.prepare(&self.device, &self.queue);
491 let coverage_timing = self.timer.as_ref().map(GpuTimer::coverage_pass);
492 self.paths.draw_page(&mut encoder, 0, coverage_timing);
493 let mut load = wgpu::LoadOp::Clear(wgpu::Color {
494 r: f64::from(clear[0]),
495 g: f64::from(clear[1]),
496 b: f64::from(clear[2]),
497 a: f64::from(clear[3]),
498 });
499 let (mut from, mut page) = (0, 0);
500 loop {
501 let next = self.batches[from..]
502 .iter()
503 .position(|batch| matches!(batch.kind, BatchKind::Paths(p) if p > page))
504 .map_or(self.batches.len(), |at| from + at);
505 let timing = if from == 0 { self.timer.as_ref().map(GpuTimer::main_pass) } else { None };
506 self.draw_main_pass(&mut encoder, target, size, load, from..next, timing);
507 let Some(BatchKind::Paths(after)) = self.batches.get(next).map(|batch| batch.kind) else { break };
508 self.paths.draw_page(&mut encoder, after, None);
509 load = wgpu::LoadOp::Load;
510 (from, page) = (next, after);
511 }
512 let gpu_time = self.timer.as_mut().and_then(|timer| timer.finish_frame(&mut encoder));
513 self.queue.submit(Some(encoder.finish()));
514 if let Some(timer) = &mut self.timer {
515 timer.frame_submitted();
516 }
517
518 RenderStats {
519 quads: self.quads.len(),
520 glyphs: self.glyphs.len(),
521 draw_calls: self.batches.len() + self.paths.pages(),
522 atlas_glyphs: self.atlas.len(),
523 images: self.image_instances.len(),
524 cached_images: self.image_cache.len(),
525 paths: self.paths.shapes(),
526 path_edges: self.paths.edges(),
527 breaks_kind: self.breaks.kind,
528 breaks_image: self.breaks.image,
529 breaks_clip: self.breaks.clip,
530 uploaded_bytes: std::mem::size_of_val(self.quads.as_slice())
531 + std::mem::size_of_val(self.glyphs.as_slice())
532 + std::mem::size_of_val(self.image_instances.as_slice())
533 + self.paths.uploaded_bytes(),
534 glyphs_rasterized: self.glyphs_rasterized + self.atlas.rasterized(),
535 atlas_resets: self.atlas_resets,
536 atlas_used: (self.atlas.used() * 1000.0) as u32,
537 atlas_size: self.atlas.size,
538 gpu_time,
539 }
540 }
541
542 fn draw_main_pass(
544 &self,
545 encoder: &mut wgpu::CommandEncoder,
546 target: &wgpu::TextureView,
547 size: (u32, u32),
548 load: wgpu::LoadOp<wgpu::Color>,
549 batches: std::ops::Range<usize>,
550 timestamp_writes: Option<wgpu::RenderPassTimestampWrites<'_>>,
551 ) {
552 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
553 label: Some("raven main pass"),
554 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
555 view: target,
556 depth_slice: None,
557 resolve_target: None,
558 ops: wgpu::Operations { load, store: wgpu::StoreOp::Store },
559 })],
560 depth_stencil_attachment: None,
561 timestamp_writes,
562 occlusion_query_set: None,
563 multiview_mask: None,
564 });
565 pass.set_bind_group(0, &self.globals_bind_group, &[]);
566 let mut current = None;
567 let mut scissor = [0, 0, size.0, size.1];
568 for batch in &self.batches[batches] {
569 if batch.scissor != scissor {
570 scissor = batch.scissor;
571 pass.set_scissor_rect(scissor[0], scissor[1], scissor[2], scissor[3]);
572 }
573 if current != Some(batch.kind) {
574 match batch.kind {
575 BatchKind::Quads => {
576 pass.set_pipeline(&self.quad_pipeline);
577 pass.set_vertex_buffer(0, self.quad_buffer.buffer.slice(..));
578 }
579 BatchKind::Glyphs => {
580 pass.set_pipeline(&self.glyph_pipeline);
581 pass.set_bind_group(1, &self.atlas_bind_group, &[]);
582 pass.set_vertex_buffer(0, self.glyph_buffer.buffer.slice(..));
583 }
584 BatchKind::Image(id) => {
585 let Some(image) = self.image_cache.get(id) else { continue };
586 pass.set_pipeline(&self.image_pipeline);
587 pass.set_bind_group(1, &image.bind_group, &[]);
588 pass.set_vertex_buffer(0, self.image_buffer.buffer.slice(..));
589 }
590 BatchKind::Paths(_) => self.paths.bind(&mut pass),
591 }
592 current = Some(batch.kind);
593 }
594 pass.draw(0..4, batch.start..batch.end);
595 }
596 }
597
598 fn color_for_target(&self, c: Color) -> [f32; 4] {
599 let [r, g, b, a] = if self.srgb_target { c.to_linear() } else { c.to_array() };
600 [r * a, g * a, b * a, a]
601 }
602
603 fn prepare(&mut self, scene: &Scene, scale: f32) {
605 let max_atlas = self.device.limits().max_texture_dimension_2d.min(8192);
606 loop {
607 match self.try_prepare(scene, scale) {
608 Ok(()) => return,
609 Err(AtlasFull) if self.atlas.size < max_atlas => {
610 let size = (self.atlas.size * 2).min(max_atlas);
611 tracing::debug!(size, "growing glyph atlas");
612 self.lose_atlas(size);
613 }
614 Err(AtlasFull) => {
615 tracing::warn!("glyph atlas is full at the device limit; clearing it");
618 self.lose_atlas(self.atlas.size);
619 let _ = self.try_prepare_lossy(scene, scale);
620 return;
621 }
622 }
623 }
624 }
625
626 fn try_prepare(&mut self, scene: &Scene, scale: f32) -> Result<(), AtlasFull> {
627 self.build(scene, scale, false)
628 }
629
630 fn try_prepare_lossy(&mut self, scene: &Scene, scale: f32) -> Result<(), AtlasFull> {
631 self.build(scene, scale, true)
632 }
633
634 fn build(&mut self, scene: &Scene, scale: f32, skip_missing: bool) -> Result<(), AtlasFull> {
635 self.quads.clear();
636 self.glyphs.clear();
637 self.image_instances.clear();
638 self.batches.clear();
639 self.clips.clear();
640 self.breaks = BatchBreaks::default();
641 self.paths.begin_frame();
642 for primitive in scene.primitives() {
643 match primitive {
644 Primitive::PushClip(rect) => {
645 let (w, h) = self.target_size;
646 let x0 = (rect.x() * scale).floor().clamp(0.0, w as f32) as u32;
647 let y0 = (rect.y() * scale).floor().clamp(0.0, h as f32) as u32;
648 let x1 = (rect.max_x() * scale).ceil().clamp(0.0, w as f32) as u32;
649 let y1 = (rect.max_y() * scale).ceil().clamp(0.0, h as f32) as u32;
650 let outer = self.current_scissor();
651 let ix0 = x0.max(outer[0]);
652 let iy0 = y0.max(outer[1]);
653 let ix1 = x1.min(outer[0] + outer[2]).max(ix0);
654 let iy1 = y1.min(outer[1] + outer[3]).max(iy0);
655 self.clips.push([ix0, iy0, ix1 - ix0, iy1 - iy0]);
656 }
657 Primitive::PopClip => {
658 self.clips.pop();
659 }
660 Primitive::Image(image) => self.push_image(image),
661 Primitive::Path(path) => {
662 let start = self.paths.shapes() as u32;
663 let scissor = self.current_scissor();
664 let limit = self.device.limits().max_texture_dimension_2d.min(8192);
665 self.paths.push(path, scale, scissor, limit);
666 self.extend_batch(BatchKind::Paths(self.paths.page()), start, self.paths.shapes() as u32);
667 }
668 Primitive::Quad(quad) => {
669 let start = self.quads.len() as u32;
670 push_quad(&mut self.quads, quad);
671 self.extend_batch(BatchKind::Quads, start, self.quads.len() as u32);
672 }
673 Primitive::Glyphs(run) => {
674 let start = self.glyphs.len() as u32;
675 let size_px = run.size * scale;
676 let size_q = (size_px * 4.0).round().max(1.0) as u32;
677 let color = run.color.to_array();
678 let inv_atlas = 1.0 / self.atlas.size as f32;
679 for g in &run.glyphs {
680 let device_x = g.x * scale;
681 let mut ix = device_x.floor();
682 let mut bin = ((device_x - ix) * SUBPIXEL_BINS).round();
683 if bin >= SUBPIXEL_BINS {
684 ix += 1.0;
685 bin = 0.0;
686 }
687 let key = GlyphKey {
688 font_id: run.font.id,
689 font_index: run.font.index,
690 glyph: g.id,
691 size_q,
692 subpixel: bin as u8,
693 };
694 let entry = match self.atlas.get_or_insert(&self.queue, key, run.font.bytes()) {
695 Lookup::Hit(entry) => entry,
696 Lookup::Full if skip_missing => None,
697 Lookup::Full => return Err(AtlasFull),
698 };
699 let Some(a) = entry else { continue };
700 let device_y = (g.y * scale).round();
701 self.glyphs.push(GlyphInstance {
702 rect: [
703 ix + a.left as f32,
704 device_y - a.top as f32,
705 a.width as f32,
706 a.height as f32,
707 ],
708 uv: [
709 a.x as f32 * inv_atlas,
710 a.y as f32 * inv_atlas,
711 (a.x + a.width) as f32 * inv_atlas,
712 (a.y + a.height) as f32 * inv_atlas,
713 ],
714 color,
715 });
716 }
717 self.extend_batch(BatchKind::Glyphs, start, self.glyphs.len() as u32);
718 }
719 _ => {}
721 }
722 }
723 Ok(())
724 }
725
726 fn push_image(&mut self, image: &ImageQuad) {
727 let layout = &self.atlas_layout;
728 self.image_cache.prepare(&self.device, &self.queue, layout, &self.image_sampler, &image.image);
729 let start = self.image_instances.len() as u32;
730 let r = image.rect;
731 self.image_instances.push(ImageInstance {
732 rect: [r.x(), r.y(), r.width(), r.height()],
733 uv: image.uv,
734 radii: image.corner_radii,
735 params: [image.opacity.clamp(0.0, 1.0), 0.0, 0.0, 0.0],
736 });
737 self.extend_batch(BatchKind::Image(image.image.id()), start, start + 1);
738 }
739
740 fn current_scissor(&self) -> [u32; 4] {
741 self.clips.last().copied().unwrap_or([0, 0, self.target_size.0, self.target_size.1])
742 }
743
744 fn extend_batch(&mut self, kind: BatchKind, start: u32, end: u32) {
745 if start == end {
746 return;
747 }
748 let scissor = self.current_scissor();
749 if scissor[2] == 0 || scissor[3] == 0 {
751 return;
752 }
753 match self.batches.last_mut() {
754 Some(last) if last.kind == kind && last.end == start && last.scissor == scissor => last.end = end,
755 Some(last) => {
756 if last.scissor != scissor {
757 self.breaks.clip += 1;
758 } else if matches!((last.kind, kind), (BatchKind::Image(_), BatchKind::Image(_))) {
759 self.breaks.image += 1;
760 } else {
761 self.breaks.kind += 1;
762 }
763 self.batches.push(Batch { kind, start, end, scissor });
764 }
765 None => self.batches.push(Batch { kind, start, end, scissor }),
766 }
767 }
768}
769
770struct AtlasFull;
771
772fn push_quad(out: &mut Vec<QuadInstance>, quad: &Quad) {
773 let r = quad.rect;
774 let rect = [r.x(), r.y(), r.width(), r.height()];
775 if let Some(shadow) = quad.shadow {
776 out.push(QuadInstance {
777 rect: [rect[0] + shadow.offset.x, rect[1] + shadow.offset.y, rect[2], rect[3]],
778 radii: quad.corner_radii,
779 color0: shadow.color.to_array(),
780 color1: [0.0; 4],
781 gradient: [0.0; 4],
782 border_color: [0.0; 4],
783 params: [0.0, shadow.blur * 0.5, 1.0, 0.0],
784 });
785 }
786 let (color0, color1, gradient, gradient_on) = match quad.fill {
787 Brush::Solid(c) => (c.to_array(), c.to_array(), [0.0; 4], 0.0),
788 Brush::LinearGradient(g) => {
789 (g.from.to_array(), g.to.to_array(), [g.start.x, g.start.y, g.end.x, g.end.y], 1.0)
790 }
791 _ => ([0.0; 4], [0.0; 4], [0.0; 4], 0.0),
793 };
794 let (border_width, border_color) = quad.border.map_or((0.0, [0.0; 4]), |b| (b.width, b.color.to_array()));
795 out.push(QuadInstance {
796 rect,
797 radii: quad.corner_radii,
798 color0,
799 color1,
800 gradient,
801 border_color,
802 params: [border_width, 0.0, 0.0, gradient_on],
803 });
804}