Skip to content

Commit e8840ff

Browse files
committed
Keep the value in its storage after destroy
in gfx-rs#4657 the destroy implementation was made to remove the value from the storage and leave an error variant in its place. Unfortunately this causes some issues with the tracking code which expects the ID to be unregistered after the value has been fully destroyed, even if the latter is not in storage anymore. To work around that, this commit adds a `Destroyed` variant in storage which keeps the value so that the tracking behavior is preserved while still making sure that most accesses to the destroyed resource lead to validation errors. ... Except for submitted command buffers that need to be consumed right away. These are replaced with `Element::Error` like before this commit.
1 parent 2e7fd75 commit e8840ff

File tree

5 files changed

+123
-35
lines changed

5 files changed

+123
-35
lines changed

tests/tests/life_cycle.rs

+49-17
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,66 @@
1-
use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters};
1+
use wgpu_test::{fail, gpu_test, GpuTestConfiguration};
22

33
#[gpu_test]
4-
static BUFFER_DESTROY: GpuTestConfiguration = GpuTestConfiguration::new()
5-
.parameters(TestParameters::default().expect_fail(FailureCase::always()))
6-
.run_sync(|ctx| {
7-
let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
8-
label: None,
9-
size: 256,
10-
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
11-
mapped_at_creation: false,
12-
});
4+
static BUFFER_DESTROY: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| {
5+
let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
6+
label: None,
7+
size: 256,
8+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
9+
mapped_at_creation: false,
10+
});
1311

14-
buffer.destroy();
12+
buffer.destroy();
1513

16-
buffer.destroy();
14+
buffer.destroy();
1715

18-
ctx.device.poll(wgpu::MaintainBase::Wait);
16+
ctx.device.poll(wgpu::MaintainBase::Wait);
1917

18+
fail(&ctx.device, &|| {
2019
buffer
2120
.slice(..)
2221
.map_async(wgpu::MapMode::Write, move |_| {});
22+
});
2323

24-
buffer.destroy();
24+
buffer.destroy();
2525

26-
ctx.device.poll(wgpu::MaintainBase::Wait);
26+
ctx.device.poll(wgpu::MaintainBase::Wait);
2727

28-
buffer.destroy();
28+
buffer.destroy();
2929

30+
buffer.destroy();
31+
32+
let descriptor = wgpu::BufferDescriptor {
33+
label: None,
34+
size: 256,
35+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
36+
mapped_at_creation: false,
37+
};
38+
39+
// Scopes to mix up the drop/poll ordering.
40+
{
41+
let buffer = ctx.device.create_buffer(&descriptor);
3042
buffer.destroy();
31-
});
43+
let buffer = ctx.device.create_buffer(&descriptor);
44+
buffer.destroy();
45+
}
46+
let buffer = ctx.device.create_buffer(&descriptor);
47+
buffer.destroy();
48+
ctx.device.poll(wgpu::MaintainBase::Wait);
49+
let buffer = ctx.device.create_buffer(&descriptor);
50+
buffer.destroy();
51+
{
52+
let buffer = ctx.device.create_buffer(&descriptor);
53+
buffer.destroy();
54+
let buffer = ctx.device.create_buffer(&descriptor);
55+
buffer.destroy();
56+
let buffer = ctx.device.create_buffer(&descriptor);
57+
ctx.device.poll(wgpu::MaintainBase::Wait);
58+
buffer.destroy();
59+
}
60+
let buffer = ctx.device.create_buffer(&descriptor);
61+
buffer.destroy();
62+
ctx.device.poll(wgpu::MaintainBase::Wait);
63+
});
3264

