// Copyright 2017 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoBackends/OGL/OGLTexture.h" #include #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/GL/GLUtil.h" #include "Common/MsgHandler.h" #include "VideoBackends/OGL/OGLConfig.h" #include "VideoBackends/OGL/OGLGfx.h" #include "VideoBackends/OGL/OGLStreamBuffer.h" #include "VideoCommon/VideoConfig.h" namespace OGL { GLenum OGLTexture::GetGLInternalFormatForTextureFormat(AbstractTextureFormat format, bool storage) { switch (format) { case AbstractTextureFormat::DXT1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; case AbstractTextureFormat::DXT3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; case AbstractTextureFormat::DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; 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; case AbstractTextureFormat::RGB10_A2: return GL_RGB10_A2; case AbstractTextureFormat::RGBA16F: return GL_RGBA16F; case AbstractTextureFormat::R16: return GL_R16; case AbstractTextureFormat::R32F: return GL_R32F; case AbstractTextureFormat::D16: return GL_DEPTH_COMPONENT16; case AbstractTextureFormat::D24_S8: return GL_DEPTH24_STENCIL8; case AbstractTextureFormat::D32F: return GL_DEPTH_COMPONENT32F; case AbstractTextureFormat::D32F_S8: return GL_DEPTH32F_STENCIL8; default: PanicAlert("Unhandled texture format."); return storage ? GL_RGBA8 : GL_RGBA; } } namespace { GLenum GetGLFormatForTextureFormat(AbstractTextureFormat format) { switch (format) { case AbstractTextureFormat::RGBA8: return GL_RGBA; case AbstractTextureFormat::BGRA8: return GL_BGRA; case AbstractTextureFormat::RGB10_A2: return GL_RGB10_A2; case AbstractTextureFormat::RGBA16F: return GL_RGBA16F; case AbstractTextureFormat::R16: case AbstractTextureFormat::R32F: return GL_RED; case AbstractTextureFormat::D16: case AbstractTextureFormat::D32F: return GL_DEPTH_COMPONENT; case AbstractTextureFormat::D24_S8: case AbstractTextureFormat::D32F_S8: return GL_DEPTH_STENCIL; // Compressed texture formats don't use this parameter. default: return GL_UNSIGNED_BYTE; } } GLenum GetGLTypeForTextureFormat(AbstractTextureFormat format) { switch (format) { case AbstractTextureFormat::RGBA8: case AbstractTextureFormat::BGRA8: return GL_UNSIGNED_BYTE; case AbstractTextureFormat::RGB10_A2: return GL_UNSIGNED_INT_2_10_10_10_REV; case AbstractTextureFormat::RGBA16F: return GL_HALF_FLOAT; case AbstractTextureFormat::R16: return GL_UNSIGNED_SHORT; case AbstractTextureFormat::R32F: return GL_FLOAT; case AbstractTextureFormat::D16: return GL_UNSIGNED_SHORT; case AbstractTextureFormat::D24_S8: return GL_UNSIGNED_INT_24_8; case AbstractTextureFormat::D32F: return GL_FLOAT; case AbstractTextureFormat::D32F_S8: return GL_FLOAT_32_UNSIGNED_INT_24_8_REV; // 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, std::string_view name) : AbstractTexture(tex_config), m_name(name) { DEBUG_ASSERT_MSG(VIDEO, !tex_config.IsMultisampled() || tex_config.levels == 1, "OpenGL does not support multisampled textures with mip levels"); const GLenum target = GetGLTarget(); glGenTextures(1, &m_texId); glActiveTexture(GL_MUTABLE_TEXTURE_INDEX); glBindTexture(target, m_texId); if (!m_name.empty() && g_backend_info.bSupportsSettingObjectNames) { glObjectLabel(GL_TEXTURE, m_texId, (GLsizei)m_name.size(), m_name.c_str()); } glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, m_config.levels - 1); GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(m_config.format, true); if (g_ogl_config.bSupportsTextureStorage && m_config.type == AbstractTextureType::Texture_CubeMap) { glTexStorage2D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height); } else if (tex_config.IsMultisampled()) { ASSERT(g_ogl_config.bSupportsMSAA); if (m_config.type == AbstractTextureType::Texture_2DArray) { if (g_ogl_config.SupportedMultisampleTexStorage != MultisampleTexStorageType::TexStorageNone) { glTexStorage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width, m_config.height, m_config.layers, GL_FALSE); } else { ASSERT(!g_ogl_config.bIsES); glTexImage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width, m_config.height, m_config.layers, GL_FALSE); } } else if (m_config.type == AbstractTextureType::Texture_2D) { if (g_ogl_config.SupportedMultisampleTexStorage != MultisampleTexStorageType::TexStorageNone) { glTexStorage2DMultisample(target, tex_config.samples, gl_internal_format, m_config.width, m_config.height, GL_FALSE); } else { ASSERT(!g_ogl_config.bIsES); glTexImage2DMultisample(target, tex_config.samples, gl_internal_format, m_config.width, m_config.height, GL_FALSE); } } else { ASSERT(false); } } else if (g_ogl_config.bSupportsTextureStorage) { if (m_config.type == AbstractTextureType::Texture_2DArray) { glTexStorage3D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height, m_config.layers); } else if (m_config.type == AbstractTextureType::Texture_2D) { glTexStorage2D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height); } else { ASSERT(false); } } if (m_config.IsRenderTarget()) { // We can't render to compressed formats. ASSERT(!IsCompressedFormat(m_config.format)); if (!g_ogl_config.bSupportsTextureStorage && !tex_config.IsMultisampled()) { for (u32 level = 0; level < m_config.levels; level++) { glTexImage3D(target, level, gl_internal_format, MAX(m_config.width >> level, 1u), MAX(m_config.height >> level, 1u), m_config.layers, 0, GetGLFormatForTextureFormat(m_config.format), GetGLTypeForTextureFormat(m_config.format), nullptr); } } } } OGLTexture::~OGLTexture() { GetOGLGfx()->UnbindTexture(this); glDeleteTextures(1, &m_texId); } void OGLTexture::CopyRectangleFromTexture(const AbstractTexture* src, const MathUtil::Rectangle& src_rect, u32 src_layer, u32 src_level, const MathUtil::Rectangle& dst_rect, u32 dst_layer, u32 dst_level) { const OGLTexture* src_gltex = static_cast(src); ASSERT(src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight()); if (g_ogl_config.bSupportsCopySubImage) { glCopyImageSubData(src_gltex->m_texId, src_gltex->GetGLTarget(), src_level, src_rect.left, src_rect.top, src_layer, m_texId, GetGLTarget(), dst_level, dst_rect.left, dst_rect.top, dst_layer, dst_rect.GetWidth(), dst_rect.GetHeight(), 1); } else { BlitFramebuffer(const_cast(src_gltex), src_rect, src_layer, src_level, dst_rect, dst_layer, dst_level); } } void OGLTexture::BlitFramebuffer(OGLTexture* srcentry, const MathUtil::Rectangle& src_rect, u32 src_layer, u32 src_level, const MathUtil::Rectangle& dst_rect, u32 dst_layer, u32 dst_level) { GetOGLGfx()->BindSharedReadFramebuffer(); glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcentry->m_texId, src_level, src_layer); GetOGLGfx()->BindSharedDrawFramebuffer(); glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level, dst_layer); // glBlitFramebuffer is still affected by the scissor test, which is enabled by default. glDisable(GL_SCISSOR_TEST); 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); // The default state for the scissor test is enabled. We don't need to do a full state // restore, as the framebuffer and scissor test are the only things we changed. glEnable(GL_SCISSOR_TEST); GetOGLGfx()->RestoreFramebufferBinding(); } void OGLTexture::ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle& rect, u32 layer, u32 level) { const OGLTexture* srcentry = static_cast(src); DEBUG_ASSERT(m_config.samples == 1 && m_config.width == srcentry->m_config.width && m_config.height == srcentry->m_config.height && srcentry->m_config.samples > 1); DEBUG_ASSERT(rect.left + rect.GetWidth() <= static_cast(srcentry->m_config.width) && rect.top + rect.GetHeight() <= static_cast(srcentry->m_config.height)); BlitFramebuffer(const_cast(srcentry), rect, layer, level, rect, layer, level); } void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer, size_t buffer_size, u32 layer) { if (level >= m_config.levels) PanicAlert("Texture only has %u levels, can't update level %u", m_config.levels, level); if (layer >= m_config.layers) PanicAlert("Texture only has %u layer, can't update layer %u", m_config.layers, layer); const auto expected_width = MAX(1U, m_config.width >> level); const auto expected_height = MAX(1U, m_config.height >> level); if (width != expected_width || height != expected_height) { PanicAlert("Size of level %u must be %ux%u, but %ux%u requested", level, expected_width, expected_height, width, height); } const GLenum target = GetGLTarget(); glActiveTexture(GL_MUTABLE_TEXTURE_INDEX); glBindTexture(target, m_texId); if (row_length != width) glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length); GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(m_config.format, false); if (IsCompressedFormat(m_config.format)) { if (m_config.type == AbstractTextureType::Texture_CubeMap) { if (g_ogl_config.bSupportsTextureStorage) { glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, level, 0, 0, width, height, gl_internal_format, static_cast(buffer_size), buffer); } else { glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, level, gl_internal_format, width, height, 0, static_cast(buffer_size), buffer); } } else if (m_config.type == AbstractTextureType::Texture_2D) { if (g_ogl_config.bSupportsTextureStorage) { glCompressedTexSubImage2D(target, level, 0, 0, width, height, gl_internal_format, static_cast(buffer_size), buffer); } else { glCompressedTexImage2D(target, level, gl_internal_format, width, height, 0, static_cast(buffer_size), buffer); } } else if (m_config.type == AbstractTextureType::Texture_2DArray) { if (g_ogl_config.bSupportsTextureStorage) { glCompressedTexSubImage3D(target, level, 0, 0, layer, width, height, 1, gl_internal_format, static_cast(buffer_size), buffer); } else { glCompressedTexImage3D(target, level, gl_internal_format, width, height, 1, 0, static_cast(buffer_size), buffer); } } else { PanicAlert("Failed to handle compressed texture load - unhandled type"); } } else { GLenum gl_format = GetGLFormatForTextureFormat(m_config.format); GLenum gl_type = GetGLTypeForTextureFormat(m_config.format); if (m_config.type == AbstractTextureType::Texture_CubeMap) { if (g_ogl_config.bSupportsTextureStorage) { glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, level, 0, 0, width, height, gl_format, gl_type, buffer); } else { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, level, gl_internal_format, width, height, 0, gl_format, gl_type, buffer); } } else if (m_config.type == AbstractTextureType::Texture_2D) { if (g_ogl_config.bSupportsTextureStorage) { glTexSubImage2D(target, level, 0, 0, width, height, gl_format, gl_type, buffer); } else { glTexImage2D(target, level, gl_internal_format, width, height, 0, gl_format, gl_type, buffer); } } else if (m_config.type == AbstractTextureType::Texture_2DArray) { if (g_ogl_config.bSupportsTextureStorage) { glTexSubImage3D(target, level, 0, 0, layer, width, height, 1, gl_format, gl_type, buffer); } else { glTexImage3D(target, level, gl_internal_format, width, height, 1, 0, gl_format, gl_type, buffer); } } else { PanicAlert("Failed to handle texture load - unhandled type"); } } if (row_length != width) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } /* What textures lend for loads they write in place: a pixel unpack stream, mapped for good in * client memory. One load at a time is lent. */ enum { UPLOAD_BUFFER_SIZE = 32 * 1024 * 1024 }; static StreamBuffer* s_upload_buffer; static bool s_upload_buffer_tried; static struct { u32 offset; u32 size; u32 level; u32 layer; u32 width; u32 height; u32 row_length; } s_lent_load; void OGLTexture::DestroyUploadBuffer() { delete s_upload_buffer; s_upload_buffer = nullptr; s_upload_buffer_tried = false; } u8* OGLTexture::LendLoad(u32 level, u32 width, u32 height, u32 row_length, size_t size, u32 layer) { std::pair mapped; if (IsCompressedFormat(m_config.format) || !g_ogl_config.bSupportsTextureStorage || m_config.type == AbstractTextureType::Texture_CubeMap || level >= m_config.levels || layer >= m_config.layers || width != MAX(1U, m_config.width >> level) || height != MAX(1U, m_config.height >> level) || size > UPLOAD_BUFFER_SIZE / 4) { return nullptr; } if (!s_upload_buffer_tried) { s_upload_buffer_tried = true; s_upload_buffer = StreamBuffer::CreatePersistentClient(GL_PIXEL_UNPACK_BUFFER, UPLOAD_BUFFER_SIZE); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } if (!s_upload_buffer) return nullptr; mapped = s_upload_buffer->Map(static_cast(size), 4); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); s_lent_load.offset = mapped.second; s_lent_load.size = static_cast(size); s_lent_load.level = level; s_lent_load.layer = layer; s_lent_load.width = width; s_lent_load.height = height; s_lent_load.row_length = row_length; return mapped.first; } void OGLTexture::CommitLoad() { const GLenum target = GetGLTarget(); const GLenum gl_format = GetGLFormatForTextureFormat(m_config.format); const GLenum gl_type = GetGLTypeForTextureFormat(m_config.format); const void* const offset = reinterpret_cast(static_cast(s_lent_load.offset)); s_upload_buffer->Unmap(s_lent_load.size); glActiveTexture(GL_MUTABLE_TEXTURE_INDEX); glBindTexture(target, m_texId); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, s_upload_buffer->m_buffer); if (s_lent_load.row_length != s_lent_load.width) glPixelStorei(GL_UNPACK_ROW_LENGTH, s_lent_load.row_length); if (m_config.type == AbstractTextureType::Texture_2D) { glTexSubImage2D(target, s_lent_load.level, 0, 0, s_lent_load.width, s_lent_load.height, gl_format, gl_type, offset); } else { glTexSubImage3D(target, s_lent_load.level, 0, 0, s_lent_load.layer, s_lent_load.width, s_lent_load.height, 1, gl_format, gl_type, offset); } if (s_lent_load.row_length != s_lent_load.width) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } GLenum OGLTexture::GetGLFormatForImageTexture() const { return GetGLInternalFormatForTextureFormat(m_config.format, true); } 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_transfer_sync != nullptr) glDeleteSync(static_cast(m_transfer_sync)); 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); } 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) { /* read by the CPU only: kept where its reads are cached */ buffer_flags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_CLIENT_STORAGE_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 = static_cast(glMapBufferRange(target, 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); OGLStagingTexture* const staging_tex = new OGLStagingTexture(type, config, target, buffer, buffer_size, buffer_ptr, stride); staging_tex->m_persistent_map = buffer_ptr && type == StagingTextureType::Upload; return staging_tex; } void OGLStagingTexture::CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle& src_rect, u32 src_layer, u32 src_level, const MathUtil::Rectangle& dst_rect) { ASSERT(m_type == StagingTextureType::Readback || m_type == StagingTextureType::Mutable); ASSERT(src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight()); ASSERT(src_rect.left >= 0 && static_cast(src_rect.right) <= src->GetConfig().width && src_rect.top >= 0 && static_cast(src_rect.bottom) <= src->GetConfig().height); ASSERT(dst_rect.left >= 0 && static_cast(dst_rect.right) <= m_config.width && dst_rect.top >= 0 && static_cast(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(src); const size_t dst_offset = dst_rect.top * m_config.GetStride() + dst_rect.left * m_texel_size; // Prefer glGetTextureSubImage(), when available. if (g_ogl_config.bSupportsTextureSubImage) { glGetTextureSubImage( gltex->GetGLTextureId(), src_level, src_rect.left, src_rect.top, src_layer, src_rect.GetWidth(), src_rect.GetHeight(), 1, GetGLFormatForTextureFormat(src->GetFormat()), GetGLTypeForTextureFormat(src->GetFormat()), static_cast(m_buffer_size - dst_offset), reinterpret_cast(dst_offset)); } else { // Mutate the shared framebuffer. GetOGLGfx()->BindSharedReadFramebuffer(); if (AbstractTexture::IsDepthFormat(gltex->GetFormat())) { glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0, 0); glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, gltex->GetGLTextureId(), src_level, src_layer); } else { glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gltex->GetGLTextureId(), src_level, src_layer); glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0, 0, 0); } glReadPixels(src_rect.left, src_rect.top, src_rect.GetWidth(), src_rect.GetHeight(), GetGLFormatForTextureFormat(src->GetFormat()), GetGLTypeForTextureFormat(src->GetFormat()), reinterpret_cast(dst_offset)); GetOGLGfx()->RestoreFramebufferBinding(); } 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_transfer_sync != nullptr) glDeleteSync(static_cast(m_transfer_sync)); glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); m_transfer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); } m_needs_flush = true; } void OGLStagingTexture::CopyToTexture(const MathUtil::Rectangle& src_rect, AbstractTexture* dst, const MathUtil::Rectangle& dst_rect, u32 dst_layer, u32 dst_level) { ASSERT(m_type == StagingTextureType::Upload || m_type == StagingTextureType::Mutable); ASSERT(src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight()); ASSERT(src_rect.left >= 0 && static_cast(src_rect.right) <= m_config.width && src_rect.top >= 0 && static_cast(src_rect.bottom) <= m_config.height); ASSERT(dst_rect.left >= 0 && static_cast(dst_rect.right) <= dst->GetConfig().width && dst_rect.top >= 0 && static_cast(dst_rect.bottom) <= dst->GetConfig().height); const OGLTexture* gltex = static_cast(dst); const size_t src_offset = src_rect.top * m_config.GetStride() + src_rect.left * m_texel_size; const 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. const GLenum target = gltex->GetGLTarget(); glActiveTexture(GL_MUTABLE_TEXTURE_INDEX); glBindTexture(target, gltex->GetGLTextureId()); glTexSubImage3D(target, 0, dst_rect.left, dst_rect.top, dst_layer, dst_rect.GetWidth(), dst_rect.GetHeight(), 1, GetGLFormatForTextureFormat(dst->GetFormat()), GetGLTypeForTextureFormat(dst->GetFormat()), reinterpret_cast(src_offset)); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); // If we support buffer storage, create a fence for synchronization. if (UsePersistentStagingBuffers()) { if (m_transfer_sync != nullptr) glDeleteSync(static_cast(m_transfer_sync)); m_transfer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); } m_needs_flush = true; } void OGLStagingTexture::Flush() { // No-op when not using buffer storage, as the transfers happen on Map(). // m_transfer_sync will always be zero in this case. if (m_transfer_sync == nullptr) { m_needs_flush = false; return; } glClientWaitSync(static_cast(m_transfer_sync), 0, GL_TIMEOUT_IGNORED); glDeleteSync(static_cast(m_transfer_sync)); m_transfer_sync = nullptr; 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 = static_cast(glMapBufferRange(m_target, 0, m_buffer_size, flags)); glBindBuffer(m_target, 0); return m_map_pointer != nullptr; } 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; } OGLFramebuffer::OGLFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment, AbstractTextureFormat color_format, AbstractTextureFormat depth_format, u32 width, u32 height, u32 layers, u32 samples, GLuint fbo) : AbstractFramebuffer(color_attachment, depth_attachment, color_format, depth_format, width, height, layers, samples), m_fbo(fbo) { } OGLFramebuffer::~OGLFramebuffer() { glDeleteFramebuffers(1, &m_fbo); } OGLFramebuffer* OGLFramebuffer::Create(OGLTexture* color_attachment, OGLTexture* depth_attachment) { if (!ValidateConfig(color_attachment, depth_attachment)) return nullptr; const AbstractTextureFormat color_format = color_attachment ? color_attachment->GetFormat() : AbstractTextureFormat::Undefined; const AbstractTextureFormat depth_format = depth_attachment ? depth_attachment->GetFormat() : AbstractTextureFormat::Undefined; const OGLTexture* either_attachment = color_attachment ? color_attachment : depth_attachment; const u32 width = either_attachment->GetWidth(); const u32 height = either_attachment->GetHeight(); const u32 layers = either_attachment->GetLayers(); const u32 samples = either_attachment->GetSamples(); GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); /* draws go to the colour attachment, if there is one */ const GLenum draw_buffer = color_attachment ? GL_COLOR_ATTACHMENT0 : GL_NONE; if (color_attachment) { if (color_attachment->GetConfig().layers > 1) { glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, color_attachment->GetGLTextureId(), 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, color_attachment->GetGLTextureId(), 0, 0); } } if (depth_attachment) { GLenum attachment = AbstractTexture::IsStencilFormat(depth_format) ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; if (depth_attachment->GetConfig().layers > 1) { glFramebufferTexture(GL_FRAMEBUFFER, attachment, depth_attachment->GetGLTextureId(), 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, depth_attachment->GetGLTextureId(), 0, 0); } } glDrawBuffers(color_attachment ? 1 : 0, &draw_buffer); DEBUG_ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); GetOGLGfx()->RestoreFramebufferBinding(); return new OGLFramebuffer(color_attachment, depth_attachment, color_format, depth_format, width, height, layers, samples, fbo); } bool SyncDone(void* sync) { GLenum result; if (!sync) return true; result = glClientWaitSync(static_cast(sync), 0, 0); return result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED; } } // namespace OGL