ContextPool support for unordered contexts.

This commit is contained in:
Anish Bhobe 2025-05-18 00:06:06 +02:00
parent 3b4ea52611
commit befa36c7f1
8 changed files with 217 additions and 115 deletions

View File

@ -10,6 +10,7 @@ find_package(fmt CONFIG REQUIRED)
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(EASTL CONFIG REQUIRED)
find_library(slang NAMES "slang" CONFIG REQUIRED)
find_package(foonathan_memory CONFIG REQUIRED)
add_library(aster_core STATIC)
@ -28,4 +29,5 @@ target_link_libraries(aster_core PRIVATE fmt::fmt)
target_link_libraries(aster_core PRIVATE EASTL)
target_link_libraries(aster_core PUBLIC Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
target_link_libraries(aster_core PUBLIC ${slang})
target_link_libraries(aster_core PRIVATE foonathan_memory)

View File

@ -5,16 +5,23 @@
#pragma once
#include "context.h"
#include <aster/aster.h>
#include <aster/core/buffer.h>
#include <aster/core/image.h>
#include <aster/core/image_view.h>
#include <aster/core/physical_device.h>
#include <aster/core/pipeline.h>
#include <EASTL/list.h>
#include <EASTL/intrusive_list.h>
#include <EASTL/optional.h>
#include <EASTL/vector.h>
#include <foonathan/memory/memory_pool.hpp>
#include <foonathan/memory/namespace_alias.hpp>
namespace systems
{
@ -26,6 +33,7 @@ namespace _internal
class GraphicsContextPool;
class TransferContextPool;
class ContextPool;
class OrderlessTransferContextPool;
} // namespace _internal
#define DEPRECATE_RAW_CALLS
@ -38,6 +46,7 @@ class Context
friend Device;
friend _internal::ContextPool;
friend _internal::OrderlessTransferContextPool;
explicit Context(_internal::ContextPool &pool, const vk::CommandBuffer cmd)
: m_Pool{&pool}
@ -132,12 +141,21 @@ class ContextPool
eastl::vector<vk::CommandBuffer> m_CommandBuffers;
u32 m_BuffersAllocated;
public:
u16 m_ExtraData;
enum class ManagedBy : u8
{
eFrame,
eDevice,
} m_ManagedBy;
protected:
eastl::vector<Ref<Buffer>> m_OwnedBuffers;
eastl::vector<Ref<Image>> m_OwnedImages;
eastl::vector<Ref<ImageView>> m_OwnedImageViews;
vk::CommandBuffer AllocateCommandBuffer();
void Destroy();
public:
[[nodiscard]] Device &
@ -147,6 +165,8 @@ class ContextPool
return *m_Device;
}
eastl::function<void(ContextPool &)> m_ResetCallback;
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(const Ref<Buffer> &buffer);
/// Keep the resource alive while the command buffers are acting.
@ -159,11 +179,17 @@ class ContextPool
void Reset();
ContextPool() = default;
ContextPool(Device &device, u32 queueFamilyIndex);
ContextPool(Device &device, u32 queueFamilyIndex, ManagedBy managedBy);
ContextPool(ContextPool &&other) noexcept;
ContextPool &operator=(ContextPool &&other) noexcept;
bool
operator==(const ContextPool &other) const
{
return m_Pool == other.m_Pool;
}
~ContextPool();
DISALLOW_COPY_AND_ASSIGN(ContextPool);
@ -175,37 +201,19 @@ class TransferContextPool : public ContextPool
TransferContext CreateTransferContext();
TransferContextPool() = default;
TransferContextPool(Device &device, const u32 queueFamilyIndex)
: ContextPool{device, queueFamilyIndex}
TransferContextPool(Device &device, const u32 queueFamilyIndex, const ManagedBy managedBy)
: ContextPool{device, queueFamilyIndex, managedBy}
{
}
TransferContextPool(TransferContextPool &&other) noexcept
: ContextPool{std::move(other)}
{
}
TransferContextPool(TransferContextPool &&other) noexcept = default;
TransferContextPool &operator=(TransferContextPool &&other) noexcept = default;
TransferContextPool &
operator=(TransferContextPool &&other) noexcept
{
if (this == &other)
return *this;
ContextPool::operator=(std::move(other));
return *this;
}
~TransferContextPool()
{
Destroy();
}
~TransferContextPool() = default;
DISALLOW_COPY_AND_ASSIGN(TransferContextPool);
};
//
// class ComputeContextPool : public TransferContextPool
//{
// ComputeCon CreateTransferContext();
//};
class GraphicsContextPool : public TransferContextPool
{
@ -213,32 +221,136 @@ class GraphicsContextPool : public TransferContextPool
GraphicsContext CreateGraphicsContext();
GraphicsContextPool() = default;
GraphicsContextPool(Device &device, const u32 queueFamilyIndex)
: TransferContextPool{device, queueFamilyIndex}
GraphicsContextPool(Device &device, const u32 queueFamilyIndex, const ManagedBy managedBy)
: TransferContextPool{device, queueFamilyIndex, managedBy}
{
}
GraphicsContextPool(GraphicsContextPool &&other) noexcept
: TransferContextPool{std::move(other)}
{
}
GraphicsContextPool(GraphicsContextPool &&other) noexcept = default;
GraphicsContextPool &operator=(GraphicsContextPool &&other) noexcept = default;
GraphicsContextPool &
operator=(GraphicsContextPool &&other) noexcept
{
if (this == &other)
return *this;
TransferContextPool::operator=(std::move(other));
return *this;
}
~GraphicsContextPool()
{
Destroy();
}
~GraphicsContextPool() = default;
DISALLOW_COPY_AND_ASSIGN(GraphicsContextPool);
};
class OrderlessTransferContextPool
{
struct TransferContextEntry : eastl::intrusive_list_node
{
TransferContextPool m_Pool;
bool
Contains(const _internal::ContextPool &other) const
{
return m_Pool == other;
}
};
using TransferContextList = eastl::intrusive_list<TransferContextEntry>;
Device *m_Device;
memory::memory_pool<> m_TransferContextMemoryPool;
TransferContextList m_FreeTransferContexts;
TransferContextList m_TransferContexts;
u32 m_QueueFamilyIndex;
constexpr static usize ENTRY_SIZE = sizeof(TransferContextEntry);
constexpr static usize ENTRIES_PER_BLOCK = 5;
constexpr static usize BLOCK_SIZE = ENTRIES_PER_BLOCK * ENTRY_SIZE;
public:
OrderlessTransferContextPool()
: m_Device{nullptr}
, m_TransferContextMemoryPool{ENTRY_SIZE, BLOCK_SIZE}
, m_QueueFamilyIndex{0}
{
}
void
Init(Device &device, const u32 queueFamilyIndex)
{
m_Device = &device;
m_QueueFamilyIndex = queueFamilyIndex;
}
TransferContext
CreateTransferContext()
{
if (!m_FreeTransferContexts.empty())
{
TransferContextEntry &entry = m_FreeTransferContexts.back();
m_FreeTransferContexts.pop_back();
m_TransferContexts.push_back(entry);
return entry.m_Pool.CreateTransferContext();
}
TransferContextEntry &entry = *static_cast<TransferContextEntry *>(m_TransferContextMemoryPool.allocate_node());
auto pool = TransferContextPool{*m_Device, m_QueueFamilyIndex, ContextPool::ManagedBy::eDevice};
pool.m_ResetCallback = [this](ContextPool &resetPool) { this->ReleasePool(resetPool); };
new (&entry) TransferContextEntry{
.m_Pool = eastl::move(pool),
};
m_TransferContexts.push_back(entry);
return entry.m_Pool.CreateTransferContext();
}
void
ReleasePool(ContextPool &pool)
{
auto const found = eastl::find_if(m_TransferContexts.begin(), m_TransferContexts.end(),
[&pool](const TransferContextEntry &v) { return v.Contains(pool); });
auto &v = *found;
TransferContextList::remove(v);
pool.Reset();
m_FreeTransferContexts.push_back(v);
}
OrderlessTransferContextPool(OrderlessTransferContextPool &&other) noexcept
: m_Device{other.m_Device}
, m_TransferContextMemoryPool{std::move(other.m_TransferContextMemoryPool)}
, m_FreeTransferContexts{other.m_FreeTransferContexts}
, m_TransferContexts{other.m_TransferContexts}
, m_QueueFamilyIndex{other.m_QueueFamilyIndex}
{
other.m_FreeTransferContexts.clear();
other.m_TransferContexts.clear();
}
OrderlessTransferContextPool &
operator=(OrderlessTransferContextPool &&other) noexcept
{
if (this == &other)
return *this;
m_Device = other.m_Device;
m_TransferContextMemoryPool = std::move(other.m_TransferContextMemoryPool);
m_FreeTransferContexts = other.m_FreeTransferContexts;
other.m_FreeTransferContexts.clear();
m_TransferContexts = other.m_TransferContexts;
other.m_TransferContexts.clear();
m_QueueFamilyIndex = other.m_QueueFamilyIndex;
return *this;
}
~OrderlessTransferContextPool()
{
for (auto &entry : m_FreeTransferContexts)
{
entry.m_Pool.~TransferContextPool();
}
for (auto &entry : m_TransferContexts)
{
entry.m_Pool.~TransferContextPool();
}
// The allocations will 'wink' away.
}
DISALLOW_COPY_AND_ASSIGN(OrderlessTransferContextPool);
};
} // namespace _internal
} // namespace systems

