mirror of https://github.com/AxioDL/metaforce.git
188 lines
5.7 KiB
C++
188 lines
5.7 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
#include "hecl/BitVector.hpp"
|
|
|
|
#include <boo/BooObject.hpp>
|
|
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
|
|
|
|
namespace hecl {
|
|
|
|
#define HECL_UBUFPOOL_ALLOCATION_BLOCK 262144
|
|
|
|
/** This class provides a uniform structure for packing instanced uniform-buffer
|
|
* data with consistent stride into a vector of 256K 'Buckets'.
|
|
*
|
|
* This results in a space-efficient way of managing GPU data of things like UI
|
|
* widgets. These can potentially have numerous binding instances, so this avoids
|
|
* allocating a full GPU buffer object for each. */
|
|
template <typename UniformStruct>
|
|
class UniformBufferPool {
|
|
public:
|
|
/* Resolve div_t type using ssize_t as basis */
|
|
#if _WIN32
|
|
using IndexTp = SSIZE_T;
|
|
#else
|
|
using IndexTp = ssize_t;
|
|
#endif
|
|
private:
|
|
struct InvalidTp {};
|
|
using DivTp = std::conditional_t<
|
|
std::is_same<IndexTp, long long>::value, std::lldiv_t,
|
|
std::conditional_t<std::is_same<IndexTp, long>::value, std::ldiv_t,
|
|
std::conditional_t<std::is_same<IndexTp, int>::value, std::div_t, InvalidTp>>>;
|
|
static_assert(!std::is_same<DivTp, InvalidTp>::value, "unsupported IndexTp for DivTp resolution");
|
|
|
|
/** Size of single element, rounded up to 256-multiple */
|
|
static constexpr IndexTp m_stride = ROUND_UP_256(sizeof(UniformStruct));
|
|
static_assert(m_stride <= HECL_UBUFPOOL_ALLOCATION_BLOCK, "Stride too large for uniform pool");
|
|
|
|
/** Number of rounded elements per 256K bucket */
|
|
static constexpr IndexTp m_countPerBucket = HECL_UBUFPOOL_ALLOCATION_BLOCK / m_stride;
|
|
|
|
/** Buffer size per bucket (ideally 256K) */
|
|
static constexpr IndexTp m_sizePerBucket = m_stride * m_countPerBucket;
|
|
|
|
/** BitVector indicating free allocation blocks */
|
|
hecl::llvm::BitVector m_freeBlocks;
|
|
|
|
/** Efficient way to get bucket and block simultaneously */
|
|
DivTp getBucketDiv(IndexTp idx) const { return std::div(idx, m_countPerBucket); }
|
|
|
|
/** Factory pointer for building additional buffers */
|
|
boo::IGraphicsDataFactory* m_factory = nullptr;
|
|
|
|
/** Private bucket info */
|
|
struct Bucket {
|
|
boo::ObjToken<boo::IGraphicsBufferD> buffer;
|
|
uint8_t* cpuBuffer = nullptr;
|
|
std::atomic_size_t useCount = {};
|
|
bool dirty = false;
|
|
Bucket() = default;
|
|
Bucket(const Bucket& other) = delete;
|
|
Bucket& operator=(const Bucket& other) = delete;
|
|
Bucket(Bucket&& other) = default;
|
|
Bucket& operator=(Bucket&& other) = default;
|
|
|
|
void updateBuffer() {
|
|
if (useCount == 0) {
|
|
destroy();
|
|
return;
|
|
}
|
|
if (dirty && cpuBuffer) {
|
|
buffer->unmap();
|
|
cpuBuffer = nullptr;
|
|
}
|
|
dirty = false;
|
|
}
|
|
|
|
void increment(UniformBufferPool& pool) {
|
|
if (useCount.fetch_add(1) == 0 && !buffer)
|
|
buffer = pool.m_factory->newPoolBuffer(boo::BufferUse::Uniform, pool.m_stride, pool.m_countPerBucket BooTrace);
|
|
}
|
|
|
|
void decrement(UniformBufferPool& pool) {
|
|
--useCount;
|
|
}
|
|
|
|
void destroy() {
|
|
if (cpuBuffer) {
|
|
buffer->unmap();
|
|
cpuBuffer = nullptr;
|
|
}
|
|
buffer.reset();
|
|
}
|
|
};
|
|
std::vector<std::unique_ptr<Bucket>> m_buckets;
|
|
|
|
public:
|
|
/** User block-owning token */
|
|
class Token {
|
|
friend class UniformBufferPool;
|
|
UniformBufferPool* m_pool = nullptr;
|
|
IndexTp m_index = -1;
|
|
DivTp m_div;
|
|
Token(UniformBufferPool* pool) : m_pool(pool) {
|
|
auto& freeSpaces = pool->m_freeBlocks;
|
|
int idx = freeSpaces.find_first();
|
|
if (idx == -1) {
|
|
pool->m_buckets.push_back(std::make_unique<Bucket>());
|
|
m_index = freeSpaces.size();
|
|
freeSpaces.resize(freeSpaces.size() + pool->m_countPerBucket, true);
|
|
} else {
|
|
m_index = idx;
|
|
}
|
|
freeSpaces.reset(m_index);
|
|
m_div = pool->getBucketDiv(m_index);
|
|
|
|
Bucket& bucket = *m_pool->m_buckets[m_div.quot];
|
|
bucket.increment(*m_pool);
|
|
}
|
|
|
|
public:
|
|
Token() = default;
|
|
Token(const Token& other) = delete;
|
|
Token& operator=(const Token& other) = delete;
|
|
Token& operator=(Token&& other) noexcept {
|
|
m_pool = other.m_pool;
|
|
m_index = other.m_index;
|
|
m_div = other.m_div;
|
|
other.m_index = -1;
|
|
return *this;
|
|
}
|
|
Token(Token&& other) noexcept : m_pool(other.m_pool), m_index(other.m_index), m_div(other.m_div) { other.m_index = -1; }
|
|
|
|
~Token() {
|
|
if (m_index != -1) {
|
|
m_pool->m_freeBlocks.set(m_index);
|
|
Bucket& bucket = *m_pool->m_buckets[m_div.quot];
|
|
bucket.decrement(*m_pool);
|
|
}
|
|
}
|
|
|
|
UniformStruct& access() {
|
|
Bucket& bucket = *m_pool->m_buckets[m_div.quot];
|
|
if (!bucket.cpuBuffer)
|
|
bucket.cpuBuffer = reinterpret_cast<uint8_t*>(bucket.buffer->map(m_sizePerBucket));
|
|
bucket.dirty = true;
|
|
return reinterpret_cast<UniformStruct&>(bucket.cpuBuffer[m_div.rem * m_pool->m_stride]);
|
|
}
|
|
|
|
std::pair<boo::ObjToken<boo::IGraphicsBufferD>, IndexTp> getBufferInfo() const {
|
|
Bucket& bucket = *m_pool->m_buckets[m_div.quot];
|
|
return {bucket.buffer, m_div.rem * m_pool->m_stride};
|
|
}
|
|
|
|
explicit operator bool() const { return m_pool != nullptr && m_index != -1; }
|
|
};
|
|
|
|
UniformBufferPool() = default;
|
|
UniformBufferPool(const UniformBufferPool& other) = delete;
|
|
UniformBufferPool& operator=(const UniformBufferPool& other) = delete;
|
|
|
|
/** Load dirty buffer data into GPU */
|
|
void updateBuffers() {
|
|
for (auto& bucket : m_buckets)
|
|
bucket->updateBuffer();
|
|
}
|
|
|
|
/** Allocate free block into client-owned Token */
|
|
Token allocateBlock(boo::IGraphicsDataFactory* factory) {
|
|
m_factory = factory;
|
|
return Token(this);
|
|
}
|
|
|
|
void doDestroy() {
|
|
for (auto& bucket : m_buckets)
|
|
bucket->buffer.reset();
|
|
}
|
|
};
|
|
|
|
} // namespace hecl
|