init
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
/* Copyright (c) 2019-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/helpers.h"
|
||||
#include "core/buffer.h"
|
||||
#include "core/device.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief An allocation of vulkan memory; different buffer allocations,
|
||||
* with different offset and size, may come from the same Vulkan buffer
|
||||
*/
|
||||
template <vkb::BindingType bindingType>
|
||||
class BufferAllocation
|
||||
{
|
||||
public:
|
||||
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
|
||||
|
||||
public:
|
||||
BufferAllocation() = default;
|
||||
BufferAllocation(const BufferAllocation &) = default;
|
||||
BufferAllocation(BufferAllocation &&) = default;
|
||||
BufferAllocation &operator=(const BufferAllocation &) = default;
|
||||
BufferAllocation &operator=(BufferAllocation &&) = default;
|
||||
|
||||
BufferAllocation(vkb::core::Buffer<bindingType> &buffer, DeviceSizeType size, DeviceSizeType offset);
|
||||
|
||||
bool empty() const;
|
||||
vkb::core::Buffer<bindingType> &get_buffer();
|
||||
DeviceSizeType get_offset() const;
|
||||
DeviceSizeType get_size() const;
|
||||
void update(const std::vector<uint8_t> &data, uint32_t offset = 0);
|
||||
template <typename T>
|
||||
void update(const T &value, uint32_t offset = 0);
|
||||
|
||||
private:
|
||||
vkb::core::BufferCpp *buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize size = 0;
|
||||
};
|
||||
|
||||
using BufferAllocationC = BufferAllocation<vkb::BindingType::C>;
|
||||
using BufferAllocationCpp = BufferAllocation<vkb::BindingType::Cpp>;
|
||||
|
||||
template <>
|
||||
inline BufferAllocation<vkb::BindingType::Cpp>::BufferAllocation(vkb::core::BufferCpp &buffer, vk::DeviceSize size, vk::DeviceSize offset) :
|
||||
buffer(&buffer),
|
||||
offset(offset),
|
||||
size(size)
|
||||
{}
|
||||
|
||||
template <>
|
||||
inline BufferAllocation<vkb::BindingType::C>::BufferAllocation(vkb::core::BufferC &buffer, VkDeviceSize size, VkDeviceSize offset) :
|
||||
buffer(reinterpret_cast<vkb::core::BufferCpp *>(&buffer)),
|
||||
offset(static_cast<vk::DeviceSize>(offset)),
|
||||
size(static_cast<vk::DeviceSize>(size))
|
||||
{}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
bool BufferAllocation<bindingType>::empty() const
|
||||
{
|
||||
return size == 0 || buffer == nullptr;
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
typename vkb::core::Buffer<bindingType> &BufferAllocation<bindingType>::get_buffer()
|
||||
{
|
||||
assert(buffer && "Invalid buffer pointer");
|
||||
if constexpr (bindingType == vkb::BindingType::Cpp)
|
||||
{
|
||||
return *buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
return reinterpret_cast<vkb::core::BufferC &>(*buffer);
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
typename BufferAllocation<bindingType>::DeviceSizeType BufferAllocation<bindingType>::get_offset() const
|
||||
{
|
||||
if constexpr (bindingType == vkb::BindingType::Cpp)
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<VkDeviceSize>(offset);
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
typename BufferAllocation<bindingType>::DeviceSizeType BufferAllocation<bindingType>::get_size() const
|
||||
{
|
||||
if constexpr (bindingType == vkb::BindingType::Cpp)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<VkDeviceSize>(size);
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
void BufferAllocation<bindingType>::update(const std::vector<uint8_t> &data, uint32_t offset)
|
||||
{
|
||||
assert(buffer && "Invalid buffer pointer");
|
||||
|
||||
if (offset + data.size() <= size)
|
||||
{
|
||||
buffer->update(data, to_u32(this->offset) + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGE("Ignore buffer allocation update");
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
template <typename T>
|
||||
void BufferAllocation<bindingType>::update(const T &value, uint32_t offset)
|
||||
{
|
||||
update(to_bytes(value), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper class which handles multiple allocation from the same underlying Vulkan buffer.
|
||||
*/
|
||||
template <vkb::BindingType bindingType>
|
||||
class BufferBlock
|
||||
{
|
||||
public:
|
||||
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
|
||||
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
|
||||
|
||||
public:
|
||||
BufferBlock() = delete;
|
||||
BufferBlock(BufferBlock const &rhs) = delete;
|
||||
BufferBlock(BufferBlock &&rhs) = default;
|
||||
BufferBlock &operator=(BufferBlock const &rhs) = delete;
|
||||
BufferBlock &operator=(BufferBlock &&rhs) = default;
|
||||
|
||||
BufferBlock(vkb::core::Device<bindingType> &device, DeviceSizeType size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage);
|
||||
|
||||
/**
|
||||
* @return An usable view on a portion of the underlying buffer
|
||||
*/
|
||||
BufferAllocation<bindingType> allocate(DeviceSizeType size);
|
||||
|
||||
/**
|
||||
* @brief check if this BufferBlock can allocate a given amount of memory
|
||||
* @param size the number of bytes to check
|
||||
* @return \c true if \a size bytes can be allocated from this \c BufferBlock, otherwise \c false.
|
||||
*/
|
||||
bool can_allocate(DeviceSizeType size) const;
|
||||
|
||||
DeviceSizeType get_size() const;
|
||||
void reset();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @ brief Determine the current aligned offset.
|
||||
* @return The current aligned offset.
|
||||
*/
|
||||
vk::DeviceSize aligned_offset() const;
|
||||
vk::DeviceSize determine_alignment(vk::BufferUsageFlags usage, vk::PhysicalDeviceLimits const &limits) const;
|
||||
|
||||
private:
|
||||
vkb::core::BufferCpp buffer;
|
||||
vk::DeviceSize alignment = 0; // Memory alignment, it may change according to the usage
|
||||
vk::DeviceSize offset = 0; // Current offset, it increases on every allocation
|
||||
};
|
||||
|
||||
using BufferBlockC = BufferBlock<vkb::BindingType::C>;
|
||||
using BufferBlockCpp = BufferBlock<vkb::BindingType::Cpp>;
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
BufferBlock<bindingType>::BufferBlock(vkb::core::Device<bindingType> &device, DeviceSizeType size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage) :
|
||||
buffer{device, size, usage, memory_usage}
|
||||
{
|
||||
if constexpr (bindingType == BindingType::Cpp)
|
||||
{
|
||||
alignment = determine_alignment(usage, device.get_gpu().get_properties().limits);
|
||||
}
|
||||
else
|
||||
{
|
||||
alignment =
|
||||
determine_alignment(static_cast<vk::BufferUsageFlags>(usage), static_cast<vk::PhysicalDeviceLimits>(device.get_gpu().get_properties().limits));
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
BufferAllocation<bindingType> BufferBlock<bindingType>::allocate(DeviceSizeType size)
|
||||
{
|
||||
if (can_allocate(size))
|
||||
{
|
||||
// Move the current offset and return an allocation
|
||||
auto aligned = aligned_offset();
|
||||
offset = aligned + size;
|
||||
if constexpr (bindingType == vkb::BindingType::Cpp)
|
||||
{
|
||||
return BufferAllocationCpp{buffer, size, aligned};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BufferAllocationC{reinterpret_cast<vkb::core::BufferC &>(buffer), static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(aligned)};
|
||||
}
|
||||
}
|
||||
|
||||
// No more space available from the underlying buffer, return empty allocation
|
||||
return BufferAllocation<bindingType>{};
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
bool BufferBlock<bindingType>::can_allocate(DeviceSizeType size) const
|
||||
{
|
||||
assert(size > 0 && "Allocation size must be greater than zero");
|
||||
return (aligned_offset() + size <= buffer.get_size());
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
typename BufferBlock<bindingType>::DeviceSizeType BufferBlock<bindingType>::get_size() const
|
||||
{
|
||||
return buffer.get_size();
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
void BufferBlock<bindingType>::reset()
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
vk::DeviceSize BufferBlock<bindingType>::aligned_offset() const
|
||||
{
|
||||
return (offset + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
vk::DeviceSize BufferBlock<bindingType>::determine_alignment(vk::BufferUsageFlags usage, vk::PhysicalDeviceLimits const &limits) const
|
||||
{
|
||||
if (usage == vk::BufferUsageFlagBits::eUniformBuffer)
|
||||
{
|
||||
return limits.minUniformBufferOffsetAlignment;
|
||||
}
|
||||
else if (usage == vk::BufferUsageFlagBits::eStorageBuffer)
|
||||
{
|
||||
return limits.minStorageBufferOffsetAlignment;
|
||||
}
|
||||
else if (usage == vk::BufferUsageFlagBits::eUniformTexelBuffer)
|
||||
{
|
||||
return limits.minTexelBufferOffsetAlignment;
|
||||
}
|
||||
else if (usage == vk::BufferUsageFlagBits::eIndexBuffer || usage == vk::BufferUsageFlagBits::eVertexBuffer ||
|
||||
usage == vk::BufferUsageFlagBits::eIndirectBuffer)
|
||||
{
|
||||
// Used to calculate the offset, required when allocating memory (its value should be power of 2)
|
||||
return 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Usage not recognised");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A pool of buffer blocks for a specific usage.
|
||||
* It may contain inactive blocks that can be recycled.
|
||||
*
|
||||
* BufferPool is a linear allocator for buffer chunks, it gives you a view of the size you want.
|
||||
* A BufferBlock is the corresponding VkBuffer and you can get smaller offsets inside it.
|
||||
* Since a shader cannot specify dynamic UBOs, it has to be done from the code
|
||||
* (set_resource_dynamic).
|
||||
*
|
||||
* When a new frame starts, buffer blocks are returned: the offset is reset and contents are
|
||||
* overwritten. The minimum allocation size is 256 kb, if you ask for more you get a dedicated
|
||||
* buffer allocation.
|
||||
*
|
||||
* We re-use descriptor sets: we only need one for the corresponding buffer infos (and we only
|
||||
* have one VkBuffer per BufferBlock), then it is bound and we use dynamic offsets.
|
||||
*/
|
||||
template <vkb::BindingType bindingType>
|
||||
class BufferPool
|
||||
{
|
||||
public:
|
||||
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
|
||||
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
|
||||
|
||||
public:
|
||||
BufferPool(
|
||||
vkb::core::Device<bindingType> &device, DeviceSizeType block_size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
BufferBlock<bindingType> &request_buffer_block(DeviceSizeType minimum_size, bool minimal = false);
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
vkb::core::DeviceCpp &device;
|
||||
std::vector<std::unique_ptr<BufferBlockCpp>> buffer_blocks; /// List of blocks requested (need to be pointers in order to keep their address constant on vector resizing)
|
||||
vk::DeviceSize block_size = 0; /// Minimum size of the blocks
|
||||
vk::BufferUsageFlags usage;
|
||||
VmaMemoryUsage memory_usage{};
|
||||
};
|
||||
|
||||
using BufferPoolC = BufferPool<vkb::BindingType::C>;
|
||||
using BufferPoolCpp = BufferPool<vkb::BindingType::Cpp>;
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
BufferPool<bindingType>::BufferPool(vkb::core::Device<bindingType> &device,
|
||||
DeviceSizeType block_size,
|
||||
BufferUsageFlagsType usage,
|
||||
VmaMemoryUsage memory_usage) :
|
||||
device{reinterpret_cast<vkb::core::DeviceCpp &>(device)}, block_size{block_size}, usage{usage}, memory_usage{memory_usage}
|
||||
{
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
BufferBlock<bindingType> &BufferPool<bindingType>::request_buffer_block(DeviceSizeType minimum_size, bool minimal)
|
||||
{
|
||||
// Find a block in the range of the blocks which can fit the minimum size
|
||||
auto it = minimal ? std::ranges::find_if(buffer_blocks,
|
||||
[&minimum_size](auto const &buffer_block) { return (buffer_block->get_size() == minimum_size) && buffer_block->can_allocate(minimum_size); }) :
|
||||
std::ranges::find_if(buffer_blocks,
|
||||
[&minimum_size](auto const &buffer_block) { return buffer_block->can_allocate(minimum_size); });
|
||||
|
||||
if (it == buffer_blocks.end())
|
||||
{
|
||||
LOGD("Building #{} buffer block ({})", buffer_blocks.size(), vk::to_string(usage));
|
||||
|
||||
vk::DeviceSize new_block_size = minimal ? minimum_size : std::max(block_size, minimum_size);
|
||||
|
||||
// Create a new block and get the iterator on it
|
||||
it = buffer_blocks.emplace(buffer_blocks.end(), std::make_unique<BufferBlockCpp>(device, new_block_size, usage, memory_usage));
|
||||
}
|
||||
|
||||
if constexpr (bindingType == vkb::BindingType::Cpp)
|
||||
{
|
||||
return *it->get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return reinterpret_cast<BufferBlockC &>(*it->get());
|
||||
}
|
||||
}
|
||||
|
||||
template <vkb::BindingType bindingType>
|
||||
void BufferPool<bindingType>::reset()
|
||||
{
|
||||
// Attention: Resetting the BufferPool is not supposed to clear the BufferBlocks, but just reset them!
|
||||
// The actual VkBuffers are used to hash the DescriptorSet in RenderFrame::request_descriptor_set.
|
||||
// Don't know (for now) how that works with resetted buffers!
|
||||
for (auto &buffer_block : buffer_blocks)
|
||||
{
|
||||
buffer_block->reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
Reference in New Issue
Block a user