Move to wgpu 29, the version Slint can share a device with
Build and test / Desktop (Linux) (push) Failing after 38s
Build and test / Layer separation (push) Successful in 24s
Traceability / Requirement traces (push) Successful in 1m3s
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Android (aarch64) (push) Failing after 9m54s
Build and test / Desktop (Linux) (push) Failing after 38s
Build and test / Layer separation (push) Successful in 24s
Traceability / Requirement traces (push) Successful in 1m3s
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Android (aarch64) (push) Failing after 9m54s
Groundwork for spike S1. Importing a texture into a Slint scene requires it
to come from the *same* `wgpu::Device` Slint renders with, and Slint hands
out a device of the version it was compiled against. Slint 1.17 offers
`unstable-wgpu-28` and `unstable-wgpu-29` and nothing older, so wgpu 23 could
never have met it: two semver-incompatible wgpu crates in one tree are two
distinct types, and the device would not typecheck across the gap.
The version is therefore not a free choice, and the manifest now says so —
Slint and wgpu move together or not at all. The Slint requirement is also
corrected from "1.9" to the 1.17 it has actually been resolving to.
Nothing about the render path changes here. The readback bridge is still in
place and still the display path, so this is verified by the tests that
already existed rather than by anything new: 39 dr-gpu tests, which compare
real pixels off a real device, and 888 across the workspace, all passing.
Zero-copy lands separately and small.
What the six releases cost, in full:
- `ImageCopyTexture`/`ImageCopyBuffer`/`ImageDataLayout` became the
`TexelCopy*` names (24).
- `Instance::new` takes the descriptor by value, and `InstanceDescriptor`
lost its `Default` — it carries a boxed display handle now, so a headless
context says `new_without_display_handle` and means it.
- `request_adapter` returns `Result` rather than `Option` (24).
- `DeviceDescriptor` absorbed the API trace from `request_device`'s second
argument and gained `experimental_features` (25).
- `PipelineLayoutDescriptor` takes `Option<&BindGroupLayout>` per slot, and
`push_constant_ranges` became `immediate_size`.
- `Maintain` became `PollType`, and `poll` is fallible.
Two of those are improvements worth having rather than churn. The error scope
is a guard whose `pop` runs on drop, so an early return from the pipeline
compiler no longer leaves a scope open on the device for whatever ran next to
fall into. And a fallible `poll` reports a lost device (NFR-R7) at the point
it happens, where before the map callback simply never arrived and the
failure surfaced later as a readback that spun out its poll limit.
Still to do for S1: dr-ui renders through `renderer-femtovg`, which is
OpenGL. Texture import needs Slint itself rendering on wgpu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+42
-26
@@ -35,12 +35,15 @@ impl GpuContext {
|
||||
///
|
||||
/// Used by tests and by the Slint path, which supplies its own surface.
|
||||
pub async fn new_headless() -> Result<Self, GpuError> {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
// Vulkan on both targets (D1). GL is allowed as a fallback so a
|
||||
// machine without a Vulkan loader still runs the tests.
|
||||
backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
|
||||
..Default::default()
|
||||
});
|
||||
// `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.
|
||||
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;
|
||||
let instance = wgpu::Instance::new(descriptor);
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
@@ -49,7 +52,10 @@ impl GpuContext {
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.ok_or(GpuError::NoAdapter)?;
|
||||
// A `Result` since wgpu 24, where it was an `Option`. The error
|
||||
// says which backends were tried, which is worth more than the
|
||||
// bare "no adapter" this used to report.
|
||||
.map_err(|_| GpuError::NoAdapter)?;
|
||||
|
||||
let adapter_info = adapter.get_info();
|
||||
log::info!(
|
||||
@@ -60,19 +66,22 @@ impl GpuContext {
|
||||
);
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: Some("darkroom-device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
// Defaults, not `downlevel_defaults`: storage textures
|
||||
// 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(),
|
||||
memory_hints: wgpu::MemoryHints::Performance,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("darkroom-device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
// Defaults, not `downlevel_defaults`: storage textures
|
||||
// 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(),
|
||||
memory_hints: wgpu::MemoryHints::Performance,
|
||||
// Nothing behind a feature flag wgpu itself calls unstable —
|
||||
// the pipeline is ordinary compute and storage textures.
|
||||
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
||||
// The API trace, absorbed into the descriptor in wgpu 25 from
|
||||
// the second argument this call used to take.
|
||||
trace: wgpu::Trace::Off,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| GpuError::DeviceRequest(e.to_string()))?;
|
||||
|
||||
@@ -182,8 +191,8 @@ impl RenderTarget {
|
||||
.device
|
||||
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("render-target-layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let pipeline = ctx
|
||||
@@ -370,15 +379,15 @@ impl RenderTarget {
|
||||
|
||||
let mut enc = self.ctx.device.create_command_encoder(&Default::default());
|
||||
enc.copy_texture_to_buffer(
|
||||
wgpu::ImageCopyTexture {
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::ImageCopyBuffer {
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: buf,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(padded),
|
||||
rows_per_image: Some(self.height),
|
||||
@@ -397,7 +406,14 @@ impl RenderTarget {
|
||||
slice.map_async(wgpu::MapMode::Read, move |r| {
|
||||
let _ = tx.send(r);
|
||||
});
|
||||
self.ctx.device.poll(wgpu::Maintain::Wait);
|
||||
// Fallible since wgpu 26, and worth propagating rather than ignoring:
|
||||
// the failure it reports is a lost device (NFR-R7), and without this
|
||||
// the map callback below simply never arrives and the error surfaces
|
||||
// as a timeout somewhere less informative.
|
||||
self.ctx
|
||||
.device
|
||||
.poll(wgpu::PollType::wait_indefinitely())
|
||||
.map_err(|e| GpuError::Readback(e.to_string()))?;
|
||||
rx.recv()
|
||||
.map_err(|e| GpuError::Readback(e.to_string()))?
|
||||
.map_err(|e| GpuError::Readback(e.to_string()))?;
|
||||
|
||||
Reference in New Issue
Block a user