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 // We are uncertain when this happens, but sometimes size can be 0 here. Most likely before
318 // the layout pass on window creation. Zero-sized texture creation causes SIGABRT.
319 // https://github.com/zed-industries/zed/issues/36229
320 if size.width.0 <= 0 || size.height.0 <= 0 {
321 self.path_intermediate_texture = None;
322 self.path_intermediate_msaa_texture = None;
323 return;
324 }
325
326 let texture_descriptor = metal::TextureDescriptor::new();
327 texture_descriptor.set_width(size.width.0 as u64);
328 texture_descriptor.set_height(size.height.0 as u64);
329 texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
330 texture_descriptor
331 .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
332 self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor));
333
334 if self.path_sample_count > 1 {
335 let mut msaa_descriptor = texture_descriptor;
336 msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
337 msaa_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
338 msaa_descriptor.set_sample_count(self.path_sample_count as _);
339 self.path_intermediate_msaa_texture = Some(self.device.new_texture(&msaa_descriptor));
340 } else {
341 self.path_intermediate_msaa_texture = None;
342 }
343 }
344
345 pub fn update_transparency(&self, _transparent: bool) {
346 // todo(mac)?
347 }
348
349 pub fn destroy(&self) {
350 // nothing to do
351 }
352
353 pub fn draw(&mut self, scene: &Scene) {
354 let layer = self.layer.clone();
355 let viewport_size = layer.drawable_size();
356 let viewport_size: Size<DevicePixels> = size(
357 (viewport_size.width.ceil() as i32).into(),
358 (viewport_size.height.ceil() as i32).into(),
359 );
360 let drawable = if let Some(drawable) = layer.next_drawable() {
361 drawable
362 } else {
363 log::error!(
364 "failed to retrieve next drawable, drawable size: {:?}",
365 viewport_size
366 );
367 return;
368 };
369
370 loop {
371 let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device);
372
373 let command_buffer =
374 self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
375
376 match command_buffer {
377 Ok(command_buffer) => {
378 let instance_buffer_pool = self.instance_buffer_pool.clone();
379 let instance_buffer = Cell::new(Some(instance_buffer));
380 let block = ConcreteBlock::new(move |_| {
381 if let Some(instance_buffer) = instance_buffer.take() {
382 instance_buffer_pool.lock().release(instance_buffer);
383 }
384 });
385 let block = block.copy();
386 command_buffer.add_completed_handler(&block);
387
388 if self.presents_with_transaction {
389 command_buffer.commit();
390 command_buffer.wait_until_scheduled();
391 drawable.present();
392 } else {
393 command_buffer.present_drawable(drawable);
394 command_buffer.commit();
395 }
396 return;
397 }
398 Err(err) => {
399 log::error!(
400 "failed to render: {}. retrying with larger instance buffer size",
401 err
402 );
403 let mut instance_buffer_pool = self.instance_buffer_pool.lock();
404 let buffer_size = instance_buffer_pool.buffer_size;
405 if buffer_size >= 256 * 1024 * 1024 {
406 log::error!("instance buffer size grew too large: {}", buffer_size);
407 break;
408 }
409 instance_buffer_pool.reset(buffer_size * 2);
410 log::info!(
411 "increased instance buffer size to {}",
412 instance_buffer_pool.buffer_size
413 );
414 }
415 }
416 }
417 }
418
419 fn draw_primitives(
420 &mut self,
421 scene: &Scene,
422 instance_buffer: &mut InstanceBuffer,
423 drawable: &metal::MetalDrawableRef,
424 viewport_size: Size<DevicePixels>,
425 ) -> Result<metal::CommandBuffer> {
426 let command_queue = self.command_queue.clone();
427 let command_buffer = command_queue.new_command_buffer();
428 let alpha = if self.layer.is_opaque() { 1. } else { 0. };
429 let mut instance_offset = 0;
430
431 let mut command_encoder = new_command_encoder(
432 command_buffer,
433 drawable,
434 viewport_size,
435 |color_attachment| {
436 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
437 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
438 },
439 );
440
441 for batch in scene.batches() {
442 let ok = match batch {
443 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
444 shadows,
445 instance_buffer,
446 &mut instance_offset,
447 viewport_size,
448 command_encoder,
449 ),
450 PrimitiveBatch::Quads(quads) => self.draw_quads(
451 quads,
452 instance_buffer,
453 &mut instance_offset,
454 viewport_size,
455 command_encoder,
456 ),
457 PrimitiveBatch::Paths(paths) => {
458 command_encoder.end_encoding();
459
460 let did_draw = self.draw_paths_to_intermediate(
461 paths,
462 instance_buffer,
463 &mut instance_offset,
464 viewport_size,
465 command_buffer,
466 );
467
468 command_encoder = new_command_encoder(
469 command_buffer,
470 drawable,
471 viewport_size,
472 |color_attachment| {
473 color_attachment.set_load_action(metal::MTLLoadAction::Load);
474 },
475 );
476
477 if did_draw {
478 self.draw_paths_from_intermediate(
479 paths,
480 instance_buffer,
481 &mut instance_offset,
482 viewport_size,
483 command_encoder,
484 )
485 } else {
486 false
487 }
488 }
489 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
490 underlines,
491 instance_buffer,
492 &mut instance_offset,
493 viewport_size,
494 command_encoder,
495 ),
496 PrimitiveBatch::MonochromeSprites {
497 texture_id,
498 sprites,
499 } => self.draw_monochrome_sprites(
500 texture_id,
501 sprites,
502 instance_buffer,
503 &mut instance_offset,
504 viewport_size,
505 command_encoder,
506 ),
507 PrimitiveBatch::PolychromeSprites {
508 texture_id,
509 sprites,
510 } => self.draw_polychrome_sprites(
511 texture_id,
512 sprites,
513 instance_buffer,
514 &mut instance_offset,
515 viewport_size,
516 command_encoder,
517 ),
518 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
519 surfaces,
520 instance_buffer,
521 &mut instance_offset,
522 viewport_size,
523 command_encoder,
524 ),
525 };
526 if !ok {
527 command_encoder.end_encoding();
528 anyhow::bail!(
529 "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
530 scene.paths.len(),
531 scene.shadows.len(),
532 scene.quads.len(),
533 scene.underlines.len(),
534 scene.monochrome_sprites.len(),
535 scene.polychrome_sprites.len(),
536 scene.surfaces.len(),
537 );
538 }
539 }
540
541 command_encoder.end_encoding();
542
543 instance_buffer.metal_buffer.did_modify_range(NSRange {
544 location: 0,
545 length: instance_offset as NSUInteger,
546 });
547 Ok(command_buffer.to_owned())
548 }
549
550 fn draw_paths_to_intermediate(
551 &self,
552 paths: &[Path<ScaledPixels>],
553 instance_buffer: &mut InstanceBuffer,
554 instance_offset: &mut usize,
555 viewport_size: Size<DevicePixels>,
556 command_buffer: &metal::CommandBufferRef,
557 ) -> bool {
558 if paths.is_empty() {
559 return true;
560 }
561 let Some(intermediate_texture) = &self.path_intermediate_texture else {
562 return false;
563 };
564
565 let render_pass_descriptor = metal::RenderPassDescriptor::new();
566 let color_attachment = render_pass_descriptor
567 .color_attachments()
568 .object_at(0)
569 .unwrap();
570 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
571 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 0.));
572
573 if let Some(msaa_texture) = &self.path_intermediate_msaa_texture {
574 color_attachment.set_texture(Some(msaa_texture));
575 color_attachment.set_resolve_texture(Some(intermediate_texture));
576 color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
577 } else {
578 color_attachment.set_texture(Some(intermediate_texture));
579 color_attachment.set_store_action(metal::MTLStoreAction::Store);
580 }
581
582 let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
583 command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
584
585 align_offset(instance_offset);
586 let mut vertices = Vec::new();
587 for path in paths {
588 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
589 xy_position: v.xy_position,
590 st_position: v.st_position,
591 color: path.color,
592 bounds: path.bounds.intersect(&path.content_mask.bounds),
593 }));
594 }
595 let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
596 let next_offset = *instance_offset + vertices_bytes_len;
597 if next_offset > instance_buffer.size {
598 command_encoder.end_encoding();
599 return false;
600 }
601 command_encoder.set_vertex_buffer(
602 PathRasterizationInputIndex::Vertices as u64,
603 Some(&instance_buffer.metal_buffer),
604 *instance_offset as u64,
605 );
606 command_encoder.set_vertex_bytes(
607 PathRasterizationInputIndex::ViewportSize as u64,
608 mem::size_of_val(&viewport_size) as u64,
609 &viewport_size as *const Size<DevicePixels> as *const _,
610 );
611 command_encoder.set_fragment_buffer(
612 PathRasterizationInputIndex::Vertices as u64,
613 Some(&instance_buffer.metal_buffer),
614 *instance_offset as u64,
615 );
616 let buffer_contents =
617 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
618 unsafe {
619 ptr::copy_nonoverlapping(
620 vertices.as_ptr() as *const u8,
621 buffer_contents,
622 vertices_bytes_len,
623 );
624 }
625 command_encoder.draw_primitives(
626 metal::MTLPrimitiveType::Triangle,
627 0,
628 vertices.len() as u64,
629 );
630 *instance_offset = next_offset;
631
632 command_encoder.end_encoding();
633 true
634 }
635
636 fn draw_shadows(
637 &self,
638 shadows: &[Shadow],
639 instance_buffer: &mut InstanceBuffer,
640 instance_offset: &mut usize,
641 viewport_size: Size<DevicePixels>,
642 command_encoder: &metal::RenderCommandEncoderRef,
643 ) -> bool {
644 if shadows.is_empty() {
645 return true;
646 }
647 align_offset(instance_offset);
648
649 command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
650 command_encoder.set_vertex_buffer(
651 ShadowInputIndex::Vertices as u64,
652 Some(&self.unit_vertices),
653 0,
654 );
655 command_encoder.set_vertex_buffer(
656 ShadowInputIndex::Shadows as u64,
657 Some(&instance_buffer.metal_buffer),
658 *instance_offset as u64,
659 );
660 command_encoder.set_fragment_buffer(
661 ShadowInputIndex::Shadows as u64,
662 Some(&instance_buffer.metal_buffer),
663 *instance_offset as u64,
664 );
665
666 command_encoder.set_vertex_bytes(
667 ShadowInputIndex::ViewportSize as u64,
668 mem::size_of_val(&viewport_size) as u64,
669 &viewport_size as *const Size<DevicePixels> as *const _,
670 );
671
672 let shadow_bytes_len = mem::size_of_val(shadows);
673 let buffer_contents =
674 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
675
676 let next_offset = *instance_offset + shadow_bytes_len;
677 if next_offset > instance_buffer.size {
678 return false;
679 }
680
681 unsafe {
682 ptr::copy_nonoverlapping(
683 shadows.as_ptr() as *const u8,
684 buffer_contents,
685 shadow_bytes_len,
686 );
687 }
688
689 command_encoder.draw_primitives_instanced(
690 metal::MTLPrimitiveType::Triangle,
691 0,
692 6,
693 shadows.len() as u64,
694 );
695 *instance_offset = next_offset;
696 true
697 }
698
699 fn draw_quads(
700 &self,
701 quads: &[Quad],
702 instance_buffer: &mut InstanceBuffer,
703 instance_offset: &mut usize,
704 viewport_size: Size<DevicePixels>,
705 command_encoder: &metal::RenderCommandEncoderRef,
706 ) -> bool {
707 if quads.is_empty() {
708 return true;
709 }
710 align_offset(instance_offset);
711
712 command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
713 command_encoder.set_vertex_buffer(
714 QuadInputIndex::Vertices as u64,
715 Some(&self.unit_vertices),
716 0,
717 );
718 command_encoder.set_vertex_buffer(
719 QuadInputIndex::Quads as u64,
720 Some(&instance_buffer.metal_buffer),
721 *instance_offset as u64,
722 );
723 command_encoder.set_fragment_buffer(
724 QuadInputIndex::Quads as u64,
725 Some(&instance_buffer.metal_buffer),
726 *instance_offset as u64,
727 );
728
729 command_encoder.set_vertex_bytes(
730 QuadInputIndex::ViewportSize as u64,
731 mem::size_of_val(&viewport_size) as u64,
732 &viewport_size as *const Size<DevicePixels> as *const _,
733 );
734
735 let quad_bytes_len = mem::size_of_val(quads);
736 let buffer_contents =
737 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
738
739 let next_offset = *instance_offset + quad_bytes_len;
740 if next_offset > instance_buffer.size {
741 return false;
742 }
743
744 unsafe {
745 ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
746 }
747
748 command_encoder.draw_primitives_instanced(
749 metal::MTLPrimitiveType::Triangle,
750 0,
751 6,
752 quads.len() as u64,
753 );
754 *instance_offset = next_offset;
755 true
756 }
757
758 fn draw_paths_from_intermediate(
759 &self,
760 paths: &[Path<ScaledPixels>],
761 instance_buffer: &mut InstanceBuffer,
762 instance_offset: &mut usize,
763 viewport_size: Size<DevicePixels>,
764 command_encoder: &metal::RenderCommandEncoderRef,
765 ) -> bool {
766 let Some(first_path) = paths.first() else {
767 return true;
768 };
769
770 let Some(ref intermediate_texture) = self.path_intermediate_texture else {
771 return false;
772 };
773
774 command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
775 command_encoder.set_vertex_buffer(
776 SpriteInputIndex::Vertices as u64,
777 Some(&self.unit_vertices),
778 0,
779 );
780 command_encoder.set_vertex_bytes(
781 SpriteInputIndex::ViewportSize as u64,
782 mem::size_of_val(&viewport_size) as u64,
783 &viewport_size as *const Size<DevicePixels> as *const _,
784 );
785
786 command_encoder.set_fragment_texture(
787 SpriteInputIndex::AtlasTexture as u64,
788 Some(intermediate_texture),
789 );
790
791 // When copying paths from the intermediate texture to the drawable,
792 // each pixel must only be copied once, in case of transparent paths.
793 //
794 // If all paths have the same draw order, then their bounds are all
795 // disjoint, so we can copy each path's bounds individually. If this
796 // batch combines different draw orders, we perform a single copy
797 // for a minimal spanning rect.
798 let sprites;
799 if paths.last().unwrap().order == first_path.order {
800 sprites = paths
801 .iter()
802 .map(|path| PathSprite {
803 bounds: path.clipped_bounds(),
804 })
805 .collect();
806 } else {
807 let mut bounds = first_path.clipped_bounds();
808 for path in paths.iter().skip(1) {
809 bounds = bounds.union(&path.clipped_bounds());
810 }
811 sprites = vec![PathSprite { bounds }];
812 }
813
814 align_offset(instance_offset);
815 let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
816 let next_offset = *instance_offset + sprite_bytes_len;
817 if next_offset > instance_buffer.size {
818 return false;
819 }
820
821 command_encoder.set_vertex_buffer(
822 SpriteInputIndex::Sprites as u64,
823 Some(&instance_buffer.metal_buffer),
824 *instance_offset as u64,
825 );
826
827 let buffer_contents =
828 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
829 unsafe {
830 ptr::copy_nonoverlapping(
831 sprites.as_ptr() as *const u8,
832 buffer_contents,
833 sprite_bytes_len,
834 );
835 }
836
837 command_encoder.draw_primitives_instanced(
838 metal::MTLPrimitiveType::Triangle,
839 0,
840 6,
841 sprites.len() as u64,
842 );
843 *instance_offset = next_offset;
844
845 true
846 }
847
848 fn draw_underlines(
849 &self,
850 underlines: &[Underline],
851 instance_buffer: &mut InstanceBuffer,
852 instance_offset: &mut usize,
853 viewport_size: Size<DevicePixels>,
854 command_encoder: &metal::RenderCommandEncoderRef,
855 ) -> bool {
856 if underlines.is_empty() {
857 return true;
858 }
859 align_offset(instance_offset);
860
861 command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
862 command_encoder.set_vertex_buffer(
863 UnderlineInputIndex::Vertices as u64,
864 Some(&self.unit_vertices),
865 0,
866 );
867 command_encoder.set_vertex_buffer(
868 UnderlineInputIndex::Underlines as u64,
869 Some(&instance_buffer.metal_buffer),
870 *instance_offset as u64,
871 );
872 command_encoder.set_fragment_buffer(
873 UnderlineInputIndex::Underlines as u64,
874 Some(&instance_buffer.metal_buffer),
875 *instance_offset as u64,
876 );
877
878 command_encoder.set_vertex_bytes(
879 UnderlineInputIndex::ViewportSize as u64,
880 mem::size_of_val(&viewport_size) as u64,
881 &viewport_size as *const Size<DevicePixels> as *const _,
882 );
883
884 let underline_bytes_len = mem::size_of_val(underlines);
885 let buffer_contents =
886 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
887
888 let next_offset = *instance_offset + underline_bytes_len;
889 if next_offset > instance_buffer.size {
890 return false;
891 }
892
893 unsafe {
894 ptr::copy_nonoverlapping(
895 underlines.as_ptr() as *const u8,
896 buffer_contents,
897 underline_bytes_len,
898 );
899 }
900
901 command_encoder.draw_primitives_instanced(
902 metal::MTLPrimitiveType::Triangle,
903 0,
904 6,
905 underlines.len() as u64,
906 );
907 *instance_offset = next_offset;
908 true
909 }
910
911 fn draw_monochrome_sprites(
912 &self,
913 texture_id: AtlasTextureId,
914 sprites: &[MonochromeSprite],
915 instance_buffer: &mut InstanceBuffer,
916 instance_offset: &mut usize,
917 viewport_size: Size<DevicePixels>,
918 command_encoder: &metal::RenderCommandEncoderRef,
919 ) -> bool {
920 if sprites.is_empty() {
921 return true;
922 }
923 align_offset(instance_offset);
924
925 let sprite_bytes_len = mem::size_of_val(sprites);
926 let buffer_contents =
927 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
928
929 let next_offset = *instance_offset + sprite_bytes_len;
930 if next_offset > instance_buffer.size {
931 return false;
932 }
933
934 let texture = self.sprite_atlas.metal_texture(texture_id);
935 let texture_size = size(
936 DevicePixels(texture.width() as i32),
937 DevicePixels(texture.height() as i32),
938 );
939 command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
940 command_encoder.set_vertex_buffer(
941 SpriteInputIndex::Vertices as u64,
942 Some(&self.unit_vertices),
943 0,
944 );
945 command_encoder.set_vertex_buffer(
946 SpriteInputIndex::Sprites as u64,
947 Some(&instance_buffer.metal_buffer),
948 *instance_offset as u64,
949 );
950 command_encoder.set_vertex_bytes(
951 SpriteInputIndex::ViewportSize as u64,
952 mem::size_of_val(&viewport_size) as u64,
953 &viewport_size as *const Size<DevicePixels> as *const _,
954 );
955 command_encoder.set_vertex_bytes(
956 SpriteInputIndex::AtlasTextureSize as u64,
957 mem::size_of_val(&texture_size) as u64,
958 &texture_size as *const Size<DevicePixels> as *const _,
959 );
960 command_encoder.set_fragment_buffer(
961 SpriteInputIndex::Sprites as u64,
962 Some(&instance_buffer.metal_buffer),
963 *instance_offset as u64,
964 );
965 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
966
967 unsafe {
968 ptr::copy_nonoverlapping(
969 sprites.as_ptr() as *const u8,
970 buffer_contents,
971 sprite_bytes_len,
972 );
973 }
974
975 command_encoder.draw_primitives_instanced(
976 metal::MTLPrimitiveType::Triangle,
977 0,
978 6,
979 sprites.len() as u64,
980 );
981 *instance_offset = next_offset;
982 true
983 }
984
985 fn draw_polychrome_sprites(
986 &self,
987 texture_id: AtlasTextureId,
988 sprites: &[PolychromeSprite],
989 instance_buffer: &mut InstanceBuffer,
990 instance_offset: &mut usize,
991 viewport_size: Size<DevicePixels>,
992 command_encoder: &metal::RenderCommandEncoderRef,
993 ) -> bool {
994 if sprites.is_empty() {
995 return true;
996 }
997 align_offset(instance_offset);
998
999 let texture = self.sprite_atlas.metal_texture(texture_id);
1000 let texture_size = size(
1001 DevicePixels(texture.width() as i32),
1002 DevicePixels(texture.height() as i32),
1003 );
1004 command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
1005 command_encoder.set_vertex_buffer(
1006 SpriteInputIndex::Vertices as u64,
1007 Some(&self.unit_vertices),
1008 0,
1009 );
1010 command_encoder.set_vertex_buffer(
1011 SpriteInputIndex::Sprites as u64,
1012 Some(&instance_buffer.metal_buffer),
1013 *instance_offset as u64,
1014 );
1015 command_encoder.set_vertex_bytes(
1016 SpriteInputIndex::ViewportSize as u64,
1017 mem::size_of_val(&viewport_size) as u64,
1018 &viewport_size as *const Size<DevicePixels> as *const _,
1019 );
1020 command_encoder.set_vertex_bytes(
1021 SpriteInputIndex::AtlasTextureSize as u64,
1022 mem::size_of_val(&texture_size) as u64,
1023 &texture_size as *const Size<DevicePixels> as *const _,
1024 );
1025 command_encoder.set_fragment_buffer(
1026 SpriteInputIndex::Sprites as u64,
1027 Some(&instance_buffer.metal_buffer),
1028 *instance_offset as u64,
1029 );
1030 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
1031
1032 let sprite_bytes_len = mem::size_of_val(sprites);
1033 let buffer_contents =
1034 unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1035
1036 let next_offset = *instance_offset + sprite_bytes_len;
1037 if next_offset > instance_buffer.size {
1038 return false;
1039 }
1040
1041 unsafe {
1042 ptr::copy_nonoverlapping(
1043 sprites.as_ptr() as *const u8,
1044 buffer_contents,
1045 sprite_bytes_len,
1046 );
1047 }
1048
1049 command_encoder.draw_primitives_instanced(
1050 metal::MTLPrimitiveType::Triangle,
1051 0,
1052 6,
1053 sprites.len() as u64,
1054 );
1055 *instance_offset = next_offset;
1056 true
1057 }
1058
1059 fn draw_surfaces(
1060 &mut self,
1061 surfaces: &[PaintSurface],
1062 instance_buffer: &mut InstanceBuffer,
1063 instance_offset: &mut usize,
1064 viewport_size: Size<DevicePixels>,
1065 command_encoder: &metal::RenderCommandEncoderRef,
1066 ) -> bool {
1067 command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
1068 command_encoder.set_vertex_buffer(
1069 SurfaceInputIndex::Vertices as u64,
1070 Some(&self.unit_vertices),
1071 0,
1072 );
1073 command_encoder.set_vertex_bytes(
1074 SurfaceInputIndex::ViewportSize as u64,
1075 mem::size_of_val(&viewport_size) as u64,
1076 &viewport_size as *const Size<DevicePixels> as *const _,
1077 );
1078
1079 for surface in surfaces {
1080 let texture_size = size(
1081 DevicePixels::from(surface.image_buffer.get_width() as i32),
1082 DevicePixels::from(surface.image_buffer.get_height() as i32),
1083 );
1084
1085 assert_eq!(
1086 surface.image_buffer.get_pixel_format(),
1087 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
1088 );
1089
1090 let y_texture = self
1091 .core_video_texture_cache
1092 .create_texture_from_image(
1093 surface.image_buffer.as_concrete_TypeRef(),
1094 None,
1095 MTLPixelFormat::R8Unorm,
1096 surface.image_buffer.get_width_of_plane(0),
1097 surface.image_buffer.get_height_of_plane(0),
1098 0,
1099 )
1100 .unwrap();
1101 let cb_cr_texture = self
1102 .core_video_texture_cache
1103 .create_texture_from_image(
1104 surface.image_buffer.as_concrete_TypeRef(),
1105 None,
1106 MTLPixelFormat::RG8Unorm,
1107 surface.image_buffer.get_width_of_plane(1),
1108 surface.image_buffer.get_height_of_plane(1),
1109 1,
1110 )
1111 .unwrap();
1112
1113 align_offset(instance_offset);
1114 let next_offset = *instance_offset + mem::size_of::<Surface>();
1115 if next_offset > instance_buffer.size {
1116 return false;
1117 }
1118
1119 command_encoder.set_vertex_buffer(
1120 SurfaceInputIndex::Surfaces as u64,
1121 Some(&instance_buffer.metal_buffer),
1122 *instance_offset as u64,
1123 );
1124 command_encoder.set_vertex_bytes(
1125 SurfaceInputIndex::TextureSize as u64,
1126 mem::size_of_val(&texture_size) as u64,
1127 &texture_size as *const Size<DevicePixels> as *const _,
1128 );
1129 // let y_texture = y_texture.get_texture().unwrap().
1130 command_encoder.set_fragment_texture(SurfaceInputIndex::YTexture as u64, unsafe {
1131 let texture = CVMetalTextureGetTexture(y_texture.as_concrete_TypeRef());
1132 Some(metal::TextureRef::from_ptr(texture as *mut _))
1133 });
1134 command_encoder.set_fragment_texture(SurfaceInputIndex::CbCrTexture as u64, unsafe {
1135 let texture = CVMetalTextureGetTexture(cb_cr_texture.as_concrete_TypeRef());
1136 Some(metal::TextureRef::from_ptr(texture as *mut _))
1137 });
1138
1139 unsafe {
1140 let buffer_contents = (instance_buffer.metal_buffer.contents() as *mut u8)
1141 .add(*instance_offset)
1142 as *mut SurfaceBounds;
1143 ptr::write(
1144 buffer_contents,
1145 SurfaceBounds {
1146 bounds: surface.bounds,
1147 content_mask: surface.content_mask.clone(),
1148 },
1149 );
1150 }
1151
1152 command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
1153 *instance_offset = next_offset;
1154 }
1155 true
1156 }
1157}
1158
1159fn new_command_encoder<'a>(
1160 command_buffer: &'a metal::CommandBufferRef,
1161 drawable: &'a metal::MetalDrawableRef,
1162 viewport_size: Size<DevicePixels>,
1163 configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef),
1164) -> &'a metal::RenderCommandEncoderRef {
1165 let render_pass_descriptor = metal::RenderPassDescriptor::new();
1166 let color_attachment = render_pass_descriptor
1167 .color_attachments()
1168 .object_at(0)
1169 .unwrap();
1170 color_attachment.set_texture(Some(drawable.texture()));
1171 color_attachment.set_store_action(metal::MTLStoreAction::Store);
1172 configure_color_attachment(color_attachment);
1173
1174 let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
1175 command_encoder.set_viewport(metal::MTLViewport {
1176 originX: 0.0,
1177 originY: 0.0,
1178 width: i32::from(viewport_size.width) as f64,
1179 height: i32::from(viewport_size.height) as f64,
1180 znear: 0.0,
1181 zfar: 1.0,
1182 });
1183 command_encoder
1184}
1185
1186fn build_pipeline_state(
1187 device: &metal::DeviceRef,
1188 library: &metal::LibraryRef,
1189 label: &str,
1190 vertex_fn_name: &str,
1191 fragment_fn_name: &str,
1192 pixel_format: metal::MTLPixelFormat,
1193) -> metal::RenderPipelineState {
1194 let vertex_fn = library
1195 .get_function(vertex_fn_name, None)
1196 .expect("error locating vertex function");
1197 let fragment_fn = library
1198 .get_function(fragment_fn_name, None)
1199 .expect("error locating fragment function");
1200
1201 let descriptor = metal::RenderPipelineDescriptor::new();
1202 descriptor.set_label(label);
1203 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1204 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1205 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1206 color_attachment.set_pixel_format(pixel_format);
1207 color_attachment.set_blending_enabled(true);
1208 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1209 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1210 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
1211 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1212 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1213 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1214
1215 device
1216 .new_render_pipeline_state(&descriptor)
1217 .expect("could not create render pipeline state")
1218}
1219
1220fn build_path_sprite_pipeline_state(
1221 device: &metal::DeviceRef,
1222 library: &metal::LibraryRef,
1223 label: &str,
1224 vertex_fn_name: &str,
1225 fragment_fn_name: &str,
1226 pixel_format: metal::MTLPixelFormat,
1227) -> metal::RenderPipelineState {
1228 let vertex_fn = library
1229 .get_function(vertex_fn_name, None)
1230 .expect("error locating vertex function");
1231 let fragment_fn = library
1232 .get_function(fragment_fn_name, None)
1233 .expect("error locating fragment function");
1234
1235 let descriptor = metal::RenderPipelineDescriptor::new();
1236 descriptor.set_label(label);
1237 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1238 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1239 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1240 color_attachment.set_pixel_format(pixel_format);
1241 color_attachment.set_blending_enabled(true);
1242 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1243 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1244 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1245 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1246 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1247 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1248
1249 device
1250 .new_render_pipeline_state(&descriptor)
1251 .expect("could not create render pipeline state")
1252}
1253
1254fn build_path_rasterization_pipeline_state(
1255 device: &metal::DeviceRef,
1256 library: &metal::LibraryRef,
1257 label: &str,
1258 vertex_fn_name: &str,
1259 fragment_fn_name: &str,
1260 pixel_format: metal::MTLPixelFormat,
1261 path_sample_count: u32,
1262) -> metal::RenderPipelineState {
1263 let vertex_fn = library
1264 .get_function(vertex_fn_name, None)
1265 .expect("error locating vertex function");
1266 let fragment_fn = library
1267 .get_function(fragment_fn_name, None)
1268 .expect("error locating fragment function");
1269
1270 let descriptor = metal::RenderPipelineDescriptor::new();
1271 descriptor.set_label(label);
1272 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1273 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1274 if path_sample_count > 1 {
1275 descriptor.set_raster_sample_count(path_sample_count as _);
1276 descriptor.set_alpha_to_coverage_enabled(false);
1277 }
1278 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1279 color_attachment.set_pixel_format(pixel_format);
1280 color_attachment.set_blending_enabled(true);
1281 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1282 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1283 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1284 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1285 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1286 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1287
1288 device
1289 .new_render_pipeline_state(&descriptor)
1290 .expect("could not create render pipeline state")
1291}
1292
1293// Align to multiples of 256 make Metal happy.
1294fn align_offset(offset: &mut usize) {
1295 *offset = (*offset).div_ceil(256) * 256;
1296}
1297
1298#[repr(C)]
1299enum ShadowInputIndex {
1300 Vertices = 0,
1301 Shadows = 1,
1302 ViewportSize = 2,
1303}
1304
1305#[repr(C)]
1306enum QuadInputIndex {
1307 Vertices = 0,
1308 Quads = 1,
1309 ViewportSize = 2,
1310}
1311
1312#[repr(C)]
1313enum UnderlineInputIndex {
1314 Vertices = 0,
1315 Underlines = 1,
1316 ViewportSize = 2,
1317}
1318
1319#[repr(C)]
1320enum SpriteInputIndex {
1321 Vertices = 0,
1322 Sprites = 1,
1323 ViewportSize = 2,
1324 AtlasTextureSize = 3,
1325 AtlasTexture = 4,
1326}
1327
1328#[repr(C)]
1329enum SurfaceInputIndex {
1330 Vertices = 0,
1331 Surfaces = 1,
1332 ViewportSize = 2,
1333 TextureSize = 3,
1334 YTexture = 4,
1335 CbCrTexture = 5,
1336}
1337
1338#[repr(C)]
1339enum PathRasterizationInputIndex {
1340 Vertices = 0,
1341 ViewportSize = 1,
1342}
1343
1344#[derive(Clone, Debug, Eq, PartialEq)]
1345#[repr(C)]
1346pub struct PathSprite {
1347 pub bounds: Bounds<ScaledPixels>,
1348}
1349
1350#[derive(Clone, Debug, Eq, PartialEq)]
1351#[repr(C)]
1352pub struct SurfaceBounds {
1353 pub bounds: Bounds<ScaledPixels>,
1354 pub content_mask: ContentMask<ScaledPixels>,
1355}