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