use crate::{CompositorGpuHint, 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 log::warn; #[cfg(not(target_family = "wasm"))] use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::cell::RefCell; use std::num::NonZeroU64; use std::rc::Rc; use std::sync::{Arc, Mutex}; #[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, is_bgr: u32, _pad: u32, } #[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, /// Preferred presentation mode. When `Some`, the renderer will use this /// mode if supported by the surface, falling back to `Fifo`. /// When `None`, defaults to `Fifo` (VSync). /// /// Mobile platforms may prefer `Mailbox` (triple-buffering) to avoid /// blocking in `get_current_texture()` during lifecycle transitions. pub preferred_present_mode: Option, } 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, } /// Shared GPU context reference, used to coordinate device recovery across multiple windows. pub type GpuContext = Rc>>; /// GPU resources that must be dropped together during device recovery. struct WgpuResources { device: Arc, queue: Arc, surface: wgpu::Surface<'static>, pipelines: WgpuPipelines, bind_group_layouts: WgpuBindGroupLayouts, atlas_sampler: wgpu::Sampler, globals_buffer: wgpu::Buffer, globals_bind_group: wgpu::BindGroup, path_globals_bind_group: wgpu::BindGroup, instance_buffer: wgpu::Buffer, path_intermediate_texture: Option, path_intermediate_view: Option, path_msaa_texture: Option, path_msaa_view: Option, } impl WgpuResources { fn invalidate_intermediate_textures(&mut self) { self.path_intermediate_texture = None; self.path_intermediate_view = None; self.path_msaa_texture = None; self.path_msaa_view = None; } } pub struct WgpuRenderer { /// Shared GPU context for device recovery coordination (unused on WASM). #[allow(dead_code)] context: Option, /// Compositor GPU hint for adapter selection (unused on WASM). #[allow(dead_code)] compositor_gpu: Option, resources: Option, surface_config: wgpu::SurfaceConfiguration, atlas: Arc, path_globals_offset: u64, gamma_offset: u64, instance_buffer_capacity: u64, max_buffer_size: u64, storage_buffer_alignment: u64, rendering_params: RenderingParameters, is_bgr: bool, dual_source_blending: bool, adapter_info: wgpu::AdapterInfo, transparent_alpha_mode: wgpu::CompositeAlphaMode, opaque_alpha_mode: wgpu::CompositeAlphaMode, max_texture_size: u32, last_error: Arc>>, failed_frame_count: u32, device_lost: std::sync::Arc, surface_configured: bool, needs_redraw: bool, } impl WgpuRenderer { fn resources(&self) -> &WgpuResources { self.resources .as_ref() .expect("GPU resources not available") } fn resources_mut(&mut self) -> &mut WgpuResources { self.resources .as_mut() .expect("GPU resources not available") } /// Creates a new WgpuRenderer from raw window handles. /// /// The `gpu_context` is a shared reference that coordinates GPU context across /// multiple windows. The first window to create a renderer will initialize the /// context; subsequent windows will share it. /// /// # Safety /// The caller must ensure that the window handle remains valid for the lifetime /// of the returned renderer. #[cfg(not(target_family = "wasm"))] pub fn new( gpu_context: GpuContext, window: &W, config: WgpuSurfaceConfig, compositor_gpu: Option, ) -> anyhow::Result where W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static, { let window_handle = window .window_handle() .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?; let target = wgpu::SurfaceTargetUnsafe::RawHandle { // Fall back to the display handle already provided via InstanceDescriptor::display. raw_display_handle: None, raw_window_handle: window_handle.as_raw(), }; // Use the existing context's instance if available, otherwise create a new one. // The surface must be created with the same instance that will be used for // adapter selection, otherwise wgpu will panic. let instance = gpu_context .borrow() .as_ref() .map(|ctx| ctx.instance.clone()) .unwrap_or_else(|| WgpuContext::instance(Box::new(window.clone()))); // 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 { instance .create_surface_unsafe(target) .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))? }; let mut ctx_ref = gpu_context.borrow_mut(); let context = match ctx_ref.as_mut() { Some(context) => { context.check_compatible_with_surface(&surface)?; context } None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?), }; let atlas = Arc::new(WgpuAtlas::from_context(context)); Self::new_internal( Some(Rc::clone(&gpu_context)), context, surface, config, compositor_gpu, atlas, ) } #[cfg(target_family = "wasm")] pub fn new_from_canvas( context: &WgpuContext, canvas: &web_sys::HtmlCanvasElement, config: WgpuSurfaceConfig, ) -> anyhow::Result { let surface = context .instance .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?; let atlas = Arc::new(WgpuAtlas::from_context(context)); Self::new_internal(None, context, surface, config, None, atlas) } fn new_internal( gpu_context: Option, context: &WgpuContext, surface: wgpu::Surface<'static>, config: WgpuSurfaceConfig, compositor_gpu: Option, atlas: Arc, ) -> anyhow::Result { let surface_caps = surface.get_capabilities(&context.adapter); 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()) .or_else(|| surface_caps.formats.first().copied()) .ok_or_else(|| { anyhow::anyhow!( "Surface reports no supported texture formats for adapter {:?}", context.adapter.get_info().name ) })?; let pick_alpha_mode = |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result { preferences .iter() .find(|p| surface_caps.alpha_modes.contains(p)) .copied() .or_else(|| surface_caps.alpha_modes.first().copied()) .ok_or_else(|| { anyhow::anyhow!( "Surface reports no supported alpha modes for adapter {:?}", context.adapter.get_info().name ) }) }; 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 device = Arc::clone(&context.device); let max_texture_size = device.limits().max_texture_dimension_2d; let requested_width = config.size.width.0 as u32; let requested_height = config.size.height.0 as u32; let clamped_width = requested_width.min(max_texture_size); let clamped_height = requested_height.min(max_texture_size); if clamped_width != requested_width || clamped_height != requested_height { warn!( "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \ Clamping to ({}, {}). Window content may not fill the entire window.", requested_width, requested_height, max_texture_size, clamped_width, clamped_height ); } let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: clamped_width.max(1), height: clamped_height.max(1), present_mode: config .preferred_present_mode .filter(|mode| surface_caps.present_modes.contains(mode)) .unwrap_or(wgpu::PresentMode::Fifo), desired_maximum_frame_latency: 2, alpha_mode, view_formats: vec![], }; // Configure the surface immediately. The adapter selection process already validated // that this adapter can successfully configure this surface. surface.configure(&context.device, &surface_config); 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_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 max_buffer_size = device.limits().max_buffer_size; 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 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(); let last_error: Arc>> = Arc::new(Mutex::new(None)); let last_error_clone = Arc::clone(&last_error); device.on_uncaptured_error(Arc::new(move |error| { let mut guard = last_error_clone.lock().unwrap(); *guard = Some(error.to_string()); })); let resources = WgpuResources { device, queue, surface, pipelines, bind_group_layouts, atlas_sampler, globals_buffer, globals_bind_group, path_globals_bind_group, instance_buffer, // Defer intermediate texture creation to first draw call via ensure_intermediate_textures(). // This avoids panics when the device/surface is in an invalid state during initialization. path_intermediate_texture: None, path_intermediate_view: None, path_msaa_texture: None, path_msaa_view: None, }; Ok(Self { context: gpu_context, compositor_gpu, resources: Some(resources), surface_config, atlas, path_globals_offset, gamma_offset, instance_buffer_capacity: initial_instance_buffer_capacity, max_buffer_size, storage_buffer_alignment, rendering_params, is_bgr: false, dual_source_blending, adapter_info, transparent_alpha_mode, opaque_alpha_mode, max_texture_size, last_error, failed_frame_count: 0, device_lost: context.device_lost_flag(), surface_configured: true, needs_redraw: false, }) } 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 { // Diagnostic guard: verify the device actually has // DUAL_SOURCE_BLENDING. We have a crash report (ZED-5G1) where a // feature mismatch caused a wgpu-hal abort, but we haven't // identified the code path that produces the mismatch. This // guard prevents the crash and logs more evidence. // Remove this check once: // a) We find and fix the root cause, or // b) There are no reports of this warning appearing for some time. let device_has_feature = device .features() .contains(wgpu::Features::DUAL_SOURCE_BLENDING); if dual_source_blending && !device_has_feature { log::error!( "BUG: dual_source_blending flag is true but device does not \ have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \ Falling back to mono text rendering. Please report this at \ https://github.com/zed-industries/zed/issues", device.features(), ); } let dual_source_blending = dual_source_blending && device_has_feature; let base_shader_source = include_str!("shaders.wgsl"); let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("gpui_shaders"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(base_shader_source)), }); let subpixel_shader_source = include_str!("shaders_subpixel.wgsl"); let subpixel_shader_module = if dual_source_blending { let combined = format!( "enable dual_source_blending;\n{base_shader_source}\n{subpixel_shader_source}" ); Some(device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("gpui_subpixel_shaders"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(combined)), })) } else { None }; 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, module: &wgpu::ShaderModule| { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some(&format!("{name}_layout")), bind_group_layouts: &[Some(globals_layout), Some(data_layout)], immediate_size: 0, }); device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some(name), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module, entry_point: Some(vs_entry), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { 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, &shader_module, ); let shadows = create_pipeline( "shadows", "vs_shadow", "fs_shadow", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, &shader_module, ); 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, &shader_module, ); 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, &shader_module, ); let underlines = create_pipeline( "underlines", "vs_underline", "fs_underline", &layouts.globals, &layouts.instances, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target.clone())], 1, &shader_module, ); 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, &shader_module, ); let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module { 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, subpixel_module, )) } 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, &shader_module, ); let surfaces = create_pipeline( "surfaces", "vs_surface", "fs_surface", &layouts.globals, &layouts.surfaces, wgpu::PrimitiveTopology::TriangleStrip, &[Some(color_target)], 1, &shader_module, ); 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 { let clamped_width = width.min(self.max_texture_size); let clamped_height = height.min(self.max_texture_size); if clamped_width != width || clamped_height != height { warn!( "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \ Clamping to ({}, {}). Window content may not fill the entire window.", width, height, self.max_texture_size, clamped_width, clamped_height ); } self.surface_config.width = clamped_width.max(1); self.surface_config.height = clamped_height.max(1); let surface_config = self.surface_config.clone(); let Some(resources) = self.resources.as_mut() else { return; }; // Wait for any in-flight GPU work to complete before destroying textures if let Err(e) = resources.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None, }) { warn!("Failed to poll device during resize: {e:?}"); } // Destroy old textures before allocating new ones to avoid GPU memory spikes if let Some(ref texture) = resources.path_intermediate_texture { texture.destroy(); } if let Some(ref texture) = resources.path_msaa_texture { texture.destroy(); } resources .surface .configure(&resources.device, &surface_config); // Invalidate intermediate textures - they will be lazily recreated // in draw() after we confirm the surface is healthy. This avoids // panics when the device/surface is in an invalid state during resize. resources.invalidate_intermediate_textures(); } } fn ensure_intermediate_textures(&mut self) { if self.resources().path_intermediate_texture.is_some() { return; } let format = self.surface_config.format; let width = self.surface_config.width; let height = self.surface_config.height; let path_sample_count = self.rendering_params.path_sample_count; let resources = self.resources_mut(); let (t, v) = Self::create_path_intermediate(&resources.device, format, width, height); resources.path_intermediate_texture = Some(t); resources.path_intermediate_view = Some(v); let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed( &resources.device, format, width, height, path_sample_count, ) .map(|(t, v)| (Some(t), Some(v))) .unwrap_or((None, None)); resources.path_msaa_texture = path_msaa_texture; resources.path_msaa_view = path_msaa_view; } pub fn set_subpixel_layout(&mut self, is_bgr: bool) { self.is_bgr = is_bgr; } 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; let surface_config = self.surface_config.clone(); let path_sample_count = self.rendering_params.path_sample_count; let dual_source_blending = self.dual_source_blending; let Some(resources) = self.resources.as_mut() else { return; }; resources .surface .configure(&resources.device, &surface_config); resources.pipelines = Self::create_pipelines( &resources.device, &resources.bind_group_layouts, surface_config.format, surface_config.alpha_mode, path_sample_count, 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 supports_dual_source_blending(&self) -> bool { self.dual_source_blending } 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 max_texture_size(&self) -> u32 { self.max_texture_size } pub fn draw(&mut self, scene: &Scene) -> bool { // Bail out early if the surface has been unconfigured (e.g. during // Android background/rotation transitions). Attempting to acquire // a texture from an unconfigured surface can block indefinitely on // some drivers (Adreno). if !self.surface_configured { return false; } let last_error = self.last_error.lock().unwrap().take(); if let Some(error) = last_error { self.failed_frame_count += 1; log::error!( "GPU error during frame (failure {} of 10): {error}", self.failed_frame_count ); // TBD. Does retrying more actually help? if self.failed_frame_count > 10 { panic!("Too many consecutive GPU errors. Last error: {error}"); } else if self.failed_frame_count > 5 { if let Some(res) = self.resources.as_mut() { res.invalidate_intermediate_textures(); } self.atlas.clear(); self.needs_redraw = true; self.failed_frame_count = 0; return false; } } else { self.failed_frame_count = 0; } self.atlas.before_frame(); let frame = match self.resources().surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(frame) => frame, wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { // Textures must be destroyed before the surface can be reconfigured. drop(frame); let surface_config = self.surface_config.clone(); let resources = self.resources_mut(); resources .surface .configure(&resources.device, &surface_config); return false; } wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => { let surface_config = self.surface_config.clone(); let resources = self.resources_mut(); resources .surface .configure(&resources.device, &surface_config); return false; } wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => { return false; } wgpu::CurrentSurfaceTexture::Validation => { *self.last_error.lock().unwrap() = Some("Surface texture validation error".to_string()); return false; } }; // Now that we know the surface is healthy, ensure intermediate textures exist self.ensure_intermediate_textures(); 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, is_bgr: self.is_bgr as u32, _pad: 0, }; 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 }; { let resources = self.resources(); resources.queue.write_buffer( &resources.globals_buffer, 0, bytemuck::bytes_of(&globals), ); resources.queue.write_buffer( &resources.globals_buffer, self.path_globals_offset, bytemuck::bytes_of(&path_globals), ); resources.queue.write_buffer( &resources.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.resources() .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 >= self.max_buffer_size { log::error!( "instance buffer size grew too large: {}", self.instance_buffer_capacity ); frame.present(); return true; } self.grow_instance_buffer(); continue; } self.resources() .queue .submit(std::iter::once(encoder.finish())); frame.present(); return true; } } 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.resources().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.resources().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.resources().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.resources().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 resources = self.resources(); let pipeline = resources .pipelines .subpixel_sprites .as_ref() .unwrap_or(&resources.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.resources().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 resources = self.resources(); let bind_group = resources .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &resources.bind_group_layouts.instances, entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.instance_binding(offset, size), }], }); pass.set_pipeline(pipeline); pass.set_bind_group(0, &resources.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 resources = self.resources(); let bind_group = resources .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &resources.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(&resources.atlas_sampler), }, ], }); pass.set_pipeline(pipeline); pass.set_bind_group(0, &resources.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 resources = self.resources(); let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else { return true; }; let sprite_data = unsafe { Self::instance_bytes(&sprites) }; self.draw_instances_with_texture( sprite_data, sprites.len() as u32, path_intermediate_view, &resources.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 resources = self.resources(); let data_bind_group = resources .device .create_bind_group(&wgpu::BindGroupDescriptor { label: Some("path_rasterization_bind_group"), layout: &resources.bind_group_layouts.instances, entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.instance_binding(vertex_offset, vertex_size), }], }); let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else { return true; }; let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view { (msaa_view, Some(path_intermediate_view)) } else { (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(&resources.pipelines.path_rasterization); pass.set_bind_group(0, &resources.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).min(self.max_buffer_size); log::info!("increased instance buffer size to {}", new_capacity); let resources = self.resources_mut(); resources.instance_buffer = resources.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; } let resources = self.resources(); resources .queue .write_buffer(&resources.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.resources().instance_buffer, offset, size: Some(size), }) } /// Mark the surface as unconfigured so rendering is skipped until a new /// surface is provided via [`replace_surface`](Self::replace_surface). /// /// This does **not** drop the renderer — the device, queue, atlas, and /// pipelines stay alive. Use this when the native window is destroyed /// (e.g. Android `TerminateWindow`) but you intend to re-create the /// surface later without losing cached atlas textures. pub fn unconfigure_surface(&mut self) { self.surface_configured = false; // Drop intermediate textures since they reference the old surface size. if let Some(res) = self.resources.as_mut() { res.invalidate_intermediate_textures(); } } /// Replace the wgpu surface with a new one (e.g. after Android destroys /// and recreates the native window). Keeps the device, queue, atlas, and /// all pipelines intact so cached `AtlasTextureId`s remain valid. /// /// The `instance` **must** be the same [`wgpu::Instance`] that was used to /// create the adapter and device (i.e. from the [`WgpuContext`]). Using a /// different instance will cause a "Device does not exist" panic because /// the wgpu device is bound to its originating instance. #[cfg(not(target_family = "wasm"))] pub fn replace_surface( &mut self, window: &W, config: WgpuSurfaceConfig, instance: &wgpu::Instance, ) -> anyhow::Result<()> { let window_handle = window .window_handle() .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?; let surface = create_surface(instance, window_handle.as_raw())?; let width = (config.size.width.0 as u32).max(1); let height = (config.size.height.0 as u32).max(1); let alpha_mode = if config.transparent { self.transparent_alpha_mode } else { self.opaque_alpha_mode }; self.surface_config.width = width; self.surface_config.height = height; self.surface_config.alpha_mode = alpha_mode; if let Some(mode) = config.preferred_present_mode { self.surface_config.present_mode = mode; } { let res = self .resources .as_mut() .expect("GPU resources not available"); surface.configure(&res.device, &self.surface_config); res.surface = surface; // Invalidate intermediate textures — they'll be recreated lazily. res.invalidate_intermediate_textures(); } self.surface_configured = true; Ok(()) } pub fn destroy(&mut self) { // Release surface-bound GPU resources eagerly so the underlying native // window can be destroyed before the renderer itself is dropped. self.resources.take(); } /// Returns true if the GPU device was lost and recovery is needed. pub fn device_lost(&self) -> bool { self.device_lost.load(std::sync::atomic::Ordering::SeqCst) } /// Returns true if a redraw is needed because GPU state was cleared. /// Calling this method clears the flag. pub fn needs_redraw(&mut self) -> bool { std::mem::take(&mut self.needs_redraw) } /// Recovers from a lost GPU device by recreating the renderer with a new context. /// /// Call this after detecting `device_lost()` returns true. /// /// This method coordinates recovery across multiple windows: /// - The first window to call this will recreate the shared context /// - Subsequent windows will adopt the already-recovered context #[cfg(not(target_family = "wasm"))] pub fn recover(&mut self, window: &W) -> anyhow::Result<()> where W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static, { let gpu_context = self.context.as_ref().expect("recover requires gpu_context"); // Check if another window already recovered the context let needs_new_context = gpu_context .borrow() .as_ref() .is_none_or(|ctx| ctx.device_lost()); let window_handle = window .window_handle() .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?; let surface = if needs_new_context { log::warn!("GPU device lost, recreating context..."); // Drop old resources to release Arc/Arc and GPU resources self.resources = None; *gpu_context.borrow_mut() = None; // Wait briefly for the GPU driver to stabilize, then try to // recreate the context without software renderers. If this fails // the caller should request another frame and retry — the real GPU // may need more time to come back (e.g. after suspend/resume). std::thread::sleep(std::time::Duration::from_millis(350)); let instance = WgpuContext::instance(Box::new(window.clone())); let surface = create_surface(&instance, window_handle.as_raw())?; let new_context = WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?; *gpu_context.borrow_mut() = Some(new_context); surface } else { let ctx_ref = gpu_context.borrow(); let instance = &ctx_ref.as_ref().unwrap().instance; create_surface(instance, window_handle.as_raw())? }; let config = WgpuSurfaceConfig { size: gpui::Size { width: gpui::DevicePixels(self.surface_config.width as i32), height: gpui::DevicePixels(self.surface_config.height as i32), }, transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque, preferred_present_mode: Some(self.surface_config.present_mode), }; let gpu_context = Rc::clone(gpu_context); let ctx_ref = gpu_context.borrow(); let context = ctx_ref.as_ref().expect("context should exist"); self.resources = None; self.atlas.handle_device_lost(context); *self = Self::new_internal( Some(gpu_context.clone()), context, surface, config, self.compositor_gpu, self.atlas.clone(), )?; log::info!("GPU recovery complete"); Ok(()) } } #[cfg(not(target_family = "wasm"))] fn create_surface( instance: &wgpu::Instance, raw_window_handle: raw_window_handle::RawWindowHandle, ) -> anyhow::Result> { unsafe { instance .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { // Fall back to the display handle already provided via InstanceDescriptor::display. raw_display_handle: None, raw_window_handle, }) .map_err(|e| anyhow::anyhow!("{e}")) } } 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, } } }