GL render manager: Simple implementation of synchronous framebuffer readbacks.

This commit is contained in:
Henrik Rydgård 2017-12-19 14:35:24 +01:00
parent 2b12776137
commit 49c3cb83fe
10 changed files with 246 additions and 121 deletions

View File

@ -832,7 +832,11 @@ endif()
set(THIN3D_PLATFORMS ext/native/thin3d/thin3d_gl.cpp
ext/native/thin3d/GLRenderManager.cpp
ext/native/thin3d/GLQueueRunner.cpp)
ext/native/thin3d/GLRenderManager.h
ext/native/thin3d/GLQueueRunner.cpp
ext/native/thin3d/GLQueueRunner.h
ext/native/thin3d/DataFormatGL.cpp
ext/native/thin3d/DataFormatGL.h)
set(THIN3D_PLATFORMS ${THIN3D_PLATFORMS}
ext/native/thin3d/thin3d_vulkan.cpp

View File

@ -83,8 +83,11 @@ LOCAL_SRC_FILES :=\
thin3d/thin3d.cpp \
thin3d/thin3d_gl.cpp \
thin3d/thin3d_vulkan.cpp \
thin3d/GLRenderManager.cpp \
thin3d/GLQueueRunner.cpp \
thin3d/VulkanRenderManager.cpp \
thin3d/VulkanQueueRunner.cpp \
thin3d/DataFormatGL.cpp \
ui/view.cpp \
ui/viewgroup.cpp \
ui/ui.cpp \

View File

@ -240,6 +240,7 @@
<ClInclude Include="gfx_es2\draw_text_win.h" />
<ClInclude Include="thin3d\d3d11_loader.h" />
<ClInclude Include="thin3d\DataFormat.h" />
<ClInclude Include="thin3d\DataFormatGL.h" />
<ClInclude Include="thin3d\GLQueueRunner.h" />
<ClInclude Include="thin3d\GLRenderManager.h" />
<ClInclude Include="thin3d\VulkanQueueRunner.h" />
@ -699,6 +700,7 @@
<ClCompile Include="gfx_es2\draw_text_win.cpp" />
<ClCompile Include="math\dataconv.cpp" />
<ClCompile Include="thin3d\d3d11_loader.cpp" />
<ClCompile Include="thin3d\DataFormatGL.cpp" />
<ClCompile Include="thin3d\GLQueueRunner.cpp" />
<ClCompile Include="thin3d\GLRenderManager.cpp" />
<ClCompile Include="thin3d\thin3d_d3d11.cpp" />

View File

@ -335,6 +335,9 @@
<ClInclude Include="thin3d\GLQueueRunner.h">
<Filter>thin3d</Filter>
</ClInclude>
<ClInclude Include="thin3d\DataFormatGL.h">
<Filter>thin3d</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gfx\gl_debug_log.cpp">
@ -805,6 +808,9 @@
<ClCompile Include="thin3d\GLRenderManager.cpp">
<Filter>thin3d</Filter>
</ClCompile>
<ClCompile Include="thin3d\DataFormatGL.cpp">
<Filter>thin3d</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="gfx">

View File

@ -0,0 +1,90 @@
#include "thin3d/DataFormatGL.h"
#include "base/logging.h"
#include "Common/Log.h"
namespace Draw {
// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage.
bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
alignment = 4;
switch (fmt) {
case DataFormat::R8G8B8A8_UNORM:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
case DataFormat::D32F:
internalFormat = GL_DEPTH_COMPONENT;
format = GL_DEPTH_COMPONENT;
type = GL_FLOAT;
break;
#ifndef USING_GLES2
case DataFormat::S8:
internalFormat = GL_STENCIL_INDEX;
format = GL_STENCIL_INDEX;
type = GL_UNSIGNED_BYTE;
alignment = 1;
break;
#endif
case DataFormat::R8G8B8_UNORM:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_BYTE;
alignment = 1;
break;
case DataFormat::B4G4R4A4_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4;
alignment = 2;
break;
case DataFormat::B5G6R5_UNORM_PACK16:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
alignment = 2;
break;
case DataFormat::B5G5R5A1_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_5_5_5_1;
alignment = 2;
break;
#ifndef USING_GLES2
case DataFormat::A4R4G4B4_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
alignment = 2;
break;
case DataFormat::R5G6B5_UNORM_PACK16:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5_REV;
alignment = 2;
break;
case DataFormat::A1R5G5B5_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
alignment = 2;
break;
#endif
default:
_assert_msg_(G3D, false, "Thin3d GL: Unsupported texture format %d", (int)fmt);
return false;
}
return true;
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include "gfx/gl_common.h"
#include "thin3d/DataFormat.h"
namespace Draw {
bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment);
}

View File

@ -1,5 +1,6 @@
#include "GLQueueRunner.h"
#include "GLRenderManager.h"
#include "DataFormatGL.h"
#include "base/logging.h"
#include "gfx/gl_common.h"
#include "gfx/gl_debug_log.h"
@ -789,6 +790,10 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) {
*/
break;
}
_dbg_assert_(G3D, srcTex);
_dbg_assert_(G3D, dstTex);
#if defined(USING_GLES2)
#ifndef IOS
glCopyImageSubDataOES(
@ -813,7 +818,49 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) {
}
void GLQueueRunner::PerformReadback(const GLRStep &pass) {
GLRFramebuffer *fb = pass.readback.src;
fbo_bind_fb_target(true, fb ? fb->handle : 0);
// Reads from the "bound for read" framebuffer.
if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
glReadBuffer(GL_COLOR_ATTACHMENT0);
CHECK_GL_ERROR_IF_DEBUG();
GLuint internalFormat;
GLuint format;
GLuint type;
int alignment;
if (!Draw::Thin3DFormatToFormatAndType(pass.readback.dstFormat, internalFormat, format, type, alignment)) {
assert(false);
}
int pixelStride = pass.readback.srcRect.w;
// Apply the correct alignment.
glPixelStorei(GL_PACK_ALIGNMENT, alignment);
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
// Some drivers seem to require we specify this. See #8254.
glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride);
}
GLRect2D rect = pass.readback.srcRect;
int size = alignment * rect.w * rect.h;
if (size > readbackBufferSize_) {
delete[] readbackBuffer_;
readbackBuffer_ = new uint8_t[size];
readbackBufferSize_ = size;
}
glReadPixels(rect.x, rect.y, rect.w, rect.h, format, type, readbackBuffer_);
#ifdef DEBUG_READ_PIXELS
LogReadPixelsError(glGetError());
#endif
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
CHECK_GL_ERROR_IF_DEBUG();
}
void GLQueueRunner::PerformReadbackImage(const GLRStep &pass) {
@ -841,7 +888,12 @@ void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) {
}
void GLQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) {
// TODO: Maybe move data format conversion here, and always read back 8888. Drivers
// don't usually provide very optimized implementations.
int bpp = Draw::DataFormatSizeInBytes(destFormat);
for (int y = 0; y < height; y++) {
memcpy(pixels + y * pixelStride * bpp, readbackBuffer_ + y * width * bpp, pixelStride * bpp);
}
}
GLuint GLQueueRunner::AllocTextureName() {

View File

@ -289,6 +289,7 @@ struct GLRStep {
int aspectMask;
GLRFramebuffer *src;
GLRect2D srcRect;
Draw::DataFormat dstFormat;
} readback;
struct {
GLint texture;
@ -354,7 +355,7 @@ private:
// Readback buffer. Currently we only support synchronous readback, so we only really need one.
// We size it generously.
GLint readbackBuffer_ = 0;
uint8_t *readbackBuffer_ = nullptr;
int readbackBufferSize_ = 0;
float maxAnisotropyLevel_ = 0.0f;

View File

@ -215,6 +215,33 @@ void GLRenderManager::BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLR
steps_.push_back(step);
}
bool GLRenderManager::CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) {
GLRStep *step = new GLRStep{ GLRStepType::READBACK };
step->readback.src = src;
step->readback.srcRect = { x, y, w, h };
step->readback.aspectMask = aspectBits;
step->readback.dstFormat = destFormat;
steps_.push_back(step);
curRenderStep_ = nullptr;
FlushSync();
Draw::DataFormat srcFormat;
if (aspectBits & GL_COLOR_BUFFER_BIT) {
srcFormat = Draw::DataFormat::R8G8B8A8_UNORM;
} else if (aspectBits & GL_STENCIL_BUFFER_BIT) {
// Copies from stencil are always S8.
srcFormat = Draw::DataFormat::S8;
} else if (aspectBits & GL_DEPTH_BUFFER_BIT) {
// TODO: Do this properly.
srcFormat = Draw::DataFormat::D24_S8;
} else {
_assert_(false);
}
queueRunner_.CopyReadbackBuffer(w, h, srcFormat, destFormat, pixelStride, pixels);
return true;
}
void GLRenderManager::BeginFrame() {
VLOG("BeginFrame");
@ -255,14 +282,18 @@ void GLRenderManager::Finish() {
FrameData &frameData = frameData_[curFrame];
if (!useThread_) {
frameData.steps = std::move(steps_);
steps_.clear();
frameData.initSteps = std::move(initSteps_);
initSteps_.clear();
frameData.type = GLRRunType::END;
Run(curFrame);
} else {
std::unique_lock<std::mutex> lock(frameData.pull_mutex);
VLOG("PUSH: Frame[%d].readyForRun = true", curFrame);
frameData.steps = std::move(steps_);
steps_.clear();
frameData.initSteps = std::move(initSteps_);
initSteps_.clear();
frameData.readyForRun = true;
frameData.type = GLRRunType::END;
frameData.pull_condVar.notify_all();
@ -340,6 +371,41 @@ void GLRenderManager::Run(int frame) {
VLOG("PULL: Finished running frame %d", frame);
}
void GLRenderManager::FlushSync() {
// TODO: Reset curRenderStep_?
int curFrame = curFrame_;
FrameData &frameData = frameData_[curFrame];
if (!useThread_) {
frameData.initSteps = std::move(initSteps_);
initSteps_.clear();
frameData.steps = std::move(steps_);
steps_.clear();
frameData.type = GLRRunType::SYNC;
Run(curFrame);
} else {
std::unique_lock<std::mutex> lock(frameData.pull_mutex);
VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame);
frameData.initSteps = std::move(initSteps_);
initSteps_.clear();
frameData.steps = std::move(steps_);
steps_.clear();
frameData.readyForRun = true;
assert(frameData.readyForFence == false);
frameData.type = GLRRunType::SYNC;
frameData.pull_condVar.notify_all();
}
if (useThread_) {
std::unique_lock<std::mutex> lock(frameData.push_mutex);
// Wait for the flush to be hit, since we're syncing.
while (!frameData.readyForFence) {
VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame);
frameData.push_condVar.wait(lock);
}
frameData.readyForFence = false;
}
}
void GLRenderManager::EndSyncFrame(int frame) {
FrameData &frameData = frameData_[frame];
Submit(frame, false);
@ -347,7 +413,6 @@ void GLRenderManager::EndSyncFrame(int frame) {
// This is brutal! Should probably wait for a fence instead, not that it'll matter much since we'll
// still stall everything.
glFinish();
// vkDeviceWaitIdle(vulkan_->GetDevice());
// At this point we can resume filling the command buffers for the current frame since
// we know the device is idle - and thus all previously enqueued command buffers have been processed.

View File

@ -9,6 +9,7 @@
#include "math/dataconv.h"
#include "math/lin/matrix4x4.h"
#include "thin3d/thin3d.h"
#include "thin3d/DataFormatGL.h"
#include "gfx/gl_common.h"
#include "gfx/gl_debug_log.h"
#include "gfx_es2/gpu_features.h"
@ -662,90 +663,6 @@ public:
FBColorDepth colorDepth;
};
// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage.
static bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
alignment = 4;
switch (fmt) {
case DataFormat::R8G8B8A8_UNORM:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
case DataFormat::D32F:
internalFormat = GL_DEPTH_COMPONENT;
format = GL_DEPTH_COMPONENT;
type = GL_FLOAT;
break;
#ifndef USING_GLES2
case DataFormat::S8:
internalFormat = GL_STENCIL_INDEX;
format = GL_STENCIL_INDEX;
type = GL_UNSIGNED_BYTE;
alignment = 1;
break;
#endif
case DataFormat::R8G8B8_UNORM:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_BYTE;
alignment = 1;
break;
case DataFormat::B4G4R4A4_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4;
alignment = 2;
break;
case DataFormat::B5G6R5_UNORM_PACK16:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
alignment = 2;
break;
case DataFormat::B5G5R5A1_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_5_5_5_1;
alignment = 2;
break;
#ifndef USING_GLES2
case DataFormat::A4R4G4B4_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
alignment = 2;
break;
case DataFormat::R5G6B5_UNORM_PACK16:
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5_REV;
alignment = 2;
break;
case DataFormat::A1R5G5B5_UNORM_PACK16:
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
alignment = 2;
break;
#endif
default:
ELOG("Thin3d GL: Unsupported texture format %d", (int)fmt);
return false;
}
return true;
}
void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) {
if (width != width_ || height != height_ || depth != depth_) {
// When switching to texStorage we need to handle this correctly.
@ -811,39 +728,14 @@ static void LogReadPixelsError(GLenum error) {
bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) {
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src;
/*
fbo_bind_fb_target(true, fb ? fb->handle : 0);
// Reads from the "bound for read" framebuffer.
if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
glReadBuffer(GL_COLOR_ATTACHMENT0);
CHECK_GL_ERROR_IF_DEBUG();
GLuint internalFormat;
GLuint format;
GLuint type;
int alignment;
if (!Thin3DFormatToFormatAndType(dataFormat, internalFormat, format, type, alignment)) {
assert(false);
}
// Apply the correct alignment.
glPixelStorei(GL_PACK_ALIGNMENT, alignment);
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
// Even if not required, some drivers seem to require we specify this. See #8254.
glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride);
}
glReadPixels(x, y, w, h, format, type, pixels);
#ifdef DEBUG_READ_PIXELS
LogReadPixelsError(glGetError());
#endif
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
CHECK_GL_ERROR_IF_DEBUG();
*/
GLuint aspect = 0;
if (channelBits & FB_COLOR_BIT)
aspect |= GL_COLOR_BUFFER_BIT;
if (channelBits & FB_DEPTH_BIT)
aspect |= GL_DEPTH_BUFFER_BIT;
if (channelBits & FB_STENCIL_BIT)
aspect |= GL_STENCIL_BUFFER_BIT;
renderManager_.CopyFramebufferToMemorySync(fb->framebuffer, aspect, x, y, w, h, dataFormat, (uint8_t *)pixels, pixelStride);
return true;
}