|
| 1 | +# Scoped Waiter |
| 2 | + |
| 3 | +A useful abstraction to have is an object that in its destructor waits/blocks until the Device is idle. It is incorrect usage to destroy Vulkan objects while they are in use by the GPU, such an object helps with making sure the device is idle before some dependent resource gets destroyed. |
| 4 | + |
| 5 | +Being able to do arbitary things on scope exit will be useful in other spots too, so we encapsulate that in a basic class template `Scoped`. It's somewhat like a `unique_ptr<Type, Deleter>` that stores the value (`Type`) instead of a pointer (`Type*`), with some constraints: |
| 6 | + |
| 7 | +1. `Type` must be default constructible |
| 8 | +1. Assumes a default constructed `Type` is equivalent to null (does not call `Deleter`) |
| 9 | + |
| 10 | +```cpp |
| 11 | +template <typename Type> |
| 12 | +concept Scopeable = |
| 13 | + std::equality_comparable<Type> && std::is_default_constructible_v<Type>; |
| 14 | + |
| 15 | +template <Scopeable Type, typename Deleter> |
| 16 | +class Scoped { |
| 17 | + public: |
| 18 | + Scoped(Scoped const&) = delete; |
| 19 | + auto operator=(Scoped const&) = delete; |
| 20 | + |
| 21 | + Scoped() = default; |
| 22 | + |
| 23 | + constexpr Scoped(Scoped&& rhs) noexcept |
| 24 | + : m_t(std::exchange(rhs.m_t, Type{})) {} |
| 25 | + |
| 26 | + constexpr auto operator=(Scoped&& rhs) noexcept -> Scoped& { |
| 27 | + if (&rhs != this) { std::swap(m_t, rhs.m_t); } |
| 28 | + return *this; |
| 29 | + } |
| 30 | + |
| 31 | + explicit constexpr Scoped(Type t) : m_t(std::move(t)) {} |
| 32 | + |
| 33 | + constexpr ~Scoped() { |
| 34 | + if (m_t == Type{}) { return; } |
| 35 | + Deleter{}(m_t); |
| 36 | + } |
| 37 | + |
| 38 | + [[nodiscard]] auto get() const -> Type const& { return m_t; } |
| 39 | + [[nodiscard]] auto get() -> Type& { return m_t; } |
| 40 | + |
| 41 | + private: |
| 42 | + Type m_t{}; |
| 43 | +}; |
| 44 | +``` |
| 45 | + |
| 46 | +Don't worry if this doesn't make a lot of sense: the implementation isn't important, what it does and how to use it is what matters. |
| 47 | + |
| 48 | +A `ScopedWaiter` can now be implemented quite easily: |
| 49 | + |
| 50 | +```cpp |
| 51 | +struct ScopedWaiterDeleter { |
| 52 | + void operator()(vk::Device const device) const noexcept { |
| 53 | + device.waitIdle(); |
| 54 | + } |
| 55 | +}; |
| 56 | + |
| 57 | +using ScopedWaiter = Scoped<vk::Device, ScopedWaiterDeleter>; |
| 58 | +``` |
| 59 | +
|
| 60 | +Add a `ScopedWaiter` member to `App` _at the end_ of its member list: this must remain at the end to be the first member that gets destroyed, thus guaranteeing the device will be idle before the destruction of any other members begins. Initialize it after creating the Device: |
| 61 | +
|
| 62 | +```cpp |
| 63 | + m_waiter = ScopedWaiter{*m_device}; |
| 64 | +``` |
0 commit comments