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