metal_renderer.rs

  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}