diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 4f99af7e5..0bb491976 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -34,6 +34,8 @@ add_library(video_core STATIC renderer_opengl/gl_shader_util.h renderer_opengl/gl_state.cpp renderer_opengl/gl_state.h + renderer_opengl/gl_stream_buffer.cpp + renderer_opengl/gl_stream_buffer.h renderer_opengl/pica_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index f028ea001..612540d25 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -41,12 +41,13 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { } // Generate VBO, VAO and UBO - vertex_buffer.Create(); + vertex_buffer = OGLStreamBuffer::MakeBuffer(GLAD_GL_ARB_buffer_storage, GL_ARRAY_BUFFER); + vertex_buffer->Create(VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE / 2); vertex_array.Create(); uniform_buffer.Create(); state.draw.vertex_array = vertex_array.handle; - state.draw.vertex_buffer = vertex_buffer.handle; + state.draw.vertex_buffer = vertex_buffer->GetHandle(); state.draw.uniform_buffer = uniform_buffer.handle; state.Apply(); @@ -434,9 +435,15 @@ void RasterizerOpenGL::DrawTriangles() { state.Apply(); // Draw the vertex batch - glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), - GL_STREAM_DRAW); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); + size_t max_vertices = 3 * (VERTEX_BUFFER_SIZE / (3 * sizeof(HardwareVertex))); + for (size_t base_vertex = 0; base_vertex < vertex_batch.size(); base_vertex += max_vertices) { + size_t vertices = std::min(max_vertices, vertex_batch.size() - base_vertex); + size_t vertex_size = vertices * sizeof(HardwareVertex); + auto map = vertex_buffer->Map(vertex_size, 1); + memcpy(map.first, vertex_batch.data() + base_vertex, vertex_size); + vertex_buffer->Unmap(); + glDrawArrays(GL_TRIANGLES, map.second / sizeof(HardwareVertex), (GLsizei)vertices); + } // Disable scissor test state.scissor.enabled = false; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 18808b1e4..5db9b6b7a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -27,6 +27,7 @@ #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" #include "video_core/renderer_opengl/pica_to_gl.h" #include "video_core/shader/shader.h" @@ -283,7 +284,8 @@ private: std::array texture_samplers; OGLVertexArray vertex_array; - OGLBuffer vertex_buffer; + static constexpr size_t VERTEX_BUFFER_SIZE = 128 * 1024 * 1024; + std::unique_ptr vertex_buffer; OGLBuffer uniform_buffer; OGLFramebuffer framebuffer; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 39fa79fc2..479ea8ad6 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -142,6 +142,39 @@ public: GLuint handle = 0; }; +class OGLSync : private NonCopyable { +public: + OGLSync() = default; + + OGLSync(OGLSync&& o) : handle(std::exchange(o.handle, nullptr)) {} + + ~OGLSync() { + Release(); + } + OGLSync& operator=(OGLSync&& o) { + Release(); + handle = std::exchange(o.handle, nullptr); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create() { + if (handle != 0) + return; + handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + /// Deletes the internal OpenGL resource + void Release() { + if (handle == 0) + return; + glDeleteSync(handle); + handle = 0; + } + + GLsync handle = 0; +}; + class OGLVertexArray : private NonCopyable { public: OGLVertexArray() = default; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp new file mode 100644 index 000000000..a2713e9f0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -0,0 +1,182 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/alignment.h" +#include "common/assert.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +class OrphanBuffer : public OGLStreamBuffer { +public: + explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~OrphanBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair Map(size_t size, size_t alignment) override; + void Unmap() override; + + std::vector data; +}; + +class StorageBuffer : public OGLStreamBuffer { +public: + explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~StorageBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair Map(size_t size, size_t alignment) override; + void Unmap() override; + + struct Fence { + OGLSync sync; + size_t offset; + }; + std::deque head; + std::deque tail; + + u8* mapped_ptr; +}; + +OGLStreamBuffer::OGLStreamBuffer(GLenum target) { + gl_target = target; +} + +GLuint OGLStreamBuffer::GetHandle() const { + return gl_buffer.handle; +} + +std::unique_ptr OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) { + if (storage_buffer) { + return std::make_unique(target); + } + return std::make_unique(target); +} + +OrphanBuffer::~OrphanBuffer() { + Release(); +} + +void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) { + buffer_pos = 0; + buffer_size = size; + data.resize(buffer_size); + + if (gl_buffer.handle == 0) { + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + } + + glBufferData(gl_target, static_cast(buffer_size), nullptr, GL_STREAM_DRAW); +} + +void OrphanBuffer::Release() { + gl_buffer.Release(); +} + +std::pair OrphanBuffer::Map(size_t size, size_t alignment) { + buffer_pos = Common::AlignUp(buffer_pos, alignment); + + if (buffer_pos + size > buffer_size) { + Create(std::max(buffer_size, size), 0); + } + + mapped_size = size; + return std::make_pair(&data[buffer_pos], static_cast(buffer_pos)); +} + +void OrphanBuffer::Unmap() { + glBufferSubData(gl_target, static_cast(buffer_pos), + static_cast(mapped_size), &data[buffer_pos]); + buffer_pos += mapped_size; +} + +StorageBuffer::~StorageBuffer() { + Release(); +} + +void StorageBuffer::Create(size_t size, size_t sync_subdivide) { + if (gl_buffer.handle != 0) + return; + + buffer_pos = 0; + buffer_size = size; + buffer_sync_subdivide = std::max(sync_subdivide, 1); + + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + + glBufferStorage(gl_target, static_cast(buffer_size), nullptr, + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); + mapped_ptr = reinterpret_cast( + glMapBufferRange(gl_target, 0, static_cast(buffer_size), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); +} + +void StorageBuffer::Release() { + if (gl_buffer.handle == 0) + return; + + glUnmapBuffer(gl_target); + + gl_buffer.Release(); + head.clear(); + tail.clear(); +} + +std::pair StorageBuffer::Map(size_t size, size_t alignment) { + ASSERT(size <= buffer_size); + + OGLSync sync; + + buffer_pos = Common::AlignUp(buffer_pos, alignment); + size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide); + + if (!head.empty() && + (effective_offset > head.back().offset || buffer_pos + size > buffer_size)) { + ASSERT(head.back().sync.handle == 0); + head.back().sync.Create(); + } + + if (buffer_pos + size > buffer_size) { + if (!tail.empty()) { + std::swap(sync, tail.back().sync); + tail.clear(); + } + std::swap(tail, head); + buffer_pos = 0; + effective_offset = 0; + } + + while (!tail.empty() && buffer_pos + size > tail.front().offset) { + std::swap(sync, tail.front().sync); + tail.pop_front(); + } + + if (sync.handle != 0) { + glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + sync.Release(); + } + + if (head.empty() || effective_offset > head.back().offset) { + head.emplace_back(); + head.back().offset = effective_offset; + } + + mapped_size = size; + return std::make_pair(&mapped_ptr[buffer_pos], static_cast(buffer_pos)); +} + +void StorageBuffer::Unmap() { + glFlushMappedBufferRange(gl_target, static_cast(buffer_pos), + static_cast(mapped_size)); + buffer_pos += mapped_size; +} diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h new file mode 100644 index 000000000..4bc2f52e0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -0,0 +1,34 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +class OGLStreamBuffer : private NonCopyable { +public: + explicit OGLStreamBuffer(GLenum target); + virtual ~OGLStreamBuffer() = default; + +public: + static std::unique_ptr MakeBuffer(bool storage_buffer, GLenum target); + + virtual void Create(size_t size, size_t sync_subdivide) = 0; + virtual void Release() {} + + GLuint GetHandle() const; + + virtual std::pair Map(size_t size, size_t alignment) = 0; + virtual void Unmap() = 0; + +protected: + OGLBuffer gl_buffer; + GLenum gl_target; + + size_t buffer_pos = 0; + size_t buffer_size = 0; + size_t buffer_sync_subdivide = 0; + size_t mapped_size = 0; +};