diff --git a/libs/render/ContinuousBuffer.h b/libs/render/ContinuousBuffer.h new file mode 100644 index 0000000000..09cab69ec5 --- /dev/null +++ b/libs/render/ContinuousBuffer.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include + +namespace render +{ + +/** + * Buffer object managing allocations within a continuous block of memory. + * + * While the memory location itself might change when the buffer is growing, + * the whole data is always stored in a single continuous memory block. + * + * Use the allocate/deallocate methods to acquire or release a chunk of + * a certain size. The chunk size is fixed and cannot be changed. + */ +template +class ContinuousBuffer +{ +public: + static constexpr std::size_t DefaultInitialSize = 65536; + + using Handle = std::uint32_t; + +private: + static constexpr std::size_t GrowthRate = 1; // 100% growth each time + + std::vector _buffer; + + struct SlotInfo + { + bool Occupied; // whether this slot is free + std::size_t Offset; // The index to the first element within the buffer + std::size_t Size; // Number of allocated elements + + SlotInfo(bool occupied, std::size_t offset, std::size_t size) : + Occupied(occupied), + Offset(offset), + Size(size) + {} + }; + + std::vector _slots; + +public: + ContinuousBuffer(std::size_t initialSize = DefaultInitialSize) + { + // Pre-allocate some memory, but don't go all the way down to zero + _buffer.resize(initialSize == 0 ? 16 : initialSize); + + // The initial slot info which is going to be cut into pieces + _slots.emplace_back(SlotInfo{ + false, + 0, + _buffer.size() + }); + } + + Handle allocate(std::size_t requiredSize) + { + return getNextFreeSlotForSize(requiredSize); + } + + ElementType* getBufferStart() + { + return _buffer.data(); + } + + std::size_t getSize(Handle handle) const + { + return _slots[handle].Size; + } + + std::size_t getOffset(Handle handle) const + { + return _slots[handle].Offset; + } + + void setData(Handle handle, const std::vector& elements) + { + const auto& slot = _slots[handle]; + + if (elements.size() != slot.Size) + { + throw std::logic_error("Allocation size mismatch in GeometryStore::Buffer::setData"); + } + + std::copy(elements.begin(), elements.end(), _buffer.begin() + slot.Offset); + } + + void deallocate(Handle handle) + { + auto releasedSlot = _slots[handle]; + releasedSlot.Occupied = false; + + // Check if the slot can merge with an adjacent one + if (handle > 0 && !_slots[handle - 1].Occupied) + { + // Merge the slot before the released one + _slots[handle - 1].Size += releasedSlot.Size; + + // The released handle goes to waste, block it + releasedSlot.Occupied = true; + releasedSlot.Size = 0; + } + else if (handle < _slots.size() - 1 && !_slots[handle + 1].Occupied) + { + auto& nextSlot = _slots[handle + 1]; + + releasedSlot.Size += nextSlot.Size; + + // The next slot will go to waste, never use that again + nextSlot.Occupied = true; + nextSlot.Size = 0; + } + } + +private: + Handle getNextFreeSlotForSize(std::size_t requiredSize) + { + auto numSlots = _slots.size(); + Handle rightmostFreeSlotIndex = 0; + std::size_t rightmostFreeOffset = 0; + + for (Handle slotIndex = 0; slotIndex < numSlots; ++slotIndex) + { + auto& slot = _slots[slotIndex]; + + if (slot.Occupied) continue; + + // Keep track of the highest slot, we need that when re-allocating + if (slot.Offset > rightmostFreeOffset) + { + rightmostFreeOffset = slot.Offset; + rightmostFreeSlotIndex = slotIndex; + } + + if (slot.Size < requiredSize) continue; // this slot is no use for us + + // Take it + slot.Occupied = true; + + if (slot.Size > requiredSize) + { + // Allocate a new free slot with the rest + _slots.emplace_back(SlotInfo{ + false, + slot.Offset + requiredSize, + slot.Size - requiredSize, + }); + } + + return slotIndex; + } + + // No space wherever, we need to expand the buffer + auto additionalSize = std::max(_buffer.size() * GrowthRate, requiredSize); + _buffer.resize(_buffer.size() + additionalSize); + + // Use the right most slot for our requirement, then cut up the rest of the space + auto& rightmostFreeSlot = _slots[rightmostFreeSlotIndex]; + + assert(rightmostFreeSlot.Size < requiredSize); // otherwise we've run wrong above + + auto remainingSize = rightmostFreeSlot.Size + additionalSize - requiredSize; + + rightmostFreeSlot.Occupied = true; + rightmostFreeSlot.Size = requiredSize; + + _slots.emplace_back(SlotInfo + { + false, + rightmostFreeSlot.Offset + rightmostFreeSlot.Size, + remainingSize, + }); + + return rightmostFreeSlotIndex; + } +}; + +} diff --git a/radiantcore/rendersystem/backend/GeometryStore.h b/radiantcore/rendersystem/backend/GeometryStore.h index dedd8f69e4..d5621fdfd6 100644 --- a/radiantcore/rendersystem/backend/GeometryStore.h +++ b/radiantcore/rendersystem/backend/GeometryStore.h @@ -4,6 +4,7 @@ #include #include #include "render/ArbitraryMeshVertex.h" +#include "render/ContinuousBuffer.h" namespace render { @@ -29,170 +30,8 @@ class GeometryStore using Slot = std::uint64_t; private: - template - class Buffer - { - public: - static constexpr std::size_t DefaultNumberOfElements = 65536; - - private: - static constexpr std::size_t GrowthRate = 1; // 100% growth each time - - std::vector _buffer; - - struct SlotInfo - { - bool Occupied; // whether this slot is free - std::size_t Offset; // The index to the first element within the buffer - std::size_t Size; // Number of allocated elements - - SlotInfo(bool occupied, std::size_t offset, std::size_t size) : - Occupied(occupied), - Offset(offset), - Size(size) - {} - }; - - std::vector _slots; - - public: - Buffer(std::size_t initialSize = DefaultNumberOfElements) - { - // Pre-allocate some memory, but don't go all the way down to zero - _buffer.resize(initialSize == 0 ? 16 : initialSize); - - // The initial slot info which is going to be cut into pieces - _slots.emplace_back(SlotInfo{ - false, - 0, - _buffer.size() - }); - } - - std::uint32_t allocate(std::size_t requiredSize) - { - return getNextFreeSlotForSize(requiredSize); - } - - ElementType* getBufferStart() - { - return _buffer.data(); - } - - std::size_t getSize(std::uint32_t handle) const - { - return _slots[handle].Size; - } - - std::size_t getOffset(std::uint32_t handle) const - { - return _slots[handle].Offset; - } - - void setData(std::uint32_t handle, const std::vector& elements) - { - const auto& slot = _slots[handle]; - - if (elements.size() != slot.Size) - { - throw std::logic_error("Allocation size mismatch in GeometryStore::Buffer::setData"); - } - - std::copy(elements.begin(), elements.end(), _buffer.begin() + slot.Offset); - } - - void deallocate(std::uint32_t handle) - { - auto releasedSlot = _slots[handle]; - releasedSlot.Occupied = false; - - // Check if the slot can merge with an adjacent one - if (handle > 0 && !_slots[handle - 1].Occupied) - { - // Merge the slot before the released one - _slots[handle - 1].Size += releasedSlot.Size; - - // The released handle goes to waste, block it - releasedSlot.Occupied = true; - releasedSlot.Size = 0; - } - else if (handle < _slots.size() - 1 && !_slots[handle + 1].Occupied) - { - auto& nextSlot = _slots[handle + 1]; - - releasedSlot.Size += nextSlot.Size; - - // The next slot will go to waste, never use that again - nextSlot.Occupied = true; - nextSlot.Size = 0; - } - } - - private: - std::uint32_t getNextFreeSlotForSize(std::size_t requiredSize) - { - auto numSlots = _slots.size(); - std::uint32_t rightmostFreeSlotIndex = 0; - std::size_t rightmostFreeOffset = 0; - - for (std::uint32_t slotIndex = 0; slotIndex < numSlots; ++slotIndex) - { - auto& slot = _slots[slotIndex]; - - if (slot.Occupied) continue; - - // Keep track of the highest slot, we need that when re-allocating - if (slot.Offset > rightmostFreeOffset) - { - rightmostFreeOffset = slot.Offset; - rightmostFreeSlotIndex = slotIndex; - } - - if (slot.Size < requiredSize) continue; // this slot is no use for us - - // Take it - slot.Occupied = true; - - if (slot.Size > requiredSize) - { - // Allocate a new free slot with the rest - _slots.emplace_back(SlotInfo{ - false, - slot.Offset + requiredSize, - slot.Size - requiredSize, - }); - } - - return slotIndex; - } - - // No space wherever, we need to expand the buffer - auto additionalSize = std::max(_buffer.size() * GrowthRate, requiredSize); - _buffer.resize(_buffer.size() + additionalSize); - - // Use the right most slot for our requirement, then cut up the rest of the space - auto& rightmostFreeSlot = _slots[rightmostFreeSlotIndex]; - - assert(rightmostFreeSlot.Size < requiredSize); // otherwise we've run wrong above - - auto remainingSize = rightmostFreeSlot.Size + additionalSize - requiredSize; - - rightmostFreeSlot.Occupied = true; - rightmostFreeSlot.Size = requiredSize; - - _slots.emplace_back(SlotInfo - { - false, - rightmostFreeSlot.Offset + rightmostFreeSlot.Size, - remainingSize, - }); - - return rightmostFreeSlotIndex; - } - }; - - Buffer _vertexBuffer; - Buffer _indexBuffer; + ContinuousBuffer _vertexBuffer; + ContinuousBuffer _indexBuffer; public: GeometryStore() diff --git a/tools/msvc/libs.vcxproj b/tools/msvc/libs.vcxproj index 504a246ced..a872d48bce 100644 --- a/tools/msvc/libs.vcxproj +++ b/tools/msvc/libs.vcxproj @@ -212,6 +212,7 @@ + diff --git a/tools/msvc/libs.vcxproj.filters b/tools/msvc/libs.vcxproj.filters index 6f67ee955d..7350827b66 100644 --- a/tools/msvc/libs.vcxproj.filters +++ b/tools/msvc/libs.vcxproj.filters @@ -365,6 +365,9 @@ render + + render +