1#![forbid(unsafe_code)]
71
72use std::cell::{Cell, RefCell};
73use std::collections::HashMap;
74use std::ops::Range;
75
76use denise::angle::{ONE, TURN};
77use denise::painter::ClipToken;
78use denise::{
79 AtlasPage, Color, ImageRef, Mask, Paint, Painter, PixelFormat, PixelView, Point, Rect, Size,
80};
81pub use wgpu;
82
83use wgpu::util::DeviceExt as _;
84
85#[derive(Debug, thiserror::Error)]
87pub enum Error {
88 #[error("no GPU adapter is available")]
91 NoAdapter,
92 #[error("requesting a device")]
94 Device(#[from] wgpu::RequestDeviceError),
95 #[error("mapping the readback buffer")]
97 Map(#[from] wgpu::BufferAsyncError),
98 #[error("waiting for the GPU")]
100 Poll(#[from] wgpu::PollError),
101 #[error("reading the readback buffer")]
103 Read(#[from] wgpu::MapRangeError),
104}
105
106#[repr(C)]
108#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
109struct Vertex {
110 pos: [f32; 2],
111 clip: [f32; 4],
112 color: [f32; 4],
113 a: [f32; 4],
114 b: [f32; 4],
115 kind: u32,
116 poly: [u32; 2],
120 _pad: u32,
121}
122
123#[repr(C)]
124#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
125struct Globals {
126 size: [f32; 2],
127 srgb: u32,
128 _pad: u32,
129}
130
131const KIND_SOLID: u32 = 0;
132const KIND_ROUNDED_FILL: u32 = 1;
133const KIND_ROUNDED_STROKE: u32 = 2;
134const KIND_CIRCLE_FILL: u32 = 3;
135const KIND_CIRCLE_STROKE: u32 = 4;
136const KIND_ARC: u32 = 5;
137const KIND_LINE: u32 = 6;
138const KIND_TEXTURED: u32 = 7;
139const KIND_MASK: u32 = 8;
140const KIND_TEXTURED_ROUNDED: u32 = 9;
141const KIND_POLYGON: u32 = 10;
142
143const WHOLE: [f32; 4] = [0.0, 0.0, 1.0, 1.0];
145
146pub struct Gpu {
151 device: wgpu::Device,
152 queue: wgpu::Queue,
153 format: wgpu::TextureFormat,
154 pipeline: wgpu::RenderPipeline,
155 globals_layout: wgpu::BindGroupLayout,
156 texture_layout: wgpu::BindGroupLayout,
157 edges_layout: wgpu::BindGroupLayout,
158 sampler: wgpu::Sampler,
159 white: wgpu::BindGroup,
162 no_edges: wgpu::BindGroup,
167 pages: RefCell<HashMap<u64, (u64, wgpu::BindGroup)>>,
173 page_uploads: Cell<u64>,
176 images: RefCell<HashMap<u64, (u64, wgpu::BindGroup)>>,
179 image_uploads: Cell<u64>,
181 scratch: RefCell<Option<wgpu::Texture>>,
185 globals: RefCell<Option<(Size, wgpu::Buffer, wgpu::BindGroup)>>,
189}
190
191impl Gpu {
192 pub fn new(device: wgpu::Device, queue: wgpu::Queue, format: wgpu::TextureFormat) -> Self {
199 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
200 label: Some("denise shapes"),
201 source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
202 });
203
204 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
205 label: Some("denise globals"),
206 entries: &[wgpu::BindGroupLayoutEntry {
207 binding: 0,
208 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
209 ty: wgpu::BindingType::Buffer {
210 ty: wgpu::BufferBindingType::Uniform,
211 has_dynamic_offset: false,
212 min_binding_size: None,
213 },
214 count: None,
215 }],
216 });
217
218 let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
219 label: Some("denise texture"),
220 entries: &[
221 wgpu::BindGroupLayoutEntry {
222 binding: 0,
223 visibility: wgpu::ShaderStages::FRAGMENT,
224 ty: wgpu::BindingType::Texture {
225 sample_type: wgpu::TextureSampleType::Float { filterable: true },
226 view_dimension: wgpu::TextureViewDimension::D2,
227 multisampled: false,
228 },
229 count: None,
230 },
231 wgpu::BindGroupLayoutEntry {
232 binding: 1,
233 visibility: wgpu::ShaderStages::FRAGMENT,
234 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
235 count: None,
236 },
237 ],
238 });
239
240 let edges_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
241 label: Some("denise polygon edges"),
242 entries: &[wgpu::BindGroupLayoutEntry {
243 binding: 0,
244 visibility: wgpu::ShaderStages::FRAGMENT,
245 ty: wgpu::BindingType::Buffer {
246 ty: wgpu::BufferBindingType::Storage { read_only: true },
247 has_dynamic_offset: false,
248 min_binding_size: None,
249 },
250 count: None,
251 }],
252 });
253
254 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
255 label: Some("denise"),
256 bind_group_layouts: &[
257 Some(&globals_layout),
258 Some(&texture_layout),
259 Some(&edges_layout),
260 ],
261 ..Default::default()
262 });
263
264 let vertex_layout = wgpu::VertexBufferLayout {
265 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
266 step_mode: wgpu::VertexStepMode::Vertex,
267 attributes: &wgpu::vertex_attr_array![
268 0 => Float32x2,
269 1 => Float32x4,
270 2 => Float32x4,
271 3 => Float32x4,
272 4 => Float32x4,
273 5 => Uint32,
274 6 => Uint32x2,
275 ],
276 };
277
278 let blend = wgpu::BlendState {
281 color: wgpu::BlendComponent {
282 src_factor: wgpu::BlendFactor::One,
283 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
284 operation: wgpu::BlendOperation::Add,
285 },
286 alpha: wgpu::BlendComponent {
287 src_factor: wgpu::BlendFactor::One,
288 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
289 operation: wgpu::BlendOperation::Add,
290 },
291 };
292
293 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
294 label: Some("denise"),
295 layout: Some(&layout),
296 vertex: wgpu::VertexState {
297 module: &shader,
298 entry_point: Some("vs"),
299 compilation_options: Default::default(),
300 buffers: &[Some(vertex_layout)],
301 },
302 fragment: Some(wgpu::FragmentState {
303 module: &shader,
304 entry_point: Some("fs"),
305 compilation_options: Default::default(),
306 targets: &[Some(wgpu::ColorTargetState {
307 format,
308 blend: Some(blend),
309 write_mask: wgpu::ColorWrites::ALL,
310 })],
311 }),
312 primitive: wgpu::PrimitiveState {
313 topology: wgpu::PrimitiveTopology::TriangleList,
314 cull_mode: None,
315 ..Default::default()
316 },
317 depth_stencil: None,
318 multisample: wgpu::MultisampleState::default(),
319 multiview_mask: None,
320 cache: None,
321 });
322
323 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
326 label: Some("denise nearest"),
327 address_mode_u: wgpu::AddressMode::ClampToEdge,
328 address_mode_v: wgpu::AddressMode::ClampToEdge,
329 address_mode_w: wgpu::AddressMode::ClampToEdge,
330 mag_filter: wgpu::FilterMode::Nearest,
331 min_filter: wgpu::FilterMode::Nearest,
332 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
333 ..Default::default()
334 });
335
336 let white = upload(
337 &device,
338 &queue,
339 &texture_layout,
340 &sampler,
341 1,
342 1,
343 wgpu::TextureFormat::Rgba8Unorm,
344 &[255, 255, 255, 255],
345 );
346
347 let no_edges = {
348 let empty = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
349 label: Some("denise polygon edges (none)"),
350 contents: &[0u8; std::mem::size_of::<[f32; 4]>()],
351 usage: wgpu::BufferUsages::STORAGE,
352 });
353 device.create_bind_group(&wgpu::BindGroupDescriptor {
354 label: Some("denise polygon edges (none)"),
355 layout: &edges_layout,
356 entries: &[wgpu::BindGroupEntry {
357 binding: 0,
358 resource: empty.as_entire_binding(),
359 }],
360 })
361 };
362
363 Self {
364 device,
365 queue,
366 format,
367 pipeline,
368 globals_layout,
369 texture_layout,
370 edges_layout,
371 sampler,
372 white,
373 no_edges,
374 pages: RefCell::new(HashMap::new()),
375 page_uploads: Cell::new(0),
376 images: RefCell::new(HashMap::new()),
377 image_uploads: Cell::new(0),
378 globals: RefCell::new(None),
379 scratch: RefCell::new(None),
380 }
381 }
382
383 fn scratch(&self, width: u32, height: u32) -> wgpu::Texture {
385 let mut slot = self.scratch.borrow_mut();
386 if let Some(texture) = slot.as_ref()
387 && texture.width() >= width
388 && texture.height() >= height
389 {
390 return texture.clone();
391 }
392 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
393 label: Some("denise scroll scratch"),
394 size: wgpu::Extent3d {
395 width: width.max(slot.as_ref().map_or(1, wgpu::Texture::width)),
396 height: height.max(slot.as_ref().map_or(1, wgpu::Texture::height)),
397 depth_or_array_layers: 1,
398 },
399 mip_level_count: 1,
400 sample_count: 1,
401 dimension: wgpu::TextureDimension::D2,
402 format: self.format,
403 usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST,
404 view_formats: &[],
405 });
406 *slot = Some(texture.clone());
407 texture
408 }
409
410 pub fn headless() -> Result<Self, Error> {
416 let instance = wgpu::Instance::default();
417 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
418 power_preference: wgpu::PowerPreference::None,
419 force_fallback_adapter: false,
420 compatible_surface: None,
421 ..Default::default()
422 }))
423 .map_err(|_| Error::NoAdapter)?;
424 let (device, queue) =
425 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
426 label: Some("denise headless"),
427 ..Default::default()
428 }))?;
429 Ok(Self::new(device, queue, wgpu::TextureFormat::Rgba8Unorm))
430 }
431
432 pub fn device(&self) -> &wgpu::Device {
434 &self.device
435 }
436
437 pub fn queue(&self) -> &wgpu::Queue {
439 &self.queue
440 }
441
442 fn globals_for(&self, size: Size) -> wgpu::BindGroup {
448 if let Some((cached, _, group)) = self.globals.borrow().as_ref()
449 && *cached == size
450 {
451 return group.clone();
452 }
453 let buffer = self
454 .device
455 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
456 label: Some("denise globals"),
457 contents: bytemuck::bytes_of(&Globals {
458 size: [size.width as f32, size.height as f32],
459 srgb: u32::from(self.format.is_srgb()),
460 _pad: 0,
461 }),
462 usage: wgpu::BufferUsages::UNIFORM,
463 });
464 let group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
465 label: Some("denise globals"),
466 layout: &self.globals_layout,
467 entries: &[wgpu::BindGroupEntry {
468 binding: 0,
469 resource: buffer.as_entire_binding(),
470 }],
471 });
472 *self.globals.borrow_mut() = Some((size, buffer, group.clone()));
473 group
474 }
475
476 pub fn read_texture(&self, texture: &wgpu::Texture) -> Result<Vec<u32>, Error> {
483 let (width, height) = (texture.width().max(1), texture.height().max(1));
484 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
486 let unpadded = width * 4;
487 let padded = unpadded.div_ceil(align) * align;
488 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
489 label: Some("denise readback"),
490 size: u64::from(padded) * u64::from(height),
491 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
492 mapped_at_creation: false,
493 });
494 let mut encoder = self
495 .device
496 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
497 label: Some("denise readback"),
498 });
499 encoder.copy_texture_to_buffer(
500 wgpu::TexelCopyTextureInfo {
501 texture,
502 mip_level: 0,
503 origin: wgpu::Origin3d::ZERO,
504 aspect: wgpu::TextureAspect::All,
505 },
506 wgpu::TexelCopyBufferInfo {
507 buffer: &readback,
508 layout: wgpu::TexelCopyBufferLayout {
509 offset: 0,
510 bytes_per_row: Some(padded),
511 rows_per_image: Some(height),
512 },
513 },
514 wgpu::Extent3d {
515 width,
516 height,
517 depth_or_array_layers: 1,
518 },
519 );
520 self.queue.submit([encoder.finish()]);
521
522 let slice = readback.slice(..);
523 let (tx, rx) = std::sync::mpsc::channel();
524 slice.map_async(wgpu::MapMode::Read, move |result| {
525 let _ = tx.send(result);
526 });
527 self.device.poll(wgpu::PollType::wait_indefinitely())?;
528 rx.recv().map_err(|_| Error::NoAdapter)??;
529
530 let bgra = matches!(
531 self.format,
532 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
533 );
534 let data = slice.get_mapped_range()?;
535 let mut pixels = Vec::with_capacity((width * height) as usize);
536 for row in data.chunks_exact(padded as usize) {
537 for &[c0, c1, c2, c3] in row[..unpadded as usize].as_chunks::<4>().0 {
538 let (r, g, b, a) = if bgra {
539 (c2, c1, c0, c3)
540 } else {
541 (c0, c1, c2, c3)
542 };
543 pixels.push(u32::from_be_bytes([a, r, g, b]));
544 }
545 }
546 drop(data);
547 readback.unmap();
548 Ok(pixels)
549 }
550
551 pub fn page_uploads(&self) -> u64 {
558 self.page_uploads.get()
559 }
560
561 pub fn image_uploads(&self) -> u64 {
567 self.image_uploads.get()
568 }
569
570 pub fn format(&self) -> wgpu::TextureFormat {
572 self.format
573 }
574
575 pub fn painter(&self, size: Size) -> GpuPainter<'_> {
577 GpuPainter {
578 gpu: self,
579 size,
580 clip: Rect::from_size(size),
581 vertices: Vec::with_capacity(4096),
582 draws: Vec::new(),
583 textures: Vec::new(),
584 edges: Vec::new(),
585 scrolls: Vec::new(),
586 }
587 }
588
589 fn page_texture(&self, page: &AtlasPage<'_>) -> wgpu::BindGroup {
591 if let Some((version, group)) = self.pages.borrow().get(&page.id)
592 && *version == page.version
593 {
594 return group.clone();
595 }
596 let mask = &page.mask;
597 let (w, h) = (mask.width().max(1) as u32, mask.height().max(1) as u32);
598 let mut bytes = Vec::with_capacity((w * h) as usize);
599 for y in 0..mask.height() {
600 bytes.extend_from_slice(mask.row(y));
601 }
602 bytes.resize((w * h) as usize, 0);
603 let group = self.upload(w, h, wgpu::TextureFormat::R8Unorm, &bytes);
604 self.page_uploads.set(self.page_uploads.get() + 1);
605 self.pages
606 .borrow_mut()
607 .insert(page.id, (page.version, group.clone()));
608 group
609 }
610
611 fn image_texture(&self, src: &ImageRef<'_>) -> wgpu::BindGroup {
613 if let Some((version, group)) = self.images.borrow().get(&src.id)
614 && *version == src.version
615 {
616 return group.clone();
617 }
618 let size = src.view.size();
619 let bytes = rgba_bytes(&src.view);
620 let group = self.upload(
621 size.width.max(1),
622 size.height.max(1),
623 wgpu::TextureFormat::Rgba8Unorm,
624 &bytes,
625 );
626 self.image_uploads.set(self.image_uploads.get() + 1);
627 self.images
628 .borrow_mut()
629 .insert(src.id, (src.version, group.clone()));
630 group
631 }
632
633 fn upload(
634 &self,
635 width: u32,
636 height: u32,
637 format: wgpu::TextureFormat,
638 bytes: &[u8],
639 ) -> wgpu::BindGroup {
640 upload(
641 &self.device,
642 &self.queue,
643 &self.texture_layout,
644 &self.sampler,
645 width,
646 height,
647 format,
648 bytes,
649 )
650 }
651}
652
653#[allow(clippy::too_many_arguments)]
655fn upload(
656 device: &wgpu::Device,
657 queue: &wgpu::Queue,
658 layout: &wgpu::BindGroupLayout,
659 sampler: &wgpu::Sampler,
660 width: u32,
661 height: u32,
662 format: wgpu::TextureFormat,
663 bytes: &[u8],
664) -> wgpu::BindGroup {
665 let bytes_per_pixel = match format {
666 wgpu::TextureFormat::R8Unorm => 1,
667 _ => 4,
668 };
669 debug_assert_eq!(bytes.len(), (width * height * bytes_per_pixel) as usize);
670 let texture = device.create_texture_with_data(
671 queue,
672 &wgpu::TextureDescriptor {
673 label: None,
674 size: wgpu::Extent3d {
675 width,
676 height,
677 depth_or_array_layers: 1,
678 },
679 mip_level_count: 1,
680 sample_count: 1,
681 dimension: wgpu::TextureDimension::D2,
682 format,
683 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
684 view_formats: &[],
685 },
686 wgpu::util::TextureDataOrder::LayerMajor,
687 bytes,
688 );
689 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
690 device.create_bind_group(&wgpu::BindGroupDescriptor {
691 label: None,
692 layout,
693 entries: &[
694 wgpu::BindGroupEntry {
695 binding: 0,
696 resource: wgpu::BindingResource::TextureView(&view),
697 },
698 wgpu::BindGroupEntry {
699 binding: 1,
700 resource: wgpu::BindingResource::Sampler(sampler),
701 },
702 ],
703 })
704}
705
706#[derive(Debug)]
708enum Draw {
709 Shapes(Range<u32>),
711 Textured { texture: usize, range: Range<u32> },
713}
714
715pub struct GpuPainter<'g> {
722 gpu: &'g Gpu,
723 size: Size,
724 clip: Rect,
725 vertices: Vec<Vertex>,
726 draws: Vec<Draw>,
727 textures: Vec<wgpu::BindGroup>,
728 edges: Vec<[f32; 4]>,
732 scrolls: Vec<(Rect, i32)>,
736}
737
738impl GpuPainter<'_> {
739 pub fn finish(self, target: &wgpu::TextureView) {
742 let gpu = self.gpu;
743 let mut encoder = gpu
744 .device
745 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
746 label: Some("denise frame"),
747 });
748 self.encode(
749 &mut encoder,
750 target,
751 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
752 None,
753 );
754 gpu.queue.submit([encoder.finish()]);
755 }
756
757 pub fn finish_onto(self, target: &wgpu::Texture, damage: &[Rect]) {
780 let union = damage
781 .iter()
782 .filter(|r| !r.is_empty())
783 .copied()
784 .reduce(|a, b| a.union(&b));
785 if union.is_none() && self.scrolls.is_empty() {
786 return;
787 }
788 let gpu = self.gpu;
789 let mut encoder = gpu
790 .device
791 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
792 label: Some("denise damaged frame"),
793 });
794 for &(rect, dy) in &self.scrolls {
795 self.encode_scroll(&mut encoder, target, rect, dy);
796 }
797 if let Some(union) = union {
798 let view = target.create_view(&wgpu::TextureViewDescriptor::default());
799 self.encode(&mut encoder, &view, wgpu::LoadOp::Load, Some(union));
800 }
801 gpu.queue.submit([encoder.finish()]);
802 }
803
804 fn encode_scroll(
808 &self,
809 encoder: &mut wgpu::CommandEncoder,
810 target: &wgpu::Texture,
811 rect: Rect,
812 dy: i32,
813 ) {
814 let shift = dy.unsigned_abs();
815 let height = (rect.height as u32).saturating_sub(shift);
816 let width = rect.width as u32;
817 if height == 0 || width == 0 {
818 return;
819 }
820 let (from_y, to_y) = if dy > 0 {
823 (rect.y as u32 + shift, rect.y as u32)
824 } else {
825 (rect.y as u32, rect.y as u32 + shift)
826 };
827 let scratch = self.gpu.scratch(width, height);
828 let extent = wgpu::Extent3d {
829 width,
830 height,
831 depth_or_array_layers: 1,
832 };
833 fn at(texture: &wgpu::Texture, x: u32, y: u32) -> wgpu::TexelCopyTextureInfo<'_> {
834 wgpu::TexelCopyTextureInfo {
835 texture,
836 mip_level: 0,
837 origin: wgpu::Origin3d { x, y, z: 0 },
838 aspect: wgpu::TextureAspect::All,
839 }
840 }
841 encoder.copy_texture_to_texture(
842 at(target, rect.x as u32, from_y),
843 at(&scratch, 0, 0),
844 extent,
845 );
846 encoder.copy_texture_to_texture(
847 at(&scratch, 0, 0),
848 at(target, rect.x as u32, to_y),
849 extent,
850 );
851 }
852
853 pub fn finish_to_pixels(self) -> Result<Vec<u32>, Error> {
859 let gpu = self.gpu;
860 let (width, height) = (self.size.width.max(1), self.size.height.max(1));
861 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
862 label: Some("denise offscreen"),
863 size: wgpu::Extent3d {
864 width,
865 height,
866 depth_or_array_layers: 1,
867 },
868 mip_level_count: 1,
869 sample_count: 1,
870 dimension: wgpu::TextureDimension::D2,
871 format: gpu.format,
872 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
873 view_formats: &[],
874 });
875 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
876
877 let mut encoder = gpu
878 .device
879 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
880 label: Some("denise offscreen frame"),
881 });
882 self.encode(
883 &mut encoder,
884 &view,
885 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
886 None,
887 );
888 gpu.queue.submit([encoder.finish()]);
889 gpu.read_texture(&texture)
890 }
891
892 fn encode(
893 &self,
894 encoder: &mut wgpu::CommandEncoder,
895 target: &wgpu::TextureView,
896 load: wgpu::LoadOp<wgpu::Color>,
897 scissor: Option<Rect>,
898 ) {
899 let gpu = self.gpu;
900 let globals_group = gpu.globals_for(self.size);
901 let vertex_bytes: &[u8] = if self.vertices.is_empty() {
903 &[0u8; std::mem::size_of::<Vertex>()]
904 } else {
905 bytemuck::cast_slice(&self.vertices)
906 };
907 let vertices = gpu
912 .device
913 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
914 label: Some("denise vertices"),
915 contents: vertex_bytes,
916 usage: wgpu::BufferUsages::VERTEX,
917 });
918
919 let edges_group = if self.edges.is_empty() {
923 None
924 } else {
925 let edges = gpu
926 .device
927 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
928 label: Some("denise polygon edges"),
929 contents: bytemuck::cast_slice(&self.edges),
930 usage: wgpu::BufferUsages::STORAGE,
931 });
932 Some(gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
933 label: Some("denise polygon edges"),
934 layout: &gpu.edges_layout,
935 entries: &[wgpu::BindGroupEntry {
936 binding: 0,
937 resource: edges.as_entire_binding(),
938 }],
939 }))
940 };
941
942 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
943 label: Some("denise"),
944 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
945 view: target,
946 depth_slice: None,
947 resolve_target: None,
948 ops: wgpu::Operations {
949 load,
950 store: wgpu::StoreOp::Store,
951 },
952 })],
953 ..Default::default()
954 });
955 pass.set_pipeline(&gpu.pipeline);
956 pass.set_bind_group(0, &globals_group, &[]);
957 pass.set_bind_group(2, edges_group.as_ref().unwrap_or(&gpu.no_edges), &[]);
958 pass.set_vertex_buffer(0, vertices.slice(..));
959 if let Some(r) = scissor {
963 let x = r.x.clamp(0, self.size.width as i32) as u32;
964 let y = r.y.clamp(0, self.size.height as i32) as u32;
965 let w = (r.right().clamp(0, self.size.width as i32) as u32).saturating_sub(x);
966 let h = (r.bottom().clamp(0, self.size.height as i32) as u32).saturating_sub(y);
967 if w == 0 || h == 0 {
968 return;
969 }
970 pass.set_scissor_rect(x, y, w, h);
971 }
972 for draw in &self.draws {
973 match draw {
974 Draw::Shapes(range) => {
975 pass.set_bind_group(1, &gpu.white, &[]);
976 pass.draw(range.clone(), 0..1);
977 }
978 Draw::Textured { texture, range } => {
979 pass.set_bind_group(1, &self.textures[*texture], &[]);
980 pass.draw(range.clone(), 0..1);
981 }
982 }
983 }
984 }
985
986 fn clip_f(&self) -> [f32; 4] {
989 [
990 self.clip.x as f32,
991 self.clip.y as f32,
992 self.clip.right() as f32,
993 self.clip.bottom() as f32,
994 ]
995 }
996
997 fn triangle(
1000 &mut self,
1001 kind: u32,
1002 color: [f32; 4],
1003 a: [f32; 4],
1004 b: [f32; 4],
1005 pts: [[f32; 2]; 3],
1006 ) {
1007 let clip = self.clip_f();
1008 let start = self.vertices.len() as u32;
1009 debug_assert!(
1010 !is_textured(kind),
1011 "textured triangles go through `textured_quad`"
1012 );
1013 for pos in pts {
1014 self.vertices.push(Vertex {
1015 pos,
1016 clip,
1017 color,
1018 a,
1019 b,
1020 kind,
1021 poly: [0; 2],
1022 _pad: 0,
1023 });
1024 }
1025 let end = start + 3;
1026 match self.draws.last_mut() {
1027 Some(Draw::Shapes(range)) if range.end == start => range.end = end,
1028 _ => self.draws.push(Draw::Shapes(start..end)),
1029 }
1030 }
1031
1032 fn quad(&mut self, kind: u32, color: [f32; 4], a: [f32; 4], b: [f32; 4], bounds: [f32; 4]) {
1034 let [x0, y0, x1, y1] = bounds;
1035 self.triangle(kind, color, a, b, [[x0, y0], [x1, y0], [x1, y1]]);
1036 self.triangle(kind, color, a, b, [[x0, y0], [x1, y1], [x0, y1]]);
1037 }
1038
1039 fn polygon_quad(&mut self, color: [f32; 4], bounds: [f32; 4], run: [u32; 2]) {
1043 let clip = self.clip_f();
1044 let [x0, y0, x1, y1] = bounds;
1045 let start = self.vertices.len() as u32;
1046 for pos in [[x0, y0], [x1, y0], [x1, y1], [x0, y0], [x1, y1], [x0, y1]] {
1047 self.vertices.push(Vertex {
1048 pos,
1049 clip,
1050 color,
1051 a: [0.0; 4],
1052 b: [0.0; 4],
1053 kind: KIND_POLYGON,
1054 poly: run,
1055 _pad: 0,
1056 });
1057 }
1058 let end = start + 6;
1059 match self.draws.last_mut() {
1060 Some(Draw::Shapes(range)) if range.end == start => range.end = end,
1061 _ => self.draws.push(Draw::Shapes(start..end)),
1062 }
1063 }
1064
1065 fn textured_quad(
1070 &mut self,
1071 kind: u32,
1072 color: [f32; 4],
1073 index: usize,
1074 dest: Rect,
1075 uv: [f32; 4],
1076 radius_box: ([f32; 4], f32),
1077 ) {
1078 let clip = self.clip_f();
1079 let (x0, y0) = (dest.x as f32, dest.y as f32);
1080 let (x1, y1) = (dest.right() as f32, dest.bottom() as f32);
1081 let [u0, v0, u1, v1] = uv;
1082 let corners = [
1083 ([x0, y0], [u0, v0]),
1084 ([x1, y0], [u1, v0]),
1085 ([x1, y1], [u1, v1]),
1086 ([x0, y1], [u0, v1]),
1087 ];
1088 let (b, radius) = radius_box;
1089 let start = self.vertices.len() as u32;
1090 for i in [0usize, 1, 2, 0, 2, 3] {
1091 let (pos, uv) = corners[i];
1092 self.vertices.push(Vertex {
1093 pos,
1094 clip,
1095 color,
1096 a: [uv[0], uv[1], radius, 0.0],
1097 b,
1098 kind,
1099 poly: [0; 2],
1100 _pad: 0,
1101 });
1102 }
1103 self.draws.push(Draw::Textured {
1104 texture: index,
1105 range: start..start + 6,
1106 });
1107 }
1108
1109 fn upload_view(&mut self, src: &PixelView<'_>) -> usize {
1110 let size = src.size();
1111 let bytes = rgba_bytes(src);
1112 self.textures.push(self.gpu.upload(
1113 size.width,
1114 size.height,
1115 wgpu::TextureFormat::Rgba8Unorm,
1116 &bytes,
1117 ));
1118 self.textures.len() - 1
1119 }
1120}
1121
1122fn rgba_bytes(src: &PixelView<'_>) -> Vec<u8> {
1124 let size = src.size();
1125 let mut bytes = Vec::with_capacity((size.width * size.height * 4) as usize);
1126 for y in 0..size.height as i32 {
1127 let row = src.row(y, 0, size.width as i32).unwrap_or(&[]);
1128 for &word in row {
1129 bytes.extend_from_slice(&[
1130 (word >> 16) as u8,
1131 (word >> 8) as u8,
1132 word as u8,
1133 (word >> 24) as u8,
1134 ]);
1135 }
1136 }
1137 bytes.resize((size.width.max(1) * size.height.max(1) * 4) as usize, 0);
1139 bytes
1140}
1141
1142fn is_textured(kind: u32) -> bool {
1143 matches!(kind, KIND_TEXTURED | KIND_MASK | KIND_TEXTURED_ROUNDED)
1144}
1145
1146fn rgba(paint: Paint) -> [f32; 4] {
1148 let w = paint.premultiplied();
1149 [
1150 ((w >> 16) & 0xFF) as f32 / 255.0,
1151 ((w >> 8) & 0xFF) as f32 / 255.0,
1152 (w & 0xFF) as f32 / 255.0,
1153 ((w >> 24) & 0xFF) as f32 / 255.0,
1154 ]
1155}
1156
1157fn box_of(rect: Rect) -> [f32; 4] {
1159 let hw = rect.width as f32 / 2.0;
1160 let hh = rect.height as f32 / 2.0;
1161 [rect.x as f32 + hw, rect.y as f32 + hh, hw, hh]
1162}
1163
1164impl Painter for GpuPainter<'_> {
1165 fn size(&self) -> Size {
1166 self.size
1167 }
1168
1169 fn format(&self) -> PixelFormat {
1170 PixelFormat::Argb8888
1171 }
1172
1173 fn clip(&self) -> Rect {
1174 self.clip
1175 }
1176
1177 fn push_clip(&mut self, rect: Rect) -> ClipToken {
1178 let previous = self.clip;
1179 self.clip = self.clip.intersect(&rect).unwrap_or(Rect::ZERO);
1180 ClipToken::restoring(previous)
1181 }
1182
1183 fn pop_clip(&mut self, token: ClipToken) {
1184 self.clip = token.previous();
1185 }
1186
1187 fn clear(&mut self, color: Color) {
1188 let clip = self.clip;
1189 self.fill_rect(clip, Paint::new(Color::rgb(color.r, color.g, color.b)));
1190 }
1191
1192 fn fill_rect(&mut self, rect: Rect, paint: Paint) {
1193 if paint.is_invisible() || rect.is_empty() || self.clip.is_empty() {
1194 return;
1195 }
1196 let c = rgba(paint);
1197 self.quad(
1198 KIND_SOLID,
1199 c,
1200 [0.0; 4],
1201 [0.0; 4],
1202 [
1203 rect.x as f32,
1204 rect.y as f32,
1205 rect.right() as f32,
1206 rect.bottom() as f32,
1207 ],
1208 );
1209 }
1210
1211 fn fill_rounded_rect(&mut self, rect: Rect, radius: i32, paint: Paint) {
1212 if paint.is_invisible() || rect.is_empty() || self.clip.is_empty() {
1213 return;
1214 }
1215 let r = radius.clamp(0, rect.width.min(rect.height) / 2);
1216 if r == 0 {
1217 return self.fill_rect(rect, paint);
1218 }
1219 let c = rgba(paint);
1220 self.quad(
1221 KIND_ROUNDED_FILL,
1222 c,
1223 box_of(rect),
1224 [r as f32, 0.0, 0.0, 0.0],
1225 [
1226 rect.x as f32,
1227 rect.y as f32,
1228 rect.right() as f32,
1229 rect.bottom() as f32,
1230 ],
1231 );
1232 }
1233
1234 fn stroke_rounded_rect(&mut self, rect: Rect, radius: i32, thickness: i32, paint: Paint) {
1235 let t = thickness.max(0);
1236 if t == 0 || paint.is_invisible() || rect.is_empty() || self.clip.is_empty() {
1237 return;
1238 }
1239 if t * 2 >= rect.width.min(rect.height) {
1240 return self.fill_rounded_rect(rect, radius, paint);
1241 }
1242 let r = radius.clamp(0, rect.width.min(rect.height) / 2);
1243 let c = rgba(paint);
1244 self.quad(
1245 KIND_ROUNDED_STROKE,
1246 c,
1247 box_of(rect),
1248 [r as f32, t as f32, 0.0, 0.0],
1249 [
1250 rect.x as f32,
1251 rect.y as f32,
1252 rect.right() as f32,
1253 rect.bottom() as f32,
1254 ],
1255 );
1256 }
1257
1258 fn fill_circle(&mut self, centre: Point, radius: i32, paint: Paint) {
1259 if radius <= 0 || paint.is_invisible() || self.clip.is_empty() {
1260 return;
1261 }
1262 let (cx, cy, r) = (centre.x as f32, centre.y as f32, radius as f32);
1263 let c = rgba(paint);
1264 self.quad(
1265 KIND_CIRCLE_FILL,
1266 c,
1267 [cx, cy, r, 0.0],
1268 [0.0; 4],
1269 [cx - r - 1.0, cy - r - 1.0, cx + r + 1.0, cy + r + 1.0],
1270 );
1271 }
1272
1273 fn stroke_circle(&mut self, centre: Point, radius: i32, thickness: i32, paint: Paint) {
1274 let t = thickness.max(0);
1275 if t == 0 || radius <= 0 || paint.is_invisible() || self.clip.is_empty() {
1276 return;
1277 }
1278 if t >= radius {
1279 return self.fill_circle(centre, radius, paint);
1280 }
1281 let (cx, cy, r) = (centre.x as f32, centre.y as f32, radius as f32);
1282 let c = rgba(paint);
1283 self.quad(
1284 KIND_CIRCLE_STROKE,
1285 c,
1286 [cx, cy, r, t as f32],
1287 [0.0; 4],
1288 [cx - r - 1.0, cy - r - 1.0, cx + r + 1.0, cy + r + 1.0],
1289 );
1290 }
1291
1292 fn stroke_arc(
1293 &mut self,
1294 centre: Point,
1295 radius: i32,
1296 thickness: i32,
1297 start: i32,
1298 sweep: i32,
1299 paint: Paint,
1300 ) {
1301 let t = thickness.max(0);
1302 if t == 0 || radius <= 0 || sweep == 0 || paint.is_invisible() || self.clip.is_empty() {
1303 return;
1304 }
1305 let (start, sweep) = if sweep < 0 {
1308 (start.wrapping_add(sweep), -(sweep as i64))
1309 } else {
1310 (start, sweep as i64)
1311 };
1312 if sweep >= TURN as i64 {
1313 return self.stroke_circle(centre, radius, thickness, paint);
1314 }
1315 let start = start.rem_euclid(TURN) as f32 / TURN as f32;
1316 let sweep = sweep as f32 / TURN as f32;
1317 let (cx, cy, r) = (centre.x as f32, centre.y as f32, radius as f32);
1318 let c = rgba(paint);
1319 self.quad(
1320 KIND_ARC,
1321 c,
1322 [cx, cy, r, t.min(radius) as f32],
1323 [start, sweep, 0.0, 0.0],
1324 [cx - r - 1.0, cy - r - 1.0, cx + r + 1.0, cy + r + 1.0],
1325 );
1326 }
1327
1328 fn draw_line(&mut self, a: Point, b: Point, paint: Paint) {
1329 if paint.is_invisible() || self.clip.is_empty() {
1330 return;
1331 }
1332 if a == b {
1333 return self.fill_rect(Rect::new(a.x, a.y, 1, 1), paint);
1334 }
1335 let (ax, ay) = (a.x as f32 + 0.5, a.y as f32 + 0.5);
1339 let (bx, by) = (b.x as f32 + 0.5, b.y as f32 + 0.5);
1340 let (dx, dy) = (bx - ax, by - ay);
1341 let len = (dx * dx + dy * dy).sqrt();
1342 let (ux, uy) = (dx / len, dy / len);
1343 let (px, py) = (-uy, ux);
1344 let c = rgba(paint);
1345 let pa = [ax, ay, bx, by];
1346 let pb = [0.5, 0.0, 0.0, 0.0];
1347 let corners = [
1348 [ax - ux - px, ay - uy - py],
1349 [bx + ux - px, by + uy - py],
1350 [bx + ux + px, by + uy + py],
1351 [ax - ux + px, ay - uy + py],
1352 ];
1353 self.triangle(KIND_LINE, c, pa, pb, [corners[0], corners[1], corners[2]]);
1354 self.triangle(KIND_LINE, c, pa, pb, [corners[0], corners[2], corners[3]]);
1355 }
1356
1357 fn fill_polygon_fx(&mut self, points: &[(i32, i32)], paint: Paint) {
1358 if points.len() < 3 || paint.is_invisible() || self.clip.is_empty() {
1359 return;
1360 }
1361 let first = self.edges.len() as u32;
1362 let (mut left, mut top) = (f32::MAX, f32::MAX);
1363 let (mut right, mut bottom) = (f32::MIN, f32::MIN);
1364 let at = |(x, y): (i32, i32)| [x as f32 / ONE as f32, y as f32 / ONE as f32];
1365 for i in 0..points.len() {
1366 let p = at(points[i]);
1367 let q = at(points[(i + 1) % points.len()]);
1368 self.edges.push([p[0], p[1], q[0], q[1]]);
1369 left = left.min(p[0]);
1370 top = top.min(p[1]);
1371 right = right.max(p[0]);
1372 bottom = bottom.max(p[1]);
1373 }
1374 let bounds = [left - 1.0, top - 1.0, right + 1.0, bottom + 1.0];
1378 let run = [first, points.len() as u32];
1379 self.polygon_quad(rgba(paint), bounds, run);
1380 }
1381
1382 fn blit_mask(&mut self, at: Point, mask: &Mask<'_>, paint: Paint) {
1383 if paint.is_invisible() || self.clip.is_empty() {
1384 return;
1385 }
1386 let (w, h) = (mask.width(), mask.height());
1387 if w <= 0 || h <= 0 {
1388 return;
1389 }
1390 let mut bytes = Vec::with_capacity((w * h) as usize);
1391 for y in 0..h {
1392 bytes.extend_from_slice(mask.row(y));
1393 }
1394 self.textures.push(self.gpu.upload(
1395 w as u32,
1396 h as u32,
1397 wgpu::TextureFormat::R8Unorm,
1398 &bytes,
1399 ));
1400 let index = self.textures.len() - 1;
1401 self.textured_quad(
1402 KIND_MASK,
1403 rgba(paint),
1404 index,
1405 mask.bounds_at(at),
1406 WHOLE,
1407 ([0.0; 4], 0.0),
1408 );
1409 }
1410
1411 fn blit_glyph(&mut self, at: Point, page: &AtlasPage<'_>, rect: Rect, paint: Paint) {
1412 if paint.is_invisible() || rect.is_empty() || self.clip.is_empty() {
1413 return;
1414 }
1415 let (pw, ph) = (page.mask.width() as f32, page.mask.height() as f32);
1416 if pw <= 0.0 || ph <= 0.0 {
1417 return;
1418 }
1419 let group = self.gpu.page_texture(page);
1421 self.textures.push(group);
1422 let index = self.textures.len() - 1;
1423 let uv = [
1424 rect.x as f32 / pw,
1425 rect.y as f32 / ph,
1426 rect.right() as f32 / pw,
1427 rect.bottom() as f32 / ph,
1428 ];
1429 let dest = Rect::new(at.x, at.y, rect.width, rect.height);
1430 self.textured_quad(KIND_MASK, rgba(paint), index, dest, uv, ([0.0; 4], 0.0));
1431 }
1432
1433 fn blit_image(&mut self, src: &ImageRef<'_>, dest: Rect) {
1434 if src.view.size().is_empty() || dest.is_empty() || self.clip.is_empty() {
1435 return;
1436 }
1437 let group = self.gpu.image_texture(src);
1439 self.textures.push(group);
1440 let index = self.textures.len() - 1;
1441 self.textured_quad(KIND_TEXTURED, [1.0; 4], index, dest, WHOLE, ([0.0; 4], 0.0));
1442 }
1443
1444 fn blit_image_rounded(&mut self, src: &ImageRef<'_>, dest: Rect, shape: Rect, radius: i32) {
1445 if src.view.size().is_empty() || dest.is_empty() || self.clip.is_empty() {
1446 return;
1447 }
1448 let group = self.gpu.image_texture(src);
1449 self.textures.push(group);
1450 let index = self.textures.len() - 1;
1451 let r = radius.clamp(0, shape.width.min(shape.height) / 2) as f32;
1452 self.textured_quad(
1453 KIND_TEXTURED_ROUNDED,
1454 [1.0; 4],
1455 index,
1456 dest,
1457 WHOLE,
1458 (box_of(shape), r),
1459 );
1460 }
1461
1462 fn blit(&mut self, src: &PixelView<'_>, at: Point) {
1463 let size = src.size();
1464 if size.is_empty() || self.clip.is_empty() {
1465 return;
1466 }
1467 let index = self.upload_view(src);
1468 let dest = Rect::new(at.x, at.y, size.width as i32, size.height as i32);
1469 self.textured_quad(KIND_TEXTURED, [1.0; 4], index, dest, WHOLE, ([0.0; 4], 0.0));
1470 }
1471
1472 fn blit_scaled(&mut self, src: &PixelView<'_>, dest: Rect) {
1473 if src.size().is_empty() || dest.is_empty() || self.clip.is_empty() {
1474 return;
1475 }
1476 let index = self.upload_view(src);
1477 self.textured_quad(KIND_TEXTURED, [1.0; 4], index, dest, WHOLE, ([0.0; 4], 0.0));
1478 }
1479
1480 fn scroll_rows(&mut self, rect: Rect, dy: i32) -> bool {
1481 let Some(rect) = rect
1482 .intersect(&self.clip)
1483 .and_then(|r| r.intersect(&Rect::from_size(self.size)))
1484 else {
1485 return false;
1486 };
1487 if dy == 0 || dy.unsigned_abs() as i32 >= rect.height {
1488 return false;
1489 }
1490 self.scrolls.push((rect, dy));
1491 true
1492 }
1493
1494 fn blit_rounded(&mut self, src: &PixelView<'_>, dest: Rect, shape: Rect, radius: i32) {
1495 if src.size().is_empty() || dest.is_empty() || self.clip.is_empty() {
1496 return;
1497 }
1498 let index = self.upload_view(src);
1499 let r = radius.clamp(0, shape.width.min(shape.height) / 2) as f32;
1500 self.textured_quad(
1501 KIND_TEXTURED_ROUNDED,
1502 [1.0; 4],
1503 index,
1504 dest,
1505 WHOLE,
1506 (box_of(shape), r),
1507 );
1508 }
1509}
1510
1511#[cfg(doctest)]
1514#[doc = include_str!("../README.md")]
1515struct Readme;
1516
1517#[cfg(test)]
1518mod tests {
1519 use super::*;
1520
1521 #[test]
1522 fn paint_converts_to_premultiplied_floats() {
1523 let c = rgba(Paint::new(Color::rgba(255, 0, 0, 128)));
1524 assert!((c[3] - 128.0 / 255.0).abs() < 1e-6);
1525 assert!(c[0] > 0.49 && c[0] < 0.51, "red is premultiplied: {}", c[0]);
1526 assert_eq!(c[1], 0.0);
1527 }
1528}