diff options
Diffstat (limited to 'Source/Core/VideoBackends/OGL/StreamBuffer.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/OGL/StreamBuffer.cpp | 510 |
1 files changed, 245 insertions, 265 deletions
diff --git a/Source/Core/VideoBackends/OGL/StreamBuffer.cpp b/Source/Core/VideoBackends/OGL/StreamBuffer.cpp index b616720ae2..1ed598b043 100644 --- a/Source/Core/VideoBackends/OGL/StreamBuffer.cpp +++ b/Source/Core/VideoBackends/OGL/StreamBuffer.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "Common/MemoryUtil.h" #include "Common/GL/GLUtil.h" +#include "Common/MemoryUtil.h" #include "VideoBackends/OGL/Render.h" #include "VideoBackends/OGL/StreamBuffer.h" @@ -13,27 +13,26 @@ namespace OGL { - // moved out of constructor, so m_buffer is allowed to be const static u32 GenBuffer() { - u32 id; - glGenBuffers(1, &id); - return id; + u32 id; + glGenBuffers(1, &id); + return id; } StreamBuffer::StreamBuffer(u32 type, u32 size) - : m_buffer(GenBuffer()), m_buffertype(type), m_size(ROUND_UP_POW2(size)), m_bit_per_slot(IntLog2(ROUND_UP_POW2(size) / SYNC_POINTS)) + : m_buffer(GenBuffer()), m_buffertype(type), m_size(ROUND_UP_POW2(size)), + m_bit_per_slot(IntLog2(ROUND_UP_POW2(size) / SYNC_POINTS)) { - m_iterator = 0; - m_used_iterator = 0; - m_free_iterator = 0; + m_iterator = 0; + m_used_iterator = 0; + m_free_iterator = 0; } - StreamBuffer::~StreamBuffer() { - glDeleteBuffers(1, &m_buffer); + glDeleteBuffers(1, &m_buffer); } /* Shared synchronization code for ring buffers @@ -44,76 +43,79 @@ StreamBuffer::~StreamBuffer() * To reduce overhead, the complete buffer is splitted up into SYNC_POINTS chunks. * For each of this chunks, there is a fence which checks if this chunk is still in use. * - * As our API allows to alloc more memory then it has to use, we have to catch how much is already written. + * As our API allows to alloc more memory then it has to use, we have to catch how much is already + * written. * * m_iterator - writing position * m_free_iterator - last position checked if free * m_used_iterator - last position known to be written * - * So on alloc, we have to wait for all slots between m_free_iterator and m_iterator (and set m_free_iterator to m_iterator afterwards). + * So on alloc, we have to wait for all slots between m_free_iterator and m_iterator (and set + * m_free_iterator to m_iterator afterwards). * * We also assume that this buffer is accessed by the GPU between the Unmap and Map function, * so we may create the fences on the start of mapping. - * Some here, new fences for the chunks between m_used_iterator and m_iterator (also update m_used_iterator). + * Some here, new fences for the chunks between m_used_iterator and m_iterator (also update + * m_used_iterator). * * As ring buffers have an ugly behavior on rollover, have fun to read this code ;) */ void StreamBuffer::CreateFences() { - for (int i = 0; i < SYNC_POINTS; i++) - { - m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } + for (int i = 0; i < SYNC_POINTS; i++) + { + m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } } void StreamBuffer::DeleteFences() { - for (int i = Slot(m_free_iterator) + 1; i < SYNC_POINTS; i++) - { - glDeleteSync(m_fences[i]); - } - for (int i = 0; i < Slot(m_iterator); i++) - { - glDeleteSync(m_fences[i]); - } + for (int i = Slot(m_free_iterator) + 1; i < SYNC_POINTS; i++) + { + glDeleteSync(m_fences[i]); + } + for (int i = 0; i < Slot(m_iterator); i++) + { + glDeleteSync(m_fences[i]); + } } void StreamBuffer::AllocMemory(u32 size) { - // insert waiting slots for used memory - for (int i = Slot(m_used_iterator); i < Slot(m_iterator); i++) - { - m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - m_used_iterator = m_iterator; - - // wait for new slots to end of buffer - for (int i = Slot(m_free_iterator) + 1; i <= Slot(m_iterator + size) && i < SYNC_POINTS; i++) - { - glClientWaitSync(m_fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); - glDeleteSync(m_fences[i]); - } - m_free_iterator = m_iterator + size; - - // if buffer is full - if (m_iterator + size >= m_size) - { - // insert waiting slots in unused space at the end of the buffer - for (int i = Slot(m_used_iterator); i < SYNC_POINTS; i++) - { - m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - - // move to the start - m_used_iterator = m_iterator = 0; // offset 0 is always aligned - - // wait for space at the start - for (int i = 0; i <= Slot(m_iterator + size); i++) - { - glClientWaitSync(m_fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); - glDeleteSync(m_fences[i]); - } - m_free_iterator = m_iterator + size; - } + // insert waiting slots for used memory + for (int i = Slot(m_used_iterator); i < Slot(m_iterator); i++) + { + m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + m_used_iterator = m_iterator; + + // wait for new slots to end of buffer + for (int i = Slot(m_free_iterator) + 1; i <= Slot(m_iterator + size) && i < SYNC_POINTS; i++) + { + glClientWaitSync(m_fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(m_fences[i]); + } + m_free_iterator = m_iterator + size; + + // if buffer is full + if (m_iterator + size >= m_size) + { + // insert waiting slots in unused space at the end of the buffer + for (int i = Slot(m_used_iterator); i < SYNC_POINTS; i++) + { + m_fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + // move to the start + m_used_iterator = m_iterator = 0; // offset 0 is always aligned + + // wait for space at the start + for (int i = 0; i <= Slot(m_iterator + size); i++) + { + glClientWaitSync(m_fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(m_fences[i]); + } + m_free_iterator = m_iterator + size; + } } /* The usual way to stream data to the GPU. @@ -126,34 +128,32 @@ void StreamBuffer::AllocMemory(u32 size) class MapAndOrphan : public StreamBuffer { public: - MapAndOrphan(u32 type, u32 size) : StreamBuffer(type, size) - { - glBindBuffer(m_buffertype, m_buffer); - glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); - } - - ~MapAndOrphan() - { - } - - std::pair<u8*, u32> Map(u32 size) override - { - if (m_iterator + size >= m_size) - { - glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); - m_iterator = 0; - } - u8* pointer = (u8*)glMapBufferRange(m_buffertype, m_iterator, size, - GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - return std::make_pair(pointer, m_iterator); - } - - void Unmap(u32 used_size) override - { - glFlushMappedBufferRange(m_buffertype, 0, used_size); - glUnmapBuffer(m_buffertype); - m_iterator += used_size; - } + MapAndOrphan(u32 type, u32 size) : StreamBuffer(type, size) + { + glBindBuffer(m_buffertype, m_buffer); + glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); + } + + ~MapAndOrphan() {} + std::pair<u8*, u32> Map(u32 size) override + { + if (m_iterator + size >= m_size) + { + glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); + m_iterator = 0; + } + u8* pointer = (u8*)glMapBufferRange(m_buffertype, m_iterator, size, + GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | + GL_MAP_UNSYNCHRONIZED_BIT); + return std::make_pair(pointer, m_iterator); + } + + void Unmap(u32 used_size) override + { + glFlushMappedBufferRange(m_buffertype, 0, used_size); + glUnmapBuffer(m_buffertype); + m_iterator += used_size; + } }; /* A modified streaming way without reallocation @@ -166,32 +166,29 @@ public: class MapAndSync : public StreamBuffer { public: - MapAndSync(u32 type, u32 size) : StreamBuffer(type, size) - { - CreateFences(); - glBindBuffer(m_buffertype, m_buffer); - glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); - } - - ~MapAndSync() - { - DeleteFences(); - } - - std::pair<u8*, u32> Map(u32 size) override - { - AllocMemory(size); - u8* pointer = (u8*)glMapBufferRange(m_buffertype, m_iterator, size, - GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - return std::make_pair(pointer, m_iterator); - } - - void Unmap(u32 used_size) override - { - glFlushMappedBufferRange(m_buffertype, 0, used_size); - glUnmapBuffer(m_buffertype); - m_iterator += used_size; - } + MapAndSync(u32 type, u32 size) : StreamBuffer(type, size) + { + CreateFences(); + glBindBuffer(m_buffertype, m_buffer); + glBufferData(m_buffertype, m_size, nullptr, GL_STREAM_DRAW); + } + + ~MapAndSync() { DeleteFences(); } + std::pair<u8*, u32> Map(u32 size) override + { + AllocMemory(size); + u8* pointer = (u8*)glMapBufferRange(m_buffertype, m_iterator, size, + GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | + GL_MAP_UNSYNCHRONIZED_BIT); + return std::make_pair(pointer, m_iterator); + } + + void Unmap(u32 used_size) override + { + glFlushMappedBufferRange(m_buffertype, 0, used_size); + glUnmapBuffer(m_buffertype); + m_iterator += used_size; + } }; /* Streaming fifo without mapping overhead. @@ -210,42 +207,45 @@ public: class BufferStorage : public StreamBuffer { public: - BufferStorage(u32 type, u32 size, bool _coherent = false) : StreamBuffer(type, size), coherent(_coherent) - { - CreateFences(); - glBindBuffer(m_buffertype, m_buffer); - - // PERSISTANT_BIT to make sure that the buffer can be used while mapped - // COHERENT_BIT is set so we don't have to use a MemoryBarrier on write - // CLIENT_STORAGE_BIT is set since we access the buffer more frequently on the client side then server side - glBufferStorage(m_buffertype, m_size, nullptr, - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0)); - m_pointer = (u8*)glMapBufferRange(m_buffertype, 0, m_size, - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT)); - } - - ~BufferStorage() - { - DeleteFences(); - glUnmapBuffer(m_buffertype); - glBindBuffer(m_buffertype, 0); - } - - std::pair<u8*, u32> Map(u32 size) override - { - AllocMemory(size); - return std::make_pair(m_pointer + m_iterator, m_iterator); - } - - void Unmap(u32 used_size) override - { - if (!coherent) - glFlushMappedBufferRange(m_buffertype, m_iterator, used_size); - m_iterator += used_size; - } - - u8* m_pointer; - const bool coherent; + BufferStorage(u32 type, u32 size, bool _coherent = false) + : StreamBuffer(type, size), coherent(_coherent) + { + CreateFences(); + glBindBuffer(m_buffertype, m_buffer); + + // PERSISTANT_BIT to make sure that the buffer can be used while mapped + // COHERENT_BIT is set so we don't have to use a MemoryBarrier on write + // CLIENT_STORAGE_BIT is set since we access the buffer more frequently on the client side then + // server side + glBufferStorage(m_buffertype, m_size, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | + (coherent ? GL_MAP_COHERENT_BIT : 0)); + m_pointer = (u8*)glMapBufferRange( + m_buffertype, 0, m_size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | + (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT)); + } + + ~BufferStorage() + { + DeleteFences(); + glUnmapBuffer(m_buffertype); + glBindBuffer(m_buffertype, 0); + } + + std::pair<u8*, u32> Map(u32 size) override + { + AllocMemory(size); + return std::make_pair(m_pointer + m_iterator, m_iterator); + } + + void Unmap(u32 used_size) override + { + if (!coherent) + glFlushMappedBufferRange(m_buffertype, m_iterator, used_size); + m_iterator += used_size; + } + + u8* m_pointer; + const bool coherent; }; /* --- AMD only --- @@ -258,38 +258,36 @@ public: class PinnedMemory : public StreamBuffer { public: - PinnedMemory(u32 type, u32 size) : StreamBuffer(type, size) - { - CreateFences(); - m_pointer = (u8*)AllocateAlignedMemory(ROUND_UP(m_size,ALIGN_PINNED_MEMORY), ALIGN_PINNED_MEMORY ); - glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_buffer); - glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, ROUND_UP(m_size,ALIGN_PINNED_MEMORY), m_pointer, GL_STREAM_COPY); - glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); - glBindBuffer(m_buffertype, m_buffer); - } - - ~PinnedMemory() - { - DeleteFences(); - glBindBuffer(m_buffertype, 0); - glFinish(); // ogl pipeline must be flushed, else this buffer can be in use - FreeAlignedMemory(m_pointer); - m_pointer = nullptr; - } - - std::pair<u8*, u32> Map(u32 size) override - { - AllocMemory(size); - return std::make_pair(m_pointer + m_iterator, m_iterator); - } - - void Unmap(u32 used_size) override - { - m_iterator += used_size; - } - - u8* m_pointer; - static const u32 ALIGN_PINNED_MEMORY = 4096; + PinnedMemory(u32 type, u32 size) : StreamBuffer(type, size) + { + CreateFences(); + m_pointer = + (u8*)AllocateAlignedMemory(ROUND_UP(m_size, ALIGN_PINNED_MEMORY), ALIGN_PINNED_MEMORY); + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_buffer); + glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, ROUND_UP(m_size, ALIGN_PINNED_MEMORY), + m_pointer, GL_STREAM_COPY); + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); + glBindBuffer(m_buffertype, m_buffer); + } + + ~PinnedMemory() + { + DeleteFences(); + glBindBuffer(m_buffertype, 0); + glFinish(); // ogl pipeline must be flushed, else this buffer can be in use + FreeAlignedMemory(m_pointer); + m_pointer = nullptr; + } + + std::pair<u8*, u32> Map(u32 size) override + { + AllocMemory(size); + return std::make_pair(m_pointer + m_iterator, m_iterator); + } + + void Unmap(u32 used_size) override { m_iterator += used_size; } + u8* m_pointer; + static const u32 ALIGN_PINNED_MEMORY = 4096; }; /* Fifo based on the glBufferSubData call. @@ -300,29 +298,17 @@ public: class BufferSubData : public StreamBuffer { public: - BufferSubData(u32 type, u32 size) : StreamBuffer(type, size) - { - glBindBuffer(m_buffertype, m_buffer); - glBufferData(m_buffertype, size, nullptr, GL_STATIC_DRAW); - m_pointer = new u8[m_size]; - } - - ~BufferSubData() - { - delete [] m_pointer; - } - - std::pair<u8*, u32> Map(u32 size) override - { - return std::make_pair(m_pointer, 0); - } - - void Unmap(u32 used_size) override - { - glBufferSubData(m_buffertype, 0, used_size, m_pointer); - } - - u8* m_pointer; + BufferSubData(u32 type, u32 size) : StreamBuffer(type, size) + { + glBindBuffer(m_buffertype, m_buffer); + glBufferData(m_buffertype, size, nullptr, GL_STATIC_DRAW); + m_pointer = new u8[m_size]; + } + + ~BufferSubData() { delete[] m_pointer; } + std::pair<u8*, u32> Map(u32 size) override { return std::make_pair(m_pointer, 0); } + void Unmap(u32 used_size) override { glBufferSubData(m_buffertype, 0, used_size, m_pointer); } + u8* m_pointer; }; /* Fifo based on the glBufferData call. @@ -333,69 +319,63 @@ public: class BufferData : public StreamBuffer { public: - BufferData(u32 type, u32 size) : StreamBuffer(type, size) - { - glBindBuffer(m_buffertype, m_buffer); - m_pointer = new u8[m_size]; - } - - ~BufferData() - { - delete [] m_pointer; - } - - std::pair<u8*, u32> Map(u32 size) override - { - return std::make_pair(m_pointer, 0); - } - - void Unmap(u32 used_size) override - { - glBufferData(m_buffertype, used_size, m_pointer, GL_STREAM_DRAW); - } - - u8* m_pointer; + BufferData(u32 type, u32 size) : StreamBuffer(type, size) + { + glBindBuffer(m_buffertype, m_buffer); + m_pointer = new u8[m_size]; + } + + ~BufferData() { delete[] m_pointer; } + std::pair<u8*, u32> Map(u32 size) override { return std::make_pair(m_pointer, 0); } + void Unmap(u32 used_size) override + { + glBufferData(m_buffertype, used_size, m_pointer, GL_STREAM_DRAW); + } + + u8* m_pointer; }; // Chooses the best streaming method based on the supported extensions and known issues std::unique_ptr<StreamBuffer> StreamBuffer::Create(u32 type, u32 size) { - // without basevertex support, only streaming methods whith uploads everything to zero works fine: - if (!g_ogl_config.bSupportsGLBaseVertex) - { - if (!DriverDetails::HasBug(DriverDetails::BUG_BROKENBUFFERSTREAM)) - return std::make_unique<BufferSubData>(type, size); - - // BufferData is by far the worst way, only use it if needed - return std::make_unique<BufferData>(type, size); - } - - // Prefer the syncing buffers over the orphaning one - if (g_ogl_config.bSupportsGLSync) - { - // pinned memory is much faster than buffer storage on AMD cards - if (g_ogl_config.bSupportsGLPinnedMemory && - !(DriverDetails::HasBug(DriverDetails::BUG_BROKENPINNEDMEMORY) && type == GL_ELEMENT_ARRAY_BUFFER)) - return std::make_unique<PinnedMemory>(type, size); - - // buffer storage works well in most situations - bool coherent = DriverDetails::HasBug(DriverDetails::BUG_BROKENEXPLICITFLUSH); - if (g_ogl_config.bSupportsGLBufferStorage && - !(DriverDetails::HasBug(DriverDetails::BUG_BROKENBUFFERSTORAGE) && type == GL_ARRAY_BUFFER) && - !(DriverDetails::HasBug(DriverDetails::BUG_INTELBROKENBUFFERSTORAGE) && type == GL_ELEMENT_ARRAY_BUFFER)) - return std::make_unique<BufferStorage>(type, size, coherent); - - // don't fall back to MapAnd* for Nvidia drivers - if (DriverDetails::HasBug(DriverDetails::BUG_BROKENUNSYNCMAPPING)) - return std::make_unique<BufferSubData>(type, size); - - // mapping fallback - if (g_ogl_config.bSupportsGLSync) - return std::make_unique<MapAndSync>(type, size); - } - - // default fallback, should work everywhere, but isn't the best way to do this job - return std::make_unique<MapAndOrphan>(type, size); + // without basevertex support, only streaming methods whith uploads everything to zero works fine: + if (!g_ogl_config.bSupportsGLBaseVertex) + { + if (!DriverDetails::HasBug(DriverDetails::BUG_BROKENBUFFERSTREAM)) + return std::make_unique<BufferSubData>(type, size); + + // BufferData is by far the worst way, only use it if needed + return std::make_unique<BufferData>(type, size); + } + + // Prefer the syncing buffers over the orphaning one + if (g_ogl_config.bSupportsGLSync) + { + // pinned memory is much faster than buffer storage on AMD cards + if (g_ogl_config.bSupportsGLPinnedMemory && + !(DriverDetails::HasBug(DriverDetails::BUG_BROKENPINNEDMEMORY) && + type == GL_ELEMENT_ARRAY_BUFFER)) + return std::make_unique<PinnedMemory>(type, size); + + // buffer storage works well in most situations + bool coherent = DriverDetails::HasBug(DriverDetails::BUG_BROKENEXPLICITFLUSH); + if (g_ogl_config.bSupportsGLBufferStorage && + !(DriverDetails::HasBug(DriverDetails::BUG_BROKENBUFFERSTORAGE) && + type == GL_ARRAY_BUFFER) && + !(DriverDetails::HasBug(DriverDetails::BUG_INTELBROKENBUFFERSTORAGE) && + type == GL_ELEMENT_ARRAY_BUFFER)) + return std::make_unique<BufferStorage>(type, size, coherent); + + // don't fall back to MapAnd* for Nvidia drivers + if (DriverDetails::HasBug(DriverDetails::BUG_BROKENUNSYNCMAPPING)) + return std::make_unique<BufferSubData>(type, size); + + // mapping fallback + if (g_ogl_config.bSupportsGLSync) + return std::make_unique<MapAndSync>(type, size); + } + + // default fallback, should work everywhere, but isn't the best way to do this job + return std::make_unique<MapAndOrphan>(type, size); } - } |
