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