1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-03-02 04:29:14 +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_physSliceLength = createInfo.size;
m_physSliceStride = align(createInfo.size, 256); m_physSliceStride = align(createInfo.size, 256);
// Initialize a single backing bufer with one slice // Allocate a single buffer slice
m_physBuffers[0] = this->allocPhysicalBuffer(1); m_physSlice = this->allocPhysicalBuffer(1)
m_physSlice = this->allocPhysicalSlice(); ->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_physSlice = slice;
m_revision += 1; m_revision += 1;
return prevSlice;
} }
DxvkPhysicalBufferSlice DxvkBuffer::allocPhysicalSlice() { DxvkPhysicalBufferSlice DxvkBuffer::allocPhysicalSlice() {
if (m_physSliceId >= m_physSliceCount) { std::unique_lock<std::mutex> lock(m_mutex);
m_physBufferId = (m_physBufferId + 1) % m_physBuffers.size();
m_physSliceId = 0; // 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) { for (uint32_t i = 0; i < m_physSliceCount; i++) {
// Make sure that all buffers have the same size. If we don't do this, m_slices.push_back(buffer->slice(
// one of the physical buffers may grow indefinitely while the others m_physSliceStride * i,
// remain small, depending on the usage pattern of the application. m_physSliceLength));
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);
} }
m_physSliceCount *= 2;
} }
return m_physBuffers[m_physBufferId]->slice( // Take the first slice from the queue
m_physSliceStride * m_physSliceId++, DxvkPhysicalBufferSlice result = std::move(m_slices.back());
m_physSliceLength); 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); 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 #pragma once
#include <mutex>
#include <vector>
#include "dxvk_buffer_res.h" #include "dxvk_buffer_res.h"
namespace dxvk { namespace dxvk {
@ -106,41 +109,50 @@ namespace dxvk {
* not call this directly as this is called implicitly * not call this directly as this is called implicitly
* by the context's \c invalidateBuffer method. * by the context's \c invalidateBuffer method.
* \param [in] slice The new backing resource * \param [in] slice The new backing resource
* \returns Previous buffer slice
*/ */
void rename( DxvkPhysicalBufferSlice rename(
const DxvkPhysicalBufferSlice& slice); const DxvkPhysicalBufferSlice& slice);
/** /**
* \brief Allocates new physical resource * \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 * \returns The new backing buffer slice
*/ */
DxvkPhysicalBufferSlice allocPhysicalSlice(); 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: private:
DxvkDevice* m_device; DxvkDevice* m_device;
DxvkBufferCreateInfo m_info; DxvkBufferCreateInfo m_info;
VkMemoryPropertyFlags m_memFlags; VkMemoryPropertyFlags m_memFlags;
DxvkPhysicalBufferSlice m_physSlice; DxvkPhysicalBufferSlice m_physSlice;
uint32_t m_revision = 0; uint32_t m_revision = 0;
// TODO maybe align this to a cache line in order std::mutex m_mutex;
// to avoid false sharing once CSMT is implemented std::vector<DxvkPhysicalBufferSlice> m_slices;
VkDeviceSize m_physBufferId = 0;
VkDeviceSize m_physSliceId = 0;
VkDeviceSize m_physSliceCount = 1;
VkDeviceSize m_physSliceLength = 0; VkDeviceSize m_physSliceLength = 0;
VkDeviceSize m_physSliceStride = 0; VkDeviceSize m_physSliceStride = 0;
VkDeviceSize m_physSliceCount = 2;
std::array<Rc<DxvkPhysicalBuffer>, 2> m_physBuffers;
Rc<DxvkPhysicalBuffer> allocPhysicalBuffer( Rc<DxvkPhysicalBuffer> allocPhysicalBuffer(
VkDeviceSize sliceCount) const; 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() { void DxvkCommandList::reset() {
m_bufferTracker.reset();
m_eventTracker.reset();
m_queryTracker.reset(); m_queryTracker.reset();
m_stagingAlloc.reset(); m_stagingAlloc.reset();
m_descAlloc.reset(); m_descAlloc.reset();

View File

@ -3,6 +3,7 @@
#include <unordered_set> #include <unordered_set>
#include "dxvk_binding.h" #include "dxvk_binding.h"
#include "dxvk_buffer.h"
#include "dxvk_descriptor.h" #include "dxvk_descriptor.h"
#include "dxvk_event_tracker.h" #include "dxvk_event_tracker.h"
#include "dxvk_lifetime.h" #include "dxvk_lifetime.h"
@ -62,6 +63,21 @@ namespace dxvk {
*/ */
void endRecording(); 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 * \brief Adds a resource to track
* *
@ -509,6 +525,7 @@ namespace dxvk {
DxvkStagingAlloc m_stagingAlloc; DxvkStagingAlloc m_stagingAlloc;
DxvkQueryTracker m_queryTracker; DxvkQueryTracker m_queryTracker;
DxvkEventTracker m_eventTracker; DxvkEventTracker m_eventTracker;
DxvkBufferTracker m_bufferTracker;
}; };

View File

@ -888,7 +888,8 @@ namespace dxvk {
const Rc<DxvkBuffer>& buffer, const Rc<DxvkBuffer>& buffer,
const DxvkPhysicalBufferSlice& slice) { const DxvkPhysicalBufferSlice& slice) {
// Allocate new backing resource // 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 // We also need to update all bindings that the buffer
// may be bound to either directly or through views. // may be bound to either directly or through views.