3365
#[gpu_test]
3466
static TEXTURE_DESTROY: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| {

wgpu-core/src/device/global.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
495495

496496
log::trace!("Buffer::destroy {buffer_id:?}");
497497
let (mut buffer_guard, _) = hub.buffers.write(&mut token);
498-
let mut buffer = buffer_guard
499-
.take_and_mark_destroyed(buffer_id)
498+
let buffer = buffer_guard
499+
.get_and_mark_destroyed(buffer_id)
500500
.map_err(|_| resource::DestroyError::Invalid)?;
501501

502502
let device = &mut device_guard[buffer.device_id.value];
@@ -506,7 +506,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
506506
| &BufferMapState::Init { .. }
507507
| &BufferMapState::Active { .. }
508508
=> {
509-
self.buffer_unmap_inner(buffer_id, &mut buffer, device)
509+
self.buffer_unmap_inner(buffer_id, buffer, device)
510510
.unwrap_or(None)
511511
}
512512
_ => None,
@@ -551,7 +551,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
551551

552552
let (ref_count, last_submit_index, device_id) = {
553553
let (mut buffer_guard, _) = hub.buffers.write(&mut token);
554-
match buffer_guard.get_mut(buffer_id) {
554+
match buffer_guard.get_occupied_or_destroyed(buffer_id) {
555555
Ok(buffer) => {
556556
let ref_count = buffer.life_guard.ref_count.take().unwrap();
557557
let last_submit_index = buffer.life_guard.life_count();
@@ -800,8 +800,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
800800
let (mut device_guard, mut token) = hub.devices.write(&mut token);
801801

802802
let (mut texture_guard, _) = hub.textures.write(&mut token);
803-
let mut texture = texture_guard
804-
.take_and_mark_destroyed(texture_id)
803+
let texture = texture_guard
804+
.get_and_mark_destroyed(texture_id)
805805
.map_err(|_| resource::DestroyError::Invalid)?;
806806

807807
let device = &mut device_guard[texture.device_id.value];
@@ -855,7 +855,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
855855

856856
let (ref_count, last_submit_index, device_id) = {
857857
let (mut texture_guard, _) = hub.textures.write(&mut token);
858-
match texture_guard.get_mut(texture_id) {
858+
match texture_guard.get_occupied_or_destroyed(texture_id) {
859859
Ok(texture) => {
860860
let ref_count = texture.life_guard.ref_count.take().unwrap();
861861
let last_submit_index = texture.life_guard.life_count();

wgpu-core/src/device/queue.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1110,9 +1110,10 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
11101110
// it, so make sure to set_size on it.
11111111
used_surface_textures.set_size(texture_guard.len());
11121112

1113+
// TODO: ideally we would use `get_and_mark_destroyed` but the code here
1114+
// wants to consume the command buffer.
11131115
#[allow(unused_mut)]
1114-
let mut cmdbuf = match command_buffer_guard.take_and_mark_destroyed(cmb_id)
1115-
{
1116+
let mut cmdbuf = match command_buffer_guard.replace_with_error(cmb_id) {
11161117
Ok(cmdbuf) => cmdbuf,
11171118
Err(_) => continue,
11181119
};

wgpu-core/src/identity.rs

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ impl IdentityManager {
5353
/// The backend is incorporated into the id, so that ids allocated with
5454
/// different `backend` values are always distinct.
5555
pub fn alloc<I: id::TypedId>(&mut self, backend: Backend) -> I {
56+
println!("alloc id");
5657
match self.free.pop() {
5758
Some(index) => I::zip(index, self.epochs[index as usize], backend),
5859
None => {
@@ -66,6 +67,7 @@ impl IdentityManager {
6667

6768
/// Free `id`. It will never be returned from `alloc` again.
6869
pub fn free<I: id::TypedId + Debug>(&mut self, id: I) {
70+
println!("free id");
6971
let (index, epoch, _backend) = id.unzip();
7072
let pe = &mut self.epochs[index as usize];
7173
assert_eq!(*pe, epoch);

wgpu-core/src/storage.rs

+62-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ pub(crate) enum Element<T> {
1414
/// epoch.
1515
Occupied(T, Epoch),
1616

17+
/// Like `Occupied`, but the resource has been marked as destroyed
18+
/// and hasn't been dropped yet.
19+
Destroyed(T, Epoch),
20+
1721
/// Like `Occupied`, but an error occurred when creating the
1822
/// resource.
1923
///
@@ -68,9 +72,11 @@ impl<T, I: id::TypedId> Storage<T, I> {
6872
let (index, epoch, _) = id.unzip();
6973
match self.map.get(index as usize) {
7074
Some(&Element::Vacant) => false,
71-
Some(&Element::Occupied(_, storage_epoch) | &Element::Error(storage_epoch, _)) => {
72-
storage_epoch == epoch
73-
}
75+
Some(
76+
&Element::Occupied(_, storage_epoch)
77+
| &Element::Destroyed(_, storage_epoch)
78+
| &Element::Error(storage_epoch, _),
79+
) => storage_epoch == epoch,
7480
None => false,
7581
}
7682
}
@@ -87,7 +93,9 @@ impl<T, I: id::TypedId> Storage<T, I> {
8793
let (result, storage_epoch) = match self.map.get(index as usize) {
8894
Some(&Element::Occupied(ref v, epoch)) => (Ok(Some(v)), epoch),
8995
Some(&Element::Vacant) => return Ok(None),
90-
Some(&Element::Error(epoch, ..)) => (Err(InvalidId), epoch),
96+
Some(&Element::Error(epoch, ..)) | Some(&Element::Destroyed(.., epoch)) => {
97+
(Err(InvalidId), epoch)
98+
}
9199
None => return Err(InvalidId),
92100
};
93101
assert_eq!(
@@ -106,6 +114,7 @@ impl<T, I: id::TypedId> Storage<T, I> {
106114
Some(&Element::Occupied(ref v, epoch)) => (Ok(v), epoch),
107115
Some(&Element::Vacant) => panic!("{}[{}] does not exist", self.kind, index),
108116
Some(&Element::Error(epoch, ..)) => (Err(InvalidId), epoch),
117+
Some(&Element::Destroyed(.., epoch)) => (Err(InvalidId), epoch),
109118
None => return Err(InvalidId),
110119
};
111120
assert_eq!(
@@ -124,6 +133,29 @@ impl<T, I: id::TypedId> Storage<T, I> {
124133
Some(&mut Element::Occupied(ref mut v, epoch)) => (Ok(v), epoch),
125134
Some(&mut Element::Vacant) | None => panic!("{}[{}] does not exist", self.kind, index),
126135
Some(&mut Element::Error(epoch, ..)) => (Err(InvalidId), epoch),
136+
Some(&mut Element::Destroyed(.., epoch)) => (Err(InvalidId), epoch),
137+
};
138+
assert_eq!(
139+
epoch, storage_epoch,
140+
"{}[{}] is no longer alive",
141+
self.kind, index
142+
);
143+
result
144+
}
145+
146+
/// Like `get_mut`, but returns the element even if it is destroyed.
147+
///
148+
/// In practice, most API entry points should use `get`/`get_mut` so that a
149+
/// destroyed resource leads to a validation error. This should be used internally
150+
/// in places where we want to do some manipulation potentially after the element
151+
/// was destroyed (for example the drop implementation).
152+
pub(crate) fn get_occupied_or_destroyed(&mut self, id: I) -> Result<&mut T, InvalidId> {
153+
let (index, epoch, _) = id.unzip();
154+
let (result, storage_epoch) = match self.map.get_mut(index as usize) {
155+
Some(&mut Element::Occupied(ref mut v, epoch))
156+
| Some(&mut Element::Destroyed(ref mut v, epoch)) => (Ok(v), epoch),
157+
Some(&mut Element::Vacant) | None => panic!("{}[{}] does not exist", self.kind, index),
158+
Some(&mut Element::Error(epoch, ..)) => (Err(InvalidId), epoch),
127159
};
128160
assert_eq!(
129161
epoch, storage_epoch,
@@ -137,7 +169,7 @@ impl<T, I: id::TypedId> Storage<T, I> {
137169
match self.map[id as usize] {
138170
Element::Occupied(ref v, _) => v,
139171
Element::Vacant => panic!("{}[{}] does not exist", self.kind, id),
140-
Element::Error(_, _) => panic!(""),
172+
Element::Error(_, _) | Element::Destroyed(..) => panic!(""),
141173
}
142174
}
143175

@@ -169,13 +201,13 @@ impl<T, I: id::TypedId> Storage<T, I> {
169201
self.insert_impl(index as usize, Element::Error(epoch, label.to_string()))
170202
}
171203

172-
pub(crate) fn take_and_mark_destroyed(&mut self, id: I) -> Result<T, InvalidId> {
204+
pub(crate) fn replace_with_error(&mut self, id: I) -> Result<T, InvalidId> {
173205
let (index, epoch, _) = id.unzip();
174206
match std::mem::replace(
175207
&mut self.map[index as usize],
176208
Element::Error(epoch, String::new()),
177209
) {
178-
Element::Vacant => panic!("Cannot mark a vacant resource destroyed"),
210+
Element::Vacant => panic!("Cannot access vacant resource"),
179211
Element::Occupied(value, storage_epoch) => {
180212
assert_eq!(epoch, storage_epoch);
181213
Ok(value)
@@ -184,6 +216,27 @@ impl<T, I: id::TypedId> Storage<T, I> {
184216
}
185217
}
186218

219+
pub(crate) fn get_and_mark_destroyed(&mut self, id: I) -> Result<&mut T, InvalidId> {
220+
let (index, epoch, _) = id.unzip();
221+
let slot = &mut self.map[index as usize];
222+
// borrowck dance: we have to move the element out before we can replace it
223+
// with another variant with the same value.
224+
if let &mut Element::Occupied(..) = slot {
225+
if let Element::Occupied(value, storage_epoch) =
226+
std::mem::replace(slot, Element::Vacant)
227+
{
228+
debug_assert_eq!(storage_epoch, epoch);
229+
*slot = Element::Destroyed(value, storage_epoch);
230+
}
231+
}
232+
233+
if let Element::Destroyed(ref mut value, ..) = *slot {
234+
Ok(value)
235+
} else {
236+
Err(InvalidId)
237+
}
238+
}
239+
187240
pub(crate) fn force_replace(&mut self, id: I, value: T) {
188241
let (index, epoch, _) = id.unzip();
189242
self.map[index as usize] = Element::Occupied(value, epoch);
@@ -192,7 +245,7 @@ impl<T, I: id::TypedId> Storage<T, I> {
192245
pub(crate) fn remove(&mut self, id: I) -> Option<T> {
193246
let (index, epoch, _) = id.unzip();
194247
match std::mem::replace(&mut self.map[index as usize], Element::Vacant) {
195-
Element::Occupied(value, storage_epoch) => {
248+
Element::Occupied(value, storage_epoch) | Element::Destroyed(value, storage_epoch) => {
196249
assert_eq!(epoch, storage_epoch);
197250
Some(value)
198251
}
@@ -239,7 +292,7 @@ impl<T, I: id::TypedId> Storage<T, I> {
239292
};
240293
for element in self.map.iter() {
241294
match *element {
242-
Element::Occupied(..) => report.num_occupied += 1,
295+
Element::Occupied(..) | Element::Destroyed(..) => report.num_occupied += 1,
243296
Element::Vacant => report.num_vacant += 1,
244297
Element::Error(..) => report.num_error += 1,
245298
}

0 commit comments

Comments
 (0)