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