diff options
| author | Stenzek <stenzek@users.noreply.github.com> | 2017-12-01 14:24:06 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-12-01 14:24:06 +1000 |
| commit | cd68b3606cea8a7041751bda482170533bae3220 (patch) | |
| tree | 4edef80bda42a82a50ff9f9e86d64aac38c8aff3 /Source/Core/VideoBackends/OGL/OGLTexture.cpp | |
| parent | 0773a48c6c990c488e70ff7c1f46713593440ddc (diff) | |
| parent | 7f217a8bb24da323042081a02b65f84f9ce9761f (diff) | |
Merge pull request #6193 from stenzek/readbacks
Abstract Staging Textures - VideoCommon interface for texture readbacks/uploads
Diffstat (limited to 'Source/Core/VideoBackends/OGL/OGLTexture.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/OGL/OGLTexture.cpp | 404 |
1 files changed, 331 insertions, 73 deletions
diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.cpp b/Source/Core/VideoBackends/OGL/OGLTexture.cpp index 659cc9764b..bf1ec266d3 100644 --- a/Source/Core/VideoBackends/OGL/OGLTexture.cpp +++ b/Source/Core/VideoBackends/OGL/OGLTexture.cpp @@ -9,7 +9,6 @@ #include "VideoBackends/OGL/FramebufferManager.h" #include "VideoBackends/OGL/OGLTexture.h" -#include "VideoBackends/OGL/Render.h" #include "VideoBackends/OGL/SamplerCache.h" #include "VideoBackends/OGL/TextureCache.h" @@ -36,7 +35,11 @@ GLenum GetGLInternalFormatForTextureFormat(AbstractTextureFormat format, bool st case AbstractTextureFormat::BPTC: return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; case AbstractTextureFormat::RGBA8: + return storage ? GL_RGBA8 : GL_RGBA; + case AbstractTextureFormat::BGRA8: + return storage ? GL_RGBA8 : GL_BGRA; default: + PanicAlert("Unhandled texture format."); return storage ? GL_RGBA8 : GL_RGBA; } } @@ -47,6 +50,8 @@ GLenum GetGLFormatForTextureFormat(AbstractTextureFormat format) { case AbstractTextureFormat::RGBA8: return GL_RGBA; + case AbstractTextureFormat::BGRA8: + return GL_BGRA; // Compressed texture formats don't use this parameter. default: return GL_UNSIGNED_BYTE; @@ -58,12 +63,22 @@ GLenum GetGLTypeForTextureFormat(AbstractTextureFormat format) switch (format) { case AbstractTextureFormat::RGBA8: + case AbstractTextureFormat::BGRA8: return GL_UNSIGNED_BYTE; // Compressed texture formats don't use this parameter. default: return GL_UNSIGNED_BYTE; } } + +bool UsePersistentStagingBuffers() +{ + // We require ARB_buffer_storage to create the persistent mapped buffer, + // ARB_shader_image_load_store for glMemoryBarrier, and ARB_sync to ensure + // the GPU has finished the copy before reading the buffer from the CPU. + return g_ogl_config.bSupportsGLBufferStorage && g_ogl_config.bSupportsImageLoadStore && + g_ogl_config.bSupportsGLSync; +} } // Anonymous namespace OGLTexture::OGLTexture(const TextureConfig& tex_config) : AbstractTexture(tex_config) @@ -85,7 +100,7 @@ OGLTexture::OGLTexture(const TextureConfig& tex_config) : AbstractTexture(tex_co if (m_config.rendertarget) { // We can't render to compressed formats. - _assert_(!IsCompressedHostTextureFormat(m_config.format)); + _assert_(!IsCompressedFormat(m_config.format)); if (!g_ogl_config.bSupportsTextureStorage) { @@ -100,6 +115,10 @@ OGLTexture::OGLTexture(const TextureConfig& tex_config) : AbstractTexture(tex_co FramebufferManager::SetFramebuffer(m_framebuffer); FramebufferManager::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_ARRAY, m_texId, 0); + + // We broke the framebuffer binding here, and need to restore it, as the CreateTexture + // method is in the base renderer class and can be called by VideoCommon. + FramebufferManager::SetFramebuffer(0); } SetStage(); @@ -148,89 +167,70 @@ void OGLTexture::Bind(unsigned int stage) } } -std::optional<AbstractTexture::RawTextureInfo> OGLTexture::MapFullImpl() -{ - if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) - return {}; - - m_staging_data.reserve(m_config.width * m_config.height * 4); - glActiveTexture(GL_TEXTURE9); - - glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); - glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data()); - OGLTexture::SetStage(); - return AbstractTexture::RawTextureInfo{reinterpret_cast<u8*>(m_staging_data.data()), - m_config.width * 4, m_config.width, m_config.height}; -} - -std::optional<AbstractTexture::RawTextureInfo> OGLTexture::MapRegionImpl(u32 level, u32 x, u32 y, - u32 width, u32 height) +void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* src, + const MathUtil::Rectangle<int>& src_rect, u32 src_layer, + u32 src_level, const MathUtil::Rectangle<int>& dst_rect, + u32 dst_layer, u32 dst_level) { - if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) - return {}; - m_staging_data.reserve(m_config.width * m_config.height * 4); - glActiveTexture(GL_TEXTURE9); - glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); - if (g_ogl_config.bSupportTextureSubImage) + const OGLTexture* srcentry = static_cast<const OGLTexture*>(src); + _assert_(src_rect.GetWidth() == dst_rect.GetWidth() && + src_rect.GetHeight() == dst_rect.GetHeight()); + if (g_ogl_config.bSupportsCopySubImage) { - glGetTextureSubImage(GL_TEXTURE_2D_ARRAY, level, GLint(x), GLint(y), 0, GLsizei(width), - GLsizei(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, GLsizei(width * height * 4), - m_staging_data.data()); + glCopyImageSubData(srcentry->m_texId, GL_TEXTURE_2D_ARRAY, src_level, src_rect.left, + src_rect.top, src_layer, m_texId, GL_TEXTURE_2D_ARRAY, dst_level, + dst_rect.left, dst_rect.top, dst_layer, dst_rect.GetWidth(), + dst_rect.GetHeight(), 1); } else { - MapRegionSlow(level, x, y, width, height); - } - OGLTexture::SetStage(); - return AbstractTexture::RawTextureInfo{m_staging_data.data(), width * 4, width, height}; -} - -void OGLTexture::MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height) -{ - glActiveTexture(GL_TEXTURE9); - - glBindTexture(GL_TEXTURE_2D_ARRAY, m_texId); - glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_staging_data.data()); - - // Now copy the region out of the staging data - - const u32 partial_stride = width * 4; + // If it isn't a single leveled/layered texture, we need to update the framebuffer. + bool update_src_framebuffer = + srcentry->m_framebuffer == 0 || srcentry->m_config.layers != 0 || src_level != 0; + bool update_dst_framebuffer = m_framebuffer == 0 || m_config.layers != 0 || dst_level != 0; + if (!m_framebuffer) + glGenFramebuffers(1, &m_framebuffer); + if (!srcentry->m_framebuffer) + glGenFramebuffers(1, &const_cast<OGLTexture*>(srcentry)->m_framebuffer); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcentry->m_framebuffer); + if (update_src_framebuffer) + { + glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcentry->m_texId, + src_level, src_layer); + } - std::vector<u8> partial_data; - partial_data.resize(partial_stride * height); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebuffer); + if (update_dst_framebuffer) + { + glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level, + dst_layer); + } - const u32 staging_stride = m_config.width * 4; - const u32 x_offset = x * 4; + glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, + dst_rect.top, dst_rect.right, dst_rect.bottom, GL_COLOR_BUFFER_BIT, + GL_NEAREST); - auto staging_location = m_staging_data.begin() + staging_stride * y; - auto partial_location = partial_data.begin(); + if (update_src_framebuffer) + { + FramebufferManager::FramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D_ARRAY, srcentry->m_texId, 0); + } + if (update_dst_framebuffer) + { + FramebufferManager::FramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D_ARRAY, m_texId, 0); + } - for (size_t i = 0; i < height; ++i) - { - auto starting_location = staging_location + x_offset; - std::copy(starting_location, starting_location + partial_stride, partial_location); - staging_location += staging_stride; - partial_location += partial_stride; + FramebufferManager::SetFramebuffer(0); } - - // Now swap the region back in for the staging data - m_staging_data.swap(partial_data); } - -void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* source, - const MathUtil::Rectangle<int>& srcrect, - const MathUtil::Rectangle<int>& dstrect) +void OGLTexture::ScaleRectangleFromTexture(const AbstractTexture* source, + const MathUtil::Rectangle<int>& srcrect, + const MathUtil::Rectangle<int>& dstrect) { const OGLTexture* srcentry = static_cast<const OGLTexture*>(source); - if (srcrect.GetWidth() == dstrect.GetWidth() && srcrect.GetHeight() == dstrect.GetHeight() && - g_ogl_config.bSupportsCopySubImage) - { - glCopyImageSubData(srcentry->m_texId, GL_TEXTURE_2D_ARRAY, 0, srcrect.left, srcrect.top, 0, - m_texId, GL_TEXTURE_2D_ARRAY, 0, dstrect.left, dstrect.top, 0, - dstrect.GetWidth(), dstrect.GetHeight(), srcentry->m_config.layers); - return; - } - else if (!m_framebuffer) + if (!m_framebuffer) { glGenFramebuffers(1, &m_framebuffer); FramebufferManager::SetFramebuffer(m_framebuffer); @@ -269,7 +269,7 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8 glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length); GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(m_config.format, false); - if (IsCompressedHostTextureFormat(m_config.format)) + if (IsCompressedFormat(m_config.format)) { if (g_ogl_config.bSupportsTextureStorage) { @@ -315,4 +315,262 @@ void OGLTexture::SetStage() glActiveTexture(GL_TEXTURE0 + s_ActiveTexture); } +OGLStagingTexture::OGLStagingTexture(StagingTextureType type, const TextureConfig& config, + GLenum target, GLuint buffer_name, size_t buffer_size, + char* map_ptr, size_t map_stride) + : AbstractStagingTexture(type, config), m_target(target), m_buffer_name(buffer_name), + m_buffer_size(buffer_size) +{ + m_map_pointer = map_ptr; + m_map_stride = map_stride; +} + +OGLStagingTexture::~OGLStagingTexture() +{ + if (m_fence != 0) + glDeleteSync(m_fence); + if (m_map_pointer) + { + glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_name); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + if (m_buffer_name != 0) + glDeleteBuffers(1, &m_buffer_name); +} + +std::unique_ptr<OGLStagingTexture> OGLStagingTexture::Create(StagingTextureType type, + const TextureConfig& config) +{ + size_t stride = config.GetStride(); + size_t buffer_size = stride * config.height; + GLenum target = + type == StagingTextureType::Readback ? GL_PIXEL_PACK_BUFFER : GL_PIXEL_UNPACK_BUFFER; + GLuint buffer; + glGenBuffers(1, &buffer); + glBindBuffer(target, buffer); + + // Prefer using buffer_storage where possible. This allows us to skip the map/unmap steps. + char* buffer_ptr; + if (UsePersistentStagingBuffers()) + { + GLenum buffer_flags; + GLenum map_flags; + if (type == StagingTextureType::Readback) + { + buffer_flags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT; + map_flags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT; + } + else if (type == StagingTextureType::Upload) + { + buffer_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; + map_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT; + } + else + { + buffer_flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; + map_flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; + } + + glBufferStorage(target, buffer_size, nullptr, buffer_flags); + buffer_ptr = + reinterpret_cast<char*>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, buffer_size, map_flags)); + _assert_(buffer_ptr != nullptr); + } + else + { + // Otherwise, fallback to mapping the buffer each time. + glBufferData(target, buffer_size, nullptr, + type == StagingTextureType::Readback ? GL_STREAM_READ : GL_STREAM_DRAW); + buffer_ptr = nullptr; + } + glBindBuffer(target, 0); + + return std::unique_ptr<OGLStagingTexture>( + new OGLStagingTexture(type, config, target, buffer, buffer_size, buffer_ptr, stride)); +} + +void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src, + const MathUtil::Rectangle<int>& src_rect, u32 src_layer, + u32 src_level, const MathUtil::Rectangle<int>& dst_rect) +{ + _assert_(m_type == StagingTextureType::Readback); + _assert_(src_rect.GetWidth() == dst_rect.GetWidth() && + src_rect.GetHeight() == dst_rect.GetHeight()); + _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src->GetConfig().width && + src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src->GetConfig().height); + _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width && + dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height); + + // Unmap the buffer before writing when not using persistent mappings. + if (!UsePersistentStagingBuffers()) + OGLStagingTexture::Unmap(); + + // Copy from the texture object to the staging buffer. + glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_name); + glPixelStorei(GL_PACK_ROW_LENGTH, m_config.width); + + const OGLTexture* gltex = static_cast<const OGLTexture*>(src); + size_t dst_offset = dst_rect.top * m_config.GetStride() + dst_rect.left * m_texel_size; + + // If we don't have a FBO associated with this texture, we need to use a slow path. + if (gltex->GetFramebuffer() != 0 && src_layer == 0 && src_level == 0) + { + // This texture has a framebuffer, so we can use glReadPixels(). + glBindFramebuffer(GL_READ_FRAMEBUFFER, gltex->GetFramebuffer()); + glReadPixels(src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(), + GetGLFormatForTextureFormat(m_config.format), + GetGLTypeForTextureFormat(m_config.format), reinterpret_cast<void*>(dst_offset)); + + // Reset both read/draw framebuffers. + glBindFramebuffer(GL_FRAMEBUFFER, FramebufferManager::GetEFBFramebuffer()); + } + else + { + glActiveTexture(GL_TEXTURE9); + glBindTexture(GL_TEXTURE_2D_ARRAY, gltex->GetRawTexIdentifier()); + if (g_ogl_config.bSupportsTextureSubImage) + { + glGetTextureSubImage( + GL_TEXTURE_2D_ARRAY, src_level, src_rect.left, src_rect.top, src_layer, + src_rect.GetWidth(), src_rect.GetHeight(), 1, + GetGLFormatForTextureFormat(m_config.format), GetGLTypeForTextureFormat(m_config.format), + static_cast<GLsizei>(m_buffer_size - dst_offset), reinterpret_cast<void*>(dst_offset)); + } + else + { + // TODO: Investigate whether it's faster to use glReadPixels() with a framebuffer, since we're + // copying the whole texture, which may waste bandwidth. So we're trading CPU work in creating + // the framebuffer for GPU work in copying potentially redundant texels. + glGetTexImage(GL_TEXTURE_2D_ARRAY, src_level, GetGLFormatForTextureFormat(m_config.format), + GetGLTypeForTextureFormat(m_config.format), nullptr); + } + + OGLTexture::SetStage(); + } + + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + // If we support buffer storage, create a fence for synchronization. + if (UsePersistentStagingBuffers()) + { + if (m_fence != 0) + glDeleteSync(m_fence); + + glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); + m_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + m_needs_flush = true; +} + +void OGLStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, + AbstractTexture* dst, + const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer, + u32 dst_level) +{ + _assert_(m_type == StagingTextureType::Upload); + _assert_(src_rect.GetWidth() == dst_rect.GetWidth() && + src_rect.GetHeight() == dst_rect.GetHeight()); + _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width && + src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height); + _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst->GetConfig().width && + dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst->GetConfig().height); + + size_t src_offset = src_rect.top * m_config.GetStride() + src_rect.left * m_texel_size; + size_t copy_size = src_rect.GetHeight() * m_config.GetStride(); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_buffer_name); + glPixelStorei(GL_UNPACK_ROW_LENGTH, m_config.width); + + if (!UsePersistentStagingBuffers()) + { + // Unmap the buffer before writing when not using persistent mappings. + if (m_map_pointer) + { + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + m_map_pointer = nullptr; + } + } + else + { + // Since we're not using coherent mapping, we must flush the range explicitly. + if (m_type == StagingTextureType::Upload) + glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, src_offset, copy_size); + glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); + } + + // Copy from the staging buffer to the texture object. + glActiveTexture(GL_TEXTURE9); + glBindTexture(GL_TEXTURE_2D_ARRAY, static_cast<const OGLTexture*>(dst)->GetRawTexIdentifier()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, dst_rect.left, dst_rect.top, dst_layer, + dst_rect.GetWidth(), dst_rect.GetHeight(), 1, + GetGLFormatForTextureFormat(m_config.format), + GetGLTypeForTextureFormat(m_config.format), reinterpret_cast<void*>(src_offset)); + OGLTexture::SetStage(); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + // If we support buffer storage, create a fence for synchronization. + if (UsePersistentStagingBuffers()) + { + if (m_fence != 0) + glDeleteSync(m_fence); + + m_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + m_needs_flush = true; +} + +void OGLStagingTexture::Flush() +{ + // No-op when not using buffer storage, as the transfers happen on Map(). + // m_fence will always be zero in this case. + if (m_fence == 0) + { + m_needs_flush = false; + return; + } + + glClientWaitSync(m_fence, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(m_fence); + m_fence = 0; + m_needs_flush = false; +} + +bool OGLStagingTexture::Map() +{ + if (m_map_pointer) + return true; + + // Slow path, map the texture, unmap it later. + GLenum flags; + if (m_type == StagingTextureType::Readback) + flags = GL_MAP_READ_BIT; + else if (m_type == StagingTextureType::Upload) + flags = GL_MAP_WRITE_BIT; + else + flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT; + glBindBuffer(m_target, m_buffer_name); + m_map_pointer = reinterpret_cast<char*>(glMapBufferRange(m_target, 0, m_buffer_size, flags)); + if (!m_map_pointer) + return false; + + return true; +} + +void OGLStagingTexture::Unmap() +{ + // No-op with persistent mapped buffers. + if (!m_map_pointer || UsePersistentStagingBuffers()) + return; + + glBindBuffer(m_target, m_buffer_name); + glUnmapBuffer(m_target); + glBindBuffer(m_target, 0); + m_map_pointer = nullptr; +} + } // namespace OGL |
