// 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; }