use crate::{WgpuAtlas, WgpuContext}; use bytemuck::{Pod, Zeroable}; use gpui::{ AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite, Underline, get_gamma_correction_ratios, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::num::NonZeroU64; use std::sync::Arc; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct GlobalParams { viewport_size: [f32; 2], premultiplied_alpha: u32, pad: u32, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct PodBounds { origin: [f32; 2], size: [f32; 2], } impl From> for PodBounds { fn from(bounds: Bounds) -> 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, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct GammaParams { gamma_ratios: [f32; 4], grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, _pad: [f32; 2], } #[derive(Clone, Debug)] #[repr(C)] struct PathSprite { bounds: Bounds, } #[derive(Clone, Debug)] #[repr(C)] struct PathRasterizationVertex { xy_position: Point, st_position: Point, color: Background, bounds: Bounds, } pub struct WgpuSurfaceConfig { pub size: Size, pub transparent: bool, } struct WgpuPipelines { quads: wgpu::RenderPipeline, shadows: wgpu::RenderPipeline, path_rasterization: wgpu::RenderPipeline, paths: wgpu::RenderPipeline, underlines: wgpu::RenderPipeline, mono_sprites: wgpu::RenderPipeline, subpixel_sprites: Option, poly_sprites: wgpu::RenderPipeline, #[allow(dead_code)] surfaces: wgpu::RenderPipeline, } struct WgpuBindGroupLayouts { globals: wgpu::BindGroupLayout, instances: wgpu::BindGroupLayout, instances_with_texture: wgpu::BindGroupLayout, surfaces: wgpu::BindGroupLayout, } pub struct WgpuRenderer { device: Arc, queue: Arc, surface: wgpu::Surface<'static>, surface_config: wgpu::SurfaceConfiguration, pipelines: WgpuPipelines, bind_group_layouts: WgpuBindGroupLayouts, atlas: Arc, atlas_sampler: wgpu::Sampler, globals_buffer: wgpu::Buffer, path_globals_offset: u64, gamma_offset: u64, globals_bind_group: wgpu::BindGroup, path_globals_bind_group: wgpu::BindGroup, instance_buffer: wgpu::Buffer, instance_buffer_capacity: u64, storage_buffer_alignment: u64, path_intermediate_texture: wgpu::Texture, path_intermediate_view: wgpu::TextureView, path_msaa_texture: Option, path_msaa_view: Option, rendering_params: RenderingParameters, dual_source_blending: bool, adapter_info: wgpu::AdapterInfo, transparent_alpha_mode: wgpu::CompositeAlphaMode, opaque_alpha_mode: wgpu::CompositeAlphaMode, } impl WgpuRenderer { /// Creates a new WgpuRenderer from raw window handles. /// /// # Safety /// The caller must ensure that the window handle remains valid for the lifetime /// of the returned renderer. pub fn new( context: &WgpuContext, window: &W, config: WgpuSurfaceConfig, ) -> anyhow::Result { let window_handle = window .window_handle() .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?; let display_handle = window .display_handle() .map_err(|e| anyhow::anyhow!("Failed to get display handle: {e}"))?; let target = wgpu::SurfaceTargetUnsafe::RawHandle { raw_display_handle: display_handle.as_raw(), raw_window_handle: window_handle.as_raw(), }; // Safety: The caller guarantees that the window handle is valid for the // lifetime of this renderer. In practice, the RawWindow struct is created // from the native window handles and the surface is dropped before the window. let surface = unsafe { context .instance .create_surface_unsafe(target) .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))? }; let surface_caps = surface.get_capabilities(&context.adapter); // Prefer standard 8-bit non-sRGB formats that don't require special features. // Other formats like Rgba16Unorm require TEXTURE_FORMAT_16BIT_NORM which may // not be available on all devices. let preferred_formats = [ wgpu::TextureFormat::Bgra8Unorm, wgpu::TextureFormat::Rgba8Unorm, ]; let surface_format = preferred_formats .iter() .find(|f| surface_caps.formats.contains(f)) .copied() .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied()) .unwrap_or(surface_caps.formats[0]); let pick_alpha_mode = |preferences: &[wgpu::CompositeAlphaMode]| -> wgpu::CompositeAlphaMode { preferences .iter() .find(|p| surface_caps.alpha_modes.contains(p)) .copied() .unwrap_or(surface_caps.alpha_modes[0]) }; let transparent_alpha_mode = pick_alpha_mode(&[ wgpu::CompositeAlphaMode::PreMultiplied, wgpu::CompositeAlphaMode::Inherit, ]); let opaque_alpha_mode = pick_alpha_mode(&[ wgpu::CompositeAlphaMode::Opaque, wgpu::CompositeAlphaMode::Inherit, ]); let alpha_mode = if config.transparent { transparent_alpha_mode } else { opaque_alpha_mode }; let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: config.size.width.0 as u32, height: config.size.height.0 as u32, present_mode: wgpu::PresentMode::Fifo, desired_maximum_frame_latency: 2, alpha_mode, view_formats: vec![], }; surface.configure(&context.device, &surface_config); let device = Arc::clone(&context.device); let queue = Arc::clone(&context.queue); let dual_source_blending = context.supports_dual_source_blending(); let rendering_params = RenderingParameters::new(&context.adapter, surface_format); let bind_group_layouts = Self::create_bind_group_layouts(&device); let pipelines = Self::create_pipelines( &device, &bind_group_layouts, surface_format, alpha_mode, rendering_params.path_sample_count, dual_source_blending, ); let atlas = Arc::new(WgpuAtlas::new(Arc::clone(&device), Arc::clone(&queue))); let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("atlas_sampler"), mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, ..Default::default() }); let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64; let globals_size = std::mem::size_of::() as u64; let gamma_size = std::mem::size_of::() as u64; let path_globals_offset = globals_size.next_multiple_of(uniform_alignment); let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment); let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("globals_buffer"), size: gamma_offset + gamma_size, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64; let initial_instance_buffer_capacity = 2 * 1024 * 1024; let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("instance_buffer"), size: initial_instance_buffer_capacity, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let (path_intermediate_texture, path_intermediate_view) = Self::create_path_intermediate( &device, surface_format, config.size.width.0 as u32, config.size.height.0 as u32, ); let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed( &device, surface_format, config.size.width.0 as u32, config.size.height.0 as u32, rendering_params.path_sample_count, ) .map(|(t, v)| (Some(t), Some(v))) .unwrap_or((None, None)); let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("globals_bind_group"), layout: &bind_group_layouts.globals, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &globals_buffer, offset: 0, size: Some(NonZeroU64::new(globals_size).unwrap()), }), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &globals_buffer, offset: gamma_offset, size: Some(NonZeroU64::new(gamma_size).unwrap()), }), }, ], }); let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("path_globals_bind_group"), layout: &bind_group_layouts.globals, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &globals_buffer, offset: path_globals_offset, size: Some(NonZeroU64::new(globals_size).unwrap()), }), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &globals_buffer, offset: gamma_offset, size: Some(NonZeroU64::new(gamma_size).unwrap()), }), }, ], }); let adapter_info = context.adapter.get_info(); Ok(Self { device, queue, surface, surface_config, pipelines, bind_group_layouts, atlas, atlas_sampler, globals_buffer, path_globals_offset, gamma_offset, globals_bind_group, path_globals_bind_group, instance_buffer, instance_buffer_capacity: initial_instance_buffer_capacity, storage_buffer_alignment, path_intermediate_texture, path_intermediate_view, path_msaa_texture, path_msaa_view, rendering_params, dual_source_blending, adapter_info, transparent_alpha_mode, opaque_alpha_mode, }) } fn create_bind_group_layouts(device: &wgpu::Device) -> WgpuBindGroupLayouts { let globals = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("globals_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: NonZeroU64::new( std::mem::size_of::() as u64 ), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: NonZeroU64::new( std::mem::size_of::() as u64 ), }, count: None, }, ], }); let storage_buffer_entry = |binding: u32| wgpu::BindGroupLayoutEntry { binding, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }; let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("instances_layout"), entries: &[storage_buffer_entry(0)], }); let instances_with_texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("instances_with_texture_layout"), entries: &[ storage_buffer_entry(0), wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("surfaces_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: NonZeroU64::new( std::mem::size_of::() as u64 ), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); WgpuBindGroupLayouts { globals, instances, instances_with_texture, surfaces, } } fn create_pipelines( device: &wgpu::Device, layouts: &WgpuBindGroupLayouts, surface_format: wgpu::TextureFormat, alpha_mode: wgpu::CompositeAlphaMode, path_sample_count: u32, dual_source_blending: bool, ) -> WgpuPipelines { let shader_source = include_str!("shaders.wgsl"); let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("gpui_shaders"), source: wgpu::ShaderSource::Wgsl(shader_source.into()), }); let blend_mode = match alpha_mode { wgpu::CompositeAlphaMode::PreMultiplied => { wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING } _ => wgpu::BlendState::ALPHA_BLENDING, }; let color_target = wgpu::ColorTargetState { format: surface_format, blend: Some(blend_mode), write_mask: wgpu::ColorWrites::ALL, }; let create_pipeline = |name: &str, vs_entry: &str, fs_entry: &str, globals_layout: &wgpu::BindGroupLayout, data_layout: &wgpu::BindGroupLayout, topology: wgpu::PrimitiveTopology, color_targets: &[Option], sample_count: u32| { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some(&format!("{name}_layout")), bind_group_layouts: &[globals_layout, data_layout], immediate_size: 0, }); device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some(name), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader_module, entry_point: Some(vs_entry), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader_module, entry_point: Some(fs_entry), targets: color_targets, compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState { count: sample_count, mask: !0, alpha_to_coverage_enabled: false, }, multiview_mask: None, cache: None, }) }; let quads = create_pipeline( "quads", "vs_quad", "fs_quad", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, ); let shadows = create_pipeline( "shadows", "vs_shadow", "fs_shadow", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, ); let path_rasterization = create_pipeline( "path_rasterization", "vs_path_rasterization", "fs_path_rasterization", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleList, &[Some(wgpu::ColorTargetState { format: surface_format, blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], path_sample_count, ); let paths_blend = wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add, }, }; let paths = create_pipeline( "paths", "vs_path", "fs_path", &layouts.globals, &layouts.instances_with_texture, wgpu::PrimitiveTopology::TriangleStrip, &[Some(wgpu::ColorTargetState { format: surface_format, blend: Some(paths_blend), write_mask: wgpu::ColorWrites::ALL, })], 1, ); let underlines = create_pipeline( "underlines", "vs_underline", "fs_underline", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, ); let mono_sprites = create_pipeline( "mono_sprites", "vs_mono_sprite", "fs_mono_sprite", &layouts.globals, &layouts.instances_with_texture, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, ); let subpixel_sprites = if dual_source_blending { let subpixel_blend = wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::Src1, dst_factor: wgpu::BlendFactor::OneMinusSrc1, operation: wgpu::BlendOperation::Add, }, alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, }; Some(create_pipeline( "subpixel_sprites", "vs_subpixel_sprite", "fs_subpixel_sprite", &layouts.globals, &layouts.instances_with_texture, wgpu::PrimitiveTopology::TriangleStrip, &[Some(wgpu::ColorTargetState { format: surface_format, blend: Some(subpixel_blend), write_mask: wgpu::ColorWrites::COLOR, })], 1, )) } else { None }; let poly_sprites = create_pipeline( "poly_sprites", "vs_poly_sprite", "fs_poly_sprite", &layouts.globals, &layouts.instances_with_texture, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, ); let surfaces = create_pipeline( "surfaces", "vs_surface", "fs_surface", &layouts.globals, &layouts.surfaces, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target)], 1, ); WgpuPipelines { quads, shadows, path_rasterization, paths, underlines, mono_sprites, subpixel_sprites, poly_sprites, surfaces, } } fn create_path_intermediate( device: &wgpu::Device, format: wgpu::TextureFormat, width: u32, height: u32, ) -> (wgpu::Texture, wgpu::TextureView) { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("path_intermediate"), size: wgpu::Extent3d { width: width.max(1), height: height.max(1), depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); (texture, view) } fn create_msaa_if_needed( device: &wgpu::Device, format: wgpu::TextureFormat, width: u32, height: u32, sample_count: u32, ) -> Option<(wgpu::Texture, wgpu::TextureView)> { if sample_count <= 1 { return None; } let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("path_msaa"), size: wgpu::Extent3d { width: width.max(1), height: height.max(1), depth_or_array_layers: 1, }, mip_level_count: 1, sample_count, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); Some((texture, view)) } pub fn update_drawable_size(&mut self, size: Size) { let width = size.width.0 as u32; let height = size.height.0 as u32; if width != self.surface_config.width || height != self.surface_config.height { self.surface_config.width = width.max(1); self.surface_config.height = height.max(1); self.surface.configure(&self.device, &self.surface_config); let (path_intermediate_texture, path_intermediate_view) = Self::create_path_intermediate( &self.device, self.surface_config.format, self.surface_config.width, self.surface_config.height, ); self.path_intermediate_texture = path_intermediate_texture; self.path_intermediate_view = path_intermediate_view; let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed( &self.device, self.surface_config.format, self.surface_config.width, self.surface_config.height, self.rendering_params.path_sample_count, ) .map(|(t, v)| (Some(t), Some(v))) .unwrap_or((None, None)); self.path_msaa_texture = path_msaa_texture; self.path_msaa_view = path_msaa_view; } } pub fn update_transparency(&mut self, transparent: bool) { let new_alpha_mode = if transparent { self.transparent_alpha_mode } else { self.opaque_alpha_mode }; if new_alpha_mode != self.surface_config.alpha_mode { self.surface_config.alpha_mode = new_alpha_mode; self.surface.configure(&self.device, &self.surface_config); self.pipelines = Self::create_pipelines( &self.device, &self.bind_group_layouts, self.surface_config.format, self.surface_config.alpha_mode, self.rendering_params.path_sample_count, self.dual_source_blending, ); } } #[allow(dead_code)] pub fn viewport_size(&self) -> Size { Size { width: DevicePixels(self.surface_config.width as i32), height: DevicePixels(self.surface_config.height as i32), } } pub fn sprite_atlas(&self) -> &Arc { &self.atlas } pub fn gpu_specs(&self) -> GpuSpecs { GpuSpecs { is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu, device_name: self.adapter_info.name.clone(), driver_name: self.adapter_info.driver.clone(), driver_info: self.adapter_info.driver_info.clone(), } } pub fn draw(&mut self, scene: &Scene) { self.atlas.before_frame(); let frame = match self.surface.get_current_texture() { Ok(frame) => frame, Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { self.surface.configure(&self.device, &self.surface_config); return; } Err(e) => { log::error!("Failed to acquire surface texture: {e}"); return; } }; let frame_view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); let gamma_params = GammaParams { gamma_ratios: self.rendering_params.gamma_ratios, grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast, subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast, _pad: [0.0; 2], }; let globals = GlobalParams { viewport_size: [ self.surface_config.width as f32, self.surface_config.height as f32, ], premultiplied_alpha: if self.surface_config.alpha_mode == wgpu::CompositeAlphaMode::PreMultiplied { 1 } else { 0 }, pad: 0, }; let path_globals = GlobalParams { premultiplied_alpha: 0, ..globals }; self.queue .write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals)); self.queue.write_buffer( &self.globals_buffer, self.path_globals_offset, bytemuck::bytes_of(&path_globals), ); self.queue.write_buffer( &self.globals_buffer, self.gamma_offset, bytemuck::bytes_of(&gamma_params), ); loop { let mut instance_offset: u64 = 0; let mut overflow = false; let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("main_encoder"), }); { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("main_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &frame_view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() }); for batch in scene.batches() { let ok = match batch { PrimitiveBatch::Quads(range) => { self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass) } PrimitiveBatch::Shadows(range) => self.draw_shadows( &scene.shadows[range], &mut instance_offset, &mut pass, ), PrimitiveBatch::Paths(range) => { let paths = &scene.paths[range]; if paths.is_empty() { continue; } drop(pass); let did_draw = self.draw_paths_to_intermediate( &mut encoder, paths, &mut instance_offset, ); pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("main_pass_continued"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &frame_view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() }); if did_draw { self.draw_paths_from_intermediate( paths, &mut instance_offset, &mut pass, ) } else { false } } PrimitiveBatch::Underlines(range) => self.draw_underlines( &scene.underlines[range], &mut instance_offset, &mut pass, ), PrimitiveBatch::MonochromeSprites { texture_id, range } => self .draw_monochrome_sprites( &scene.monochrome_sprites[range], texture_id, &mut instance_offset, &mut pass, ), PrimitiveBatch::SubpixelSprites { texture_id, range } => self .draw_subpixel_sprites( &scene.subpixel_sprites[range], texture_id, &mut instance_offset, &mut pass, ), PrimitiveBatch::PolychromeSprites { texture_id, range } => self .draw_polychrome_sprites( &scene.polychrome_sprites[range], texture_id, &mut instance_offset, &mut pass, ), PrimitiveBatch::Surfaces(_surfaces) => { // Surfaces are macOS-only for video playback // Not implemented for Linux/wgpu true } }; if !ok { overflow = true; break; } } } if overflow { drop(encoder); if self.instance_buffer_capacity >= 256 * 1024 * 1024 { log::error!( "instance buffer size grew too large: {}", self.instance_buffer_capacity ); frame.present(); return; } self.grow_instance_buffer(); continue; } self.queue.submit(std::iter::once(encoder.finish())); frame.present(); return; } } fn draw_quads( &self, quads: &[Quad], instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let data = unsafe { Self::instance_bytes(quads) }; self.draw_instances( data, quads.len() as u32, &self.pipelines.quads, instance_offset, pass, ) } fn draw_shadows( &self, shadows: &[Shadow], instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let data = unsafe { Self::instance_bytes(shadows) }; self.draw_instances( data, shadows.len() as u32, &self.pipelines.shadows, instance_offset, pass, ) } fn draw_underlines( &self, underlines: &[Underline], instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let data = unsafe { Self::instance_bytes(underlines) }; self.draw_instances( data, underlines.len() as u32, &self.pipelines.underlines, instance_offset, pass, ) } fn draw_monochrome_sprites( &self, sprites: &[MonochromeSprite], texture_id: AtlasTextureId, instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let tex_info = self.atlas.get_texture_info(texture_id); let data = unsafe { Self::instance_bytes(sprites) }; self.draw_instances_with_texture( data, sprites.len() as u32, &tex_info.view, &self.pipelines.mono_sprites, instance_offset, pass, ) } fn draw_subpixel_sprites( &self, sprites: &[SubpixelSprite], texture_id: AtlasTextureId, instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let tex_info = self.atlas.get_texture_info(texture_id); let data = unsafe { Self::instance_bytes(sprites) }; let pipeline = self .pipelines .subpixel_sprites .as_ref() .unwrap_or(&self.pipelines.mono_sprites); self.draw_instances_with_texture( data, sprites.len() as u32, &tex_info.view, pipeline, instance_offset, pass, ) } fn draw_polychrome_sprites( &self, sprites: &[PolychromeSprite], texture_id: AtlasTextureId, instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let tex_info = self.atlas.get_texture_info(texture_id); let data = unsafe { Self::instance_bytes(sprites) }; self.draw_instances_with_texture( data, sprites.len() as u32, &tex_info.view, &self.pipelines.poly_sprites, instance_offset, pass, ) } fn draw_instances( &self, data: &[u8], instance_count: u32, pipeline: &wgpu::RenderPipeline, instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { if instance_count == 0 { return true; } let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else { return false; }; let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &self.bind_group_layouts.instances, entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.instance_binding(offset, size), }], }); pass.set_pipeline(pipeline); pass.set_bind_group(0, &self.globals_bind_group, &[]); pass.set_bind_group(1, &bind_group, &[]); pass.draw(0..4, 0..instance_count); true } fn draw_instances_with_texture( &self, data: &[u8], instance_count: u32, texture_view: &wgpu::TextureView, pipeline: &wgpu::RenderPipeline, instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { if instance_count == 0 { return true; } let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else { return false; }; let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &self.bind_group_layouts.instances_with_texture, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: self.instance_binding(offset, size), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(texture_view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.atlas_sampler), }, ], }); pass.set_pipeline(pipeline); pass.set_bind_group(0, &self.globals_bind_group, &[]); pass.set_bind_group(1, &bind_group, &[]); pass.draw(0..4, 0..instance_count); true } unsafe fn instance_bytes(instances: &[T]) -> &[u8] { unsafe { std::slice::from_raw_parts( instances.as_ptr() as *const u8, std::mem::size_of_val(instances), ) } } fn draw_paths_from_intermediate( &self, paths: &[Path], instance_offset: &mut u64, pass: &mut wgpu::RenderPass<'_>, ) -> bool { let first_path = &paths[0]; let sprites: Vec = if paths.last().map(|p| &p.order) == Some(&first_path.order) { paths .iter() .map(|p| PathSprite { bounds: p.clipped_bounds(), }) .collect() } else { let mut bounds = first_path.clipped_bounds(); for path in paths.iter().skip(1) { bounds = bounds.union(&path.clipped_bounds()); } vec![PathSprite { bounds }] }; let sprite_data = unsafe { Self::instance_bytes(&sprites) }; self.draw_instances_with_texture( sprite_data, sprites.len() as u32, &self.path_intermediate_view, &self.pipelines.paths, instance_offset, pass, ) } fn draw_paths_to_intermediate( &self, encoder: &mut wgpu::CommandEncoder, paths: &[Path], instance_offset: &mut u64, ) -> bool { let mut vertices = Vec::new(); for path in paths { let bounds = path.clipped_bounds(); vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex { xy_position: v.xy_position, st_position: v.st_position, color: path.color, bounds, })); } if vertices.is_empty() { return true; } let vertex_data = unsafe { Self::instance_bytes(&vertices) }; let Some((vertex_offset, vertex_size)) = self.write_to_instance_buffer(instance_offset, vertex_data) else { return false; }; let data_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("path_rasterization_bind_group"), layout: &self.bind_group_layouts.instances, entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.instance_binding(vertex_offset, vertex_size), }], }); let (target_view, resolve_target) = if let Some(ref msaa_view) = self.path_msaa_view { (msaa_view, Some(&self.path_intermediate_view)) } else { (&self.path_intermediate_view, None) }; { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("path_rasterization_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: target_view, resolve_target, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() }); pass.set_pipeline(&self.pipelines.path_rasterization); pass.set_bind_group(0, &self.path_globals_bind_group, &[]); pass.set_bind_group(1, &data_bind_group, &[]); pass.draw(0..vertices.len() as u32, 0..1); } true } fn grow_instance_buffer(&mut self) { let new_capacity = self.instance_buffer_capacity * 2; log::info!("increased instance buffer size to {}", new_capacity); self.instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor { label: Some("instance_buffer"), size: new_capacity, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); self.instance_buffer_capacity = new_capacity; } fn write_to_instance_buffer( &self, instance_offset: &mut u64, data: &[u8], ) -> Option<(u64, NonZeroU64)> { let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment); let size = (data.len() as u64).max(16); if offset + size > self.instance_buffer_capacity { return None; } self.queue.write_buffer(&self.instance_buffer, offset, data); *instance_offset = offset + size; Some((offset, NonZeroU64::new(size).expect("size is at least 16"))) } fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> { wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &self.instance_buffer, offset, size: Some(size), }) } pub fn destroy(&mut self) { // wgpu resources are automatically cleaned up when dropped } } struct RenderingParameters { path_sample_count: u32, gamma_ratios: [f32; 4], grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, } impl RenderingParameters { fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self { use std::env; let format_features = adapter.get_texture_format_features(surface_format); let path_sample_count = [4, 2, 1] .into_iter() .find(|&n| format_features.flags.sample_count_supported(n)) .unwrap_or(1); let gamma = env::var("ZED_FONTS_GAMMA") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(1.8_f32) .clamp(1.0, 2.2); let gamma_ratios = get_gamma_correction_ratios(gamma); let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(1.0_f32) .max(0.0); let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(0.5_f32) .max(0.0); Self { path_sample_count, gamma_ratios, grayscale_enhanced_contrast, subpixel_enhanced_contrast, } } }