Add device destroy method (#4163)

Plus tests that ensure that an invalid device behaves correctly.
Mostly a stub implementation otherwise.
This commit is contained in:
Brad Werth 2023-09-27 05:34:23 -07:00 committed by GitHub
parent 8d32dc2b47
commit 57f8757fad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 601 additions and 8 deletions

View File

@ -102,6 +102,7 @@ By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147)
- `wgpu::CreateSurfaceError` and `wgpu::RequestDeviceError` now give details of the failure, but no longer implement `PartialEq` and cannot be constructed. By @kpreid in [#4066](https://github.com/gfx-rs/wgpu/pull/4066) and [#4145](https://github.com/gfx-rs/wgpu/pull/4145)
- Make `WGPU_POWER_PREF=none` a valid value. By @fornwall in [4076](https://github.com/gfx-rs/wgpu/pull/4076)
- Support dual source blending in OpenGL ES, Metal, Vulkan & DX12. By @freqmod in [4022](https://github.com/gfx-rs/wgpu/pull/4022)
- Add stub support for device destroy and device validity. By @bradwerth in [4163](https://github.com/gfx-rs/wgpu/pull/4163)
#### Vulkan

View File

@ -1,6 +1,6 @@
use wasm_bindgen_test::*;
use wgpu_test::{initialize_test, FailureCase, TestParameters};
use wgpu_test::{fail, initialize_test, FailureCase, TestParameters};
#[test]
#[wasm_bindgen_test]
@ -91,3 +91,355 @@ async fn request_device_error_message() {
}
assert!(device_error.contains(expected), "{device_error}");
}
#[test]
fn device_destroy_then_more() {
// This is a test of device behavior after device.destroy. Specifically, all operations
// should trigger errors since the device is lost.
//
// On DX12 this test fails with a validation error in the very artifical actions taken
// after lose the device. The error is "ID3D12CommandAllocator::Reset: The command
// allocator cannot be reset because a command list is currently being recorded with the
// allocator." That may indicate that DX12 doesn't like opened command buffers staying
// open even after they return an error. For now, this test is skipped on DX12.
//
// The DX12 issue may be related to https://github.com/gfx-rs/wgpu/issues/3193.
initialize_test(
TestParameters::default()
.features(wgpu::Features::CLEAR_TEXTURE)
.skip(FailureCase::backend(wgpu::Backends::DX12)),
|ctx| {
// Create some resources on the device that we will attempt to use *after* losing
// the device.
// Create some 512 x 512 2D textures.
let texture_extent = wgpu::Extent3d {
width: 512,
height: 512,
depth_or_array_layers: 1,
};
let texture_for_view = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: texture_extent,
mip_level_count: 2,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg8Uint,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default());
let texture_for_read = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: texture_extent,
mip_level_count: 2,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg8Uint,
usage: wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let texture_for_write = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: texture_extent,
mip_level_count: 2,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg8Uint,
usage: wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
// Create some buffers.
let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 256,
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 256,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
// Create a bind group layout.
let bind_group_layout =
ctx.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[],
});
// Create a shader module.
let shader_module = ctx
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")),
});
// Create some command encoders.
let mut encoder_for_clear = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_compute_pass = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_render_pass = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_buffer_buffer_copy = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_buffer_texture_copy = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_texture_buffer_copy = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut encoder_for_texture_texture_copy = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
// Destroy the device. This will cause all other requests to return some variation of
// a device invalid error.
ctx.device.destroy();
// TODO: verify the following operations will return an invalid device error:
// * Run a compute pass
// * Run a render pass
// * Finish a render bundle encoder
// * Create a texture from HAL
// * Create a buffer from HAL
// * Create a sampler
// * Validate a surface configuration
// * Start capture
// * Stop capture
// * Buffer map
// TODO: figure out how to structure a test around these operations which panic when
// the device is invalid:
// * device.features()
// * device.limits()
// * device.downlevel_properties()
// * device.create_query_set()
// TODO: change these fail calls to check for the specific errors which indicate that
// the device is not valid.
// Creating a commmand encoder should fail.
fail(&ctx.device, || {
ctx.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
});
// Creating a buffer should fail.
fail(&ctx.device, || {
ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 256,
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
});
// Creating a texture should fail.
fail(&ctx.device, || {
ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: 512,
height: 512,
depth_or_array_layers: 1,
},
mip_level_count: 2,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg8Uint,
usage: wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
});
// Texture clear should fail.
fail(&ctx.device, || {
encoder_for_clear.clear_texture(
&texture_for_write,
&wgpu::ImageSubresourceRange {
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
},
);
});
// Creating a compute pass should fail.
fail(&ctx.device, || {
encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: None,
timestamp_writes: None,
});
});
// Creating a render pass should fail.
fail(&ctx.device, || {
encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations::default(),
resolve_target: None,
view: &target_view,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
});
// Copying a buffer to a buffer should fail.
fail(&ctx.device, || {
encoder_for_buffer_buffer_copy.copy_buffer_to_buffer(
&buffer_source,
0,
&buffer_dest,
0,
256,
);
});
// Copying a buffer to a texture should fail.
fail(&ctx.device, || {
encoder_for_buffer_texture_copy.copy_buffer_to_texture(
wgpu::ImageCopyBuffer {
buffer: &buffer_source,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: None,
},
},
texture_for_write.as_image_copy(),
texture_extent,
);
});
// Copying a texture to a buffer should fail.
fail(&ctx.device, || {
encoder_for_texture_buffer_copy.copy_texture_to_buffer(
texture_for_read.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &buffer_source,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: None,
},
},
texture_extent,
);
});
// Copying a texture to a texture should fail.
fail(&ctx.device, || {
encoder_for_texture_texture_copy.copy_texture_to_texture(
texture_for_read.as_image_copy(),
texture_for_write.as_image_copy(),
texture_extent,
);
});
// Creating a bind group layout should fail.
fail(&ctx.device, || {
ctx.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[],
});
});
// Creating a bind group should fail.
fail(&ctx.device, || {
ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
buffer_source.as_entire_buffer_binding(),
),
}],
});
});
// Creating a pipeline layout should fail.
fail(&ctx.device, || {
ctx.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
});
// Creating a shader module should fail.
fail(&ctx.device, || {
ctx.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")),
});
});
// Creating a shader module spirv should fail.
fail(&ctx.device, || unsafe {
ctx.device
.create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV {
label: None,
source: std::borrow::Cow::Borrowed(&[]),
});
});
// Creating a render pipeline should fail.
fail(&ctx.device, || {
ctx.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: None,
vertex: wgpu::VertexState {
module: &shader_module,
entry_point: "",
buffers: &[],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: None,
multiview: None,
});
});
// Creating a compute pipeline should fail.
fail(&ctx.device, || {
ctx.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: None,
layout: None,
module: &shader_module,
entry_point: "",
});
});
},
)
}