View File

@ -21,7 +21,6 @@
#include "aster/core/size.h"
#include "aster/core/swapchain.h"
#include "EASTL/deque.h"
#include <EASTL/hash_map.h>
#include <EASTL/optional.h>
#include <EASTL/variant.h>
@ -29,8 +28,6 @@
#include <slang-com-ptr.h>
#include <slang.h>
#include <variant>
constexpr static u32 MAX_FRAMES_IN_FLIGHT = 3;
struct Window;
@ -404,6 +401,8 @@ class Device final
vk::Queue m_ComputeQueue;
u32 m_ComputeQueueFamily;
_internal::OrderlessTransferContextPool m_TransferContextPool;
std::array<Frame, MAX_FRAMES_IN_FLIGHT> m_Frames;
u32 m_CurrentFrameIdx = 0;
@ -515,9 +514,6 @@ class Device final
// Frames
// ----------------------------------------------------------------------------------------------------
private:
Frame CreateFrame(u32 frameIndex);
public:
Frame &GetNextFrame();
Size2D
@ -535,11 +531,7 @@ class Device final
friend GraphicsContext;
friend TransferContext;
vk::CommandPool m_TransferPool;
eastl::deque<TransferContext> m_TransferContexts;
eastl::vector<u32> m_TransferContextFreeList;
TransferContext &CreateTransferContext();
TransferContext CreateTransferContext();
Receipt Submit(Context &context);
//

