Merge branch 'zero-copy-display'

# Conflicts:
#	core/dr-gpu/src/adjust.rs
#	ui/dr-ui/src/develop.rs
This commit is contained in:
2026-08-17 10:04:14 +02:00
11 changed files with 564 additions and 139 deletions
+89 -11
View File
@@ -6,6 +6,12 @@
//!
//! Deliberately free of UI dependencies (ARCH §6.5a). The texture is handed
//! out as a `wgpu::Texture`; who composites it is not this crate's concern.
//!
//! That independence is why [`GpuContext::new_shared`] hands back the raw
//! instance and adapter rather than talking to a compositor itself: the
//! compositor will only sample a texture that came from the device *it* draws
//! with, so somebody has to make one device for both — but it does not have to
//! be this crate, and this crate must not know who it is.
use std::sync::Arc;
@@ -38,19 +44,72 @@ pub struct GpuContext {
adapter_info: wgpu::AdapterInfo,
}
/// TRACES: FR-DSP-1 | AC-8
/// One device, opened so that a compositor can be made to share it.
///
/// The texture the adjust pass writes only reaches the screen without a copy
/// if the compositor is drawing with the *same* `wgpu::Device` — two devices
/// are two address spaces, and a texture from one is not a texture the other
/// can sample. So the device cannot be an implementation detail of either
/// side; it has to be made once and handed to both.
///
/// [`Self::ctx`] is what the compute passes want. The instance and adapter are
/// what a compositor wants in order to adopt the same setup — Slint's
/// `WGPUConfiguration::Manual` asks for all four pieces — and they are handed
/// out raw rather than wrapped, because naming Slint here would put a UI
/// dependency in the one crate that must not have one (ARCH §6.5a).
pub struct SharedGpu {
/// The context every compute pass in this crate runs on.
pub ctx: GpuContext,
/// The instance the compositor will create its window surface from.
pub instance: wgpu::Instance,
/// The adapter [`Self::ctx`]'s device came from.
pub adapter: wgpu::Adapter,
}
impl GpuContext {
/// Create a headless context — no surface, no window.
///
/// Used by tests and by the Slint path, which supplies its own surface.
/// Used by tests, by the examples, and by anything that only needs to
/// compute. A context opened this way cannot be shared with a compositor:
/// see [`Self::new_shared`] for that, and for why the difference matters.
pub async fn new_headless() -> Result<Self, GpuError> {
// GL is allowed alongside Vulkan here and nowhere else: a machine with
// no Vulkan loader should still run the tests, and a headless context
// never has to produce a window surface — which is precisely the thing
// the GL backend cannot do from an instance opened without a display
// handle.
Self::open(wgpu::Backends::VULKAN | wgpu::Backends::GL)
.await
.map(|shared| shared.ctx)
}
/// TRACES: FR-DSP-1 | AC-8
/// Open a device intended to be shared with the compositor.
///
/// Vulkan only, unlike [`Self::new_headless`]. The caller will hand the
/// instance to a compositor that has to create a *window surface* from it,
/// and wgpu's GL backend reaches its display through EGL at instance
/// creation — an instance opened without a display handle, which is the
/// only kind available before a window exists, cannot then produce a GL
/// surface. Vulkan takes the window handle at surface creation instead, so
/// it is the only backend this order of operations permits.
///
/// A machine with no Vulkan therefore gets no shared device, and the
/// caller is expected to carry on without the develop path rather than
/// refuse to start.
pub async fn new_shared() -> Result<SharedGpu, GpuError> {
// Vulkan on both targets (D1), and here it is not merely the
// preference — see above.
Self::open(wgpu::Backends::VULKAN).await
}
async fn open(backends: wgpu::Backends) -> Result<SharedGpu, GpuError> {
// `new_without_display_handle` rather than a struct literal: the
// descriptor carries a boxed display handle and so has no `Default`,
// and a headless context is precisely the case with no display to
// hand it.
// and there is no window yet to take one from in either case.
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
// Vulkan on both targets (D1). GL is allowed as a fallback so a
// machine without a Vulkan loader still runs the tests.
descriptor.backends = wgpu::Backends::VULKAN | wgpu::Backends::GL;
descriptor.backends = backends;
let instance = wgpu::Instance::new(descriptor);
let adapter = instance
@@ -81,7 +140,14 @@ impl GpuContext {
// in compute shaders are required, and the downlevel tier
// does not guarantee them. This is effectively our GPU
// floor (NFR-COMPAT-1).
required_limits: wgpu::Limits::default(),
//
// `using_resolution` raises only the texture-dimension limits,
// to whatever this adapter actually offers. That matters once
// a compositor shares this device: the default ceiling is
// 8192, and a swapchain image for a large or scaled display
// can exceed it — a limit we chose for our own compute passes
// would otherwise silently cap somebody else's window.
required_limits: wgpu::Limits::default().using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::Performance,
// Nothing behind a feature flag wgpu itself calls unstable —
// the pipeline is ordinary compute and storage textures.
@@ -93,10 +159,14 @@ impl GpuContext {
.await
.map_err(|e| GpuError::DeviceRequest(e.to_string()))?;
Ok(Self {
device: Arc::new(device),
queue: Arc::new(queue),
adapter_info,
Ok(SharedGpu {
ctx: Self {
device: Arc::new(device),
queue: Arc::new(queue),
adapter_info,
},
instance,
adapter,
})
}
@@ -264,8 +334,16 @@ impl RenderTarget {
// STORAGE_BINDING to write from compute; TEXTURE_BINDING so the
// compositor can sample it. COPY_SRC exists only for tests —
// production never reads this back (ARCH §6.1).
//
// RENDER_ATTACHMENT is not something this pass ever uses. It is
// there because Slint refuses to import a texture without it
// (`TextureImportError::InvalidUsage`), the compositor having to
// assume it may need to draw into what it was given. Declaring an
// unused capability costs an allocation flag and buys the whole
// zero-copy path, so it is a cheap price for AC-8.
usage: wgpu::TextureUsages::STORAGE_BINDING
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});