diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9aeeed988..ee283b107 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/ext/native/Android.mk b/ext/native/Android.mk
index fd0e69181..78a75db9d 100644
--- a/ext/native/Android.mk
+++ b/ext/native/Android.mk
@@ -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 \
diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj
index a9929e396..ebb1517ea 100644
--- a/ext/native/native.vcxproj
+++ b/ext/native/native.vcxproj
@@ -240,6 +240,7 @@
+
@@ -699,6 +700,7 @@
+
diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters
index 7336d6226..e0507e183 100644
--- a/ext/native/native.vcxproj.filters
+++ b/ext/native/native.vcxproj.filters
@@ -335,6 +335,9 @@
thin3d
+
+ thin3d
+
@@ -805,6 +808,9 @@
thin3d
+
+ thin3d
+
diff --git a/ext/native/thin3d/DataFormatGL.cpp b/ext/native/thin3d/DataFormatGL.cpp
new file mode 100644
index 000000000..389d6046e
--- /dev/null
+++ b/ext/native/thin3d/DataFormatGL.cpp
@@ -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;
+}
+
+}
\ No newline at end of file
diff --git a/ext/native/thin3d/DataFormatGL.h b/ext/native/thin3d/DataFormatGL.h
new file mode 100644
index 000000000..28b48f60f
--- /dev/null
+++ b/ext/native/thin3d/DataFormatGL.h
@@ -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);
+
+}
diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp
index c1230dbb5..21d78f757 100644
--- a/ext/native/thin3d/GLQueueRunner.cpp
+++ b/ext/native/thin3d/GLQueueRunner.cpp
@@ -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() {
diff --git a/ext/native/thin3d/GLQueueRunner.h b/ext/native/thin3d/GLQueueRunner.h
index a2a4ae9da..6bea39855 100644
--- a/ext/native/thin3d/GLQueueRunner.h
+++ b/ext/native/thin3d/GLQueueRunner.h
@@ -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;
diff --git a/ext/native/thin3d/GLRenderManager.cpp b/ext/native/thin3d/GLRenderManager.cpp
index 2ccccd5ad..27b9fe6ad 100644
--- a/ext/native/thin3d/GLRenderManager.cpp
+++ b/ext/native/thin3d/GLRenderManager.cpp
@@ -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 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 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 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.
diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp
index f65b765e4..194d065ec 100644
--- a/ext/native/thin3d/thin3d_gl.cpp
+++ b/ext/native/thin3d/thin3d_gl.cpp
@@ -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;
}