1use crate::{
2 point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, DevicePixels, MetalAtlas,
3 MonochromeSprite, PathId, PolychromeSprite, PrimitiveBatch, Quad, Scene, Shadow, Size,
4 Underline,
5};
6use cocoa::{
7 base::{NO, YES},
8 foundation::NSUInteger,
9 quartzcore::AutoresizingMask,
10};
11use collections::HashMap;
12use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
13use objc::{self, msg_send, sel, sel_impl};
14use std::{ffi::c_void, mem, ptr, sync::Arc};
15
16const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
17const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
18
19pub(crate) struct MetalRenderer {
20 layer: metal::MetalLayer,
21 command_queue: CommandQueue,
22 shadows_pipeline_state: metal::RenderPipelineState,
23 quads_pipeline_state: metal::RenderPipelineState,
24 underlines_pipeline_state: metal::RenderPipelineState,
25 monochrome_sprites_pipeline_state: metal::RenderPipelineState,
26 polychrome_sprites_pipeline_state: metal::RenderPipelineState,
27 unit_vertices: metal::Buffer,
28 instances: metal::Buffer,
29 sprite_atlas: Arc<MetalAtlas>,
30}
31
32impl MetalRenderer {
33 pub fn new(is_opaque: bool) -> Self {
34 const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
35
36 let device: metal::Device = if let Some(device) = metal::Device::system_default() {
37 device
38 } else {
39 log::error!("unable to access a compatible graphics device");
40 std::process::exit(1);
41 };
42
43 let layer = metal::MetalLayer::new();
44 layer.set_device(&device);
45 layer.set_pixel_format(PIXEL_FORMAT);
46 layer.set_presents_with_transaction(true);
47 layer.set_opaque(is_opaque);
48 unsafe {
49 let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
50 let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
51 let _: () = msg_send![
52 &*layer,
53 setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
54 | AutoresizingMask::HEIGHT_SIZABLE
55 ];
56 }
57
58 let library = device
59 .new_library_with_data(SHADERS_METALLIB)
60 .expect("error building metal library");
61
62 fn to_float2_bits(point: crate::PointF) -> u64 {
63 unsafe {
64 let mut output = mem::transmute::<_, u32>(point.y.to_bits()) as u64;
65 output <<= 32;
66 output |= mem::transmute::<_, u32>(point.x.to_bits()) as u64;
67 output
68 }
69 }
70
71 let unit_vertices = [
72 to_float2_bits(point(0., 0.)),
73 to_float2_bits(point(1., 0.)),
74 to_float2_bits(point(0., 1.)),
75 to_float2_bits(point(0., 1.)),
76 to_float2_bits(point(1., 0.)),
77 to_float2_bits(point(1., 1.)),
78 ];
79 let unit_vertices = device.new_buffer_with_data(
80 unit_vertices.as_ptr() as *const c_void,
81 (unit_vertices.len() * mem::size_of::<u64>()) as u64,
82 MTLResourceOptions::StorageModeManaged,
83 );
84 let instances = device.new_buffer(
85 INSTANCE_BUFFER_SIZE as u64,
86 MTLResourceOptions::StorageModeManaged,
87 );
88
89 let shadows_pipeline_state = build_pipeline_state(
90 &device,
91 &library,
92 "shadows",
93 "shadow_vertex",
94 "shadow_fragment",
95 PIXEL_FORMAT,
96 );
97 let quads_pipeline_state = build_pipeline_state(
98 &device,
99 &library,
100 "quads",
101 "quad_vertex",
102 "quad_fragment",
103 PIXEL_FORMAT,
104 );
105 let underlines_pipeline_state = build_pipeline_state(
106 &device,
107 &library,
108 "underlines",
109 "underline_vertex",
110 "underline_fragment",
111 PIXEL_FORMAT,
112 );
113 let monochrome_sprites_pipeline_state = build_pipeline_state(
114 &device,
115 &library,
116 "monochrome_sprites",
117 "monochrome_sprite_vertex",
118 "monochrome_sprite_fragment",
119 PIXEL_FORMAT,
120 );
121 let polychrome_sprites_pipeline_state = build_pipeline_state(
122 &device,
123 &library,
124 "polychrome_sprites",
125 "polychrome_sprite_vertex",
126 "polychrome_sprite_fragment",
127 PIXEL_FORMAT,
128 );
129
130 let command_queue = device.new_command_queue();
131 let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
132
133 Self {
134 layer,
135 command_queue,
136 shadows_pipeline_state,
137 quads_pipeline_state,
138 underlines_pipeline_state,
139 monochrome_sprites_pipeline_state,
140 polychrome_sprites_pipeline_state,
141 unit_vertices,
142 instances,
143 sprite_atlas,
144 }
145 }
146
147 pub fn layer(&self) -> &metal::MetalLayerRef {
148 &*self.layer
149 }
150
151 pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
152 &self.sprite_atlas
153 }
154
155 pub fn draw(&mut self, scene: &Scene) {
156 let layer = self.layer.clone();
157 let viewport_size = layer.drawable_size();
158 let viewport_size: Size<DevicePixels> = size(
159 (viewport_size.width.ceil() as i32).into(),
160 (viewport_size.height.ceil() as i32).into(),
161 );
162 let drawable = if let Some(drawable) = layer.next_drawable() {
163 drawable
164 } else {
165 log::error!(
166 "failed to retrieve next drawable, drawable size: {:?}",
167 viewport_size
168 );
169 return;
170 };
171 let command_queue = self.command_queue.clone();
172 let command_buffer = command_queue.new_command_buffer();
173
174 let render_pass_descriptor = metal::RenderPassDescriptor::new();
175 let color_attachment = render_pass_descriptor
176 .color_attachments()
177 .object_at(0)
178 .unwrap();
179
180 color_attachment.set_texture(Some(drawable.texture()));
181 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
182 color_attachment.set_store_action(metal::MTLStoreAction::Store);
183 let alpha = if self.layer.is_opaque() { 1. } else { 0. };
184 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
185 let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
186
187 command_encoder.set_viewport(metal::MTLViewport {
188 originX: 0.0,
189 originY: 0.0,
190 width: i32::from(viewport_size.width) as f64,
191 height: i32::from(viewport_size.height) as f64,
192 znear: 0.0,
193 zfar: 1.0,
194 });
195
196 let mut instance_offset = 0;
197
198 let mut path_tiles: HashMap<PathId, AtlasTile> = HashMap::default();
199 for path in scene.paths() {
200 let tile = self
201 .sprite_atlas
202 .allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
203 path_tiles.insert(path.id, tile);
204 }
205
206 for batch in scene.batches() {
207 match batch {
208 PrimitiveBatch::Shadows(shadows) => {
209 self.draw_shadows(
210 shadows,
211 &mut instance_offset,
212 viewport_size,
213 command_encoder,
214 );
215 }
216 PrimitiveBatch::Quads(quads) => {
217 self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
218 }
219 PrimitiveBatch::Paths(paths) => {
220 // self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
221 }
222 PrimitiveBatch::Underlines(underlines) => {
223 self.draw_underlines(
224 underlines,
225 &mut instance_offset,
226 viewport_size,
227 command_encoder,
228 );
229 }
230 PrimitiveBatch::MonochromeSprites {
231 texture_id,
232 sprites,
233 } => {
234 self.draw_monochrome_sprites(
235 texture_id,
236 sprites,
237 &mut instance_offset,
238 viewport_size,
239 command_encoder,
240 );
241 }
242 PrimitiveBatch::PolychromeSprites {
243 texture_id,
244 sprites,
245 } => {
246 self.draw_polychrome_sprites(
247 texture_id,
248 sprites,
249 &mut instance_offset,
250 viewport_size,
251 command_encoder,
252 );
253 }
254 }
255 }
256
257 command_encoder.end_encoding();
258
259 self.instances.did_modify_range(NSRange {
260 location: 0,
261 length: instance_offset as NSUInteger,
262 });
263
264 command_buffer.commit();
265 command_buffer.wait_until_completed();
266 drawable.present();
267 }
268
269 fn draw_shadows(
270 &mut self,
271 shadows: &[Shadow],
272 offset: &mut usize,
273 viewport_size: Size<DevicePixels>,
274 command_encoder: &metal::RenderCommandEncoderRef,
275 ) {
276 if shadows.is_empty() {
277 return;
278 }
279 align_offset(offset);
280
281 command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
282 command_encoder.set_vertex_buffer(
283 ShadowInputIndex::Vertices as u64,
284 Some(&self.unit_vertices),
285 0,
286 );
287 command_encoder.set_vertex_buffer(
288 ShadowInputIndex::Shadows as u64,
289 Some(&self.instances),
290 *offset as u64,
291 );
292 command_encoder.set_fragment_buffer(
293 ShadowInputIndex::Shadows as u64,
294 Some(&self.instances),
295 *offset as u64,
296 );
297
298 command_encoder.set_vertex_bytes(
299 ShadowInputIndex::ViewportSize as u64,
300 mem::size_of_val(&viewport_size) as u64,
301 &viewport_size as *const Size<DevicePixels> as *const _,
302 );
303
304 let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
305 let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
306 unsafe {
307 ptr::copy_nonoverlapping(
308 shadows.as_ptr() as *const u8,
309 buffer_contents,
310 shadow_bytes_len,
311 );
312 }
313
314 let next_offset = *offset + shadow_bytes_len;
315 assert!(
316 next_offset <= INSTANCE_BUFFER_SIZE,
317 "instance buffer exhausted"
318 );
319
320 command_encoder.draw_primitives_instanced(
321 metal::MTLPrimitiveType::Triangle,
322 0,
323 6,
324 shadows.len() as u64,
325 );
326 *offset = next_offset;
327 }
328
329 fn draw_quads(
330 &mut self,
331 quads: &[Quad],
332 offset: &mut usize,
333 viewport_size: Size<DevicePixels>,
334 command_encoder: &metal::RenderCommandEncoderRef,
335 ) {
336 if quads.is_empty() {
337 return;
338 }
339 align_offset(offset);
340
341 command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
342 command_encoder.set_vertex_buffer(
343 QuadInputIndex::Vertices as u64,
344 Some(&self.unit_vertices),
345 0,
346 );
347 command_encoder.set_vertex_buffer(
348 QuadInputIndex::Quads as u64,
349 Some(&self.instances),
350 *offset as u64,
351 );
352 command_encoder.set_fragment_buffer(
353 QuadInputIndex::Quads as u64,
354 Some(&self.instances),
355 *offset as u64,
356 );
357
358 command_encoder.set_vertex_bytes(
359 QuadInputIndex::ViewportSize as u64,
360 mem::size_of_val(&viewport_size) as u64,
361 &viewport_size as *const Size<DevicePixels> as *const _,
362 );
363
364 let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
365 let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
366 unsafe {
367 ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
368 }
369
370 let next_offset = *offset + quad_bytes_len;
371 assert!(
372 next_offset <= INSTANCE_BUFFER_SIZE,
373 "instance buffer exhausted"
374 );
375
376 command_encoder.draw_primitives_instanced(
377 metal::MTLPrimitiveType::Triangle,
378 0,
379 6,
380 quads.len() as u64,
381 );
382 *offset = next_offset;
383 }
384
385 fn draw_underlines(
386 &mut self,
387 underlines: &[Underline],
388 offset: &mut usize,
389 viewport_size: Size<DevicePixels>,
390 command_encoder: &metal::RenderCommandEncoderRef,
391 ) {
392 if underlines.is_empty() {
393 return;
394 }
395 align_offset(offset);
396
397 command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
398 command_encoder.set_vertex_buffer(
399 UnderlineInputIndex::Vertices as u64,
400 Some(&self.unit_vertices),
401 0,
402 );
403 command_encoder.set_vertex_buffer(
404 UnderlineInputIndex::Underlines as u64,
405 Some(&self.instances),
406 *offset as u64,
407 );
408 command_encoder.set_fragment_buffer(
409 UnderlineInputIndex::Underlines as u64,
410 Some(&self.instances),
411 *offset as u64,
412 );
413
414 command_encoder.set_vertex_bytes(
415 UnderlineInputIndex::ViewportSize as u64,
416 mem::size_of_val(&viewport_size) as u64,
417 &viewport_size as *const Size<DevicePixels> as *const _,
418 );
419
420 let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
421 let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
422 unsafe {
423 ptr::copy_nonoverlapping(
424 underlines.as_ptr() as *const u8,
425 buffer_contents,
426 quad_bytes_len,
427 );
428 }
429
430 let next_offset = *offset + quad_bytes_len;
431 assert!(
432 next_offset <= INSTANCE_BUFFER_SIZE,
433 "instance buffer exhausted"
434 );
435
436 command_encoder.draw_primitives_instanced(
437 metal::MTLPrimitiveType::Triangle,
438 0,
439 6,
440 underlines.len() as u64,
441 );
442 *offset = next_offset;
443 }
444
445 fn draw_monochrome_sprites(
446 &mut self,
447 texture_id: AtlasTextureId,
448 sprites: &[MonochromeSprite],
449 offset: &mut usize,
450 viewport_size: Size<DevicePixels>,
451 command_encoder: &metal::RenderCommandEncoderRef,
452 ) {
453 if sprites.is_empty() {
454 return;
455 }
456 align_offset(offset);
457
458 let texture = self.sprite_atlas.metal_texture(texture_id);
459 let texture_size = size(
460 DevicePixels(texture.width() as i32),
461 DevicePixels(texture.height() as i32),
462 );
463 command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
464 command_encoder.set_vertex_buffer(
465 SpriteInputIndex::Vertices as u64,
466 Some(&self.unit_vertices),
467 0,
468 );
469 command_encoder.set_vertex_buffer(
470 SpriteInputIndex::Sprites as u64,
471 Some(&self.instances),
472 *offset as u64,
473 );
474 command_encoder.set_vertex_bytes(
475 SpriteInputIndex::ViewportSize as u64,
476 mem::size_of_val(&viewport_size) as u64,
477 &viewport_size as *const Size<DevicePixels> as *const _,
478 );
479 command_encoder.set_vertex_bytes(
480 SpriteInputIndex::AtlasTextureSize as u64,
481 mem::size_of_val(&texture_size) as u64,
482 &texture_size as *const Size<DevicePixels> as *const _,
483 );
484 command_encoder.set_fragment_buffer(
485 SpriteInputIndex::Sprites as u64,
486 Some(&self.instances),
487 *offset as u64,
488 );
489 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
490
491 let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
492 let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
493 unsafe {
494 ptr::copy_nonoverlapping(
495 sprites.as_ptr() as *const u8,
496 buffer_contents,
497 sprite_bytes_len,
498 );
499 }
500
501 let next_offset = *offset + sprite_bytes_len;
502 assert!(
503 next_offset <= INSTANCE_BUFFER_SIZE,
504 "instance buffer exhausted"
505 );
506
507 command_encoder.draw_primitives_instanced(
508 metal::MTLPrimitiveType::Triangle,
509 0,
510 6,
511 sprites.len() as u64,
512 );
513 *offset = next_offset;
514 }
515
516 fn draw_polychrome_sprites(
517 &mut self,
518 texture_id: AtlasTextureId,
519 sprites: &[PolychromeSprite],
520 offset: &mut usize,
521 viewport_size: Size<DevicePixels>,
522 command_encoder: &metal::RenderCommandEncoderRef,
523 ) {
524 if sprites.is_empty() {
525 return;
526 }
527 align_offset(offset);
528
529 let texture = self.sprite_atlas.metal_texture(texture_id);
530 let texture_size = size(
531 DevicePixels(texture.width() as i32),
532 DevicePixels(texture.height() as i32),
533 );
534 command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
535 command_encoder.set_vertex_buffer(
536 SpriteInputIndex::Vertices as u64,
537 Some(&self.unit_vertices),
538 0,
539 );
540 command_encoder.set_vertex_buffer(
541 SpriteInputIndex::Sprites as u64,
542 Some(&self.instances),
543 *offset as u64,
544 );
545 command_encoder.set_vertex_bytes(
546 SpriteInputIndex::ViewportSize as u64,
547 mem::size_of_val(&viewport_size) as u64,
548 &viewport_size as *const Size<DevicePixels> as *const _,
549 );
550 command_encoder.set_vertex_bytes(
551 SpriteInputIndex::AtlasTextureSize as u64,
552 mem::size_of_val(&texture_size) as u64,
553 &texture_size as *const Size<DevicePixels> as *const _,
554 );
555 command_encoder.set_fragment_buffer(
556 SpriteInputIndex::Sprites as u64,
557 Some(&self.instances),
558 *offset as u64,
559 );
560 command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
561
562 let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
563 let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
564 unsafe {
565 ptr::copy_nonoverlapping(
566 sprites.as_ptr() as *const u8,
567 buffer_contents,
568 sprite_bytes_len,
569 );
570 }
571
572 let next_offset = *offset + sprite_bytes_len;
573 assert!(
574 next_offset <= INSTANCE_BUFFER_SIZE,
575 "instance buffer exhausted"
576 );
577
578 command_encoder.draw_primitives_instanced(
579 metal::MTLPrimitiveType::Triangle,
580 0,
581 6,
582 sprites.len() as u64,
583 );
584 *offset = next_offset;
585 }
586}
587
588fn build_pipeline_state(
589 device: &metal::DeviceRef,
590 library: &metal::LibraryRef,
591 label: &str,
592 vertex_fn_name: &str,
593 fragment_fn_name: &str,
594 pixel_format: metal::MTLPixelFormat,
595) -> metal::RenderPipelineState {
596 let vertex_fn = library
597 .get_function(vertex_fn_name, None)
598 .expect("error locating vertex function");
599 let fragment_fn = library
600 .get_function(fragment_fn_name, None)
601 .expect("error locating fragment function");
602
603 let descriptor = metal::RenderPipelineDescriptor::new();
604 descriptor.set_label(label);
605 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
606 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
607 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
608 color_attachment.set_pixel_format(pixel_format);
609 color_attachment.set_blending_enabled(true);
610 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
611 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
612 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
613 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
614 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
615 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
616 descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
617
618 device
619 .new_render_pipeline_state(&descriptor)
620 .expect("could not create render pipeline state")
621}
622
623// Align to multiples of 256 make Metal happy.
624fn align_offset(offset: &mut usize) {
625 *offset = ((*offset + 255) / 256) * 256;
626}
627
628#[repr(C)]
629enum ShadowInputIndex {
630 Vertices = 0,
631 Shadows = 1,
632 ViewportSize = 2,
633}
634
635#[repr(C)]
636enum QuadInputIndex {
637 Vertices = 0,
638 Quads = 1,
639 ViewportSize = 2,
640}
641
642#[repr(C)]
643enum UnderlineInputIndex {
644 Vertices = 0,
645 Underlines = 1,
646 ViewportSize = 2,
647}
648
649#[repr(C)]
650enum SpriteInputIndex {
651 Vertices = 0,
652 Sprites = 1,
653 ViewportSize = 2,
654 AtlasTextureSize = 3,
655 AtlasTexture = 4,
656}