View File

@ -219,6 +219,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(ClearError::InvalidDevice(cmd_buf.device_id.value.0));
}
clear_texture(
&*texture_guard,

View File

@ -16,6 +16,7 @@ use crate::{
hal_api::HalApi,
hub::Token,
id,
id::DeviceId,
identity::GlobalIdentityHandlerFactory,
init_tracker::MemoryInitKind,
pipeline,
@ -193,6 +194,8 @@ pub enum ComputePassErrorInner {
Encoder(#[from] CommandEncoderError),
#[error("Bind group {0:?} is invalid")]
InvalidBindGroup(id::BindGroupId),
#[error("Device {0:?} is invalid")]
InvalidDevice(DeviceId),
#[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")]
BindGroupIndexOutOfRange { index: u32, max: u32 },
#[error("Compute pipeline {0:?} is invalid")]
@ -390,6 +393,12 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let raw = cmd_buf.encoder.open();
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(ComputePassErrorInner::InvalidDevice(
cmd_buf.device_id.value.0,
))
.map_pass_err(init_scope);
}
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {

View File

@ -18,6 +18,7 @@ use crate::{
hal_api::HalApi,
hub::Token,
id,
id::DeviceId,
identity::GlobalIdentityHandlerFactory,
init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction},
pipeline::{self, PipelineFlags},
@ -523,6 +524,8 @@ pub enum RenderPassErrorInner {
ColorAttachment(#[from] ColorAttachmentError),
#[error(transparent)]
Encoder(#[from] CommandEncoderError),
#[error("Device {0:?} is invalid")]
InvalidDevice(DeviceId),
#[error("Attachment texture view {0:?} is invalid")]
InvalidAttachment(id::TextureViewId),
#[error("The format of the depth-stencil attachment ({0:?}) is not a depth-stencil format")]
@ -1346,6 +1349,12 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(RenderPassErrorInner::InvalidDevice(
cmd_buf.device_id.value.0,
))
.map_pass_err(init_scope);
}
cmd_buf.encoder.open_pass(base.label);
let (bundle_guard, mut token) = hub.render_bundles.read(&mut token);

View File

@ -8,7 +8,7 @@ use crate::{
global::Global,
hal_api::HalApi,
hub::Token,
id::{BufferId, CommandEncoderId, TextureId, Valid},
id::{BufferId, CommandEncoderId, DeviceId, TextureId, Valid},
identity::GlobalIdentityHandlerFactory,
init_tracker::{
has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange,
@ -40,6 +40,8 @@ pub enum CopySide {
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum TransferError {
#[error("Device {0:?} is invalid")]
InvalidDevice(DeviceId),
#[error("Buffer {0:?} is invalid or destroyed")]
InvalidBuffer(BufferId),
#[error("Texture {0:?} is invalid or destroyed")]
@ -569,6 +571,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let (buffer_guard, _) = hub.buffers.read(&mut token);
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into());
}
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
@ -714,6 +719,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let (texture_guard, _) = hub.textures.read(&mut token);
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into());
}
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
@ -858,6 +866,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let (texture_guard, _) = hub.textures.read(&mut token);
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into());
}
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
@ -1020,6 +1031,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let (texture_guard, _) = hub.textures.read(&mut token);
let device = &device_guard[cmd_buf.device_id.value];
if !device.is_valid() {
return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into());
}
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {

View File

@ -102,6 +102,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut token = Token::root();
let (device_guard, _) = hub.devices.read(&mut token);
let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
if !device.valid {
return Err(InvalidDevice);
}
Ok(device.features)
}
@ -114,6 +117,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut token = Token::root();
let (device_guard, _) = hub.devices.read(&mut token);
let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
if !device.valid {
return Err(InvalidDevice);
}
Ok(device.limits.clone())
}
@ -126,6 +132,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut token = Token::root();
let (device_guard, _) = hub.devices.read(&mut token);
let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
if !device.valid {
return Err(InvalidDevice);
}
Ok(device.downlevel.clone())
}
@ -148,6 +157,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
if desc.usage.is_empty() {
// Per spec, `usage` must not be zero.
@ -586,6 +598,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -641,6 +656,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
// NB: Any change done through the raw texture handle will not be
// recorded in the replay
@ -717,6 +735,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
// NB: Any change done through the raw buffer handle will not be
// recorded in the replay
@ -978,6 +999,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -1056,6 +1081,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -1170,6 +1199,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -1249,6 +1282,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -1351,6 +1388,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
let mut trace = trace.lock();
@ -1416,6 +1457,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
let mut trace = trace.lock();
@ -1486,6 +1531,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid,
};
if !device.valid {
break DeviceError::Invalid;
}
let dev_stored = Stored {
value: id::Valid(device_id),
ref_count: device.life_guard.add_ref(),
@ -1579,6 +1628,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break command::RenderBundleError::INVALID_DEVICE,
};
if !device.valid {
break command::RenderBundleError::INVALID_DEVICE;
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace.lock().add(trace::Action::CreateRenderBundle {
@ -1661,6 +1714,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace.lock().add(trace::Action::CreateQuerySet {
@ -1750,6 +1807,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
let adapter = &adapter_guard[device.adapter_id.value];
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
@ -1893,6 +1954,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace.lock().add(trace::Action::CreateComputePipeline {
@ -2141,6 +2206,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(device) => device,
Err(_) => break DeviceError::Invalid.into(),
};
if !device.valid {
break DeviceError::Invalid.into();
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
trace
@ -2405,6 +2474,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut token = Token::root();
let (device_guard, _) = hub.devices.read(&mut token);
if let Ok(device) = device_guard.get(id) {
if !device.valid {
return;
}
unsafe { device.raw.start_capture() };
}
}
@ -2414,6 +2486,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut token = Token::root();
let (device_guard, _) = hub.devices.read(&mut token);
if let Ok(device) = device_guard.get(id) {
if !device.valid {
return;
}
unsafe { device.raw.stop_capture() };
}
}
@ -2435,6 +2510,46 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}
}
pub fn device_destroy<A: HalApi>(&self, device_id: DeviceId) {
let hub = A::hub(self);
let mut token = Token::root();
let (mut device_guard, _) = hub.devices.write(&mut token);
if let Ok(device) = device_guard.get_mut(device_id) {
// Follow the steps at
// https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy.
// It's legal to call destroy multiple times, but if the device
// is already invalid, there's nothing more to do. There's also
// no need to return an error.
if !device.valid {
return;
}
// The last part of destroy is to lose the device. The spec says
// delay that until all "currently-enqueued operations on any
// queue on this device are completed."
// TODO: implement this delay.
// Finish by losing the device.
// TODO: associate this "destroyed" reason more tightly with
// the GPUDeviceLostReason defined in webgpu.idl.
device.lose(Some("destroyed"));
}
}
pub fn device_lose<A: HalApi>(&self, device_id: DeviceId, reason: Option<&str>) {
let hub = A::hub(self);
let mut token = Token::root();
let (mut device_guard, _) = hub.devices.write(&mut token);
if let Ok(device) = device_guard.get_mut(device_id) {
device.lose(reason);
}
}
/// Exit the unreferenced, inactive device `device_id`.
fn exit_device<A: HalApi>(&self, device_id: DeviceId) {
let hub = A::hub(self);
@ -2519,6 +2634,11 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}
};
let device = &device_guard[buffer.device_id.value];
if !device.valid {
return Err((op, BufferAccessError::Invalid));
}
if let Err(e) = check_buffer_usage(buffer.usage, pub_usage) {
return Err((op, e.into()));
}
@ -2559,8 +2679,6 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
};
log::debug!("Buffer {:?} map state -> Waiting", buffer_id);
let device = &device_guard[buffer.device_id.value];
let ret = (buffer.device_id.value, buffer.life_guard.add_ref());
let mut trackers = device.trackers.lock();
@ -2573,7 +2691,6 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
};
let device = &device_guard[device_id];
device
.lock_life(&mut token)
.map(id::Valid(buffer_id), ref_count);

View File

@ -292,11 +292,11 @@ pub struct InvalidDevice;
#[derive(Clone, Debug, Error)]
pub enum DeviceError {
#[error("Parent device is invalid")]
#[error("Parent device is invalid.")]
Invalid,
#[error("Parent device is lost")]
Lost,
#[error("Not enough memory left")]
#[error("Not enough memory left.")]
OutOfMemory,
#[error("Creation of a resource failed for a reason other than running out of memory.")]
ResourceCreationFailed,
@ -305,7 +305,7 @@ pub enum DeviceError {
impl From<hal::DeviceError> for DeviceError {
fn from(error: hal::DeviceError) -> Self {
match error {
hal::DeviceError::Lost => DeviceError::Lost,
hal::DeviceError::Lost => DeviceError::Invalid,
hal::DeviceError::OutOfMemory => DeviceError::OutOfMemory,
hal::DeviceError::ResourceCreationFailed => DeviceError::ResourceCreationFailed,
}

View File

@ -72,6 +72,19 @@ pub struct Device<A: HalApi> {
pub(crate) active_submission_index: SubmissionIndex,
pub(super) fence: A::Fence,
/// Is this device valid? Valid is closely associated with "lose the device",
/// which can be triggered by various methods, including at the end of device
/// destroy, and by any GPU errors that cause us to no longer trust the state
/// of the device. Ideally we would like to fold valid into the storage of
/// the device itself (for example as an Error enum), but unfortunately we
/// need to continue to be able to retrieve the device in poll_devices to
/// determine if it can be dropped. If our internal accesses of devices were
/// done through ref-counted references and external accesses checked for
/// Error enums, we wouldn't need this. For now, we need it. All the call
/// sites where we check it are areas that should be revisited if we start
/// using ref-counted references for internal access.
pub(crate) valid: bool,
/// All live resources allocated with this [`Device`].
///
/// Has to be locked temporarily only (locked last)
@ -189,6 +202,7 @@ impl<A: HalApi> Device<A> {
command_allocator: Mutex::new(com_alloc),
active_submission_index: 0,
fence,
valid: true,
trackers: Mutex::new(Tracker::new()),
life_tracker: Mutex::new(life::LifetimeTracker::new()),
temp_suspected: life::SuspectedResources::default(),
@ -214,6 +228,10 @@ impl<A: HalApi> Device<A> {
})
}
pub fn is_valid(&self) -> bool {
self.valid
}
pub(super) fn lock_life<'this, 'token: 'this>(
&'this self,
//TODO: fix this - the token has to be borrowed for the lock
@ -3155,6 +3173,27 @@ impl<A: HalApi> Device<A> {
desc: desc.map_label(|_| ()),
})
}
pub(crate) fn lose(&mut self, _reason: Option<&str>) {
// Follow the steps at https://gpuweb.github.io/gpuweb/#lose-the-device.
// Mark the device explicitly as invalid. This is checked in various
// places to prevent new work from being submitted.
self.valid = false;
// The following steps remain in "lose the device":
// 1) Resolve the GPUDevice device.lost promise.
// TODO: triggger this passively or actively, and supply the reason.
// 2) Complete any outstanding mapAsync() steps.
// 3) Complete any outstanding onSubmittedWorkDone() steps.
// These parts are passively accomplished by setting valid to false,
// since that will prevent any new work from being added to the queues.
// Future calls to poll_devices will continue to check the work queues
// until they are cleared, and then drop the device.
}
}
impl<A: HalApi> Device<A> {

View File

@ -138,6 +138,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let (device, config) = match surface.presentation {
Some(ref present) => {
let device = &device_guard[present.device_id.value];
if !device.is_valid() {
return Err(DeviceError::Invalid.into());
}
(device, present.config.clone())
}
None => return Err(SurfaceError::NotConfigured),
@ -290,6 +293,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
};
let device = &mut device_guard[present.device_id.value];
if !device.is_valid() {
return Err(DeviceError::Invalid.into());
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {
@ -376,6 +382,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
};
let device = &mut device_guard[present.device_id.value];
if !device.is_valid() {
return Err(DeviceError::Invalid.into());
}
#[cfg(feature = "trace")]
if let Some(ref trace) = device.trace {

View File

@ -1439,6 +1439,15 @@ impl crate::Context for Context {
wgc::gfx_select!(device => global.device_drop(*device));
}
fn device_destroy(&self, device: &Self::DeviceId, _device_data: &Self::DeviceData) {
let global = &self.0;
wgc::gfx_select!(device => global.device_destroy(*device));
}
fn device_lose(&self, device: &Self::DeviceId, _device_data: &Self::DeviceData) {
// TODO: accept a reason, and pass it to device_lose.
let global = &self.0;
wgc::gfx_select!(device => global.device_lose(*device, None));
}
fn device_poll(
&self,
device: &Self::DeviceId,

View File

@ -1914,6 +1914,16 @@ impl crate::context::Context for Context {
// Device is dropped automatically
}
fn device_destroy(&self, _buffer: &Self::DeviceId, device_data: &Self::DeviceData) {
device_data.0.destroy();
}
fn device_lose(&self, _device: &Self::DeviceId, _device_data: &Self::DeviceData) {
// TODO: figure out the GPUDevice implementation of this, including resolving
// the device.lost promise, which will require a different invocation pattern
// with a callback.
}
fn device_poll(
&self,
_device: &Self::DeviceId,

View File

@ -269,6 +269,8 @@ pub trait Context: Debug + WasmNotSend + WasmNotSync + Sized {
desc: &RenderBundleEncoderDescriptor,
) -> (Self::RenderBundleEncoderId, Self::RenderBundleEncoderData);
fn device_drop(&self, device: &Self::DeviceId, device_data: &Self::DeviceData);
fn device_destroy(&self, device: &Self::DeviceId, device_data: &Self::DeviceData);
fn device_lose(&self, device: &Self::DeviceId, device_data: &Self::DeviceData);
fn device_poll(
&self,
device: &Self::DeviceId,
@ -1363,6 +1365,8 @@ pub(crate) trait DynContext: Debug + WasmNotSend + WasmNotSync {
desc: &RenderBundleEncoderDescriptor,
) -> (ObjectId, Box<crate::Data>);
fn device_drop(&self, device: &ObjectId, device_data: &crate::Data);
fn device_destroy(&self, device: &ObjectId, device_data: &crate::Data);
fn device_lose(&self, device: &ObjectId, device_data: &crate::Data);
fn device_poll(&self, device: &ObjectId, device_data: &crate::Data, maintain: Maintain)
-> bool;
fn device_on_uncaptured_error(
@ -2424,6 +2428,18 @@ where
Context::device_drop(self, &device, device_data)
}
fn device_destroy(&self, device: &ObjectId, device_data: &crate::Data) {
let device = <T::DeviceId>::from(*device);
let device_data = downcast_ref(device_data);
Context::device_destroy(self, &device, device_data)
}
fn device_lose(&self, device: &ObjectId, device_data: &crate::Data) {
let device = <T::DeviceId>::from(*device);
let device_data = downcast_ref(device_data);
Context::device_lose(self, &device, device_data)
}
fn device_poll(
&self,
device: &ObjectId,

View File

@ -2767,6 +2767,11 @@ impl Device {
)
}
}
/// Destroy this device.
pub fn destroy(&self) {
DynContext::device_destroy(&*self.context, &self.id, self.data.as_ref())
}
}
impl Drop for Device {