1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-27 04:54:15 +01:00

[dxvk] Rework buffer slice allocation

Temporary solution that hits the allocator on every single invalidation,
which isn't great but will do for now.
This commit is contained in:
Philip Rebohle 2024-09-23 02:25:04 +02:00 committed by Philip Rebohle
parent ec2f43e5e3
commit 14990dbb49
7 changed files with 114 additions and 444 deletions

View File

@ -9,63 +9,15 @@ namespace dxvk {
DxvkBuffer::DxvkBuffer(
DxvkDevice* device,
const DxvkBufferCreateInfo& createInfo,
DxvkMemoryAllocator& memAlloc,
DxvkMemoryAllocator& allocator,
VkMemoryPropertyFlags memFlags)
: m_vkd (device->vkd()),
m_info (createInfo),
m_memAlloc (&memAlloc),
m_memFlags (memFlags),
m_shaderStages (util::shaderStages(createInfo.stages)) {
if (!(m_info.flags & VK_BUFFER_CREATE_SPARSE_BINDING_BIT)) {
// Align slices so that we don't violate any alignment
// requirements imposed by the Vulkan device/driver
VkDeviceSize sliceAlignment = computeSliceAlignment(device);
m_physSliceLength = createInfo.size;
m_physSliceStride = align(createInfo.size, sliceAlignment);
// Determine optimal allocation size for the buffer. If the buffer
// is small enough to hit the pool allocator, round its size up to
// a power of two to minimize the amount of internal fragmentation.
VkDeviceSize sliceSize = align(createInfo.size, sliceAlignment);
m_allocationSize = std::max(MinAllocationSize, sliceSize);
if (m_allocationSize <= DxvkPoolAllocator::MaxSize)
m_allocationSize = (VkDeviceSize(-1) >> bit::lzcnt(m_allocationSize - 1u)) + 1u;
m_maxAllocationSize = MaxSlicesPerAllocation * sliceSize;
m_maxAllocationSize = std::max(m_maxAllocationSize, MinAllocationSizeLimit);
m_maxAllocationSize = std::min(m_maxAllocationSize, MaxAllocationSize);
// Allocate the initial set of buffer slices. Only clear
// buffer memory if there is more than one slice, since
// we expect the client api to initialize the first slice.
bool hasMultipleSlices = m_allocationSize >= sliceSize + sliceSize;
m_buffer = allocBuffer(m_allocationSize, hasMultipleSlices);
m_physSlice.handle = m_buffer.buffer;
m_physSlice.offset = m_buffer.getBaseOffset();
m_physSlice.length = m_physSliceLength;
m_physSlice.mapPtr = m_buffer.memory.mapPtr(0);
m_lazyAlloc = hasMultipleSlices;
} else {
m_physSliceLength = createInfo.size;
m_physSliceStride = createInfo.size;
m_allocationSize = createInfo.size;
m_buffer = createSparseBuffer();
m_physSlice.handle = m_buffer.buffer;
m_physSlice.offset = 0;
m_physSlice.length = createInfo.size;
m_physSlice.mapPtr = nullptr;
m_lazyAlloc = false;
m_sparsePageTable = DxvkSparsePageTable(device, this);
}
m_allocator (&allocator),
m_properties (memFlags),
m_shaderStages (util::shaderStages(createInfo.stages)),
m_info (createInfo) {
// Create and assign actual buffer resource
assignSlice(allocateSlice());
}
@ -75,155 +27,16 @@ namespace dxvk {
const DxvkBufferImportInfo& importInfo,
VkMemoryPropertyFlags memFlags)
: m_vkd (device->vkd()),
m_properties (memFlags),
m_shaderStages (util::shaderStages(createInfo.stages)),
m_info (createInfo),
m_import (importInfo),
m_memAlloc (nullptr),
m_memFlags (memFlags),
m_shaderStages (util::shaderStages(createInfo.stages)) {
m_physSliceLength = createInfo.size;
m_physSliceStride = createInfo.size;
m_import (importInfo) {
m_physSlice.handle = importInfo.buffer;
m_physSlice.offset = importInfo.offset;
m_physSlice.length = createInfo.size;
m_physSlice.mapPtr = importInfo.mapPtr;
m_allocationSize = createInfo.size;
m_lazyAlloc = false;
}
DxvkBuffer::~DxvkBuffer() {
for (const auto& buffer : m_buffers) {
if (buffer.buffer != buffer.memory.buffer())
m_vkd->vkDestroyBuffer(m_vkd->device(), buffer.buffer, nullptr);
}
if (m_buffer.buffer != m_buffer.memory.buffer())
m_vkd->vkDestroyBuffer(m_vkd->device(), m_buffer.buffer, nullptr);
}
DxvkBufferHandle DxvkBuffer::allocBuffer(VkDeviceSize allocationSize, bool clear) const {
VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
info.flags = m_info.flags;
info.size = allocationSize;
info.usage = m_info.usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
DxvkBufferHandle handle;
// Query memory requirements and whether to use a dedicated allocation
DxvkMemoryRequirements memoryRequirements = { };
memoryRequirements.tiling = VK_IMAGE_TILING_LINEAR;
memoryRequirements.dedicated = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS };
memoryRequirements.core = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, &memoryRequirements.dedicated };
m_memAlloc->getBufferMemoryRequirements(info, memoryRequirements.core);
// Fill in desired memory properties
DxvkMemoryProperties memoryProperties = { };
memoryProperties.flags = m_memFlags;
if (memoryRequirements.dedicated.prefersDedicatedAllocation) {
handle.buffer = createBuffer(info);
memoryProperties.dedicated = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO };
memoryProperties.dedicated.buffer = handle.buffer;
}
handle.memory = m_memAlloc->alloc(memoryRequirements, memoryProperties);
if (!handle.buffer && (!handle.memory.buffer() || (handle.memory.getBufferUsage() & info.usage) != info.usage))
handle.buffer = createBuffer(info);
if (handle.buffer) {
if (m_vkd->vkBindBufferMemory(m_vkd->device(), handle.buffer,
handle.memory.memory(), handle.memory.offset()) != VK_SUCCESS)
throw DxvkError("DxvkBuffer: Failed to bind device memory");
} else {
handle.buffer = handle.memory.buffer();
}
if (clear && (m_memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))
std::memset(handle.memory.mapPtr(0), 0, info.size);
return handle;
}
DxvkBufferHandle DxvkBuffer::createSparseBuffer() const {
VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
info.flags = m_info.flags;
info.size = m_info.size;
info.usage = m_info.usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
DxvkBufferHandle handle = { };
if (m_vkd->vkCreateBuffer(m_vkd->device(),
&info, nullptr, &handle.buffer) != VK_SUCCESS) {
throw DxvkError(str::format(
"DxvkBuffer: Failed to create buffer:"
"\n flags: ", std::hex, info.flags,
"\n size: ", std::dec, info.size,
"\n usage: ", std::hex, info.usage));
}
return handle;
}
VkBuffer DxvkBuffer::createBuffer(const VkBufferCreateInfo& info) const {
VkBuffer buffer = VK_NULL_HANDLE;
if (m_vkd->vkCreateBuffer(m_vkd->device(), &info, nullptr, &buffer)) {
throw DxvkError(str::format(
"DxvkBuffer: Failed to create buffer:"
"\n flags: ", std::hex, info.flags,
"\n size: ", std::dec, info.size,
"\n usage: ", std::hex, info.usage));
}
return buffer;
}
VkDeviceSize DxvkBuffer::computeSliceAlignment(DxvkDevice* device) const {
const auto& devInfo = device->properties();
VkDeviceSize result = sizeof(uint32_t);
if (m_info.usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
result = std::max(result, devInfo.core.properties.limits.minUniformBufferOffsetAlignment);
result = std::max(result, devInfo.extRobustness2.robustUniformBufferAccessSizeAlignment);
}
if (m_info.usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
result = std::max(result, devInfo.core.properties.limits.minStorageBufferOffsetAlignment);
result = std::max(result, devInfo.extRobustness2.robustStorageBufferAccessSizeAlignment);
}
if (m_info.usage & (VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) {
result = std::max(result, devInfo.core.properties.limits.minTexelBufferOffsetAlignment);
result = std::max(result, VkDeviceSize(16));
}
if (m_info.usage & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
&& m_info.size > (devInfo.core.properties.limits.optimalBufferCopyOffsetAlignment / 2))
result = std::max(result, devInfo.core.properties.limits.optimalBufferCopyOffsetAlignment);
// For some reason, Warhammer Chaosbane breaks otherwise
if (m_info.usage & (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT))
result = std::max(result, VkDeviceSize(256));
if (m_memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
result = std::max(result, devInfo.core.properties.limits.nonCoherentAtomSize);
result = std::max(result, VkDeviceSize(64));
}
return result;
}
@ -304,21 +117,4 @@ namespace dxvk {
}
}
DxvkBufferTracker:: DxvkBufferTracker() { }
DxvkBufferTracker::~DxvkBufferTracker() { }
void DxvkBufferTracker::reset() {
std::sort(m_entries.begin(), m_entries.end(),
[] (const Entry& a, const Entry& b) {
return a.slice.handle < b.slice.handle;
});
for (const auto& e : m_entries)
e.buffer->freeSlice(e.slice);
m_entries.clear();
}
}

