1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-02 19:24:12 +01:00

[dxvk] Implement thread-safe buffer renaming

This is required for resource mapping on deferred contexts.
May also fix a potential synchronization issue where a buffer
could be mapped multiple times before the CS thread would mark
the physical buffer as used, which would result in invalid data.
This commit is contained in:
Philip Rebohle 2018-03-19 02:18:44 +01:00
parent d6d6ed4efd
commit 0eaa3fea3b
No known key found for this signature in database
GPG Key ID: C8CC613427A31C99
5 changed files with 126 additions and 38 deletions

View File

@ -15,44 +15,49 @@ namespace dxvk {
m_physSliceLength = createInfo.size;
m_physSliceStride = align(createInfo.size, 256);
// Initialize a single backing bufer with one slice
m_physBuffers[0] = this->allocPhysicalBuffer(1);
m_physSlice = this->allocPhysicalSlice();
// Allocate a single buffer slice
m_physSlice = this->allocPhysicalBuffer(1)
->slice(0, m_physSliceStride);
}
void DxvkBuffer::rename(const DxvkPhysicalBufferSlice& slice) {
DxvkPhysicalBufferSlice DxvkBuffer::rename(const DxvkPhysicalBufferSlice& slice) {
DxvkPhysicalBufferSlice prevSlice = std::move(m_physSlice);
m_physSlice = slice;
m_revision += 1;
return prevSlice;
}
DxvkPhysicalBufferSlice DxvkBuffer::allocPhysicalSlice() {
if (m_physSliceId >= m_physSliceCount) {
m_physBufferId = (m_physBufferId + 1) % m_physBuffers.size();
m_physSliceId = 0;
std::unique_lock<std::mutex> lock(m_mutex);
// If necessary, create a new buffer
// that we'll allocate slices from.
if (m_slices.size() == 0) {
const Rc<DxvkPhysicalBuffer> buffer
= this->allocPhysicalBuffer(m_physSliceCount);
if (m_physBuffers[m_physBufferId] == nullptr) {
// Make sure that all buffers have the same size. If we don't do this,
// one of the physical buffers may grow indefinitely while the others
// remain small, depending on the usage pattern of the application.
m_physBuffers[m_physBufferId] = this->allocPhysicalBuffer(m_physSliceCount);
} else if (m_physBuffers[m_physBufferId]->isInUse()) {
// Allocate a new physical buffer if the current one is still in use.
// This also indicates that the buffer gets updated frequently, so we
// will double the size of the physical buffers to accomodate for it.
if (m_physBufferId == 0) {
std::fill(m_physBuffers.begin(), m_physBuffers.end(), nullptr);
m_physSliceCount *= 2;
}
m_physBuffers[m_physBufferId] = this->allocPhysicalBuffer(m_physSliceCount);
for (uint32_t i = 0; i < m_physSliceCount; i++) {
m_slices.push_back(buffer->slice(
m_physSliceStride * i,
m_physSliceLength));
}
m_physSliceCount *= 2;
}
return m_physBuffers[m_physBufferId]->slice(
m_physSliceStride * m_physSliceId++,
m_physSliceLength);
// Take the first slice from the queue
DxvkPhysicalBufferSlice result = std::move(m_slices.back());
m_slices.pop_back();
return result;
}
void DxvkBuffer::freePhysicalSlice(const DxvkPhysicalBufferSlice& slice) {
std::unique_lock<std::mutex> lock(m_mutex);
m_slices.push_back(slice);
}
@ -93,4 +98,23 @@ namespace dxvk {
m_vkd, m_buffer->slice(), m_info);
}
DxvkBufferTracker:: DxvkBufferTracker() { }
DxvkBufferTracker::~DxvkBufferTracker() { }
void DxvkBufferTracker::freeBufferSlice(
const Rc<DxvkBuffer>& buffer,
const DxvkPhysicalBufferSlice& slice) {
m_entries.push_back({ buffer, slice });
}
void DxvkBufferTracker::reset() {
for (const auto& e : m_entries)
e.buffer->freePhysicalSlice(e.slice);
m_entries.clear();
}
}

View File

@ -1,5 +1,8 @@
#pragma once
#include <mutex>
#include <vector>
#include "dxvk_buffer_res.h"
namespace dxvk {
@ -106,41 +109,50 @@ 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
*/
void rename(
DxvkPhysicalBufferSlice rename(
const DxvkPhysicalBufferSlice& slice);
/**
* \brief Allocates new physical resource
*
* This method must not be called from multiple threads
* simultaneously, but it can be called in parallel with
* \ref rename and other methods of this class.
* \returns The new backing buffer slice
*/
DxvkPhysicalBufferSlice allocPhysicalSlice();
/**
* \brief Frees a physical 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 freePhysicalSlice(
const DxvkPhysicalBufferSlice& slice);
private:
DxvkDevice* m_device;
DxvkBufferCreateInfo m_info;
VkMemoryPropertyFlags m_memFlags;
DxvkPhysicalBufferSlice m_physSlice;
uint32_t m_revision = 0;
// TODO maybe align this to a cache line in order
// to avoid false sharing once CSMT is implemented
VkDeviceSize m_physBufferId = 0;
VkDeviceSize m_physSliceId = 0;
VkDeviceSize m_physSliceCount = 1;
std::mutex m_mutex;
std::vector<DxvkPhysicalBufferSlice> m_slices;
VkDeviceSize m_physSliceLength = 0;
VkDeviceSize m_physSliceStride = 0;
std::array<Rc<DxvkPhysicalBuffer>, 2> m_physBuffers;
VkDeviceSize m_physSliceCount = 2;
Rc<DxvkPhysicalBuffer> allocPhysicalBuffer(
VkDeviceSize sliceCount) const;
void lock();
void unlock();
};
@ -360,4 +372,36 @@ namespace dxvk {
};
/**
* \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();
void freeBufferSlice(
const Rc<DxvkBuffer>& buffer,
const DxvkPhysicalBufferSlice& slice);
void reset();
private:
struct Entry {
Rc<DxvkBuffer> buffer;
DxvkPhysicalBufferSlice slice;
};
std::vector<Entry> m_entries;
};
}

View File

@ -82,6 +82,8 @@ namespace dxvk {
void DxvkCommandList::reset() {
m_bufferTracker.reset();
m_eventTracker.reset();
m_queryTracker.reset();
m_stagingAlloc.reset();
m_descAlloc.reset();

View File

@ -3,6 +3,7 @@
#include <unordered_set>
#include "dxvk_binding.h"
#include "dxvk_buffer.h"
#include "dxvk_descriptor.h"
#include "dxvk_event_tracker.h"
#include "dxvk_lifetime.h"
@ -62,6 +63,21 @@ namespace dxvk {
*/
void endRecording();
/**
* \brief Frees physical buffer slice
*
* After the command buffer execution has finished,
* the given physical 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 physical buffer slice
*/
void freePhysicalBufferSlice(
const Rc<DxvkBuffer>& buffer,
const DxvkPhysicalBufferSlice& slice) {
m_bufferTracker.freeBufferSlice(buffer, slice);
}
/**
* \brief Adds a resource to track
*
@ -509,6 +525,7 @@ namespace dxvk {
DxvkStagingAlloc m_stagingAlloc;
DxvkQueryTracker m_queryTracker;
DxvkEventTracker m_eventTracker;
DxvkBufferTracker m_bufferTracker;
};

View File

@ -888,7 +888,8 @@ namespace dxvk {
const Rc<DxvkBuffer>& buffer,
const DxvkPhysicalBufferSlice& slice) {
// Allocate new backing resource
buffer->rename(slice);
DxvkPhysicalBufferSlice prevSlice = buffer->rename(slice);
m_cmd->freePhysicalBufferSlice(buffer, prevSlice);
// We also need to update all bindings that the buffer
// may be bound to either directly or through views.