Merge pull request #6193 from stenzek/readbacks

Abstract Staging Textures - VideoCommon interface for texture readbacks/uploads
This commit is contained in:
Stenzek
2017-12-01 14:24:06 +10:00
committed by GitHub
62 changed files with 1878 additions and 1188 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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);
}

View File

@ -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,