forked from suyu/suyu
vk_scheduler: Drop execution context in favor of views
Instead of passing by copy an execution context through out the whole Vulkan call hierarchy, use a command buffer view and fence view approach. This internally dereferences the command buffer or fence forcing the user to be unable to use an outdated version of it on normal usage. It is still possible to keep store an outdated if it is casted to VKFence& or vk::CommandBuffer. While changing this file, add an extra parameter for Flush and Finish to allow releasing the fence from this calls.
This commit is contained in:
parent
9e689a81f8
commit
86a874a2fc
6 changed files with 60 additions and 50 deletions
|
@ -109,8 +109,8 @@ void VKBufferCache::Reserve(std::size_t max_size) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VKExecutionContext VKBufferCache::Send(VKExecutionContext exctx) {
|
void VKBufferCache::Send() {
|
||||||
return stream_buffer->Send(exctx, buffer_offset - buffer_offset_base);
|
stream_buffer->Send(buffer_offset - buffer_offset_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKBufferCache::AlignBuffer(std::size_t alignment) {
|
void VKBufferCache::AlignBuffer(std::size_t alignment) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ public:
|
||||||
void Reserve(std::size_t max_size);
|
void Reserve(std::size_t max_size);
|
||||||
|
|
||||||
/// Ensures that the set data is sent to the device.
|
/// Ensures that the set data is sent to the device.
|
||||||
[[nodiscard]] VKExecutionContext Send(VKExecutionContext exctx);
|
void Send();
|
||||||
|
|
||||||
/// Returns the buffer cache handle.
|
/// Returns the buffer cache handle.
|
||||||
vk::Buffer GetBuffer() const {
|
vk::Buffer GetBuffer() const {
|
||||||
|
|
|
@ -19,23 +19,19 @@ VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_man
|
||||||
|
|
||||||
VKScheduler::~VKScheduler() = default;
|
VKScheduler::~VKScheduler() = default;
|
||||||
|
|
||||||
VKExecutionContext VKScheduler::GetExecutionContext() const {
|
void VKScheduler::Flush(bool release_fence, vk::Semaphore semaphore) {
|
||||||
return VKExecutionContext(current_fence, current_cmdbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
VKExecutionContext VKScheduler::Flush(vk::Semaphore semaphore) {
|
|
||||||
SubmitExecution(semaphore);
|
SubmitExecution(semaphore);
|
||||||
current_fence->Release();
|
if (release_fence)
|
||||||
|
current_fence->Release();
|
||||||
AllocateNewContext();
|
AllocateNewContext();
|
||||||
return GetExecutionContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VKExecutionContext VKScheduler::Finish(vk::Semaphore semaphore) {
|
void VKScheduler::Finish(bool release_fence, vk::Semaphore semaphore) {
|
||||||
SubmitExecution(semaphore);
|
SubmitExecution(semaphore);
|
||||||
current_fence->Wait();
|
current_fence->Wait();
|
||||||
current_fence->Release();
|
if (release_fence)
|
||||||
|
current_fence->Release();
|
||||||
AllocateNewContext();
|
AllocateNewContext();
|
||||||
return GetExecutionContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKScheduler::SubmitExecution(vk::Semaphore semaphore) {
|
void VKScheduler::SubmitExecution(vk::Semaphore semaphore) {
|
||||||
|
|
|
@ -10,10 +10,43 @@
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
class VKDevice;
|
class VKDevice;
|
||||||
class VKExecutionContext;
|
|
||||||
class VKFence;
|
class VKFence;
|
||||||
class VKResourceManager;
|
class VKResourceManager;
|
||||||
|
|
||||||
|
class VKFenceView {
|
||||||
|
public:
|
||||||
|
VKFenceView() = default;
|
||||||
|
VKFenceView(VKFence* const& fence) : fence{fence} {}
|
||||||
|
|
||||||
|
VKFence* operator->() const noexcept {
|
||||||
|
return fence;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator VKFence&() const noexcept {
|
||||||
|
return *fence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
VKFence* const& fence;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VKCommandBufferView {
|
||||||
|
public:
|
||||||
|
VKCommandBufferView() = default;
|
||||||
|
VKCommandBufferView(const vk::CommandBuffer& cmdbuf) : cmdbuf{cmdbuf} {}
|
||||||
|
|
||||||
|
const vk::CommandBuffer* operator->() const noexcept {
|
||||||
|
return &cmdbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator vk::CommandBuffer() const noexcept {
|
||||||
|
return cmdbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const vk::CommandBuffer& cmdbuf;
|
||||||
|
};
|
||||||
|
|
||||||
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
||||||
/// OpenGL-like operations on Vulkan command buffers.
|
/// OpenGL-like operations on Vulkan command buffers.
|
||||||
class VKScheduler {
|
class VKScheduler {
|
||||||
|
@ -21,16 +54,21 @@ public:
|
||||||
explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
|
explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
|
||||||
~VKScheduler();
|
~VKScheduler();
|
||||||
|
|
||||||
/// Gets the current execution context.
|
/// Gets a reference to the current fence.
|
||||||
[[nodiscard]] VKExecutionContext GetExecutionContext() const;
|
VKFenceView GetFence() const {
|
||||||
|
return current_fence;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends the current execution context to the GPU. It invalidates the current execution context
|
/// Gets a reference to the current command buffer.
|
||||||
/// and returns a new one.
|
VKCommandBufferView GetCommandBuffer() const {
|
||||||
VKExecutionContext Flush(vk::Semaphore semaphore = nullptr);
|
return current_cmdbuf;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends the current execution context to the GPU and waits for it to complete. It invalidates
|
/// Sends the current execution context to the GPU.
|
||||||
/// the current execution context and returns a new one.
|
void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
||||||
VKExecutionContext Finish(vk::Semaphore semaphore = nullptr);
|
|
||||||
|
/// Sends the current execution context to the GPU and waits for it to complete.
|
||||||
|
void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SubmitExecution(vk::Semaphore semaphore);
|
void SubmitExecution(vk::Semaphore semaphore);
|
||||||
|
@ -44,26 +82,4 @@ private:
|
||||||
VKFence* next_fence = nullptr;
|
VKFence* next_fence = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VKExecutionContext {
|
|
||||||
friend class VKScheduler;
|
|
||||||
|
|
||||||
public:
|
|
||||||
VKExecutionContext() = default;
|
|
||||||
|
|
||||||
VKFence& GetFence() const {
|
|
||||||
return *fence;
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::CommandBuffer GetCommandBuffer() const {
|
|
||||||
return cmdbuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit VKExecutionContext(VKFence* fence, vk::CommandBuffer cmdbuf)
|
|
||||||
: fence{fence}, cmdbuf{cmdbuf} {}
|
|
||||||
|
|
||||||
VKFence* fence{};
|
|
||||||
vk::CommandBuffer cmdbuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
|
@ -46,12 +46,12 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Reserve(u64 size) {
|
||||||
return {mapped_pointer + offset, offset, invalidation_mark.has_value()};
|
return {mapped_pointer + offset, offset, invalidation_mark.has_value()};
|
||||||
}
|
}
|
||||||
|
|
||||||
VKExecutionContext VKStreamBuffer::Send(VKExecutionContext exctx, u64 size) {
|
void VKStreamBuffer::Send(u64 size) {
|
||||||
ASSERT_MSG(size <= mapped_size, "Reserved size is too small");
|
ASSERT_MSG(size <= mapped_size, "Reserved size is too small");
|
||||||
|
|
||||||
if (invalidation_mark) {
|
if (invalidation_mark) {
|
||||||
// TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish.
|
// TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish.
|
||||||
exctx = scheduler.Flush();
|
scheduler.Flush();
|
||||||
std::for_each(watches.begin(), watches.begin() + *invalidation_mark,
|
std::for_each(watches.begin(), watches.begin() + *invalidation_mark,
|
||||||
[&](auto& resource) { resource->Wait(); });
|
[&](auto& resource) { resource->Wait(); });
|
||||||
invalidation_mark = std::nullopt;
|
invalidation_mark = std::nullopt;
|
||||||
|
@ -62,11 +62,9 @@ VKExecutionContext VKStreamBuffer::Send(VKExecutionContext exctx, u64 size) {
|
||||||
ReserveWatches(WATCHES_RESERVE_CHUNK);
|
ReserveWatches(WATCHES_RESERVE_CHUNK);
|
||||||
}
|
}
|
||||||
// Add a watch for this allocation.
|
// Add a watch for this allocation.
|
||||||
watches[used_watches++]->Watch(exctx.GetFence());
|
watches[used_watches++]->Watch(scheduler.GetFence());
|
||||||
|
|
||||||
offset += size;
|
offset += size;
|
||||||
|
|
||||||
return exctx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) {
|
void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
std::tuple<u8*, u64, bool> Reserve(u64 size);
|
std::tuple<u8*, u64, bool> Reserve(u64 size);
|
||||||
|
|
||||||
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
|
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
|
||||||
[[nodiscard]] VKExecutionContext Send(VKExecutionContext exctx, u64 size);
|
void Send(u64 size);
|
||||||
|
|
||||||
vk::Buffer GetBuffer() const {
|
vk::Buffer GetBuffer() const {
|
||||||
return *buffer;
|
return *buffer;
|
||||||
|
|
Loading…
Reference in a new issue