// 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. Load more
// 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-supportstexturesamplecount const 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,