View File

@ -45,16 +45,16 @@ namespace dxvk {
*/
struct DxvkBufferViewCreateInfo {
/// Buffer data format, like image data
VkFormat format;
VkFormat format = VK_FORMAT_UNDEFINED;
/// Offset of the buffer region to include in the view
VkDeviceSize rangeOffset;
VkDeviceSize rangeOffset = 0u;
/// Size of the buffer region to include in the view
VkDeviceSize rangeLength;
VkDeviceSize rangeLength = 0u;
/// Buffer view usage flags
VkBufferUsageFlags usage;
VkBufferUsageFlags usage = 0u;
};
@ -129,36 +129,20 @@ namespace dxvk {
*/
class DxvkBufferAllocation {
friend class DxvkBuffer;
friend class DxvkContext; /* TODO remove */
public:
DxvkBufferAllocation() = default;
explicit DxvkBufferAllocation(DxvkBufferSliceHandle slice)
: m_slice(slice) { }
DxvkBufferAllocation(const DxvkBufferAllocation&) = default;
DxvkBufferAllocation& operator = (const DxvkBufferAllocation&) = default;
DxvkBufferAllocation(DxvkBufferAllocation&& other)
: m_slice(other.m_slice) {
other.m_slice = DxvkBufferSliceHandle();
}
DxvkBufferAllocation& operator = (DxvkBufferAllocation&& other) {
m_slice = other.m_slice;
other.m_slice = DxvkBufferSliceHandle();
return *this;
}
~DxvkBufferAllocation() = default;
DxvkBufferAllocation(
Rc<DxvkResourceAllocation> allocation)
: m_allocation(std::move(allocation)) { }
/**
* \brief Retrieves CPU pointer
* \returns Pointer to the mapped buffer slice
*/
void* mapPtr() const {
return m_slice.mapPtr;
return m_allocation->getBufferInfo().mapPtr;
}
/**
@ -166,12 +150,20 @@ namespace dxvk {
* \returns \c true if the slice is valid
*/
explicit operator bool () const {
return m_slice.handle != VK_NULL_HANDLE;
return bool(m_allocation);
}
/**
* \brief Extracts resource allocation
* \returns Underlying resource allocation
*/
Rc<DxvkResourceAllocation> extract() {
return std::move(m_allocation);
}
private:
DxvkBufferSliceHandle m_slice = { };
Rc<DxvkResourceAllocation> m_allocation;
};
@ -189,9 +181,8 @@ namespace dxvk {
constexpr static VkDeviceSize MaxAllocationSize = DxvkPageAllocator::PageSize;
constexpr static VkDeviceSize MinAllocationSize = DxvkPoolAllocator::MinSize;
constexpr static VkDeviceSize MinAllocationSizeLimit = MaxAllocationSize / 32u;
constexpr static uint32_t MaxSlicesPerAllocation = 64u;
constexpr static VkDeviceSize MinMappedAllocationSize = DxvkPageAllocator::PageSize / 32u;
constexpr static VkDeviceSize MinMappedSlicesPerAllocation = 3u;
public:
DxvkBuffer(
@ -224,7 +215,7 @@ namespace dxvk {
* \returns Vulkan memory flags
*/
VkMemoryPropertyFlags memFlags() const {
return m_memFlags;
return m_properties;
}
/**
@ -237,7 +228,9 @@ namespace dxvk {
* \returns Pointer to mapped memory region
*/
void* mapPtr(VkDeviceSize offset) const {
return reinterpret_cast<char*>(m_physSlice.mapPtr) + offset;
return m_bufferInfo.mapPtr
? reinterpret_cast<char*>(m_bufferInfo.mapPtr) + offset
: nullptr;
}
/**
@ -255,7 +248,12 @@ namespace dxvk {
* \returns Buffer slice handle
*/
DxvkBufferSliceHandle getSliceHandle() const {
return m_physSlice;
DxvkBufferSliceHandle result = { };
result.handle = m_bufferInfo.buffer;
result.offset = m_bufferInfo.offset;
result.length = m_info.size;
result.mapPtr = mapPtr(0);
return result;
}
/**
@ -266,9 +264,9 @@ namespace dxvk {
* \returns Buffer slice handle
*/
DxvkBufferSliceHandle getSliceHandle(VkDeviceSize offset, VkDeviceSize length) const {
DxvkBufferSliceHandle result;
result.handle = m_physSlice.handle;
result.offset = m_physSlice.offset + offset;
DxvkBufferSliceHandle result = { };
result.handle = m_bufferInfo.buffer;
result.offset = m_bufferInfo.offset + offset;
result.length = length;
result.mapPtr = mapPtr(offset);
return result;
@ -282,10 +280,10 @@ namespace dxvk {
* \returns Buffer slice descriptor
*/
DxvkDescriptorInfo getDescriptor(VkDeviceSize offset, VkDeviceSize length) const {
DxvkDescriptorInfo result;
result.buffer.buffer = m_physSlice.handle;
result.buffer.offset = m_physSlice.offset + offset;
result.buffer.range = length;
DxvkDescriptorInfo result = { };
result.buffer.buffer = m_bufferInfo.buffer;
result.buffer.offset = m_bufferInfo.offset + offset;
result.buffer.range = length;
return result;
}
@ -296,7 +294,7 @@ namespace dxvk {
* \returns The current xfb vertex stride
*/
uint32_t getXfbVertexStride() const {
return m_vertexStride;
return m_xfbStride;
}
/**
@ -308,7 +306,7 @@ namespace dxvk {
* \param [in] stride Vertex stride
*/
void setXfbVertexStride(uint32_t stride) {
m_vertexStride = stride;
m_xfbStride = stride;
}
/**
@ -316,39 +314,13 @@ namespace dxvk {
* \returns The new buffer slice
*/
DxvkBufferAllocation allocateSlice() {
std::unique_lock<sync::Spinlock> freeLock(m_freeMutex);
// If no slices are available, swap the two free lists.
if (unlikely(m_freeSlices.empty())) {
std::unique_lock<sync::Spinlock> swapLock(m_swapMutex);
std::swap(m_freeSlices, m_nextSlices);
}
VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
info.flags = m_info.flags;
info.usage = m_info.usage;
info.size = m_info.size;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
// If there are still no slices available, create a new
// backing buffer and add all slices to the free list.
if (unlikely(m_freeSlices.empty())) {
if (likely(!m_lazyAlloc)) {
DxvkBufferHandle handle = allocBuffer(m_allocationSize, true);
for (uint32_t i = 0; (i + 1) * m_physSliceStride <= m_allocationSize; i++)
pushSlice(handle, i);
m_buffers.push_back(std::move(handle));
if (2u * m_allocationSize <= m_maxAllocationSize)
m_allocationSize *= 2u;
} else {
for (uint32_t i = 1; (i + 1) * m_physSliceStride <= m_allocationSize; i++)
pushSlice(m_buffer, i);
m_lazyAlloc = false;
}
}
// Take the first slice from the queue
DxvkBufferAllocation result(m_freeSlices.back());
m_freeSlices.pop_back();
return result;
return DxvkBufferAllocation(m_allocator->createBufferResource(info, m_properties));
}
/**
@ -359,12 +331,13 @@ namespace dxvk {
* not call this directly as this is called implicitly
* by the context's \c invalidateBuffer method.
* \param [in] slice The new backing resource
* \returns Previous buffer slice
* \returns Previous buffer allocation
*/
DxvkBufferAllocation assignSlice(DxvkBufferAllocation&& slice) {
DxvkBufferAllocation result(m_physSlice);
m_physSlice = slice.m_slice;
slice.m_slice = DxvkBufferSliceHandle();
Rc<DxvkResourceAllocation> assignSlice(DxvkBufferAllocation&& slice) {
Rc<DxvkResourceAllocation> result = std::move(m_storage);
m_storage = std::move(slice.m_allocation);
m_bufferInfo = m_storage->getBufferInfo();
return result;
}
@ -373,21 +346,7 @@ namespace dxvk {
* \returns Current buffer allocation
*/
DxvkBufferAllocation getAllocation() const {
return DxvkBufferAllocation(m_physSlice);
}
/**
* \brief Frees a buffer slice
*
* Marks the slice as free so that it can be used for
* subsequent allocations. Called automatically when
* the slice is no longer needed by the GPU.
* \param [in] slice The buffer slice to free
*/
void freeSlice(const DxvkBufferSliceHandle& slice) {
// Add slice to a separate free list to reduce lock contention.
std::unique_lock<sync::Spinlock> swapLock(m_swapMutex);
m_nextSlices.emplace_back(slice);
return DxvkBufferAllocation(m_storage);
}
/**
@ -400,53 +359,19 @@ namespace dxvk {
private:
Rc<vk::DeviceFn> m_vkd;
DxvkBufferCreateInfo m_info;
DxvkBufferImportInfo m_import;
DxvkMemoryAllocator* m_memAlloc;
VkMemoryPropertyFlags m_memFlags;
VkShaderStageFlags m_shaderStages;
DxvkBufferHandle m_buffer;
DxvkBufferSliceHandle m_physSlice;
uint32_t m_vertexStride = 0;
Rc<vk::DeviceFn> m_vkd;
DxvkMemoryAllocator* m_allocator = nullptr;
VkMemoryPropertyFlags m_properties = 0u;
VkShaderStageFlags m_shaderStages = 0u;
alignas(CACHE_LINE_SIZE)
sync::Spinlock m_freeMutex;
DxvkBufferCreateInfo m_info = { };
DxvkBufferImportInfo m_import = { };
uint32_t m_lazyAlloc = false;
VkDeviceSize m_physSliceLength = 0;
VkDeviceSize m_physSliceStride = 0;
VkDeviceSize m_xfbStride = 0u;
VkDeviceSize m_allocationSize = 0;
VkDeviceSize m_maxAllocationSize = 0;
DxvkResourceBufferInfo m_bufferInfo = { };
std::vector<DxvkBufferHandle> m_buffers;
std::vector<DxvkBufferAllocation> m_freeSlices;
alignas(CACHE_LINE_SIZE)
sync::Spinlock m_swapMutex;
std::vector<DxvkBufferAllocation> m_nextSlices;
void pushSlice(const DxvkBufferHandle& handle, uint32_t index) {
DxvkBufferSliceHandle slice;
slice.handle = handle.buffer;
slice.offset = handle.getBaseOffset() + m_physSliceStride * index;
slice.length = m_physSliceLength;
slice.mapPtr = handle.memory.mapPtr(m_physSliceStride * index);
m_freeSlices.emplace_back(slice);
}
DxvkBufferHandle allocBuffer(
VkDeviceSize allocationSize,
bool clear) const;
DxvkBufferHandle createSparseBuffer() const;
VkBuffer createBuffer(const VkBufferCreateInfo& info) const;
VkDeviceSize computeSliceAlignment(
DxvkDevice* device) const;
Rc<DxvkResourceAllocation> m_storage;
};
@ -772,48 +697,5 @@ namespace dxvk {
const DxvkBufferSliceHandle& slice);
};
/**
* \brief Buffer slice tracker
*
* Stores a list of buffer slices that can be
* freed. Useful when buffers have been renamed
* and the original slice is no longer needed.
*/
class DxvkBufferTracker {
public:
DxvkBufferTracker();
~DxvkBufferTracker();
/**
* \brief Add buffer slice for tracking
*
* The slice will be returned to the
* buffer on the next call to \c reset.
* \param [in] buffer The parent buffer
* \param [in] slice The buffer slice
*/
void freeBufferSlice(const Rc<DxvkBuffer>& buffer, const DxvkBufferSliceHandle& slice) {
m_entries.push_back({ buffer, slice });
}
/**
* \brief Returns tracked buffer slices
*/
void reset();
private:
struct Entry {
Rc<DxvkBuffer> buffer;
DxvkBufferSliceHandle slice;
};
std::vector<Entry> m_entries;
};
}

View File

@ -368,9 +368,6 @@ namespace dxvk {
// that are no longer in use
m_resources.reset();
// Return buffer memory slices
m_bufferTracker.reset();
// Return query and event handles
m_gpuQueryTracker.reset();
m_gpuEventTracker.reset();

View File

@ -248,21 +248,6 @@ namespace dxvk {
*/
void next();
/**
* \brief Frees buffer slice
*
* After the command buffer execution has finished,
* the given buffer slice will be released to the
* virtual buffer object so that it can be reused.
* \param [in] buffer The virtual buffer object
* \param [in] slice The buffer slice handle
*/
void freeBufferSlice(
const Rc<DxvkBuffer>& buffer,
const DxvkBufferSliceHandle& slice) {
m_bufferTracker.freeBufferSlice(buffer, slice);
}
/**
* \brief Adds a resource to track
*
@ -323,7 +308,7 @@ namespace dxvk {
* \brief Notifies resources and signals
*/
void notifyObjects() {
m_resources.notify();
m_resources.reset();
m_signalTracker.notify();
}
@ -1048,7 +1033,6 @@ namespace dxvk {
DxvkSignalTracker m_signalTracker;
DxvkGpuEventTracker m_gpuEventTracker;
DxvkGpuQueryTracker m_gpuQueryTracker;
DxvkBufferTracker m_bufferTracker;
DxvkStatCounters m_statCounters;
DxvkCommandSubmission m_commandSubmission;

View File

@ -1830,9 +1830,9 @@ namespace dxvk {
const Rc<DxvkBuffer>& buffer,
DxvkBufferAllocation&& slice) {
// Allocate new backing resource
DxvkBufferAllocation prevSlice = buffer->assignSlice(std::move(slice));
m_cmd->freeBufferSlice(buffer, prevSlice.m_slice);
Rc<DxvkResourceAllocation> prevAllocation = buffer->assignSlice(std::move(slice));
m_cmd->trackResource<DxvkAccess::None>(prevAllocation);
// We also need to update all bindings that the buffer
// may be bound to either directly or through views.
VkBufferUsageFlags usage = buffer->info().usage &

View File

@ -6,13 +6,9 @@ namespace dxvk {
DxvkLifetimeTracker::~DxvkLifetimeTracker() { }
void DxvkLifetimeTracker::notify() {
m_resources.clear();
}
void DxvkLifetimeTracker::reset() {
m_resources.clear();
m_allocations.clear();
}
}

View File

@ -11,39 +11,38 @@ namespace dxvk {
*
* Keeps a resource alive and stores access information.
*/
template<typename T>
class DxvkLifetime {
static constexpr uintptr_t AccessMask = 0x3u;
static constexpr uintptr_t PointerMask = ~AccessMask;
static_assert(alignof(T) > AccessMask);
public:
DxvkLifetime()
: m_resource(nullptr), m_access(DxvkAccess::None) { }
DxvkLifetime() = default;
DxvkLifetime(
DxvkResource* resource,
T* resource,
DxvkAccess access)
: m_resource(resource), m_access(access) {
: m_ptr(reinterpret_cast<uintptr_t>(resource) | uintptr_t(access)) {
acquire();
}
DxvkLifetime(DxvkLifetime&& other)
: m_resource(other.m_resource), m_access(other.m_access) {
other.m_resource = nullptr;
other.m_access = DxvkAccess::None;
: m_ptr(other.m_ptr) {
other.m_ptr = 0u;
}
DxvkLifetime(const DxvkLifetime& other)
: m_resource(other.m_resource), m_access(other.m_access) {
: m_ptr(other.m_ptr) {
acquire();
}
DxvkLifetime& operator = (DxvkLifetime&& other) {
release();
m_resource = other.m_resource;
m_access = other.m_access;
other.m_resource = nullptr;
other.m_access = DxvkAccess::None;
m_ptr = other.m_ptr;
other.m_ptr = 0u;
return *this;
}
@ -51,8 +50,7 @@ namespace dxvk {
other.acquire();
release();
m_resource = other.m_resource;
m_access = other.m_access;
m_ptr = other.m_ptr;
return *this;
}
@ -62,17 +60,24 @@ namespace dxvk {
private:
DxvkResource* m_resource;
DxvkAccess m_access;
uintptr_t m_ptr = 0u;
T* ptr() const {
return reinterpret_cast<T*>(m_ptr & PointerMask);
}
DxvkAccess access() const {
return DxvkAccess(m_ptr & AccessMask);
}
void acquire() const {
if (m_resource)
m_resource->acquire(m_access);
if (m_ptr)
ptr()->acquire(access());
}
void release() const {
if (m_resource)
m_resource->release(m_access);
if (m_ptr)
ptr()->release(access());
}
};
@ -102,6 +107,15 @@ namespace dxvk {
m_resources.emplace_back(rc, Access);
}
/**
* \brief Adds a resource allocation to track
* \param [in] rc The allocation to track
*/
template<DxvkAccess Access>
void trackResource(DxvkResourceAllocation* rc) {
m_allocations.emplace_back(rc, Access);
}
/**
* \brief Releases resources
*
@ -119,7 +133,8 @@ namespace dxvk {
private:
std::vector<DxvkLifetime> m_resources;
std::vector<DxvkLifetime<DxvkResource>> m_resources;
std::vector<DxvkLifetime<DxvkResourceAllocation>> m_allocations;
};