metal_renderer.rs

   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}