gpui: Add wgpu Surface element and GPU device requirements API for Linux (#39)

This commit is contained in:
Leonard Seibold
2026-06-07 01:53:18 -04:00
committed by GitHub
parent d54ef9dcbe
commit 6d043b22e4
13 changed files with 304 additions and 56 deletions
+8
View File
@@ -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<dyn std::any::Any>) {
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<AnyWindowHandle> {
self.platform.active_window()
+47 -6
View File
@@ -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<wgpu::Texture>`)
texture: Arc<dyn std::any::Any + Send + Sync>,
/// Dimensions of the texture in device pixels
size: Size<DevicePixels>,
},
}
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<SurfaceSource>) -> 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<Pixels>,
bounds: Bounds<Pixels>,
_: &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);
}
}
}
}
+13
View File
@@ -242,6 +242,12 @@ pub trait Platform: 'static {
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
/// 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<dyn std::any::Any>) {}
}
/// 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<GpuSpecs>;
/// Returns the GPU context for this window's renderer.
/// The returned `Box` contains `(Arc<wgpu::Device>, Arc<wgpu::Queue>)`.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
fn gpu_context(&self) -> Option<Box<dyn std::any::Any>> {
None
}
fn update_ime_position(&self, _bounds: Bounds<Pixels>);
fn play_system_bell(&self) {}
+4
View File
@@ -723,6 +723,10 @@ pub struct PaintSurface {
pub content_mask: ContentMask<ScaledPixels>,
#[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<dyn std::any::Any + Send + Sync>,
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub texture_size: Size<crate::DevicePixels>,
}
impl From<PaintSurface> for Primitive {
+33
View File
@@ -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<Pixels>,
texture: std::sync::Arc<dyn std::any::Any + Send + Sync>,
texture_size: Size<DevicePixels>,
) {
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<RenderImage>) -> 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<wgpu::Device>, Arc<wgpu::Queue>)`.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn gpu_context(&self) -> Option<Box<dyn std::any::Any>> {
self.platform_window.gpu_context()
}
/// Perform titlebar double-click action.
/// This is macOS specific.
pub fn titlebar_double_click(&self) {
+5
View File
@@ -92,6 +92,7 @@ pub(crate) trait LinuxClient {
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn active_window(&self) -> Option<AnyWindowHandle>;
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>>;
fn set_gpu_requirements(&self, _requirements: Box<dyn std::any::Any>) {}
fn run(&self);
#[cfg(any(feature = "wayland", feature = "x11"))]
@@ -316,6 +317,10 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
self.inner.open_window(handle, options)
}
fn set_gpu_requirements(&self, requirements: Box<dyn std::any::Any>) {
self.inner.set_gpu_requirements(requirements);
}
fn open_url(&self, url: &str) {
self.inner.open_uri(url);
}
@@ -217,6 +217,7 @@ pub(crate) struct WaylandClientState {
globals: Globals,
pub gpu_context: GpuContext,
pub compositor_gpu: Option<CompositorGpuHint>,
pub gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
pinch_gesture: Option<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1>,
@@ -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<dyn std::any::Any>) {
if let Ok(reqs) = requirements.downcast::<gpui_wgpu::WgpuDeviceRequirements>() {
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,
+15 -1
View File
@@ -328,6 +328,7 @@ impl WaylandWindowState {
globals: Globals,
gpu_context: gpui_wgpu::GpuContext,
compositor_gpu: Option<CompositorGpuHint>,
gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
options: WindowParams,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
@@ -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<CompositorGpuHint>,
gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
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<Box<dyn std::any::Any>> {
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() {
+12
View File
@@ -180,6 +180,7 @@ pub struct X11ClientState {
pub(crate) gpu_context: GpuContext,
pub(crate) compositor_gpu: Option<CompositorGpuHint>,
pub(crate) gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
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<dyn std::any::Any>) {
if let Ok(reqs) = requirements.downcast::<gpui_wgpu::WgpuDeviceRequirements>() {
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,
+15 -1
View File
@@ -416,6 +416,7 @@ impl X11WindowState {
executor: ForegroundExecutor,
gpu_context: gpui_wgpu::GpuContext,
compositor_gpu: Option<CompositorGpuHint>,
gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
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<CompositorGpuHint>,
gpu_requirements: Option<gpui_wgpu::WgpuDeviceRequirements>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
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<Box<dyn std::any::Any>> {
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);
+3 -17
View File
@@ -1320,16 +1320,8 @@ struct SurfaceParams {
}
@group(1) @binding(0) var<uniform> surface_locals: SurfaceParams;
@group(1) @binding(1) var t_y: texture_2d<f32>;
@group(1) @binding(2) var t_cb_cr: texture_2d<f32>;
@group(1) @binding(3) var s_surface: sampler;
const ycbcr_to_RGB = mat4x4<f32>(
vec4<f32>( 1.0000f, 1.0000f, 1.0000f, 0.0),
vec4<f32>( 0.0000f, -0.3441f, 1.7720f, 0.0),
vec4<f32>( 1.4020f, -0.7141f, 0.0000f, 0.0),
vec4<f32>(-0.7010f, 0.5291f, -0.8860f, 1.0),
);
@group(1) @binding(1) var t_surface: texture_2d<f32>;
@group(1) @binding(2) var s_surface: sampler;
struct SurfaceVarying {
@builtin(position) position: vec4<f32>,
@@ -1350,15 +1342,9 @@ fn vs_surface(@builtin(vertex_index) vertex_id: u32) -> SurfaceVarying {
@fragment
fn fs_surface(input: SurfaceVarying) -> @location(0) vec4<f32> {
// Alpha clip after using the derivatives.
if (any(input.clip_distances < vec4<f32>(0.0))) {
return vec4<f32>(0.0);
}
let y_cb_cr = vec4<f32>(
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);
}
+37 -8
View File
@@ -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<CompositorGpuHint>,
extra_requirements: Option<&WgpuDeviceRequirements>,
) -> anyhow::Result<Self> {
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<CompositorGpuHint>,
extra_requirements: Option<&WgpuDeviceRequirements>,
) -> anyhow::Result<Self> {
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<CompositorGpuHint>,
reject_software: bool,
extra_requirements: Option<&WgpuDeviceRequirements>,
) -> anyhow::Result<Self> {
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 {
+100 -23
View File
@@ -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<wgpu::RenderPipeline>,
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<CompositorGpuHint>,
/// Application-requested extra wgpu features/limits, stored for device recovery.
#[allow(dead_code)]
extra_requirements: Option<WgpuDeviceRequirements>,
resources: Option<WgpuResources>,
surface_config: wgpu::SurfaceConfiguration,
atlas: Arc<WgpuAtlas>,
@@ -188,6 +192,7 @@ impl WgpuRenderer {
window: &W,
config: WgpuSurfaceConfig,
compositor_gpu: Option<CompositorGpuHint>,
extra_requirements: Option<WgpuDeviceRequirements>,
) -> anyhow::Result<Self>
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<CompositorGpuHint>,
extra_requirements: Option<WgpuDeviceRequirements>,
atlas: Arc<WgpuAtlas>,
) -> anyhow::Result<Self> {
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::<SurfaceParams>() 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::<GlobalParams>() as u64;
let gamma_size = std::mem::size_of::<GammaParams>() 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<wgpu::Device>, Arc<wgpu::Queue>) {
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::<wgpu::Texture>() 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(&params),
);
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(),
)?;