diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c8f1032fbd..fc159b0229 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1121,6 +1121,14 @@ impl App { self.platform.window_stack() } + /// Register additional GPU device requirements (extra features and/or + /// limits) before opening any windows. The `Box` must contain a + /// `gpui_wgpu::WgpuDeviceRequirements`. + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub fn set_gpu_requirements(&self, requirements: Box) { + self.platform.set_gpu_requirements(requirements); + } + /// Returns a handle to the window that is currently focused at the platform level, if one exists. pub fn active_window(&self) -> Option { self.platform.active_window() diff --git a/crates/gpui/src/elements/surface.rs b/crates/gpui/src/elements/surface.rs index ac1c247b47..d46f212863 100644 --- a/crates/gpui/src/elements/surface.rs +++ b/crates/gpui/src/elements/surface.rs @@ -2,16 +2,55 @@ use crate::{ App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, ObjectFit, Pixels, Style, StyleRefinement, Styled, Window, }; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +use crate::{DevicePixels, Size}; #[cfg(target_os = "macos")] use core_video::pixel_buffer::CVPixelBuffer; use refineable::Refineable; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +use std::sync::Arc; /// A source of a surface's content. -#[derive(Clone, Debug, PartialEq, Eq)] pub enum SurfaceSource { /// A macOS image buffer from CoreVideo #[cfg(target_os = "macos")] Surface(CVPixelBuffer), + /// A GPU texture handle (type-erased to avoid depending on wgpu) + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + Texture { + /// The GPU texture, type-erased (expected to be `Arc`) + texture: Arc, + /// Dimensions of the texture in device pixels + size: Size, + }, +} + +impl Clone for SurfaceSource { + fn clone(&self) -> Self { + match self { + #[cfg(target_os = "macos")] + SurfaceSource::Surface(buf) => SurfaceSource::Surface(buf.clone()), + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SurfaceSource::Texture { texture, size } => SurfaceSource::Texture { + texture: Arc::clone(texture), + size: *size, + }, + } + } +} + +impl std::fmt::Debug for SurfaceSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(target_os = "macos")] + SurfaceSource::Surface(buf) => f.debug_tuple("Surface").field(buf).finish(), + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SurfaceSource::Texture { size, .. } => f + .debug_struct("Texture") + .field("size", size) + .finish_non_exhaustive(), + } + } } #[cfg(target_os = "macos")] @@ -29,7 +68,6 @@ pub struct Surface { } /// Create a new surface element. -#[cfg(target_os = "macos")] pub fn surface(source: impl Into) -> Surface { Surface { source: source.into(), @@ -86,10 +124,10 @@ impl Element for Surface { &mut self, _global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, - #[cfg_attr(not(target_os = "macos"), allow(unused_variables))] bounds: Bounds, + bounds: Bounds, _: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - #[cfg_attr(not(target_os = "macos"), allow(unused_variables))] window: &mut Window, + window: &mut Window, _: &mut App, ) { match &self.source { @@ -100,8 +138,11 @@ impl Element for Surface { // TODO: Add support for corner_radii window.paint_surface(new_bounds, surface.clone()); } - #[allow(unreachable_patterns)] - _ => {} + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SurfaceSource::Texture { texture, size } => { + let new_bounds = self.object_fit.get_bounds(bounds, *size); + window.paint_surface(new_bounds, Arc::clone(texture), *size); + } } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index fe93222a50..1504b6e114 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -242,6 +242,12 @@ pub trait Platform: 'static { fn keyboard_layout(&self) -> Box; fn keyboard_mapper(&self) -> Rc; fn on_keyboard_layout_change(&self, callback: Box); + + /// Register additional GPU device requirements (features, limits) before + /// the first window is opened. The concrete type inside the `Box` must be + /// `gpui_wgpu::WgpuDeviceRequirements`. + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + fn set_gpu_requirements(&self, _requirements: Box) {} } /// A handle to a platform's display, e.g. a monitor or laptop screen. @@ -706,6 +712,13 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn set_client_inset(&self, _inset: Pixels) {} fn gpu_specs(&self) -> Option; + /// Returns the GPU context for this window's renderer. + /// The returned `Box` contains `(Arc, Arc)`. + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + fn gpu_context(&self) -> Option> { + None + } + fn update_ime_position(&self, _bounds: Bounds); fn play_system_bell(&self) {} diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index bc7f5d79ea..e1e987f3df 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -723,6 +723,10 @@ pub struct PaintSurface { pub content_mask: ContentMask, #[cfg(target_os = "macos")] pub image_buffer: core_video::pixel_buffer::CVPixelBuffer, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub texture: std::sync::Arc, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub texture_size: Size, } impl From for Primitive { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 122f79ea1e..b8805a3c4f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4086,6 +4086,32 @@ impl Window { }); } + /// Paint a surface into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub fn paint_surface( + &mut self, + bounds: Bounds, + texture: std::sync::Arc, + texture_size: Size, + ) { + use crate::PaintSurface; + + self.invalidator.debug_assert_paint(); + + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + let content_mask = self.content_mask().scale(scale_factor); + self.next_frame.scene.insert_primitive(PaintSurface { + order: 0, + bounds, + content_mask, + texture, + texture_size, + }); + } + /// Removes an image from the sprite atlas. pub fn drop_image(&mut self, data: Arc) -> Result<()> { for frame_index in 0..data.frame_count() { @@ -5386,6 +5412,13 @@ impl Window { self.platform_window.gpu_specs() } + /// Returns the GPU context (device + queue) if available. + /// The returned `Box` contains `(Arc, Arc)`. + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub fn gpu_context(&self) -> Option> { + self.platform_window.gpu_context() + } + /// Perform titlebar double-click action. /// This is macOS specific. pub fn titlebar_double_click(&self) { diff --git a/crates/gpui_linux/src/linux/platform.rs b/crates/gpui_linux/src/linux/platform.rs index 343600cd86..ce412568fe 100644 --- a/crates/gpui_linux/src/linux/platform.rs +++ b/crates/gpui_linux/src/linux/platform.rs @@ -92,6 +92,7 @@ pub(crate) trait LinuxClient { fn read_from_clipboard(&self) -> Option; fn active_window(&self) -> Option; fn window_stack(&self) -> Option>; + fn set_gpu_requirements(&self, _requirements: Box) {} fn run(&self); #[cfg(any(feature = "wayland", feature = "x11"))] @@ -316,6 +317,10 @@ impl Platform for LinuxPlatform

