metal_renderer.rs

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