View File

@ -4,6 +4,7 @@
// =============================================
#include "aster/aster.h"
#include "context.h"
#include <EASTL/deque.h>
#include <EASTL/intrusive_list.h>
@ -28,11 +29,20 @@ class SyncServer
{
vk::Semaphore m_Semaphore;
TimelinePoint m_CurrentPoint;
ContextPool *m_AttachedPool;
static Entry Create(Device &device);
explicit Entry(Device &device);
void Destroy(Device &device);
void Wait(Device &device);
void Next();
void AttachPool(ContextPool *pool);
Entry(Entry &&) = default;
Entry &operator=(Entry &&) = default;
~Entry() = default;
DISALLOW_COPY_AND_ASSIGN(Entry);
};
Device *m_Device;

View File

@ -277,9 +277,12 @@ systems::TransferContext::operator=(TransferContext &&other) noexcept
using namespace systems::_internal;
ContextPool::ContextPool(Device &device, const u32 queueFamilyIndex)
ContextPool::ContextPool(Device &device, const u32 queueFamilyIndex, const ManagedBy managedBy)
: m_Device{&device}
, m_BuffersAllocated{0}
, m_ExtraData{0}
, m_ManagedBy{managedBy}
, m_ResetCallback{}
{
const vk::CommandPoolCreateInfo commandPoolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
@ -293,9 +296,12 @@ ContextPool::ContextPool(ContextPool &&other) noexcept
, m_Pool{Take(other.m_Pool)}
, m_CommandBuffers{std::move(other.m_CommandBuffers)}
, m_BuffersAllocated{other.m_BuffersAllocated}
, m_ExtraData{other.m_ExtraData}
, m_ManagedBy{other.m_ManagedBy}
, m_OwnedBuffers{std::move(other.m_OwnedBuffers)}
, m_OwnedImages{std::move(other.m_OwnedImages)}
, m_OwnedImageViews{std::move(other.m_OwnedImageViews)}
, m_ResetCallback{std::move(other.m_ResetCallback)}
{
}
@ -308,16 +314,22 @@ ContextPool::operator=(ContextPool &&other) noexcept
swap(m_Device, other.m_Device);
swap(m_Pool, other.m_Pool);
swap(m_CommandBuffers, other.m_CommandBuffers);
swap(m_ExtraData, other.m_ExtraData);
swap(m_ManagedBy, other.m_ManagedBy);
swap(m_BuffersAllocated, other.m_BuffersAllocated);
swap(m_OwnedBuffers, other.m_OwnedBuffers);
swap(m_OwnedImages, other.m_OwnedImages);
swap(m_OwnedImageViews, other.m_OwnedImageViews);
swap(m_ResetCallback, other.m_ResetCallback);
return *this;
}
ContextPool::~ContextPool()
{
Destroy();
if (!m_Pool)
return;
m_Device->m_Device->destroy(Take(m_Pool), nullptr);
}
void
@ -358,15 +370,6 @@ ContextPool::AllocateCommandBuffer()
return cmd;
}
void
ContextPool::Destroy()
{
if (!m_Pool)
return;
m_Device->m_Device->destroy(Take(m_Pool), nullptr);
}
systems::Context
ContextPool::CreateContext()
{

View File

@ -12,6 +12,7 @@
#include "aster/systems/sync_server.h"
#include "aster/util/files.h"
#include "systems/commit_manager.h"
#include "systems/context.h"
#include <EASTL/vector_map.h>
#include <fmt/ranges.h>
@ -938,11 +939,7 @@ systems::Device::Device(const DeviceCreateInfo &createInfo)
m_CommitManager = std::make_unique<CommitManager>(this, 1000, 1000, 1000, CreateSampler({}));
}
const vk::CommandPoolCreateInfo transferPoolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = m_TransferQueueFamily,
};
AbortIfFailed(m_Device->createCommandPool(&transferPoolCreateInfo, nullptr, &m_TransferPool));
m_TransferContextPool.Init(*this, m_TransferQueueFamily);
constexpr SlangGlobalSessionDesc globalSessionDesc = {};
auto result = slang::createGlobalSession(&globalSessionDesc, m_GlobalSlangSession.writeRef());
@ -996,8 +993,6 @@ systems::Device::~Device()
m_Device->destroy(Take(frame.m_ImageAcquireSem), nullptr);
m_Device->destroy(Take(frame.m_RenderFinishSem), nullptr);
}
m_Device->destroy(Take(m_TransferPool), nullptr);
m_TransferContexts.clear();
m_HashToSamplerIdx.clear();
}
@ -1082,38 +1077,20 @@ systems::Device::Present(Frame &frame, GraphicsContext &graphicsContext)
}
}
systems::TransferContext &
systems::TransferContext
systems::Device::CreateTransferContext()
{
/*if (!m_TransferContextFreeList.empty())
{
u32 freeIndex = m_TransferContextFreeList.back();
m_TransferContextFreeList.pop_back();
TransferContext &context = m_TransferContexts[freeIndex];
return context;
}
vk::CommandBuffer cmd;
vk::CommandBufferAllocateInfo allocateInfo = {
.commandPool = m_TransferPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
AbortIfFailed(m_Device->allocateCommandBuffers(&allocateInfo, &cmd));
m_TransferContexts.push_back(TransferContext{*this, cmd});
return m_TransferContexts.back();*/
TODO();
return m_TransferContexts.back();
return m_TransferContextPool.CreateTransferContext();
}
systems::Receipt
systems::Device::Submit(Context &context)
{
const auto rect = m_SyncServer->Allocate();
auto entry = m_SyncServer->GetEntry(rect);
auto &entry = m_SyncServer->GetEntry(rect);
if (context.m_Pool->m_ManagedBy == _internal::ContextPool::ManagedBy::eDevice)
entry.AttachPool(context.m_Pool);
auto [wait, next] = entry.m_CurrentPoint;
vk::TimelineSemaphoreSubmitInfo timelineSubmit = {
@ -1205,9 +1182,9 @@ systems::Frame::operator=(Frame &&other) noexcept
systems::Frame::Frame(Device &device, u32 frameIndex, u32 const primaryQueueFamily, u32 const asyncTransferQueue,
u32 const asyncComputeQueue)
: m_Device{&device}
, m_PrimaryPool{device, primaryQueueFamily}
, m_AsyncTransferPool{device, asyncTransferQueue}
, m_AsyncComputePool{device, asyncComputeQueue}
, m_PrimaryPool{device, primaryQueueFamily, _internal::ContextPool::ManagedBy::eFrame}
, m_AsyncTransferPool{device, asyncTransferQueue, _internal::ContextPool::ManagedBy::eFrame}
, m_AsyncComputePool{device, asyncComputeQueue, _internal::ContextPool::ManagedBy::eFrame}
, m_FrameIdx{frameIndex}
, m_ImageIdx{0}
{

View File

@ -9,8 +9,9 @@
using namespace systems::_internal;
SyncServer::Entry
SyncServer::Entry::Create(Device &device)
SyncServer::Entry::Entry(Device &device)
: m_CurrentPoint{0, 1}
, m_AttachedPool{nullptr}
{
constexpr static vk::SemaphoreTypeCreateInfo TYPE_CREATE_INFO = {
.semaphoreType = vk::SemaphoreType::eTimeline,
@ -18,13 +19,7 @@ SyncServer::Entry::Create(Device &device)
};
constexpr static vk::SemaphoreCreateInfo SEMAPHORE_CREATE_INFO = {.pNext = &TYPE_CREATE_INFO};
vk::Semaphore semaphore;
AbortIfFailed(device.m_Device->createSemaphore(&SEMAPHORE_CREATE_INFO, nullptr, &semaphore));
return {
.m_Semaphore = semaphore,
.m_CurrentPoint = {.m_WaitValue = 0, .m_NextValue = 1},
};
AbortIfFailed(device.m_Device->createSemaphore(&SEMAPHORE_CREATE_INFO, nullptr, &m_Semaphore));
}
void
@ -51,6 +46,11 @@ SyncServer::Entry::Wait(Device &device)
// Thus, this is safe.
m_CurrentPoint.m_WaitValue = m_CurrentPoint.m_NextValue;
m_CurrentPoint.m_WaitValue = m_CurrentPoint.m_NextValue + 1;
if (m_AttachedPool)
{
m_AttachedPool->Reset();
m_AttachedPool = nullptr;
}
}
void
@ -60,6 +60,13 @@ SyncServer::Entry::Next()
++m_CurrentPoint.m_NextValue;
}
void
SyncServer::Entry::AttachPool(ContextPool *pool)
{
assert(!m_AttachedPool);
m_AttachedPool = pool;
}
systems::Receipt
SyncServer::Allocate()
{
@ -91,8 +98,7 @@ SyncServer::AllocateEntry()
return alloc;
}
m_Allocations.push_back(Entry::Create(*m_Device));
return m_Allocations.back();
return m_Allocations.emplace_back(*m_Device);
}
void

View File

@ -246,7 +246,7 @@ main(int, char **)
.pImageMemoryBarriers = &imageReadyToRead,
};
auto &context = device.CreateTransferContext();
auto context = device.CreateTransferContext();
context.Begin();
context.Dependency(imageReadyToWriteDependency);