1273 lines · 44.0 KB · Rust
RawDownload
use super::metal_atlas::MetalAtlas;use crate::{    point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask,    DevicePixels, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,};use anyhow::{anyhow, Result};use block::ConcreteBlock;use cocoa::{    base::{NO, YES},    foundation::{NSSize, NSUInteger},    quartzcore::AutoresizingMask,};use collections::HashMap;use core_foundation::base::TCFType;use foreign_types::ForeignType;use media::core_video::CVMetalTextureCache;use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};use objc::{self, msg_send, sel, sel_impl};use parking_lot::Mutex;use smallvec::SmallVec;use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; // Exported to metalpub(crate) type PointF = crate::Point<f32>; #[cfg(not(feature = "runtime_shaders"))]const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));#[cfg(feature = "runtime_shaders")]const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));// Use 4x MSAA, all devices support it.// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecountconst PATH_SAMPLE_COUNT: u32 = 4; pub type Context = Arc<Mutex<InstanceBufferPool>>;pub type Renderer = MetalRenderer; pub unsafe fn new_renderer(    context: self::Context,    _native_window: *mut c_void,    _native_view: *mut c_void,    _bounds: crate::Size<f32>,    _transparent: bool,) -> Renderer {    MetalRenderer::new(context)} pub(crate) struct InstanceBufferPool {    buffer_size: usize,    buffers: Vec<metal::Buffer>,} impl Default for InstanceBufferPool {    fn default() -> Self {        Self {            buffer_size: 2 * 1024 * 1024,            buffers: Vec::new(),        }    }} pub(crate) struct InstanceBuffer {    metal_buffer: metal::Buffer,    size: usize,} impl InstanceBufferPool {    pub(crate) fn reset(&mut self, buffer_size: usize) {        self.buffer_size = buffer_size;        self.buffers.clear();    }     pub(crate) fn acquire(&mut self, device: &metal::Device) -> InstanceBuffer {        let buffer = self.buffers.pop().unwrap_or_else(|| {            device.new_buffer(                self.buffer_size as u64,                MTLResourceOptions::StorageModeManaged,            )        });        InstanceBuffer {            metal_buffer: buffer,            size: self.buffer_size,        }    }     pub(crate) fn release(&mut self, buffer: InstanceBuffer) {        if buffer.size == self.buffer_size {            self.buffers.push(buffer.metal_buffer)        }    }} pub(crate) struct MetalRenderer {    device: metal::Device,    layer: metal::MetalLayer,    presents_with_transaction: bool,    command_queue: CommandQueue,    paths_rasterization_pipeline_state: metal::RenderPipelineState,    path_sprites_pipeline_state: metal::RenderPipelineState,    shadows_pipeline_state: metal::RenderPipelineState,    quads_pipeline_state: metal::RenderPipelineState,    underlines_pipeline_state: metal::RenderPipelineState,    monochrome_sprites_pipeline_state: metal::RenderPipelineState,    polychrome_sprites_pipeline_state: metal::RenderPipelineState,    surfaces_pipeline_state: metal::RenderPipelineState,    unit_vertices: metal::Buffer,    #[allow(clippy::arc_with_non_send_sync)]    instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,    sprite_atlas: Arc<MetalAtlas>,    core_video_texture_cache: CVMetalTextureCache,} impl MetalRenderer {    pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self {        // Prefer low‐power integrated GPUs on Intel Mac. On Apple        // Silicon, there is only ever one GPU, so this is equivalent to        // `metal::Device::system_default()`.        let mut devices = metal::Device::all();        devices.sort_by_key(|device| (device.is_removable(), device.is_low_power()));        let Some(device) = devices.pop() else {            log::error!("unable to access a compatible graphics device");            std::process::exit(1);        };         let layer = metal::MetalLayer::new();        layer.set_device(&device);        layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);        layer.set_opaque(false);        layer.set_maximum_drawable_count(3);        unsafe {            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];            let _: () = msg_send![                &*layer,                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE                    | AutoresizingMask::HEIGHT_SIZABLE            ];        }        #[cfg(feature = "runtime_shaders")]        let library = device            .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new())            .expect("error building metal library");        #[cfg(not(feature = "runtime_shaders"))]        let library = device            .new_library_with_data(SHADERS_METALLIB)            .expect("error building metal library");         fn to_float2_bits(point: PointF) -> u64 {            let mut output = point.y.to_bits() as u64;            output <<= 32;            output |= point.x.to_bits() as u64;            output        }         let unit_vertices = [            to_float2_bits(point(0., 0.)),            to_float2_bits(point(1., 0.)),            to_float2_bits(point(0., 1.)),            to_float2_bits(point(0., 1.)),            to_float2_bits(point(1., 0.)),            to_float2_bits(point(1., 1.)),        ];        let unit_vertices = device.new_buffer_with_data(            unit_vertices.as_ptr() as *const c_void,            mem::size_of_val(&unit_vertices) as u64,            MTLResourceOptions::StorageModeManaged,        );         let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(            &device,            &library,            "paths_rasterization",            "path_rasterization_vertex",            "path_rasterization_fragment",            MTLPixelFormat::R16Float,            PATH_SAMPLE_COUNT,        );        let path_sprites_pipeline_state = build_pipeline_state(            &device,            &library,            "path_sprites",            "path_sprite_vertex",            "path_sprite_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let shadows_pipeline_state = build_pipeline_state(            &device,            &library,            "shadows",            "shadow_vertex",            "shadow_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let quads_pipeline_state = build_pipeline_state(            &device,            &library,            "quads",            "quad_vertex",            "quad_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let underlines_pipeline_state = build_pipeline_state(            &device,            &library,            "underlines",            "underline_vertex",            "underline_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let monochrome_sprites_pipeline_state = build_pipeline_state(            &device,            &library,            "monochrome_sprites",            "monochrome_sprite_vertex",            "monochrome_sprite_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let polychrome_sprites_pipeline_state = build_pipeline_state(            &device,            &library,            "polychrome_sprites",            "polychrome_sprite_vertex",            "polychrome_sprite_fragment",            MTLPixelFormat::BGRA8Unorm,        );        let surfaces_pipeline_state = build_pipeline_state(            &device,            &library,            "surfaces",            "surface_vertex",            "surface_fragment",            MTLPixelFormat::BGRA8Unorm,        );         let command_queue = device.new_command_queue();        let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));        let core_video_texture_cache =            unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };         Self {            device,            layer,            presents_with_transaction: false,            command_queue,            paths_rasterization_pipeline_state,            path_sprites_pipeline_state,            shadows_pipeline_state,            quads_pipeline_state,            underlines_pipeline_state,            monochrome_sprites_pipeline_state,            polychrome_sprites_pipeline_state,            surfaces_pipeline_state,            unit_vertices,            instance_buffer_pool,            sprite_atlas,            core_video_texture_cache,        }    }     pub fn layer(&self) -> &metal::MetalLayerRef {        &self.layer    }     pub fn layer_ptr(&self) -> *mut CAMetalLayer {        self.layer.as_ptr()    }     pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {        &self.sprite_atlas    }     pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {        self.presents_with_transaction = presents_with_transaction;        self.layer            .set_presents_with_transaction(presents_with_transaction);    }     pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {        let size = NSSize {            width: size.width.0 as f64,            height: size.height.0 as f64,        };        unsafe {            let _: () = msg_send![                self.layer(),                setDrawableSize: size            ];        }    }     pub fn update_transparency(&self, _transparent: bool) {        // todo(mac)?    }     pub fn destroy(&self) {        // nothing to do    }     pub fn draw(&mut self, scene: &Scene) {        let layer = self.layer.clone();        let viewport_size = layer.drawable_size();        let viewport_size: Size<DevicePixels> = size(            (viewport_size.width.ceil() as i32).into(),            (viewport_size.height.ceil() as i32).into(),        );        let drawable = if let Some(drawable) = layer.next_drawable() {            drawable        } else {            log::error!(                "failed to retrieve next drawable, drawable size: {:?}",                viewport_size            );            return;        };         loop {            let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device);             let command_buffer =                self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);             match command_buffer {                Ok(command_buffer) => {                    let instance_buffer_pool = self.instance_buffer_pool.clone();                    let instance_buffer = Cell::new(Some(instance_buffer));                    let block = ConcreteBlock::new(move |_| {                        if let Some(instance_buffer) = instance_buffer.take() {                            instance_buffer_pool.lock().release(instance_buffer);                        }                    });                    let block = block.copy();                    command_buffer.add_completed_handler(&block);                     if self.presents_with_transaction {                        command_buffer.commit();                        command_buffer.wait_until_scheduled();                        drawable.present();                    } else {                        command_buffer.present_drawable(drawable);                        command_buffer.commit();                    }                    return;                }                Err(err) => {                    log::error!(                        "failed to render: {}. retrying with larger instance buffer size",                        err                    );                    let mut instance_buffer_pool = self.instance_buffer_pool.lock();                    let buffer_size = instance_buffer_pool.buffer_size;                    if buffer_size >= 256 * 1024 * 1024 {                        log::error!("instance buffer size grew too large: {}", buffer_size);                        break;                    }                    instance_buffer_pool.reset(buffer_size * 2);                    log::info!(                        "increased instance buffer size to {}",                        instance_buffer_pool.buffer_size                    );                }            }        }    }     fn draw_primitives(        &mut self,        scene: &Scene,        instance_buffer: &mut InstanceBuffer,        drawable: &metal::MetalDrawableRef,        viewport_size: Size<DevicePixels>,    ) -> Result<metal::CommandBuffer> {        let command_queue = self.command_queue.clone();        let command_buffer = command_queue.new_command_buffer();        let mut instance_offset = 0;         let Some(path_tiles) = self.rasterize_paths(            scene.paths(),            instance_buffer,            &mut instance_offset,            command_buffer,        ) else {            return Err(anyhow!("failed to rasterize {} paths", scene.paths().len()));        };         let render_pass_descriptor = metal::RenderPassDescriptor::new();        let color_attachment = render_pass_descriptor            .color_attachments()            .object_at(0)            .unwrap();         color_attachment.set_texture(Some(drawable.texture()));        color_attachment.set_load_action(metal::MTLLoadAction::Clear);        color_attachment.set_store_action(metal::MTLStoreAction::Store);        let alpha = if self.layer.is_opaque() { 1. } else { 0. };        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);         command_encoder.set_viewport(metal::MTLViewport {            originX: 0.0,            originY: 0.0,            width: i32::from(viewport_size.width) as f64,            height: i32::from(viewport_size.height) as f64,            znear: 0.0,            zfar: 1.0,        });         for batch in scene.batches() {            let ok = match batch {                PrimitiveBatch::Shadows(shadows) => self.draw_shadows(                    shadows,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::Quads(quads) => self.draw_quads(                    quads,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::Paths(paths) => self.draw_paths(                    paths,                    &path_tiles,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::Underlines(underlines) => self.draw_underlines(                    underlines,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::MonochromeSprites {                    texture_id,                    sprites,                } => self.draw_monochrome_sprites(                    texture_id,                    sprites,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::PolychromeSprites {                    texture_id,                    sprites,                } => self.draw_polychrome_sprites(                    texture_id,                    sprites,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),                PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(                    surfaces,                    instance_buffer,                    &mut instance_offset,                    viewport_size,                    command_encoder,                ),            };             if !ok {                command_encoder.end_encoding();                return Err(anyhow!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",                    scene.paths.len(),                    scene.shadows.len(),                    scene.quads.len(),                    scene.underlines.len(),                    scene.monochrome_sprites.len(),                    scene.polychrome_sprites.len(),                    scene.surfaces.len(),                ));            }        }         command_encoder.end_encoding();         instance_buffer.metal_buffer.did_modify_range(NSRange {            location: 0,            length: instance_offset as NSUInteger,        });        Ok(command_buffer.to_owned())    }     fn rasterize_paths(        &self,        paths: &[Path<ScaledPixels>],        instance_buffer: &mut InstanceBuffer,        instance_offset: &mut usize,        command_buffer: &metal::CommandBufferRef,    ) -> Option<HashMap<PathId, AtlasTile>> {        self.sprite_atlas.clear_textures(AtlasTextureKind::Path); 

Lines 1–500 of 1273.