796 lines · 31.4 KB · Rust
RawDownload
// Doing `if let` gives you nice scoping with passes/encoders#![allow(irrefutable_let_patterns)] use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};use crate::{    AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,    MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,    ScaledPixels, Scene, Shadow, Size, Underline,};use blade_graphics as gpu;use blade_util::{BufferBelt, BufferBeltDescriptor};use bytemuck::{Pod, Zeroable};use collections::HashMap;#[cfg(target_os = "macos")]use media::core_video::CVMetalTextureCache;use std::{mem, sync::Arc}; const MAX_FRAME_TIME_MS: u32 = 10000;// Use 4x MSAA, all devices support it.// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecountconst PATH_SAMPLE_COUNT: u32 = 4; #[repr(C)]#[derive(Clone, Copy, Pod, Zeroable)]struct GlobalParams {    viewport_size: [f32; 2],    premultiplied_alpha: u32,    pad: u32,} //Note: we can't use `Bounds` directly here because// it doesn't implement Pod + Zeroable#[repr(C)]#[derive(Clone, Copy, Pod, Zeroable)]struct PodBounds {    origin: [f32; 2],    size: [f32; 2],} impl From<Bounds<ScaledPixels>> for PodBounds {    fn from(bounds: Bounds<ScaledPixels>) -> Self {        Self {            origin: [bounds.origin.x.0, bounds.origin.y.0],            size: [bounds.size.width.0, bounds.size.height.0],        }    }} #[repr(C)]#[derive(Clone, Copy, Pod, Zeroable)]struct SurfaceParams {    bounds: PodBounds,    content_mask: PodBounds,} #[derive(blade_macros::ShaderData)]struct ShaderQuadsData {    globals: GlobalParams,    b_quads: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderShadowsData {    globals: GlobalParams,    b_shadows: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderPathRasterizationData {    globals: GlobalParams,    b_path_vertices: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderPathsData {    globals: GlobalParams,    t_sprite: gpu::TextureView,    s_sprite: gpu::Sampler,    b_path_sprites: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderUnderlinesData {    globals: GlobalParams,    b_underlines: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderMonoSpritesData {    globals: GlobalParams,    t_sprite: gpu::TextureView,    s_sprite: gpu::Sampler,    b_mono_sprites: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderPolySpritesData {    globals: GlobalParams,    t_sprite: gpu::TextureView,    s_sprite: gpu::Sampler,    b_poly_sprites: gpu::BufferPiece,} #[derive(blade_macros::ShaderData)]struct ShaderSurfacesData {    globals: GlobalParams,    surface_locals: SurfaceParams,    t_y: gpu::TextureView,    t_cb_cr: gpu::TextureView,    s_surface: gpu::Sampler,} #[derive(Clone, Debug, Eq, PartialEq)]#[repr(C)]struct PathSprite {    bounds: Bounds<ScaledPixels>,    color: Background,    tile: AtlasTile,} struct BladePipelines {    quads: gpu::RenderPipeline,    shadows: gpu::RenderPipeline,    path_rasterization: gpu::RenderPipeline,    paths: gpu::RenderPipeline,    underlines: gpu::RenderPipeline,    mono_sprites: gpu::RenderPipeline,    poly_sprites: gpu::RenderPipeline,    surfaces: gpu::RenderPipeline,} impl BladePipelines {    fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo) -> Self {        use gpu::ShaderData as _;         log::info!(            "Initializing Blade pipelines for surface {:?}",            surface_info        );        let shader = gpu.create_shader(gpu::ShaderDesc {            source: include_str!("shaders.wgsl"),        });        shader.check_struct_size::<GlobalParams>();        shader.check_struct_size::<SurfaceParams>();        shader.check_struct_size::<Quad>();        shader.check_struct_size::<Shadow>();        assert_eq!(            mem::size_of::<PathVertex<ScaledPixels>>(),            shader.get_struct_size("PathVertex") as usize,        );        shader.check_struct_size::<PathSprite>();        shader.check_struct_size::<Underline>();        shader.check_struct_size::<MonochromeSprite>();        shader.check_struct_size::<PolychromeSprite>();         // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/        let blend_mode = match surface_info.alpha {            gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,            gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,            gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,        };        let color_targets = &[gpu::ColorTargetState {            format: surface_info.format,            blend: Some(blend_mode),            write_mask: gpu::ColorWrites::default(),        }];         Self {            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "quads",                data_layouts: &[&ShaderQuadsData::layout()],                vertex: shader.at("vs_quad"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_quad")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "shadows",                data_layouts: &[&ShaderShadowsData::layout()],                vertex: shader.at("vs_shadow"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_shadow")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "path_rasterization",                data_layouts: &[&ShaderPathRasterizationData::layout()],                vertex: shader.at("vs_path_rasterization"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleList,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_path_rasterization")),                color_targets: &[gpu::ColorTargetState {                    format: PATH_TEXTURE_FORMAT,                    blend: Some(gpu::BlendState::ADDITIVE),                    write_mask: gpu::ColorWrites::default(),                }],                multisample_state: gpu::MultisampleState {                    sample_count: PATH_SAMPLE_COUNT,                    ..Default::default()                },            }),            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "paths",                data_layouts: &[&ShaderPathsData::layout()],                vertex: shader.at("vs_path"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_path")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "underlines",                data_layouts: &[&ShaderUnderlinesData::layout()],                vertex: shader.at("vs_underline"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_underline")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "mono-sprites",                data_layouts: &[&ShaderMonoSpritesData::layout()],                vertex: shader.at("vs_mono_sprite"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_mono_sprite")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "poly-sprites",                data_layouts: &[&ShaderPolySpritesData::layout()],                vertex: shader.at("vs_poly_sprite"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_poly_sprite")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),            surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {                name: "surfaces",                data_layouts: &[&ShaderSurfacesData::layout()],                vertex: shader.at("vs_surface"),                vertex_fetches: &[],                primitive: gpu::PrimitiveState {                    topology: gpu::PrimitiveTopology::TriangleStrip,                    ..Default::default()                },                depth_stencil: None,                fragment: Some(shader.at("fs_surface")),                color_targets,                multisample_state: gpu::MultisampleState::default(),            }),        }    }     fn destroy(&mut self, gpu: &gpu::Context) {        gpu.destroy_render_pipeline(&mut self.quads);        gpu.destroy_render_pipeline(&mut self.shadows);        gpu.destroy_render_pipeline(&mut self.path_rasterization);        gpu.destroy_render_pipeline(&mut self.paths);        gpu.destroy_render_pipeline(&mut self.underlines);        gpu.destroy_render_pipeline(&mut self.mono_sprites);        gpu.destroy_render_pipeline(&mut self.poly_sprites);        gpu.destroy_render_pipeline(&mut self.surfaces);    }} pub struct BladeSurfaceConfig {    pub size: gpu::Extent,    pub transparent: bool,} //Note: we could see some of these fields moved into `BladeContext`// so that they are shared between windows. E.g. `pipelines`.// But that is complicated by the fact that pipelines depend on// the format and alpha mode.pub struct BladeRenderer {    gpu: Arc<gpu::Context>,    surface: gpu::Surface,    surface_config: gpu::SurfaceConfig,    command_encoder: gpu::CommandEncoder,    last_sync_point: Option<gpu::SyncPoint>,    pipelines: BladePipelines,    instance_belt: BufferBelt,    path_tiles: HashMap<PathId, AtlasTile>,    atlas: Arc<BladeAtlas>,    atlas_sampler: gpu::Sampler,    #[cfg(target_os = "macos")]    core_video_texture_cache: CVMetalTextureCache,} impl BladeRenderer {    pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(        context: &BladeContext,        window: &I,        config: BladeSurfaceConfig,    ) -> anyhow::Result<Self> {        let surface_config = gpu::SurfaceConfig {            size: config.size,            usage: gpu::TextureUsage::TARGET,            display_sync: gpu::DisplaySync::Recent,            color_space: gpu::ColorSpace::Linear,            allow_exclusive_full_screen: false,            transparent: config.transparent,        };        let surface = context            .gpu            .create_surface_configured(window, surface_config)            .unwrap();         let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {            name: "main",            buffer_count: 2,        });        let pipelines = BladePipelines::new(&context.gpu, surface.info());        let instance_belt = BufferBelt::new(BufferBeltDescriptor {            memory: gpu::Memory::Shared,            min_chunk_size: 0x1000,            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe        });        let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));        let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {            name: "atlas",            mag_filter: gpu::FilterMode::Linear,            min_filter: gpu::FilterMode::Linear,            ..Default::default()        });         #[cfg(target_os = "macos")]        let core_video_texture_cache = unsafe {            CVMetalTextureCache::new(                objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _            )            .unwrap()        };         Ok(Self {            gpu: Arc::clone(&context.gpu),            surface,            surface_config,            command_encoder,            last_sync_point: None,            pipelines,            instance_belt,            path_tiles: HashMap::default(),            atlas,            atlas_sampler,            #[cfg(target_os = "macos")]            core_video_texture_cache,        })    }     fn wait_for_gpu(&mut self) {        if let Some(last_sp) = self.last_sync_point.take() {            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {                log::error!("GPU hung");                while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}            }        }    }     pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {        self.update_drawable_size_impl(size, false);    }     /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in    /// cases like restoring a window from minimization where the size is the same but the    /// renderer's swap chain needs to be recreated.    #[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]    pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {        self.update_drawable_size_impl(size, true);    }     fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {        let gpu_size = gpu::Extent {            width: size.width.0 as u32,            height: size.height.0 as u32,            depth: 1,        };         if always_resize || gpu_size != self.surface_config.size {            self.wait_for_gpu();            self.surface_config.size = gpu_size;            self.gpu                .reconfigure_surface(&mut self.surface, self.surface_config);        }    }     pub fn update_transparency(&mut self, transparent: bool) {        if transparent != self.surface_config.transparent {            self.wait_for_gpu();            self.surface_config.transparent = transparent;            self.gpu                .reconfigure_surface(&mut self.surface, self.surface_config);            self.pipelines.destroy(&self.gpu);            self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());        }    }     #[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))]    pub fn viewport_size(&self) -> gpu::Extent {        self.surface_config.size    }     pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {        &self.atlas    }     #[cfg_attr(target_os = "macos", allow(dead_code))]    pub fn gpu_specs(&self) -> GpuSpecs {        let info = self.gpu.device_information();         GpuSpecs {            is_software_emulated: info.is_software_emulated,            device_name: info.device_name.clone(),            driver_name: info.driver_name.clone(),            driver_info: info.driver_info.clone(),        }    }     #[cfg(target_os = "macos")]    pub fn layer(&self) -> metal::MetalLayer {        unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }    }     #[cfg(target_os = "macos")]    pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {        objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _    }     #[profiling::function]    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {        self.path_tiles.clear();        let mut vertices_by_texture_id = HashMap::default();         for path in paths {            let clipped_bounds = path                .bounds                .intersect(&path.content_mask.bounds)                .map_origin(|origin| origin.floor())                .map_size(|size| size.ceil());            let tile = self.atlas.allocate_for_rendering(                clipped_bounds.size.map(Into::into),                AtlasTextureKind::Path,                &mut self.command_encoder,            );            vertices_by_texture_id                .entry(tile.texture_id)                .or_insert(Vec::new())                .extend(path.vertices.iter().map(|vertex| PathVertex {                    xy_position: vertex.xy_position - clipped_bounds.origin                        + tile.bounds.origin.map(Into::into),                    st_position: vertex.st_position,                    content_mask: ContentMask {                        bounds: tile.bounds.map(Into::into),                    },                }));            self.path_tiles.insert(path.id, tile);        }         for (texture_id, vertices) in vertices_by_texture_id {            let tex_info = self.atlas.get_texture_info(texture_id);            let globals = GlobalParams {                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],                premultiplied_alpha: 0,

Lines 1–500 of 796.