{ self.inner.open_window(handle, options) } + fn set_gpu_requirements(&self, requirements: Box) { + self.inner.set_gpu_requirements(requirements); + } + fn open_url(&self, url: &str) { self.inner.open_uri(url); } diff --git a/crates/gpui_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index 6a7c4c76e4..f360ede462 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -217,6 +217,7 @@ pub(crate) struct WaylandClientState { globals: Globals, pub gpu_context: GpuContext, pub compositor_gpu: Option, + pub gpu_requirements: Option, wl_seat: wl_seat::WlSeat, // TODO: Multi seat support wl_pointer: Option, pinch_gesture: Option, @@ -667,6 +668,7 @@ impl WaylandClient { globals, gpu_context, compositor_gpu, + gpu_requirements: None, wl_seat: seat, wl_pointer: None, wl_keyboard: None, @@ -800,6 +802,14 @@ impl LinuxClient for WaylandClient { sources_rx } + fn set_gpu_requirements(&self, requirements: Box) { + if let Ok(reqs) = requirements.downcast::() { + self.0.borrow_mut().gpu_requirements = Some(*reqs); + } else { + log::warn!("set_gpu_requirements: unexpected type, expected WgpuDeviceRequirements"); + } + } + fn open_window( &self, handle: AnyWindowHandle, @@ -820,11 +830,13 @@ impl LinuxClient for WaylandClient { let appearance = state.common.appearance; let compositor_gpu = state.compositor_gpu.take(); + let gpu_requirements = state.gpu_requirements.clone(); let (window, surface_id) = WaylandWindow::new( handle, state.globals.clone(), state.gpu_context.clone(), compositor_gpu, + gpu_requirements, WaylandClientStatePtr(Rc::downgrade(&self.0)), params, appearance, diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index d8cf35686e..dbc71d77e3 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -328,6 +328,7 @@ impl WaylandWindowState { globals: Globals, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, + gpu_requirements: Option, options: WindowParams, parent: Option, ) -> anyhow::Result { @@ -350,7 +351,13 @@ impl WaylandWindowState { // Prefer Mailbox to avoid blocking. Falls back to FIFO if Mailbox is unsupported. preferred_present_mode: Some(wgpu::PresentMode::Mailbox), }; - WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? + WgpuRenderer::new( + gpu_context, + &raw_window, + config, + compositor_gpu, + gpu_requirements, + )? }; if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state { @@ -514,6 +521,7 @@ impl WaylandWindow { globals: Globals, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, + gpu_requirements: Option, client: WaylandClientStatePtr, params: WindowParams, appearance: WindowAppearance, @@ -544,6 +552,7 @@ impl WaylandWindow { globals, gpu_context, compositor_gpu, + gpu_requirements, params, parent, )?)), @@ -1513,6 +1522,11 @@ impl PlatformWindow for WaylandWindow { self.borrow().renderer.gpu_specs().into() } + fn gpu_context(&self) -> Option> { + let (device, queue) = self.borrow().renderer.gpu_context(); + Some(Box::new((device, queue))) + } + fn play_system_bell(&self) { let state = self.borrow(); let surface = if state.surface_state.toplevel().is_some() { diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 1e573a54bf..273bbbe825 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -180,6 +180,7 @@ pub struct X11ClientState { pub(crate) gpu_context: GpuContext, pub(crate) compositor_gpu: Option, + pub(crate) gpu_requirements: Option, pub(crate) scale_factor: f32, @@ -523,6 +524,7 @@ impl X11Client { pinch_scale: 1.0, gpu_context: Rc::new(RefCell::new(None)), compositor_gpu, + gpu_requirements: None, scale_factor, xkb_context, @@ -1586,6 +1588,14 @@ impl LinuxClient for X11Client { gpui::scap_screen_capture::scap_screen_sources(&self.0.borrow().common.foreground_executor) } + fn set_gpu_requirements(&self, requirements: Box) { + if let Ok(reqs) = requirements.downcast::() { + self.0.borrow_mut().gpu_requirements = Some(*reqs); + } else { + log::warn!("set_gpu_requirements: unexpected type, expected WgpuDeviceRequirements"); + } + } + fn open_window( &self, handle: AnyWindowHandle, @@ -1608,6 +1618,7 @@ impl LinuxClient for X11Client { let scale_factor = state.scale_factor; let appearance = state.common.appearance; let compositor_gpu = state.compositor_gpu.take(); + let gpu_requirements = state.gpu_requirements.clone(); let supports_xinput_gestures = state.supports_xinput_gestures; let is_bgr = state .resource_database @@ -1619,6 +1630,7 @@ impl LinuxClient for X11Client { state.common.foreground_executor.clone(), state.gpu_context.clone(), compositor_gpu, + gpu_requirements, params, &xcb_connection, client_side_decorations_supported, diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 1f3ca51155..7cd66b982c 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -416,6 +416,7 @@ impl X11WindowState { executor: ForegroundExecutor, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, + gpu_requirements: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -724,7 +725,13 @@ impl X11WindowState { transparent: false, preferred_present_mode: None, }; - WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? + WgpuRenderer::new( + gpu_context, + &raw_window, + config, + compositor_gpu, + gpu_requirements, + )? }; renderer.set_subpixel_layout(is_bgr); @@ -878,6 +885,7 @@ impl X11Window { executor: ForegroundExecutor, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, + gpu_requirements: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -897,6 +905,7 @@ impl X11Window { executor, gpu_context, compositor_gpu, + gpu_requirements, params, xcb, client_side_decorations_supported, @@ -1887,6 +1896,11 @@ impl PlatformWindow for X11Window { self.0.state.borrow().renderer.gpu_specs().into() } + fn gpu_context(&self) -> Option> { + let (device, queue) = self.0.state.borrow().renderer.gpu_context(); + Some(Box::new((device, queue))) + } + fn play_system_bell(&self) { // Volume 0% means don't increase or decrease from system volume let _ = self.0.xcb.bell(0); diff --git a/crates/gpui_wgpu/src/shaders.wgsl b/crates/gpui_wgpu/src/shaders.wgsl index 933b88e84d..b318a6829c 100644 --- a/crates/gpui_wgpu/src/shaders.wgsl +++ b/crates/gpui_wgpu/src/shaders.wgsl @@ -1320,16 +1320,8 @@ struct SurfaceParams { } @group(1) @binding(0) var surface_locals: SurfaceParams; -@group(1) @binding(1) var t_y: texture_2d; -@group(1) @binding(2) var t_cb_cr: texture_2d; -@group(1) @binding(3) var s_surface: sampler; - -const ycbcr_to_RGB = mat4x4( - vec4( 1.0000f, 1.0000f, 1.0000f, 0.0), - vec4( 0.0000f, -0.3441f, 1.7720f, 0.0), - vec4( 1.4020f, -0.7141f, 0.0000f, 0.0), - vec4(-0.7010f, 0.5291f, -0.8860f, 1.0), -); +@group(1) @binding(1) var t_surface: texture_2d; +@group(1) @binding(2) var s_surface: sampler; struct SurfaceVarying { @builtin(position) position: vec4, @@ -1350,15 +1342,9 @@ fn vs_surface(@builtin(vertex_index) vertex_id: u32) -> SurfaceVarying { @fragment fn fs_surface(input: SurfaceVarying) -> @location(0) vec4 { - // Alpha clip after using the derivatives. if (any(input.clip_distances < vec4(0.0))) { return vec4(0.0); } - let y_cb_cr = vec4( - textureSampleLevel(t_y, s_surface, input.texture_position, 0.0).r, - textureSampleLevel(t_cb_cr, s_surface, input.texture_position, 0.0).rg, - 1.0); - - return ycbcr_to_RGB * y_cb_cr; + return textureSampleLevel(t_surface, s_surface, input.texture_position, 0.0); } diff --git a/crates/gpui_wgpu/src/wgpu_context.rs b/crates/gpui_wgpu/src/wgpu_context.rs index 9dd5599393..21ca362979 100644 --- a/crates/gpui_wgpu/src/wgpu_context.rs +++ b/crates/gpui_wgpu/src/wgpu_context.rs @@ -22,14 +22,29 @@ pub struct CompositorGpuHint { pub device_id: u32, } +/// Extra wgpu features and limits that an application can request on top of +/// gpui's baseline. Pass an instance to the platform via +/// [`gpui::App::set_gpu_requirements`] *before* opening any windows. +#[derive(Clone, Debug, Default)] +pub struct WgpuDeviceRequirements { + /// Additional [`wgpu::Features`] to enable. These are OR-ed with gpui's + /// own required features. + pub features: wgpu::Features, + /// Additional [`wgpu::Limits`] to request. Each field is merged by taking + /// `max(gpui_limit, app_limit)` for upper-bound limits and + /// `min(gpui_limit, app_limit)` for alignment/lower-bound limits. + pub limits: wgpu::Limits, +} + impl WgpuContext { #[cfg(not(target_family = "wasm"))] pub fn new( instance: wgpu::Instance, surface: &wgpu::Surface<'_>, compositor_gpu: Option, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result { - Self::new_with_options(instance, surface, compositor_gpu, false) + Self::new_with_options(instance, surface, compositor_gpu, false, extra_requirements) } #[cfg(not(target_family = "wasm"))] @@ -37,8 +52,9 @@ impl WgpuContext { instance: wgpu::Instance, surface: &wgpu::Surface<'_>, compositor_gpu: Option, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result { - Self::new_with_options(instance, surface, compositor_gpu, true) + Self::new_with_options(instance, surface, compositor_gpu, true, extra_requirements) } #[cfg(not(target_family = "wasm"))] @@ -47,6 +63,7 @@ impl WgpuContext { surface: &wgpu::Surface<'_>, compositor_gpu: Option, reject_software: bool, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result { let device_id_filter = match std::env::var("ZED_DEVICE_ID") { Ok(val) => parse_pci_id(&val) @@ -69,6 +86,7 @@ impl WgpuContext { surface, compositor_gpu.as_ref(), reject_software, + extra_requirements, ))?; let device_lost = Arc::new(AtomicBool::new(false)); @@ -126,7 +144,7 @@ impl WgpuContext { let device_lost = Arc::new(AtomicBool::new(false)); let (device, queue, dual_source_blending, color_texture_format) = - Self::create_device(&adapter).await?; + Self::create_device(&adapter, None).await?; Ok(Self { instance, @@ -141,6 +159,7 @@ impl WgpuContext { async fn create_device( adapter: &wgpu::Adapter, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> { let dual_source_blending = adapter .features() @@ -158,13 +177,21 @@ impl WgpuContext { let color_atlas_texture_format = Self::select_color_texture_format(adapter)?; + let mut required_limits = wgpu::Limits::downlevel_defaults() + .using_resolution(adapter.limits()) + .using_alignment(adapter.limits()); + + // Merge application-requested requirements. + if let Some(reqs) = extra_requirements { + required_features |= reqs.features; + required_limits = required_limits.or_better_values_from(&reqs.limits); + } + let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: Some("gpui_device"), required_features, - required_limits: wgpu::Limits::downlevel_defaults() - .using_resolution(adapter.limits()) - .using_alignment(adapter.limits()), + required_limits, memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, experimental_features: wgpu::ExperimentalFeatures::disabled(), @@ -218,6 +245,7 @@ impl WgpuContext { surface: &wgpu::Surface<'_>, compositor_gpu: Option<&CompositorGpuHint>, reject_software: bool, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result<( wgpu::Adapter, wgpu::Device, @@ -319,7 +347,7 @@ impl WgpuContext { log::info!("Testing adapter: {} ({:?})...", info.name, info.backend); - match Self::try_adapter_with_surface(&adapter, surface).await { + match Self::try_adapter_with_surface(&adapter, surface, extra_requirements).await { Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => { log::info!( "Selected GPU (passed configuration test): {} ({:?})", @@ -354,6 +382,7 @@ impl WgpuContext { async fn try_adapter_with_surface( adapter: &wgpu::Adapter, surface: &wgpu::Surface<'_>, + extra_requirements: Option<&WgpuDeviceRequirements>, ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> { let caps = surface.get_capabilities(adapter); if caps.formats.is_empty() { @@ -364,7 +393,7 @@ impl WgpuContext { } let (device, queue, dual_source_blending, color_atlas_texture_format) = - Self::create_device(adapter).await?; + Self::create_device(adapter, extra_requirements).await?; let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation); let test_config = wgpu::SurfaceConfiguration { diff --git a/crates/gpui_wgpu/src/wgpu_renderer.rs b/crates/gpui_wgpu/src/wgpu_renderer.rs index 08f30dc009..811fb39893 100644 --- a/crates/gpui_wgpu/src/wgpu_renderer.rs +++ b/crates/gpui_wgpu/src/wgpu_renderer.rs @@ -1,9 +1,9 @@ -use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext}; +use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext, WgpuDeviceRequirements}; 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, + AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, PaintSurface, + Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, + SubpixelSprite, Underline, get_gamma_correction_ratios, }; use log::warn; #[cfg(not(target_family = "wasm"))] @@ -90,7 +90,6 @@ struct WgpuPipelines { mono_sprites: wgpu::RenderPipeline, subpixel_sprites: Option, poly_sprites: wgpu::RenderPipeline, - #[allow(dead_code)] surfaces: wgpu::RenderPipeline, } @@ -112,6 +111,8 @@ struct WgpuResources { pipelines: WgpuPipelines, bind_group_layouts: WgpuBindGroupLayouts, atlas_sampler: wgpu::Sampler, + surface_sampler: wgpu::Sampler, + surface_uniform_buffer: wgpu::Buffer, globals_buffer: wgpu::Buffer, globals_bind_group: wgpu::BindGroup, path_globals_bind_group: wgpu::BindGroup, @@ -138,6 +139,9 @@ pub struct WgpuRenderer { /// Compositor GPU hint for adapter selection (unused on WASM). #[allow(dead_code)] compositor_gpu: Option, + /// Application-requested extra wgpu features/limits, stored for device recovery. + #[allow(dead_code)] + extra_requirements: Option, resources: Option, surface_config: wgpu::SurfaceConfiguration, atlas: Arc, @@ -188,6 +192,7 @@ impl WgpuRenderer { window: &W, config: WgpuSurfaceConfig, compositor_gpu: Option, + extra_requirements: Option, ) -> anyhow::Result where W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static, @@ -226,7 +231,12 @@ impl WgpuRenderer { context.check_compatible_with_surface(&surface)?; context } - None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?), + None => ctx_ref.insert(WgpuContext::new( + instance, + &surface, + compositor_gpu, + extra_requirements.as_ref(), + )?), }; let atlas = Arc::new(WgpuAtlas::from_context(context)); @@ -237,6 +247,7 @@ impl WgpuRenderer { surface, config, compositor_gpu, + extra_requirements, atlas, ) } @@ -254,7 +265,7 @@ impl WgpuRenderer { let atlas = Arc::new(WgpuAtlas::from_context(context)); - Self::new_internal(None, context, surface, config, None, atlas) + Self::new_internal(None, context, surface, config, None, None, atlas) } fn new_internal( @@ -263,6 +274,7 @@ impl WgpuRenderer { surface: wgpu::Surface<'static>, config: WgpuSurfaceConfig, compositor_gpu: Option, + extra_requirements: Option, atlas: Arc, ) -> anyhow::Result { let surface_caps = surface.get_capabilities(&context.adapter); @@ -368,6 +380,20 @@ impl WgpuRenderer { ..Default::default() }); + let surface_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("surface_sampler"), + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let surface_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("surface_uniform_buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + 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; @@ -453,6 +479,8 @@ impl WgpuRenderer { pipelines, bind_group_layouts, atlas_sampler, + surface_sampler, + surface_uniform_buffer, globals_buffer, globals_bind_group, path_globals_bind_group, @@ -468,6 +496,7 @@ impl WgpuRenderer { Ok(Self { context: gpu_context, compositor_gpu, + extra_requirements, resources: Some(resources), surface_config, atlas, @@ -591,16 +620,6 @@ impl WgpuRenderer { 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, }, @@ -1066,6 +1085,11 @@ impl WgpuRenderer { self.dual_source_blending } + pub fn gpu_context(&self) -> (Arc, Arc) { + let resources = self.resources(); + (resources.device.clone(), resources.queue.clone()) + } + pub fn gpu_specs(&self) -> GpuSpecs { GpuSpecs { is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu, @@ -1300,10 +1324,8 @@ impl WgpuRenderer { &mut instance_offset, &mut pass, ), - PrimitiveBatch::Surfaces(_surfaces) => { - // Surfaces are macOS-only for video playback - // Not implemented for Linux/wgpu - true + PrimitiveBatch::Surfaces(range) => { + self.draw_surfaces(&scene.surfaces[range], &mut pass) } }; if !ok { @@ -1427,6 +1449,55 @@ impl WgpuRenderer { ) } + fn draw_surfaces(&self, surfaces: &[PaintSurface], pass: &mut wgpu::RenderPass<'_>) -> bool { + let resources = self.resources(); + for surface in surfaces { + let Some(wgpu_texture) = surface.texture.downcast_ref::() else { + continue; + }; + + let texture_view = wgpu_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let params = SurfaceParams { + bounds: surface.bounds.into(), + content_mask: surface.content_mask.bounds.into(), + }; + + resources.queue.write_buffer( + &resources.surface_uniform_buffer, + 0, + bytemuck::bytes_of(¶ms), + ); + + let bind_group = resources + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("surface_bind_group"), + layout: &resources.bind_group_layouts.surfaces, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: resources.surface_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&resources.surface_sampler), + }, + ], + }); + + pass.set_pipeline(&resources.pipelines.surfaces); + pass.set_bind_group(0, &resources.globals_bind_group, &[]); + pass.set_bind_group(1, &bind_group, &[]); + pass.draw(0..4, 0..1); + } + true + } + fn draw_polychrome_sprites( &self, sprites: &[PolychromeSprite], @@ -1808,8 +1879,12 @@ impl WgpuRenderer { 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)?; + let new_context = WgpuContext::new_rejecting_software( + instance, + &surface, + self.compositor_gpu, + self.extra_requirements.as_ref(), + )?; *gpu_context.borrow_mut() = Some(new_context); surface } else { @@ -1833,12 +1908,14 @@ impl WgpuRenderer { self.resources = None; self.atlas.handle_device_lost(context); + let extra_reqs = self.extra_requirements.clone(); *self = Self::new_internal( Some(gpu_context.clone()), context, surface, config, self.compositor_gpu, + extra_reqs, self.atlas.clone(), )?;