mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-30 01:29:42 -06:00
Merge pull request #6193 from stenzek/readbacks
Abstract Staging Textures - VideoCommon interface for texture readbacks/uploads
This commit is contained in:
@ -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()
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebuffer);
|
||||
if (update_dst_framebuffer)
|
||||
{
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texId, dst_level,
|
||||
dst_layer);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
FramebufferManager::SetFramebuffer(0);
|
||||
}
|
||||
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;
|
||||
|
||||
std::vector<u8> partial_data;
|
||||
partial_data.resize(partial_stride * height);
|
||||
|
||||
const u32 staging_stride = m_config.width * 4;
|
||||
const u32 x_offset = x * 4;
|
||||
|
||||
auto staging_location = m_staging_data.begin() + staging_stride * y;
|
||||
auto partial_location = partial_data.begin();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "Common/GL/GLUtil.h"
|
||||
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
|
||||
namespace OGL
|
||||
@ -20,9 +21,13 @@ public:
|
||||
|
||||
void Bind(unsigned int stage) override;
|
||||
|
||||
void CopyRectangleFromTexture(const AbstractTexture* source,
|
||||
const MathUtil::Rectangle<int>& srcrect,
|
||||
const MathUtil::Rectangle<int>& dstrect) override;
|
||||
void 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) override;
|
||||
void ScaleRectangleFromTexture(const AbstractTexture* source,
|
||||
const MathUtil::Rectangle<int>& srcrect,
|
||||
const MathUtil::Rectangle<int>& dstrect) override;
|
||||
void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
||||
size_t buffer_size) override;
|
||||
|
||||
@ -33,14 +38,39 @@ public:
|
||||
static void SetStage();
|
||||
|
||||
private:
|
||||
std::optional<RawTextureInfo> MapFullImpl() override;
|
||||
std::optional<RawTextureInfo> MapRegionImpl(u32 level, u32 x, u32 y, u32 width,
|
||||
u32 height) override;
|
||||
void MapRegionSlow(u32 level, u32 x, u32 y, u32 width, u32 height);
|
||||
|
||||
GLuint m_texId;
|
||||
GLuint m_framebuffer = 0;
|
||||
std::vector<u8> m_staging_data;
|
||||
};
|
||||
|
||||
class OGLStagingTexture final : public AbstractStagingTexture
|
||||
{
|
||||
public:
|
||||
OGLStagingTexture() = delete;
|
||||
~OGLStagingTexture();
|
||||
|
||||
void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect,
|
||||
u32 src_layer, u32 src_level,
|
||||
const MathUtil::Rectangle<int>& dst_rect) override;
|
||||
void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
|
||||
const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
|
||||
u32 dst_level) override;
|
||||
|
||||
bool Map() override;
|
||||
void Unmap() override;
|
||||
void Flush() override;
|
||||
|
||||
static std::unique_ptr<OGLStagingTexture> Create(StagingTextureType type,
|
||||
const TextureConfig& config);
|
||||
|
||||
private:
|
||||
OGLStagingTexture(StagingTextureType type, const TextureConfig& config, GLenum target,
|
||||
GLuint buffer_name, size_t buffer_size, char* map_ptr, size_t map_stride);
|
||||
|
||||
private:
|
||||
GLenum m_target;
|
||||
GLuint m_buffer_name;
|
||||
size_t m_buffer_size;
|
||||
GLsync m_fence = 0;
|
||||
};
|
||||
|
||||
} // namespace OGL
|
||||
|
@ -458,7 +458,7 @@ Renderer::Renderer()
|
||||
GLExtensions::Supports("GL_EXT_copy_image") ||
|
||||
GLExtensions::Supports("GL_OES_copy_image")) &&
|
||||
!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_COPYIMAGE);
|
||||
g_ogl_config.bSupportTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");
|
||||
g_ogl_config.bSupportsTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");
|
||||
|
||||
// Desktop OpenGL supports the binding layout if it supports 420pack
|
||||
// OpenGL ES 3.1 supports it implicitly without an extension
|
||||
@ -623,6 +623,8 @@ Renderer::Renderer()
|
||||
|
||||
// Compute shaders are core in GL4.3.
|
||||
g_Config.backend_info.bSupportsComputeShaders = true;
|
||||
if (GLExtensions::Version() >= 450)
|
||||
g_ogl_config.bSupportsTextureSubImage = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -814,6 +816,17 @@ void Renderer::Init()
|
||||
OpenGL_CreateAttributelessVAO();
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
|
||||
{
|
||||
return std::make_unique<OGLTexture>(config);
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
|
||||
const TextureConfig& config)
|
||||
{
|
||||
return OGLStagingTexture::Create(type, config);
|
||||
}
|
||||
|
||||
void Renderer::RenderText(const std::string& text, int left, int top, u32 color)
|
||||
{
|
||||
u32 backbuffer_width = std::max(GLInterface->GetBackBufferWidth(), 1u);
|
||||
|
@ -58,7 +58,7 @@ struct VideoConfig
|
||||
bool bSupportsImageLoadStore;
|
||||
bool bSupportsAniso;
|
||||
bool bSupportsBitfield;
|
||||
bool bSupportTextureSubImage;
|
||||
bool bSupportsTextureSubImage;
|
||||
|
||||
const char* gl_vendor;
|
||||
const char* gl_renderer;
|
||||
@ -77,6 +77,10 @@ public:
|
||||
void Init();
|
||||
void Shutdown();
|
||||
|
||||
std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
|
||||
std::unique_ptr<AbstractStagingTexture>
|
||||
CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override;
|
||||
|
||||
void SetBlendingState(const BlendingState& state) override;
|
||||
void SetScissorRect(const EFBRectangle& rc) override;
|
||||
void SetRasterizationState(const RasterizationState& state) override;
|
||||
|
@ -33,11 +33,6 @@ namespace OGL
|
||||
{
|
||||
//#define TIME_TEXTURE_DECODING 1
|
||||
|
||||
std::unique_ptr<AbstractTexture> TextureCache::CreateTexture(const TextureConfig& config)
|
||||
{
|
||||
return std::make_unique<OGLTexture>(config);
|
||||
}
|
||||
|
||||
void TextureCache::CopyEFB(u8* dst, const EFBCopyParams& params, u32 native_width,
|
||||
u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
|
||||
const EFBRectangle& src_rect, bool scale_by_half)
|
||||
|
@ -59,7 +59,6 @@ private:
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) override;
|
||||
void ConvertTexture(TCacheEntry* destination, TCacheEntry* source, const void* palette,
|
||||
TLUTFormat format) override;
|
||||
|
||||
@ -98,7 +97,4 @@ private:
|
||||
std::map<std::pair<u32, u32>, TextureDecodingProgramInfo> m_texture_decoding_program_info;
|
||||
std::array<GLuint, TextureConversionShader::BUFFER_FORMAT_COUNT> m_texture_decoding_buffer_views;
|
||||
};
|
||||
|
||||
bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width,
|
||||
int virtual_height, unsigned int level);
|
||||
}
|
||||
|
@ -34,9 +34,8 @@ namespace TextureConverter
|
||||
{
|
||||
using OGL::TextureCache;
|
||||
|
||||
static GLuint s_texConvFrameBuffer[2] = {0, 0};
|
||||
static GLuint s_srcTexture = 0; // for decoding from RAM
|
||||
static GLuint s_dstTexture = 0; // for encoding to RAM
|
||||
std::unique_ptr<AbstractTexture> s_encoding_render_texture;
|
||||
std::unique_ptr<AbstractStagingTexture> s_encoding_readback_texture;
|
||||
|
||||
const int renderBufferWidth = EFB_WIDTH * 4;
|
||||
const int renderBufferHeight = 1024;
|
||||
@ -49,8 +48,6 @@ struct EncodingProgram
|
||||
};
|
||||
static std::map<EFBCopyParams, EncodingProgram> s_encoding_programs;
|
||||
|
||||
static GLuint s_PBO = 0; // for readback with different strides
|
||||
|
||||
static EncodingProgram& GetOrCreateEncodingShader(const EFBCopyParams& params)
|
||||
{
|
||||
auto iter = s_encoding_programs.find(params);
|
||||
@ -87,42 +84,21 @@ static EncodingProgram& GetOrCreateEncodingShader(const EFBCopyParams& params)
|
||||
|
||||
void Init()
|
||||
{
|
||||
glGenFramebuffers(2, s_texConvFrameBuffer);
|
||||
|
||||
glActiveTexture(GL_TEXTURE9);
|
||||
glGenTextures(1, &s_srcTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, s_srcTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
|
||||
glGenTextures(1, &s_dstTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, s_dstTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, renderBufferWidth, renderBufferHeight, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
|
||||
FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s_dstTexture, 0);
|
||||
FramebufferManager::SetFramebuffer(0);
|
||||
|
||||
glGenBuffers(1, &s_PBO);
|
||||
TextureConfig config(renderBufferWidth, renderBufferHeight, 1, 1, AbstractTextureFormat::BGRA8,
|
||||
true);
|
||||
s_encoding_render_texture = g_renderer->CreateTexture(config);
|
||||
s_encoding_readback_texture =
|
||||
g_renderer->CreateStagingTexture(StagingTextureType::Readback, config);
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
glDeleteTextures(1, &s_srcTexture);
|
||||
glDeleteTextures(1, &s_dstTexture);
|
||||
glDeleteBuffers(1, &s_PBO);
|
||||
glDeleteFramebuffers(2, s_texConvFrameBuffer);
|
||||
s_encoding_readback_texture.reset();
|
||||
s_encoding_render_texture.reset();
|
||||
|
||||
for (auto& program : s_encoding_programs)
|
||||
program.second.program.Destroy();
|
||||
s_encoding_programs.clear();
|
||||
|
||||
s_srcTexture = 0;
|
||||
s_dstTexture = 0;
|
||||
s_PBO = 0;
|
||||
s_texConvFrameBuffer[0] = 0;
|
||||
s_texConvFrameBuffer[1] = 0;
|
||||
}
|
||||
|
||||
// dst_line_size, writeStride in bytes
|
||||
@ -130,9 +106,8 @@ void Shutdown()
|
||||
static void EncodeToRamUsingShader(GLuint srcTexture, u8* destAddr, u32 dst_line_size,
|
||||
u32 dstHeight, u32 writeStride, bool linearFilter, float y_scale)
|
||||
{
|
||||
// switch to texture converter frame buffer
|
||||
// attach render buffer as color destination
|
||||
FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);
|
||||
FramebufferManager::SetFramebuffer(
|
||||
static_cast<OGLTexture*>(s_encoding_render_texture.get())->GetFramebuffer());
|
||||
|
||||
OpenGL_BindAttributelessVAO();
|
||||
|
||||
@ -153,33 +128,13 @@ static void EncodeToRamUsingShader(GLuint srcTexture, u8* destAddr, u32 dst_line
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
int dstSize = dst_line_size * dstHeight;
|
||||
MathUtil::Rectangle<int> copy_rect(0, 0, dst_line_size / 4, dstHeight);
|
||||
s_encoding_readback_texture->CopyFromTexture(s_encoding_render_texture.get(), copy_rect, 0, 0,
|
||||
copy_rect);
|
||||
s_encoding_readback_texture->ReadTexels(copy_rect, destAddr, writeStride);
|
||||
|
||||
// When the dst_line_size and writeStride are the same, we could use glReadPixels directly to RAM.
|
||||
// But instead we always copy the data via a PBO, because macOS inexplicably prefers this (most
|
||||
// noticeably in the Super Mario Sunshine transition).
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, s_PBO);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, dstSize, nullptr, GL_STREAM_READ);
|
||||
glReadPixels(0, 0, (GLsizei)(dst_line_size / 4), (GLsizei)dstHeight, GL_BGRA, GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
u8* pbo = (u8*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dstSize, GL_MAP_READ_BIT);
|
||||
|
||||
if (dst_line_size == writeStride)
|
||||
{
|
||||
memcpy(destAddr, pbo, dst_line_size * dstHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < dstHeight; ++i)
|
||||
{
|
||||
memcpy(destAddr, pbo, dst_line_size);
|
||||
pbo += dst_line_size;
|
||||
destAddr += writeStride;
|
||||
}
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
FramebufferManager::SetFramebuffer(0);
|
||||
OGLTexture::SetStage();
|
||||
}
|
||||
|
||||
void EncodeToRamFromTexture(u8* dest_ptr, const EFBCopyParams& params, u32 native_width,
|
||||
|
Reference in New Issue
Block a user