mirror of
https://github.com/stenzek/duckstation.git
synced 2025-06-06 19:45:33 +00:00
OpenGLDevice: Add map-based stream buffer paths
MapAndSync ends up around 25% faster on ancient Mali drivers without ARB_buffer_storage support.
This commit is contained in:
parent
1daa60c64d
commit
45afc8f7b1
@ -6,9 +6,12 @@
|
||||
#include "common/align.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
OpenGLStreamBuffer::OpenGLStreamBuffer(GLenum target, GLuint buffer_id, u32 size)
|
||||
: m_target(target), m_buffer_id(buffer_id), m_size(size)
|
||||
{
|
||||
@ -154,6 +157,83 @@ private:
|
||||
u8* m_cpu_buffer;
|
||||
};
|
||||
|
||||
// Uses multiple buffers with unsynchronized mapping.
|
||||
class MapAndOrphanStreamBuffer final : public OpenGLStreamBuffer
|
||||
{
|
||||
public:
|
||||
~MapAndOrphanStreamBuffer() override = default;
|
||||
|
||||
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error)
|
||||
{
|
||||
glGetError();
|
||||
|
||||
GLuint buffer_id;
|
||||
glGenBuffers(1, &buffer_id);
|
||||
glBindBuffer(target, buffer_id);
|
||||
glBufferData(target, size, nullptr, GL_STREAM_DRAW);
|
||||
|
||||
const GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) [[unlikely]]
|
||||
{
|
||||
Error::SetStringFmt(error, "Failed to create buffer: 0x{:X}", err);
|
||||
glBindBuffer(target, 0);
|
||||
glDeleteBuffers(1, &buffer_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::unique_ptr<OpenGLStreamBuffer>(new MapAndOrphanStreamBuffer(target, buffer_id, size));
|
||||
}
|
||||
|
||||
MappingResult Map(u32 alignment, u32 min_size) override
|
||||
{
|
||||
Bind();
|
||||
|
||||
if (m_position > 0)
|
||||
m_position = Common::AlignUp(m_position, alignment);
|
||||
|
||||
if ((m_position + min_size) > m_size)
|
||||
{
|
||||
// create a new buffer
|
||||
m_position = 0;
|
||||
glBufferData(m_target, m_size, nullptr, GL_STREAM_DRAW);
|
||||
}
|
||||
|
||||
// explicit flush because we may not use the whole range
|
||||
// NOTE: using GL_MAP_INVALIDATE_RANGE_BIT causes massive memory blowup on AMD, but it's not using this path anyway
|
||||
void* map = glMapBufferRange(m_target, m_position, m_size - m_position,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
|
||||
Assert(map != nullptr);
|
||||
|
||||
return MappingResult{.pointer = map,
|
||||
.buffer_offset = m_position,
|
||||
.index_aligned = m_position / alignment,
|
||||
.space_aligned = (m_size - m_position) / alignment};
|
||||
}
|
||||
|
||||
u32 Unmap(u32 used_size) override
|
||||
{
|
||||
DebugAssert((m_position + used_size) <= m_size);
|
||||
|
||||
Bind();
|
||||
|
||||
if (used_size > 0)
|
||||
glFlushMappedBufferRange(m_target, 0, used_size);
|
||||
|
||||
glUnmapBuffer(m_target);
|
||||
|
||||
const u32 prev_position = m_position;
|
||||
m_position += used_size;
|
||||
return prev_position;
|
||||
}
|
||||
|
||||
u32 GetChunkSize() const override { return m_size; }
|
||||
|
||||
private:
|
||||
MapAndOrphanStreamBuffer(GLenum target, GLuint buffer_id, u32 size) : OpenGLStreamBuffer(target, buffer_id, size) {}
|
||||
|
||||
u32 m_position = 0;
|
||||
};
|
||||
|
||||
// Base class for implementations which require syncing.
|
||||
class SyncingStreamBuffer : public OpenGLStreamBuffer
|
||||
{
|
||||
@ -243,6 +323,73 @@ protected:
|
||||
std::array<GLsync, NUM_SYNC_POINTS> m_sync_objects{};
|
||||
};
|
||||
|
||||
// Uses a single buffer with unsynchronized mapping.
|
||||
class MapAndSyncStreamBuffer : public SyncingStreamBuffer
|
||||
{
|
||||
public:
|
||||
~MapAndSyncStreamBuffer() override = default;
|
||||
|
||||
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error, bool coherent = true)
|
||||
{
|
||||
glGetError();
|
||||
|
||||
GLuint buffer_id;
|
||||
glGenBuffers(1, &buffer_id);
|
||||
glBindBuffer(target, buffer_id);
|
||||
glBufferData(target, size, nullptr, GL_STREAM_DRAW);
|
||||
|
||||
const GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) [[unlikely]]
|
||||
{
|
||||
Error::SetStringFmt(error, "Failed to create buffer: 0x{:X}", err);
|
||||
glBindBuffer(target, 0);
|
||||
glDeleteBuffers(1, &buffer_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::unique_ptr<OpenGLStreamBuffer>(new MapAndSyncStreamBuffer(target, buffer_id, size));
|
||||
}
|
||||
|
||||
MappingResult Map(u32 alignment, u32 min_size) override
|
||||
{
|
||||
if (m_position > 0)
|
||||
m_position = Common::AlignUp(m_position, alignment);
|
||||
|
||||
AllocateSpace(min_size);
|
||||
DebugAssert((m_position + min_size) <= (m_available_block_index * m_bytes_per_block));
|
||||
|
||||
// explicit flush because we may not use the whole range
|
||||
Bind();
|
||||
const u32 space = ((m_available_block_index * m_bytes_per_block) - m_position);
|
||||
void* map = glMapBufferRange(m_target, m_position, space,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
|
||||
Assert(map != nullptr);
|
||||
|
||||
return MappingResult{.pointer = map,
|
||||
.buffer_offset = m_position,
|
||||
.index_aligned = m_position / alignment,
|
||||
.space_aligned = space / alignment};
|
||||
}
|
||||
|
||||
u32 Unmap(u32 used_size) override
|
||||
{
|
||||
Bind();
|
||||
|
||||
DebugAssert((m_position + used_size) <= m_size);
|
||||
|
||||
if (used_size > 0)
|
||||
glFlushMappedBufferRange(m_target, 0, used_size);
|
||||
glUnmapBuffer(m_target);
|
||||
|
||||
const u32 prev_position = m_position;
|
||||
m_position += used_size;
|
||||
return prev_position;
|
||||
}
|
||||
|
||||
private:
|
||||
MapAndSyncStreamBuffer(GLenum target, GLuint buffer_id, u32 size) : SyncingStreamBuffer(target, buffer_id, size) {}
|
||||
};
|
||||
|
||||
class BufferStorageStreamBuffer : public SyncingStreamBuffer
|
||||
{
|
||||
public:
|
||||
@ -332,12 +479,39 @@ private:
|
||||
|
||||
std::unique_ptr<OpenGLStreamBuffer> OpenGLStreamBuffer::Create(GLenum target, u32 size, Error* error /* = nullptr */)
|
||||
{
|
||||
// In terms of speed: persistent mapping > map+sync -> map+orphan -> bufferdata/subdata
|
||||
|
||||
std::unique_ptr<OpenGLStreamBuffer> buf;
|
||||
if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage)
|
||||
{
|
||||
buf = BufferStorageStreamBuffer::Create(target, size, error);
|
||||
if (buf)
|
||||
{
|
||||
DEV_LOG("Using BufferStorageStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer doing our own synchronization if supported.
|
||||
if (GLAD_GL_VERSION_3_2 || GLAD_GL_ARB_sync || GLAD_GL_ES_VERSION_3_0)
|
||||
{
|
||||
buf = MapAndSyncStreamBuffer::Create(target, size, error);
|
||||
if (buf)
|
||||
{
|
||||
DEV_LOG("Using MapAndSyncStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
// Should be supported everywhere...
|
||||
if (GLAD_GL_VERSION_3_0)
|
||||
{
|
||||
buf = MapAndOrphanStreamBuffer::Create(target, size, error);
|
||||
if (buf)
|
||||
{
|
||||
DEV_LOG("Using MapAndOrphanStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
// BufferSubData is slower on all drivers except NVIDIA...
|
||||
@ -346,11 +520,14 @@ std::unique_ptr<OpenGLStreamBuffer> OpenGLStreamBuffer::Create(GLenum target, u3
|
||||
if (std::strcmp(vendor, "ARM") == 0 || std::strcmp(vendor, "Qualcomm") == 0)
|
||||
{
|
||||
// Mali and Adreno drivers can't do sub-buffer tracking...
|
||||
DEV_LOG("Using BufferDataStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return BufferDataStreamBuffer::Create(target, size, error);
|
||||
}
|
||||
|
||||
DEV_LOG("Using BufferSubDataStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return BufferSubDataStreamBuffer::Create(target, size, error);
|
||||
#else
|
||||
DEV_LOG("Using BufferDataStreamBuffer for {} byte 0x{:X} buffer.", size, target);
|
||||
return BufferDataStreamBuffer::Create(target, size, error);
|
||||
#endif
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user