1use super::metal_atlas::MetalAtlas;
2use crate::{
3 AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
4 Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
5 Surface, Underline, point, size,
6};
7use anyhow::Result;
8use block::ConcreteBlock;
9use cocoa::{
10 base::{NO, YES},
11 foundation::{NSSize, NSUInteger},
12 quartzcore::AutoresizingMask,
13};
14
15use core_foundation::base::TCFType;
16use core_video::{
17 metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
18 pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
19};
20use foreign_types::{ForeignType, ForeignTypeRef};
21use metal::{
22 CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange,
23 RenderPassColorAttachmentDescriptorRef,
24};
25use objc::{self, msg_send, sel, sel_impl};
26use parking_lot::Mutex;
27
28use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
29
30// Exported to metal
31pub(crate) type PointF = crate::Point<f32>;
32
33#[cfg(not(feature = "runtime_shaders"))]
34const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
35#[cfg(feature = "runtime_shaders")]
36const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
37// Use 4x MSAA, all devices support it.
38// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
39const PATH_SAMPLE_COUNT: u32 = 4;
40
41pub type Context = Arc<Mutex<InstanceBufferPool>>;
42pub type Renderer = MetalRenderer;
43
44pub unsafe fn new_renderer(
45 context: self::Context,
46 _native_window: *mut c_void,
47 _native_view: *mut c_void,
48 _bounds: crate::Size<f32>,
49 _transparent: bool,
50) -> Renderer {
51 MetalRenderer::new(context)
52}
53
54pub(crate) struct InstanceBufferPool {
55 buffer_size: usize,
56 buffers: Vec<metal::Buffer>,
57}
58
59impl Default for InstanceBufferPool {
60 fn default() -> Self {
61 Self {
62 buffer_size: 2 * 1024 * 1024,
63 buffers: Vec::new(),
64 }
65 }
66}
67
68pub(crate) struct InstanceBuffer {
69 metal_buffer: metal::Buffer,
70 size: usize,
71}
72
73impl InstanceBufferPool {
74 pub(crate) fn reset(&mut self, buffer_size: usize) {
75 self.buffer_size = buffer_size;
76 self.buffers.clear();
77 }
78
79 pub(crate) fn acquire(&mut self, device: &metal::Device) -> InstanceBuffer {
80 let buffer = self.buffers.pop().unwrap_or_else(|| {
81 device.new_buffer(
82 self.buffer_size as u64,
83 MTLResourceOptions::StorageModeManaged,
84 )
85 });
86 InstanceBuffer {
87 metal_buffer: buffer,
88 size: self.buffer_size,
89 }
90 }
91
92 pub(crate) fn release(&mut self, buffer: InstanceBuffer) {
93 if buffer.size == self.buffer_size {
94 self.buffers.push(buffer.metal_buffer)
95 }
96 }
97}
98
99pub(crate) struct MetalRenderer {
100 device: metal::Device,
101 layer: metal::MetalLayer,
102 presents_with_transaction: bool,
103 command_queue: CommandQueue,
104 paths_rasterization_pipeline_state: metal::RenderPipelineState,
105 path_sprites_pipeline_state: metal::RenderPipelineState,
106 shadows_pipeline_state: metal::RenderPipelineState,
107 quads_pipeline_state: metal::RenderPipelineState,
108 underlines_pipeline_state: metal::RenderPipelineState,
109 monochrome_sprites_pipeline_state: metal::RenderPipelineState,
110 polychrome_sprites_pipeline_state: metal::RenderPipelineState,
111 surfaces_pipeline_state: metal::RenderPipelineState,
112 unit_vertices: metal::Buffer,
113 #[allow(clippy::arc_with_non_send_sync)]
114 instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
115 sprite_atlas: Arc<MetalAtlas>,
116 core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
117 path_intermediate_texture: Option<metal::Texture>,
118 path_intermediate_msaa_texture: Option<metal::Texture>,
119 path_sample_count: u32,
120}
121
122#[repr(C)]
123pub struct PathRasterizationVertex {
124 pub xy_position: Point<ScaledPixels>,
125 pub st_position: Point<f32>,
126 pub color: Background,
127 pub bounds: Bounds<ScaledPixels>,
128}
129
130impl MetalRenderer {
131 pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self {
132 // Prefer low‐power integrated GPUs on Intel Mac. On Apple
133 // Silicon, there is only ever one GPU, so this is equivalent to
134 // `metal::Device::system_default()`.
135 let mut devices = metal::Device::all();
136 devices.sort_by_key(|device| (device.is_removable(), device.is_low_power()));
137 let Some(device) = devices.pop() else {
138 log::error!("unable to access a compatible graphics device");
139 std::process::exit(1);
140 };
141
142 let layer = metal::MetalLayer::new();
143 layer.set_device(&device);
144 layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
145 layer.set_opaque(false);
146 layer.set_maximum_drawable_count(3);
147 unsafe {
148 let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
149 let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
150 let _: () = msg_send![
151 &*layer,
152 setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
153 | AutoresizingMask::HEIGHT_SIZABLE
154 ];
155 }
156 #[cfg(feature = "runtime_shaders")]
157 let library = device
158 .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new())
159 .expect("error building metal library");
160 #[cfg(not(feature = "runtime_shaders"))]
161 let library = device
162 .new_library_with_data(SHADERS_METALLIB)
163 .expect("error building metal library");
164
165 fn to_float2_bits(point: PointF) -> u64 {
166 let mut output = point.y.to_bits() as u64;
167 output <<= 32;
168 output |= point.x.to_bits() as u64;
169 output
170 }
171
172 let unit_vertices = [
173 to_float2_bits(point(0., 0.)),
174 to_float2_bits(point(1., 0.)),
175 to_float2_bits(point(0., 1.)),
176 to_float2_bits(point(0., 1.)),
177 to_float2_bits(point(1., 0.)),
178 to_float2_bits(point(1., 1.)),
179 ];
180 let unit_vertices = device.new_buffer_with_data(
181 unit_vertices.as_ptr() as *const c_void,
182 mem::size_of_val(&unit_vertices) as u64,
183 MTLResourceOptions::StorageModeManaged,
184 );
185
186 let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
187 &device,
188 &library,
189 "paths_rasterization",
190 "path_rasterization_vertex",
191 "path_rasterization_fragment",
192 MTLPixelFormat::BGRA8Unorm,
193 PATH_SAMPLE_COUNT,
194 );
195 let path_sprites_pipeline_state = build_path_sprite_pipeline_state(
196 &device,
197 &library,
198 "path_sprites",
199 "path_sprite_vertex",
200 "path_sprite_fragment",
201 MTLPixelFormat::BGRA8Unorm,
202 );
203 let shadows_pipeline_state = build_pipeline_state(
204 &device,
205 &library,
206 "shadows",
207 "shadow_vertex",
208 "shadow_fragment",
209 MTLPixelFormat::BGRA8Unorm,
210 );
211 let quads_pipeline_state = build_pipeline_state(
212 &device,
213 &library,
214 "quads",
215 "quad_vertex",
216 "quad_fragment",
217 MTLPixelFormat::BGRA8Unorm,
218 );
219 let underlines_pipeline_state = build_pipeline_state(
220 &device,
221 &library,
222 "underlines",
223 "underline_vertex",
224 "underline_fragment",
225 MTLPixelFormat::BGRA8Unorm,
226 );
227 let monochrome_sprites_pipeline_state = build_pipeline_state(
228 &device,
229 &library,
230 "monochrome_sprites",
231 "monochrome_sprite_vertex",
232 "monochrome_sprite_fragment",
233 MTLPixelFormat::BGRA8Unorm,
234 );
235 let polychrome_sprites_pipeline_state = build_pipeline_state(
236 &device,
237 &library,
238 "polychrome_sprites",
239 "polychrome_sprite_vertex",
240 "polychrome_sprite_fragment",
241 MTLPixelFormat::BGRA8Unorm,
242 );
243 let surfaces_pipeline_state = build_pipeline_state(
244 &device,
245 &library,
246 "surfaces",
247 "surface_vertex",
248 "surface_fragment",
249 MTLPixelFormat::BGRA8Unorm,
250 );
251
252 let command_queue = device.new_command_queue();
253 let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
254 let core_video_texture_cache =
255 CVMetalTextureCache::new(None, device.clone(), None).unwrap();
256
257 Self {
258 device,
259 layer,
260 presents_with_transaction: false,
261 command_queue,
262 paths_rasterization_pipeline_state,
263 path_sprites_pipeline_state,
264 shadows_pipeline_state,
265 quads_pipeline_state,
266 underlines_pipeline_state,
267 monochrome_sprites_pipeline_state,
268 polychrome_sprites_pipeline_state,
269 surfaces_pipeline_state,
270 unit_vertices,
271 instance_buffer_pool,
272 sprite_atlas,
273 core_video_texture_cache,
274 path_intermediate_texture: None,
275 path_intermediate_msaa_texture: None,
276 path_sample_count: PATH_SAMPLE_COUNT,
277 }
278 }
279
280 pub fn layer(&self) -> &metal::MetalLayerRef {
281 &self.layer
282 }
283
284 pub fn layer_ptr(&self) -> *mut CAMetalLayer {
285 self.layer.as_ptr()
286 }
287
288 pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
289 &self.sprite_atlas
290 }
291
292 pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
293 self.presents_with_transaction = presents_with_transaction;
294 self.layer
295 .set_presents_with_transaction(presents_with_transaction);
296 }
297
298 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
299 let size = NSSize {
300 width: size.width.0 as f64,
301 height: size.height.0 as f64,
302 };
303 unsafe {
304 let _: () = msg_send![
305 self.layer(),
306 setDrawableSize: size
307 ];
308 }
309 let device_pixels_size = Size {
310 width: DevicePixels(size.width as i32),
311 height: DevicePixels(size.height as i32),
312 };
313 self.update_path_intermediate_textures(device_pixels_size);
314 }
315
316 fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
317 let texture_descriptor = metal::TextureDescriptor::new();
318 texture_descriptor.set_width(size.width.0 as u64);
319 texture_descriptor.set_height(size.height.0 as u64);
320 texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
321 texture_descriptor
322 .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
323 self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor));
324
325 if self.path_sample_count > 1 {
326 let mut msaa_descriptor = texture_descriptor.clone();
327 msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
328 msaa_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
329 msaa_descriptor.set_sample_count(self.path_sample_count as _);
330 self.path_intermediate_msaa_texture = Some(self.device.new_texture(&msaa_descriptor));
331 } else {
332 self.path_intermediate_msaa_texture = None;
333 }
334 }
335
336 pub fn update_transparency(&self, _transparent: bool) {
337 // todo(mac)?
338 }
339
340 pub fn destroy(&self) {
341 // nothing to do
342 }
343
344 pub fn draw(&mut self, scene: &Scene) {
345 let layer = self.layer.clone();
346 let viewport_size = layer.drawable_size();
347 let viewport_size: Size<DevicePixels> = size(
348 (viewport_size.width.ceil() as i32).into(),
349 (viewport_size.height.ceil() as i32).into(),
350 );
351 let drawable = if let Some(drawable) = layer.next_drawable() {
352 drawable
353 } else {
354 log::error!(
355 "failed to retrieve next drawable, drawable size: {:?}",
356 viewport_size
357 );
358 return;
359 };
360
361 loop {
362 let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device);
363
364 let command_buffer =
365 self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
366
367 match command_buffer {
368 Ok(command_buffer) => {
369 let instance_buffer_pool = self.instance_buffer_pool.clone();
370 let instance_buffer = Cell::new(Some(instance_buffer));
371 let block = ConcreteBlock::new(move |_| {
372 if let Some(instance_buffer) = instance_buffer.take() {
373 instance_buffer_pool.lock().release(instance_buffer);
374 }
375 });
376 let block = block.copy();
377 command_buffer.add_completed_handler(&block);
378
379 if self.presents_with_transaction {
380 command_buffer.commit();
381 command_buffer.wait_until_scheduled();
382 drawable.present();
383 } else {
384 command_buffer.present_drawable(drawable);
385 command_buffer.commit();
386 }
387 return;
388 }
389 Err(err) => {
390 log::error!(
391 "failed to render: {}. retrying with larger instance buffer size",
392 err
393 );
394 let mut instance_buffer_pool = self.instance_buffer_pool.lock();
395 let buffer_size = instance_buffer_pool.buffer_size;
396 if buffer_size >= 256 * 1024 * 1024 {
397 log::error!("instance buffer size grew too large: {}", buffer_size);
398 break;
399 }
400 instance_buffer_pool.reset(buffer_size * 2);
401 log::info!(
402 "increased instance buffer size to {}",
403 instance_buffer_pool.buffer_size
404 );
405 }
406 }
407 }
408 }
409
410 fn draw_primitives(
411 &mut self,
412 scene: &Scene,
413 instance_buffer: &mut InstanceBuffer,
414 drawable: &metal::MetalDrawableRef,
415 viewport_size: Size<DevicePixels>,
416 ) -> Result<metal::CommandBuffer> {
417 let command_queue = self.command_queue.clone();
418 let command_buffer = command_queue.new_command_buffer();
419 let alpha = if self.layer.is_opaque() { 1. } else { 0. };
420 let mut instance_offset = 0;
421
422 let mut command_encoder = new_command_encoder(
423 command_buffer,
424 drawable,
425 viewport_size,
426 |color_attachment| {
427 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
428 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
429 },
430 );
431
432 for batch in scene.batches() {
433 let ok = match batch {
434 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
435 shadows,
436 instance_buffer,
437 &mut instance_offset,
438 viewport_size,
439 &command_encoder,
440 ),
441 PrimitiveBatch::Quads(quads) => self.draw_quads(
442 quads,
443 instance_buffer,
444 &mut instance_offset,
445 viewport_size,
446 &command_encoder,
447 ),
448 PrimitiveBatch::Paths(paths) => {
449 command_encoder.end_encoding();
450
451 let did_draw = self.draw_paths_to_intermediate(
452 paths,
453 instance_buffer,
454 &mut instance_offset,
455 viewport_size,
456 command_buffer,
457 );
458
459 command_encoder = new_command_encoder(
460 command_buffer,
461 drawable,
462 viewport_size,
463 |color_attachment| {
464 color_attachment.set_load_action(metal::MTLLoadAction::Load);
465 },
466 );
467
468 if did_draw {
469 self.draw_paths_from_intermediate(
470 paths,
471 instance_buffer,
472 &mut instance_offset,
473 viewport_size,
474 &command_encoder,
475 )
476 } else {
477 false
478 }
479 }
480 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
481 underlines,
482 instance_buffer,
483 &mut instance_offset,
484 viewport_size,
485 &command_encoder,
486 ),
487 PrimitiveBatch::MonochromeSprites {
488 texture_id,
489 sprites,
490 } => self.draw_monochrome_sprites(
491 texture_id,
492 sprites,
493 instance_buffer,
494 &mut instance_offset,
495 viewport_size,
496 &command_encoder,
497 ),
498 PrimitiveBatch::PolychromeSprites {
499 texture_id,
500 sprites,
501 } => self.draw_polychrome_sprites(
502 texture_id,
503 sprites,
504 instance_buffer,
505 &mut instance_offset,
506 viewport_size,
507 &command_encoder,
508 ),
509 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
510 surfaces,
511 instance_buffer,
512 &mut instance_offset,
513 viewport_size,
514 &command_encoder,
515 ),
516 };
517 if !ok {
518 command_encoder.end_encoding();
519 anyhow::bail!(
520 "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
521 scene.paths.len(),
522 scene.shadows.len(),
523 scene.quads.len(),
524 scene.underlines.len(),
525 scene.monochrome_sprites.len(),
526 scene.polychrome_sprites.len(),
527 scene.surfaces.len(),
528 );
529 }
530 }
531
532 command_encoder.end_encoding();
533
534 instance_buffer.metal_buffer.did_modify_range(NSRange {
535 location: 0,
536 length: instance_offset as NSUInteger,
537 });
538 Ok(command_buffer.to_owned())
539 }
540
541 fn draw_paths_to_intermediate(
542 &self,
543 paths: &[Path<ScaledPixels>],
544 instance_buffer: &mut InstanceBuffer,
545 instance_offset: &mut usize,
546 viewport_size: Size<DevicePixels>,
547 command_buffer: &metal::CommandBufferRef,
548 ) -> bool {
549 if paths.is_empty() {
550 return true;
551 }
552 let Some(intermediate_texture) = &self.path_intermediate_texture else {
553 return false;
554 };
555
556 let render_pass_descriptor = metal::RenderPassDescriptor::new();
557 let color_attachment = render_pass_descriptor
558 .color_attachments()
559 .object_at(0)
560 .unwrap();
561 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
562 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 0.));
563
564 if let Some(msaa_texture) = &self.path_intermediate_msaa_texture {
565 color_attachment.set_texture(Some(msaa_texture));
566 color_attachment.set_resolve_texture(Some(intermediate_texture));
567 color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
568 } else {
569 color_attachment.set_texture(Some(intermediate_texture));
570 color_attachment.set_store_action(metal::MTLStoreAction::Store);
571 }
572
573 let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
574 command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
575
576 align_offset(instance_offset);
577 let mut vertices = Vec::new();
578 for path in paths {
579 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
580 xy_position: v.xy_position,
581 st_position: v.st_position,
582 color: path.color,
583 bounds: path.bounds.intersect(&path.content_mask.bounds),
584 }));
585 }
586 let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
587 let next_offset = *instance_offset + vertices_bytes_len;
588 if next_offset > instance_buffer.size {
589 command_encoder.end_encoding();
590 return false;
591 }
592 command_encoder.set_vertex_buffer(
593 PathRasterizationInputIndex::Vertices as u64,
594 Some(&instance_buffer.metal_buffer),
595 *instance_offset as u64,
596 );
597 command_encoder.set_vertex_bytes(
598 PathRasterizationInputIndex::ViewportSize as u64,
599 mem::size_of_val(&viewport_size) as u64,
600 &viewport_size as *const Size<DevicePixels> as *const _,
601 );
602 command_encoder.set_fragment_buffer(
603 PathRasterizationInputIndex::Vertices as u64,
604 Some(&instance_buffer.metal_buffer),
605 *instance_offset as u64,
606 );
607 let buffer_contents =
608 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
609 unsafe {
610 ptr::copy_nonoverlapping(
611 vertices.as_ptr() as *const u8,
612 buffer_contents,
613 vertices_bytes_len,
614 );
615 }
616 command_encoder.draw_primitives(
617 metal::MTLPrimitiveType::Triangle,
618 0,
619 vertices.len() as u64,
620 );
621 *instance_offset = next_offset;
622
623 command_encoder.end_encoding();
624 true
625 }
626
627 fn draw_shadows(
628 &self,
629 shadows: &[Shadow],
630 instance_buffer: &mut InstanceBuffer,
631 instance_offset: &mut usize,
632 viewport_size: Size<DevicePixels>,
633 command_encoder: &metal::RenderCommandEncoderRef,
634 ) -> bool {
635 if shadows.is_empty() {
636 return true;
637 }
638 align_offset(instance_offset);
639
640 command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
641 command_encoder.set_vertex_buffer(
642 ShadowInputIndex::Vertices as u64,
643 Some(&self.unit_vertices),
644 0,
645 );
646 command_encoder.set_vertex_buffer(
647 ShadowInputIndex::Shadows as u64,
648 Some(&instance_buffer.metal_buffer),
649 *instance_offset as u64,
650 );
651 command_encoder.set_fragment_buffer(
652 ShadowInputIndex::Shadows as u64,
653 Some(&instance_buffer.metal_buffer),
654 *instance_offset as u64,
655 );
656
657 command_encoder.set_vertex_bytes(
658 ShadowInputIndex::ViewportSize as u64,
659 mem::size_of_val(&viewport_size) as u64,
660 &viewport_size as *const Size<DevicePixels> as *const _,
661 );
662
663 let shadow_bytes_len = mem::size_of_val(shadows);
664 let buffer_contents =
665 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
666
667 let next_offset = *instance_offset + shadow_bytes_len;
668 if next_offset > instance_buffer.size {
669 return false;
670 }
671
672 unsafe {
673 ptr::copy_nonoverlapping(
674 shadows.as_ptr() as *const u8,
675 buffer_contents,
676 shadow_bytes_len,
677 );
678 }
679
680 command_encoder.draw_primitives_instanced(
681 metal::MTLPrimitiveType::Triangle,
682 0,
683 6,
684 shadows.len() as u64,
685 );
686 *instance_offset = next_offset;
687 true
688 }
689
690 fn draw_quads(
691 &self,
692 quads: &[Quad],
693 instance_buffer: &mut InstanceBuffer,
694 instance_offset: &mut usize,
695 viewport_size: Size<DevicePixels>,
696 command_encoder: &metal::RenderCommandEncoderRef,
697 ) -> bool {
698 if quads.is_empty() {
699 return true;
700 }
701 align_offset(instance_offset);
702
703 command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
704 command_encoder.set_vertex_buffer(
705 QuadInputIndex::Vertices as u64,
706 Some(&self.unit_vertices),
707 0,
708 );
709 command_encoder.set_vertex_buffer(
710 QuadInputIndex::Quads as u64,
711 Some(&instance_buffer.metal_buffer),
712 *instance_offset as u64,
713 );
714 command_encoder.set_fragment_buffer(
715 QuadInputIndex::Quads as u64,
716 Some(&instance_buffer.metal_buffer),
717 *instance_offset as u64,
718 );
719
720 command_encoder.set_vertex_bytes(
721 QuadInputIndex::ViewportSize as u64,
722 mem::size_of_val(&viewport_size) as u64,
723 &viewport_size as *const Size<DevicePixels> as *const _,
724 );
725
726 let quad_bytes_len = mem::size_of_val(quads);
727 let buffer_contents =
728 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
729
730 let next_offset = *instance_offset + quad_bytes_len;
731 if next_offset > instance_buffer.size {
732 return false;
733 }
734
735 unsafe {
736 ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
737 }
738
739 command_encoder.draw_primitives_instanced(
740 metal::MTLPrimitiveType::Triangle,
741 0,
742 6,
743 quads.len() as u64,
744 );
745 *instance_offset = next_offset;
746 true
747 }
748
749 fn draw_paths_from_intermediate(
750 &self,
751 paths: &[Path<ScaledPixels>],
752 instance_buffer: &mut InstanceBuffer,
753 instance_offset: &mut usize,
754 viewport_size: Size<DevicePixels>,
755 command_encoder: &metal::RenderCommandEncoderRef,
756 ) -> bool {
757 let Some(ref first_path) = paths.first() else {
758 return true;
759 };
760
761 let Some(ref intermediate_texture) = self.path_intermediate_texture else {
762 return false;
763 };
764
765 command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
766 command_encoder.set_vertex_buffer(
767 SpriteInputIndex::Vertices as u64,
768 Some(&self.unit_vertices),
769 0,
770 );
771 command_encoder.set_vertex_bytes(
772 SpriteInputIndex::ViewportSize as u64,
773 mem::size_of_val(&viewport_size) as u64,
774 &viewport_size as *const Size<DevicePixels> as *const _,
775 );
776
777 command_encoder.set_fragment_texture(
778 SpriteInputIndex::AtlasTexture as u64,
779 Some(intermediate_texture),
780 );
781
782 // When copying paths from the intermediate texture to the drawable,
783 // each pixel must only be copied once, in case of transparent paths.
784 //
785 // If all paths have the same draw order, then their bounds are all
786 // disjoint, so we can copy each path's bounds individually. If this
787 // batch combines different draw orders, we perform a single copy
788 // for a minimal spanning rect.
789 let sprites;
790 if paths.last().unwrap().order == first_path.order {
791 sprites = paths
792 .iter()
793 .map(|path| PathSprite {
794 bounds: path.clipped_bounds(),
795 })
796 .collect();
797 } else {
798 let mut bounds = first_path.clipped_bounds();
799 for path in paths.iter().skip(1) {
800 bounds = bounds.union(&path.clipped_bounds());
801 }
802 sprites = vec![PathSprite { bounds }];
803 }
804
805 align_offset(instance_offset);
806 let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
807 let next_offset = *instance_offset + sprite_bytes_len;
808 if next_offset > instance_buffer.size {
809 return false;
810 }
811
812 command_encoder.set_vertex_buffer(
813 SpriteInputIndex::Sprites as u64,
814 Some(&instance_buffer.metal_buffer),
815 *instance_offset as u64,
816 );
817
818 let buffer_contents =
819 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
820 unsafe {
821 ptr::copy_nonoverlapping(
822 sprites.as_ptr() as *const u8,
823 buffer_contents,
824 sprite_bytes_len,
825 );
826 }
827
828 command_encoder.draw_primitives_instanced(
829 metal::MTLPrimitiveType::Triangle,
830 0,
831 6,
832 sprites.len() as u64,
833 );
834 *instance_offset = next_offset;
835
836 true
837 }
838
839 fn draw_underlines(
840 &self,
841 underlines: &[Underline],
842 instance_buffer: &mut InstanceBuffer,
843 instance_offset: &mut usize,
844 viewport_size: Size<DevicePixels>,
845 command_encoder: &metal::RenderCommandEncoderRef,
846 ) -> bool {
847 if underlines.is_empty() {
848 return true;
849 }
850 align_offset(instance_offset);
851
852 command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
853 command_encoder.set_vertex_buffer(
854 UnderlineInputIndex::Vertices as u64,
855 Some(&self.unit_vertices),
856 0,
857 );
858 command_encoder.set_vertex_buffer(
859 UnderlineInputIndex::Underlines as u64,
860 Some(&instance_buffer.metal_buffer),
861 *instance_offset as u64,
862 );
863 command_encoder.set_fragment_buffer(
864 UnderlineInputIndex::Underlines as u64,
865 Some(&instance_buffer.metal_buffer),
866 *instance_offset as u64,
867 );
868
869 command_encoder.set_vertex_bytes(
870 UnderlineInputIndex::ViewportSize as u64,
871 mem::size_of_val(&viewport_size) as u64,
872 &viewport_size as *const Size<DevicePixels> as *const _,
873 );
874
875 let underline_bytes_len = mem::size_of_val(underlines);
876 let buffer_contents =
877 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
878
879 let next_offset = *instance_offset + underline_bytes_len;
880 if next_offset > instance_buffer.size {
881 return false;
882 }
883
884 unsafe {
885 ptr::copy_nonoverlapping(
886 underlines.as_ptr() as *const u8,
887 buffer_contents,
888 underline_bytes_len,
889 );
890 }
891
892 command_encoder.draw_primitives_instanced(
893 metal::MTLPrimitiveType::Triangle,
894 0,
895 6,
896 underlines.len() as u64,
897 );
898 *instance_offset = next_offset;
899 true
900 }
901
902 fn draw_monochrome_sprites(
903 &self,
904 texture_id: AtlasTextureId,
905 sprites: &[MonochromeSprite],
906 instance_buffer: &mut InstanceBuffer,
907 instance_offset: &mut usize,
908 viewport_size: Size<DevicePixels>,
909 command_encoder: &metal::RenderCommandEncoderRef,
910 ) -> bool {
911 if sprites.is_empty() {
912 return true;
913 }
914 align_offset(instance_offset);
915
916 let sprite_bytes_len = mem::size_of_val(sprites);
917 let buffer_contents =
918 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
919
920 let next_offset = *instance_offset + sprite_bytes_len;
921 if next_offset > instance_buffer.size {
922 return false;
923 }
924
925 let texture = self.sprite_atlas.metal_texture(texture_id);
926 let texture_size = size(
927 DevicePixels(texture.width() as i32),
928 DevicePixels(texture.height() as i32),
929 );
930 command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
931 command_encoder.set_vertex_buffer(
932 SpriteInputIndex::Vertices as u64,
933 Some(&self.unit_vertices),
934 0,
935 );
936 command_encoder.set_vertex_buffer(
937 SpriteInputIndex::Sprites as u64,
938 Some(&instance_buffer.metal_buffer),
939 *instance_offset as u64,
940 );
941 command_encoder.set_vertex_bytes(
942 SpriteInputIndex::ViewportSize as u64,
943 mem::size_of_val(&viewport_size) as u64,
944 &viewport_size as *const Size<DevicePixels> as *const _,
945 );
946 command_encoder.set_vertex_bytes(
947 SpriteInputIndex::AtlasTextureSize as u64,
948 mem::size_of_val(&texture_size) as u64,
949 &texture_size as *const Size<DevicePixels> as *const _,
950 );
951 command_encoder.set_fragment_buffer(
952 SpriteInputIndex::Sprites as u64,
953 Some(&instance_buffer.metal_buffer),
954 *instance_offset as u64,
955 );
956 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
957
958 unsafe {
959 ptr::copy_nonoverlapping(
960 sprites.as_ptr() as *const u8,
961 buffer_contents,
962 sprite_bytes_len,
963 );
964 }
965
966 command_encoder.draw_primitives_instanced(
967 metal::MTLPrimitiveType::Triangle,
968 0,
969 6,
970 sprites.len() as u64,
971 );
972 *instance_offset = next_offset;
973 true
974 }
975
976 fn draw_polychrome_sprites(
977 &self,
978 texture_id: AtlasTextureId,
979 sprites: &[PolychromeSprite],
980 instance_buffer: &mut InstanceBuffer,
981 instance_offset: &mut usize,
982 viewport_size: Size<DevicePixels>,
983 command_encoder: &metal::RenderCommandEncoderRef,
984 ) -> bool {
985 if sprites.is_empty() {
986 return true;
987 }
988 align_offset(instance_offset);
989
990 let texture = self.sprite_atlas.metal_texture(texture_id);
991 let texture_size = size(
992 DevicePixels(texture.width() as i32),
993 DevicePixels(texture.height() as i32),
994 );
995 command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
996 command_encoder.set_vertex_buffer(
997 SpriteInputIndex::Vertices as u64,
998 Some(&self.unit_vertices),
999 0,
1000 );
1001 command_encoder.set_vertex_buffer(
1002 SpriteInputIndex::Sprites as u64,
1003 Some(&instance_buffer.metal_buffer),
1004 *instance_offset as u64,
1005 );
1006 command_encoder.set_vertex_bytes(
1007 SpriteInputIndex::ViewportSize as u64,
1008 mem::size_of_val(&viewport_size) as u64,
1009 &viewport_size as *const Size<DevicePixels> as *const _,
1010 );
1011 command_encoder.set_vertex_bytes(
1012 SpriteInputIndex::AtlasTextureSize as u64,
1013 mem::size_of_val(&texture_size) as u64,
1014 &texture_size as *const Size<DevicePixels> as *const _,
1015 );
1016 command_encoder.set_fragment_buffer(
1017 SpriteInputIndex::Sprites as u64,
1018 Some(&instance_buffer.metal_buffer),
1019 *instance_offset as u64,
1020 );
1021 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
1022
1023 let sprite_bytes_len = mem::size_of_val(sprites);
1024 let buffer_contents =
1025 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1026
1027 let next_offset = *instance_offset + sprite_bytes_len;
1028 if next_offset > instance_buffer.size {
1029 return false;
1030 }
1031
1032 unsafe {
1033 ptr::copy_nonoverlapping(
1034 sprites.as_ptr() as *const u8,
1035 buffer_contents,
1036 sprite_bytes_len,
1037 );
1038 }
1039
1040 command_encoder.draw_primitives_instanced(
1041 metal::MTLPrimitiveType::Triangle,
1042 0,
1043 6,
1044 sprites.len() as u64,
1045 );
1046 *instance_offset = next_offset;
1047 true
1048 }
1049
1050 fn draw_surfaces(
1051 &mut self,
1052 surfaces: &[PaintSurface],
1053 instance_buffer: &mut InstanceBuffer,
1054 instance_offset: &mut usize,
1055 viewport_size: Size<DevicePixels>,
1056 command_encoder: &metal::RenderCommandEncoderRef,
1057 ) -> bool {
1058 command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
1059 command_encoder.set_vertex_buffer(
1060 SurfaceInputIndex::Vertices as u64,
1061 Some(&self.unit_vertices),
1062 0,
1063 );
1064 command_encoder.set_vertex_bytes(
1065 SurfaceInputIndex::ViewportSize as u64,
1066 mem::size_of_val(&viewport_size) as u64,
1067 &viewport_size as *const Size<DevicePixels> as *const _,
1068 );
1069
1070 for surface in surfaces {
1071 let texture_size = size(
1072 DevicePixels::from(surface.image_buffer.get_width() as i32),
1073 DevicePixels::from(surface.image_buffer.get_height() as i32),
1074 );
1075
1076 assert_eq!(
1077 surface.image_buffer.get_pixel_format(),
1078 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
1079 );
1080
1081 let y_texture = self
1082 .core_video_texture_cache
1083 .create_texture_from_image(
1084 surface.image_buffer.as_concrete_TypeRef(),
1085 None,
1086 MTLPixelFormat::R8Unorm,
1087 surface.image_buffer.get_width_of_plane(0),
1088 surface.image_buffer.get_height_of_plane(0),
1089 0,
1090 )
1091 .unwrap();
1092 let cb_cr_texture = self
1093 .core_video_texture_cache
1094 .create_texture_from_image(
1095 surface.image_buffer.as_concrete_TypeRef(),
1096 None,
1097 MTLPixelFormat::RG8Unorm,
1098 surface.image_buffer.get_width_of_plane(1),
1099 surface.image_buffer.get_height_of_plane(1),
1100 1,
1101 )
1102 .unwrap();
1103
1104 align_offset(instance_offset);
1105 let next_offset = *instance_offset + mem::size_of::<Surface>();
1106 if next_offset > instance_buffer.size {
1107 return false;
1108 }
1109
1110 command_encoder.set_vertex_buffer(
1111 SurfaceInputIndex::Surfaces as u64,
1112 Some(&instance_buffer.metal_buffer),
1113 *instance_offset as u64,
1114 );
1115 command_encoder.set_vertex_bytes(
1116 SurfaceInputIndex::TextureSize as u64,
1117 mem::size_of_val(&texture_size) as u64,
1118 &texture_size as *const Size<DevicePixels> as *const _,
1119 );
1120 // let y_texture = y_texture.get_texture().unwrap().
1121 command_encoder.set_fragment_texture(SurfaceInputIndex::YTexture as u64, unsafe {
1122 let texture = CVMetalTextureGetTexture(y_texture.as_concrete_TypeRef());
1123 Some(metal::TextureRef::from_ptr(texture as *mut _))
1124 });
1125 command_encoder.set_fragment_texture(SurfaceInputIndex::CbCrTexture as u64, unsafe {
1126 let texture = CVMetalTextureGetTexture(cb_cr_texture.as_concrete_TypeRef());
1127 Some(metal::TextureRef::from_ptr(texture as *mut _))
1128 });
1129
1130 unsafe {
1131 let buffer_contents = (instance_buffer.metal_buffer.contents() as *mut u8)
1132 .add(*instance_offset)
1133 as *mut SurfaceBounds;
1134 ptr::write(
1135 buffer_contents,
1136 SurfaceBounds {
1137 bounds: surface.bounds,
1138 content_mask: surface.content_mask.clone(),
1139 },
1140 );
1141 }
1142
1143 command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
1144 *instance_offset = next_offset;
1145 }
1146 true
1147 }
1148}
1149
1150fn new_command_encoder<'a>(
1151 command_buffer: &'a metal::CommandBufferRef,
1152 drawable: &'a metal::MetalDrawableRef,
1153 viewport_size: Size<DevicePixels>,
1154 configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef),
1155) -> &'a metal::RenderCommandEncoderRef {
1156 let render_pass_descriptor = metal::RenderPassDescriptor::new();
1157 let color_attachment = render_pass_descriptor
1158 .color_attachments()
1159 .object_at(0)
1160 .unwrap();
1161 color_attachment.set_texture(Some(drawable.texture()));
1162 color_attachment.set_store_action(metal::MTLStoreAction::Store);
1163 configure_color_attachment(color_attachment);
1164
1165 let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
1166 command_encoder.set_viewport(metal::MTLViewport {
1167 originX: 0.0,
1168 originY: 0.0,
1169 width: i32::from(viewport_size.width) as f64,
1170 height: i32::from(viewport_size.height) as f64,
1171 znear: 0.0,
1172 zfar: 1.0,
1173 });
1174 command_encoder
1175}
1176
1177fn build_pipeline_state(
1178 device: &metal::DeviceRef,
1179 library: &metal::LibraryRef,
1180 label: &str,
1181 vertex_fn_name: &str,
1182 fragment_fn_name: &str,
1183 pixel_format: metal::MTLPixelFormat,
1184) -> metal::RenderPipelineState {
1185 let vertex_fn = library
1186 .get_function(vertex_fn_name, None)
1187 .expect("error locating vertex function");
1188 let fragment_fn = library
1189 .get_function(fragment_fn_name, None)
1190 .expect("error locating fragment function");
1191
1192 let descriptor = metal::RenderPipelineDescriptor::new();
1193 descriptor.set_label(label);
1194 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1195 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1196 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1197 color_attachment.set_pixel_format(pixel_format);
1198 color_attachment.set_blending_enabled(true);
1199 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1200 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1201 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
1202 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1203 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1204 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1205
1206 device
1207 .new_render_pipeline_state(&descriptor)
1208 .expect("could not create render pipeline state")
1209}
1210
1211fn build_path_sprite_pipeline_state(
1212 device: &metal::DeviceRef,
1213 library: &metal::LibraryRef,
1214 label: &str,
1215 vertex_fn_name: &str,
1216 fragment_fn_name: &str,
1217 pixel_format: metal::MTLPixelFormat,
1218) -> metal::RenderPipelineState {
1219 let vertex_fn = library
1220 .get_function(vertex_fn_name, None)
1221 .expect("error locating vertex function");
1222 let fragment_fn = library
1223 .get_function(fragment_fn_name, None)
1224 .expect("error locating fragment function");
1225
1226 let descriptor = metal::RenderPipelineDescriptor::new();
1227 descriptor.set_label(label);
1228 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1229 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1230 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1231 color_attachment.set_pixel_format(pixel_format);
1232 color_attachment.set_blending_enabled(true);
1233 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1234 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1235 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1236 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1237 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1238 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1239
1240 device
1241 .new_render_pipeline_state(&descriptor)
1242 .expect("could not create render pipeline state")
1243}
1244
1245fn build_path_rasterization_pipeline_state(
1246 device: &metal::DeviceRef,
1247 library: &metal::LibraryRef,
1248 label: &str,
1249 vertex_fn_name: &str,
1250 fragment_fn_name: &str,
1251 pixel_format: metal::MTLPixelFormat,
1252 path_sample_count: u32,
1253) -> metal::RenderPipelineState {
1254 let vertex_fn = library
1255 .get_function(vertex_fn_name, None)
1256 .expect("error locating vertex function");
1257 let fragment_fn = library
1258 .get_function(fragment_fn_name, None)
1259 .expect("error locating fragment function");
1260
1261 let descriptor = metal::RenderPipelineDescriptor::new();
1262 descriptor.set_label(label);
1263 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1264 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1265 if path_sample_count > 1 {
1266 descriptor.set_raster_sample_count(path_sample_count as _);
1267 descriptor.set_alpha_to_coverage_enabled(false);
1268 }
1269 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1270 color_attachment.set_pixel_format(pixel_format);
1271 color_attachment.set_blending_enabled(true);
1272 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1273 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1274 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1275 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1276 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1277 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1278
1279 device
1280 .new_render_pipeline_state(&descriptor)
1281 .expect("could not create render pipeline state")
1282}
1283
1284// Align to multiples of 256 make Metal happy.
1285fn align_offset(offset: &mut usize) {
1286 *offset = (*offset).div_ceil(256) * 256;
1287}
1288
1289#[repr(C)]
1290enum ShadowInputIndex {
1291 Vertices = 0,
1292 Shadows = 1,
1293 ViewportSize = 2,
1294}
1295
1296#[repr(C)]
1297enum QuadInputIndex {
1298 Vertices = 0,
1299 Quads = 1,
1300 ViewportSize = 2,
1301}
1302
1303#[repr(C)]
1304enum UnderlineInputIndex {
1305 Vertices = 0,
1306 Underlines = 1,
1307 ViewportSize = 2,
1308}
1309
1310#[repr(C)]
1311enum SpriteInputIndex {
1312 Vertices = 0,
1313 Sprites = 1,
1314 ViewportSize = 2,
1315 AtlasTextureSize = 3,
1316 AtlasTexture = 4,
1317}
1318
1319#[repr(C)]
1320enum SurfaceInputIndex {
1321 Vertices = 0,
1322 Surfaces = 1,
1323 ViewportSize = 2,
1324 TextureSize = 3,
1325 YTexture = 4,
1326 CbCrTexture = 5,
1327}
1328
1329#[repr(C)]
1330enum PathRasterizationInputIndex {
1331 Vertices = 0,
1332 ViewportSize = 1,
1333}
1334
1335#[derive(Clone, Debug, Eq, PartialEq)]
1336#[repr(C)]
1337pub struct PathSprite {
1338 pub bounds: Bounds<ScaledPixels>,
1339}
1340
1341#[derive(Clone, Debug, Eq, PartialEq)]
1342#[repr(C)]
1343pub struct SurfaceBounds {
1344 pub bounds: Bounds<ScaledPixels>,
1345 pub content_mask: ContentMask<ScaledPixels>,
1346}