Merge branch 'zero-copy-display'
# Conflicts: # core/dr-gpu/src/adjust.rs # ui/dr-ui/src/develop.rs
This commit is contained in:
+89
-11
@@ -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: &[],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user