diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 25afd4ef..6878eac9 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -8,8 +8,11 @@ #include "DirectGLES.h" #include "GLES3/gl32.h" +#include "MG_State/GLState/ErrorState/Error.h" +#include "MG_State/GLState/RenderState/RenderState.h" #include "MG_State/GLState/SamplerState/SamplerObject.h" #include "MG_Util/Debug/Log.h" +#include "MG_Util/Types.h" #include "Utils.h" #include "Managers.h" #include @@ -882,8 +885,8 @@ namespace MobileGL::MG_Backend::DirectGLES { auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); if (!TextureImpl::IsSupportedTextureTarget(textureTarget)) { - MOBILEGL_ASSERT(false, " Texture target %s is not supported, skipping.", - MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); + MGLOG_E(" Texture target %s is not supported, skipping.", + MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); return false; } @@ -909,26 +912,40 @@ namespace MobileGL::MG_Backend::DirectGLES { } static GLuint s_prevDrawFBO = 0; - void BindTempDrawFBO() { + static GLuint s_prevReadFBO = 0; + void BindTempFBO(Bool isRead) { MGLOG_D("%s: Binding temporary FBO for operations like CopyTexImage2D that require framebuffer binding, " - "previous draw FBO=%u", - __func__, s_prevDrawFBO); + "previous draw FBO=%u, read FBO=%u", + __func__, s_prevDrawFBO, s_prevReadFBO); static GLuint tempFBO = 0; if (!tempFBO) { MG_External::GLES::glGenFramebuffers(1, &tempFBO); } - MG_External::GLES::glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&s_prevDrawFBO); - MG_External::GLES::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, tempFBO); + if (isRead) { + MG_External::GLES::glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (GLint*)&s_prevReadFBO); + MG_External::GLES::glBindFramebuffer(GL_READ_FRAMEBUFFER, tempFBO); + } else { + MG_External::GLES::glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&s_prevDrawFBO); + MG_External::GLES::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, tempFBO); + } } - void RestoreDrawFBOFromTemp() { - MGLOG_D("%s: Restoring previous draw FBO=%u", __func__, s_prevDrawFBO); - MG_External::GLES::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_prevDrawFBO); + void RestoreFBOFromTemp(Bool isRead) { + if (isRead) { + MGLOG_D("%s: Restoring previous read FBO=%u", __func__, s_prevReadFBO); + MG_External::GLES::glBindFramebuffer(GL_READ_FRAMEBUFFER, s_prevReadFBO); + } else { + MGLOG_D("%s: Restoring previous draw FBO=%u", __func__, s_prevDrawFBO); + MG_External::GLES::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_prevDrawFBO); + } } class TempFBOBinder { public: - TempFBOBinder() { BindTempDrawFBO(); } - ~TempFBOBinder() { RestoreDrawFBOFromTemp(); } + TempFBOBinder(Bool isRead) : m_isRead(isRead) { BindTempFBO(isRead); } + ~TempFBOBinder() { RestoreFBOFromTemp(m_isRead); } + + private: + const Bool m_isRead = false; }; void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, @@ -1001,7 +1018,7 @@ namespace MobileGL::MG_Backend::DirectGLES { }); GLenum attachment = isStencilFormat ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; - TempFBOBinder tempFBOBinder; + TempFBOBinder tempFBOBinder(false); MG_External::GLES::glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, target, currentTex, level); if (MG_External::GLES::glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { @@ -1080,7 +1097,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); GLenum attachment = isStencilFormat ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; - TempFBOBinder tempFBOBinder; + TempFBOBinder tempFBOBinder(false); MG_External::GLES::glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, target, currentTex, level); errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); @@ -1136,6 +1153,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_External::GLES::glClearBufferfv(buffer, drawbuffer, value); } + void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) { TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); @@ -1154,4 +1172,289 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_External::GLES::glClearBufferuiv(buffer, drawbuffer, value); } -} // namespace MobileGL::MG_Backend::DirectGLES + class TempPixelStoreParameterSync { + public: + TempPixelStoreParameterSync(Bool isUnpack) : m_isUnpack(isUnpack) { + const auto& currentParams = MG_State::pGLContext->GetPixelStoreParameters(isUnpack); + m_prevParams = QueryCurrentGLPixelStoreParams(isUnpack); + Sync(isUnpack, currentParams); + } + + ~TempPixelStoreParameterSync() { Sync(m_isUnpack, m_prevParams); } + + private: + const Bool m_isUnpack; + + PixelStoreParameters m_prevParams; + + PixelStoreParameters QueryCurrentGLPixelStoreParams(Bool isUnpack) { + PixelStoreParameters p; + if (!isUnpack) { + MG_External::GLES::glGetIntegerv(GL_PACK_ALIGNMENT, (GLint*)&p.Alignment); + MG_External::GLES::glGetIntegerv(GL_PACK_ROW_LENGTH, (GLint*)&p.RowLength); + MG_External::GLES::glGetIntegerv(GL_PACK_SKIP_ROWS, (GLint*)&p.SkipRows); + MG_External::GLES::glGetIntegerv(GL_PACK_SKIP_PIXELS, (GLint*)&p.SkipPixels); + // MG_External::GLES::glGetIntegerv(GL_PACK_IMAGE_HEIGHT, (GLint*)&p.ImageHeight); + // MG_External::GLES::glGetIntegerv(GL_PACK_SKIP_IMAGES, (GLint*)&p.SkipImages); + // GLint tmp; + // MG_External::GLES::glGetIntegerv(GL_PACK_SWAP_BYTES, &tmp); + // p.SwapBytes = tmp ? true : false; + // MG_External::GLES::glGetIntegerv(GL_PACK_LSB_FIRST, &tmp); + // p.LSBFirst = tmp ? true : false; + } else { + MG_External::GLES::glGetIntegerv(GL_UNPACK_ALIGNMENT, (GLint*)&p.Alignment); + MG_External::GLES::glGetIntegerv(GL_UNPACK_ROW_LENGTH, (GLint*)&p.RowLength); + MG_External::GLES::glGetIntegerv(GL_UNPACK_SKIP_ROWS, (GLint*)&p.SkipRows); + MG_External::GLES::glGetIntegerv(GL_UNPACK_SKIP_PIXELS, (GLint*)&p.SkipPixels); + MG_External::GLES::glGetIntegerv(GL_UNPACK_IMAGE_HEIGHT, (GLint*)&p.ImageHeight); + MG_External::GLES::glGetIntegerv(GL_UNPACK_SKIP_IMAGES, (GLint*)&p.SkipImages); + // GLint tmp; + // MG_External::GLES::glGetIntegerv(GL_UNPACK_SWAP_BYTES, &tmp); + // p.SwapBytes = tmp ? true : false; + // MG_External::GLES::glGetIntegerv(GL_UNPACK_LSB_FIRST, &tmp); + // p.LSBFirst = tmp ? true : false; + } + return p; + } + + void Sync(Bool isUnpack, const PixelStoreParameters& params) { + if (!isUnpack) { + MG_External::GLES::glPixelStorei(GL_PACK_ALIGNMENT, params.Alignment); + MG_External::GLES::glPixelStorei(GL_PACK_ROW_LENGTH, params.RowLength); + MG_External::GLES::glPixelStorei(GL_PACK_SKIP_ROWS, params.SkipRows); + MG_External::GLES::glPixelStorei(GL_PACK_SKIP_PIXELS, params.SkipPixels); + // MG_External::GLES::glPixelStorei(GL_PACK_IMAGE_HEIGHT, params.ImageHeight); + // MG_External::GLES::glPixelStorei(GL_PACK_SKIP_IMAGES, params.SkipImages); + // MG_External::GLES::glPixelStorei(GL_PACK_SWAP_BYTES, params.SwapBytes ? GL_TRUE : GL_FALSE); + // MG_External::GLES::glPixelStorei(GL_PACK_LSB_FIRST, params.LSBFirst ? GL_TRUE : GL_FALSE); + } else { + MG_External::GLES::glPixelStorei(GL_UNPACK_ALIGNMENT, params.Alignment); + MG_External::GLES::glPixelStorei(GL_UNPACK_ROW_LENGTH, params.RowLength); + MG_External::GLES::glPixelStorei(GL_UNPACK_SKIP_ROWS, params.SkipRows); + MG_External::GLES::glPixelStorei(GL_UNPACK_SKIP_PIXELS, params.SkipPixels); + MG_External::GLES::glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, params.ImageHeight); + MG_External::GLES::glPixelStorei(GL_UNPACK_SKIP_IMAGES, params.SkipImages); + // MG_External::GLES::glPixelStorei(GL_UNPACK_SWAP_BYTES, params.SwapBytes ? GL_TRUE : GL_FALSE); + // MG_External::GLES::glPixelStorei(GL_UNPACK_LSB_FIRST, params.LSBFirst ? GL_TRUE : GL_FALSE); + } + } + }; + + void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { + MGLOG_D("ReadPixels: x=%d y=%d w=%d h=%d format=%s type=%s pixels=%p", x, y, width, height, + MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str(), pixels); + + MOBILEGL_ASSERT(format == GL_RGBA || format == GL_RGBA_INTEGER, + "Only GL_RGBA and GL_RGBA_INTEGER are supported currently, while requested %s.", + MG_Util::ConvertGLEnumToString(format).c_str()); + MOBILEGL_ASSERT(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_INT || type == GL_UNSIGNED_INT_2_10_10_10_REV || + type == GL_INT || type == GL_FLOAT, + "Only GL_UNSIGNED_BYTE, GL_UNSIGNED_INT, GL_UNSIGNED_INT_2_10_10_10_REV, " + "GL_INT and GL_FLOAT are supported currently, while requested %s.", + MG_Util::ConvertGLEnumToString(type).c_str()); + + MGLOG_D("ReadPixels: SyncNeccessaryTextures()"); + TextureImpl::SyncNeccessaryTextures(); + + MGLOG_D("ReadPixels: SyncCurrentFBO()"); + FramebufferImpl::SyncCurrentFBO(); + + MGLOG_D("ReadPixels: BindCurrentFBO(Read)"); + BindCurrentFBO(FramebufferTarget::Read); + + MGLOG_D("ReadPixels: Applying TempPixelStoreParameterSync (PACK)"); + TempPixelStoreParameterSync tempPackParamsSync(false); + + GLenum fbStatus = MG_External::GLES::glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + MGLOG_D("ReadPixels: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str()); + + if (fbStatus != GL_FRAMEBUFFER_COMPLETE) { + MGLOG_E("ReadPixels: bound READ FBO is not complete"); + return; + } + + // Handle PBO + auto pixelPackBufferObject = + MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + Bool usePBO; + GLuint prevPixelPackBuffer = 0; + if (pixelPackBufferObject) { + BufferImpl::CreateAndSyncBufferObject(pixelPackBufferObject); + MGLOG_D("ReadPixels: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); + usePBO = true; + const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(pixelPackBufferObject); + + if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { + MGLOG_E("ReadPixels: No backend buffer found for PBO %u.", + pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); + return; + } + const auto& backendBufferObject = backendBufferIt->second; + backendBufferObject->Bind(GL_PIXEL_PACK_BUFFER); + MG_External::GLES::glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer); + } else { + usePBO = false; + MGLOG_D("ReadPixels: Not using PBO"); + } + + MGLOG_D("ReadPixels: glReadPixels()"); + MG_External::GLES::glReadPixels(x, y, width, height, format, type, pixels); + if (usePBO) { + // pull back to client memory if PBO is used + MGLOG_D("ReadPixels: PBO used, mapping buffer to client memory"); + GLvoid* pboMappedPtr = MG_External::GLES::glMapBufferRange( + GL_PIXEL_PACK_BUFFER, 0, pixelPackBufferObject->GetSize(), GL_MAP_READ_BIT); + if (pboMappedPtr) { + MGLOG_D("ReadPixels: Copying data from PBO to client memory"); + SizeT size = pixelPackBufferObject->GetSize(); + pixelPackBufferObject->UploadSubData({pboMappedPtr, size}, 0); + pixelPackBufferObject->ClearDirty(); + MGLOG_D("ReadPixels: Unmapping PBO"); + MG_External::GLES::glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + } else { + MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + } + MGLOG_D("ReadPixels: Restoring previous pixel pack buffer binding %u", prevPixelPackBuffer); + MG_External::GLES::glBindBuffer(GL_PIXEL_PACK_BUFFER, prevPixelPackBuffer); + } + MGLOG_D("ReadPixels: finished"); + } + + void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { + MGLOG_D("GetTexImage: target=%s level=%d format=%s type=%s pixels=%p", + MG_Util::ConvertGLEnumToString(target).c_str(), level, MG_Util::ConvertGLEnumToString(format).c_str(), + MG_Util::ConvertGLEnumToString(type).c_str(), pixels); + + MOBILEGL_ASSERT(format == GL_RGBA || format == GL_RGBA_INTEGER, + "Only GL_RGBA and GL_RGBA_INTEGER are supported currently, while requested %s.", + MG_Util::ConvertGLEnumToString(format).c_str()); + MOBILEGL_ASSERT(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_INT || type == GL_UNSIGNED_INT_2_10_10_10_REV || + type == GL_INT || type == GL_FLOAT, + "Only GL_UNSIGNED_BYTE, GL_UNSIGNED_INT, GL_UNSIGNED_INT_2_10_10_10_REV, " + "GL_INT and GL_FLOAT are supported currently, while requested %s.", + MG_Util::ConvertGLEnumToString(type).c_str()); + + MGLOG_D("GetTexImage: SyncNeccessaryTextures()"); + TextureImpl::SyncNeccessaryTextures(); + + MGLOG_D("GetTexImage: SyncCurrentFBO()"); + FramebufferImpl::SyncCurrentFBO(); + + Uint activeTextureUnit = MG_State::pGLContext->GetActiveTextureUnit(); + MGLOG_D("GetTexImage: active texture unit = %u", activeTextureUnit); + + const auto& textureObject = MG_State::pGLContext->GetTextureUnitObject(activeTextureUnit) + .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)) + .GetBoundObject(); + + MGLOG_D("GetTexImage: bound texture object = %p (name=%u)", textureObject.get(), + textureObject ? textureObject->GetExternalIndex() : 0); + + const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject); + + if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) { + MGLOG_E("GetTexImage: No backend texture found for texture %u.", + textureObject ? textureObject->GetExternalIndex() : 0); + return; + } + + GLuint backendTexId = backendTextureIt->second->GetBackendTextureId(); + MGLOG_D("GetTexImage: backend texture id = %u", backendTexId); + + MGLOG_D("GetTexImage: Binding temporary FBO"); + TempFBOBinder tempFBOBinder(true); + + MGLOG_D("GetTexImage: glFramebufferTexture2D(level=%d)", level); + MG_External::GLES::glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, backendTexId, + level); + MGLOG_D("GetTexImage: glReadBuffer(GL_COLOR_ATTACHMENT0)"); + MG_External::GLES::glReadBuffer(GL_COLOR_ATTACHMENT0); + + GLenum fbStatus = MG_External::GLES::glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + MGLOG_D("GetTexImage: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str()); + + if (fbStatus != GL_FRAMEBUFFER_COMPLETE) { + MGLOG_E("GetTexImage: READ FBO incomplete"); + MGLOG_E("GetTexImage: bound READ FBO is not complete"); + return; + } + + MGLOG_D("GetTexImage: Applying TempPixelStoreParameterSync (PACK)"); + TempPixelStoreParameterSync tempPackParamsSync(false); + + const auto& storageType = textureObject->GetStorageType(); + MGLOG_D("GetTexImage: texture storage type = %d", (int)storageType); + + if (storageType == TextureStorageType::Buffer) { + MGLOG_E("GetTexImage: Texture storage type Buffer is not supported."); + MGLOG_E("GetTexImage: Texture storage type Buffer is not supported."); + return; + } + + auto* textureMipmapObject = static_cast(textureObject.get()); + + auto levelRange = textureMipmapObject->GetLevelRange(); + MGLOG_D("GetTexImage: mipmap level range = [%d, %d)", levelRange.x(), levelRange.y()); + + if (level < levelRange.x() || level >= levelRange.y()) { + MGLOG_E("GetTexImage: Requested level %d out of range", level); + MOBILEGL_ASSERT(false, + "GetTexImage: Requested level %d is out of range " + "(base level %d, max level %d).", + level, levelRange.x(), levelRange.y()); + return; + } + + auto size = textureMipmapObject->GetMipmapTexelSize(MG_Util::ConvertGLEnumToTextureUploadTarget(target), level); + + MGLOG_D("GetTexImage: mip level %d size = %dx%d", level, size.x(), size.y()); + + // Handle PBO + auto pixelPackBufferObject = + MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + Bool usePBO; + GLuint prevPixelPackBuffer = 0; + if (pixelPackBufferObject) { + BufferImpl::CreateAndSyncBufferObject(pixelPackBufferObject); + MGLOG_D("GetTexImage: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); + usePBO = true; + const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(pixelPackBufferObject); + if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { + MGLOG_E("GetTexImage: No backend buffer found for PBO %u.", + pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); + return; + } + const auto& backendBufferObject = backendBufferIt->second; + backendBufferObject->Bind(GL_PIXEL_PACK_BUFFER); + MG_External::GLES::glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer); + } else { + usePBO = false; + MGLOG_D("GetTexImage: Not using PBO"); + } + MGLOG_D("GetTexImage: glReadPixels()"); + MG_External::GLES::glReadPixels(0, 0, size.x(), size.y(), format, type, pixels); + if (usePBO) { + // pull back to client memory if PBO is used + MGLOG_D("ReadPixels: PBO used, mapping buffer to client memory"); + GLvoid* pboMappedPtr = MG_External::GLES::glMapBufferRange( + GL_PIXEL_PACK_BUFFER, 0, pixelPackBufferObject->GetSize(), GL_MAP_READ_BIT); + if (pboMappedPtr) { + MGLOG_D("ReadPixels: Copying data from PBO to client memory"); + SizeT size = pixelPackBufferObject->GetSize(); + pixelPackBufferObject->UploadSubData({pboMappedPtr, size}, 0); + pixelPackBufferObject->ClearDirty(); + MGLOG_D("ReadPixels: Unmapping PBO"); + MG_External::GLES::glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + } else { + MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + } + MGLOG_D("ReadPixels: Restoring previous pixel pack buffer binding %u", prevPixelPackBuffer); + + MG_External::GLES::glBindBuffer(GL_PIXEL_PACK_BUFFER, prevPixelPackBuffer); + } + MGLOG_D("GetTexImage: finished"); + } + +} // namespace MobileGL::MG_Backend::DirectGLES \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index f6ad4149..4c5241cc 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -53,4 +53,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLsizei height); void GenerateMipmap(GLenum target); const GLubyte* GetString(GLenum name); + void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); + void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); + } // namespace MobileGL::MG_Backend::DirectGLES \ No newline at end of file diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 3f40d24c..30e87679 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -151,7 +151,7 @@ DECLARE_GL_FUNCTION_HEAD(void, LineWidth, GLfloat width) DECLARE_GL_FUNCTION_END DECLARE_GL_FUNCTION_HEAD(void, LinkProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, LinkProgram, program) DECLARE_GL_FUNCTION_HEAD(void, PixelStorei, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PixelStorei, pname, param) DECLARE_GL_FUNCTION_HEAD(void, PolygonOffset, GLfloat factor, GLfloat units) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PolygonOffset, factor, units) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ReadPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ReadPixels, x, y, width, height, format, type, pixels) +DECLARE_GL_FUNCTION_HEAD(void, ReadPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadPixels, x, y, width, height, format, type, pixels) DECLARE_GL_FUNCTION_STUB_HEAD(void, ReleaseShaderCompiler) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ReleaseShaderCompiler) DECLARE_GL_FUNCTION_HEAD(void, RenderbufferStorage, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, RenderbufferStorage, target, internalformat, width, height) DECLARE_GL_FUNCTION_HEAD(void, SampleCoverage, GLfloat value, GLboolean invert) DECLARE_GL_FUNCTION_END_NO_RETURN(void, SampleCoverage, value, invert) diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index f0746053..d463fca3 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -9,6 +9,7 @@ #include "GL_Framebuffer.h" #include "Validators.h" #include "Config.h" +#include #include #include #include @@ -310,10 +311,10 @@ namespace MobileGL { // ------------------- Check validity begin ------------------------ if (attType == FramebufferAttachmentType::Unknown) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeShared("MG_Impl/GLImpl", __func__, - std::format("`mode` = {} is not an accepted value.", - MG_Util::ConvertGLEnumToString(mode)))); + ErrorCode::InvalidEnum, + MakeShared( + "MG_Impl/GLImpl", __func__, + std::format("`mode` = {} is not an accepted value.", MG_Util::ConvertGLEnumToString(mode)))); return; } @@ -507,7 +508,142 @@ namespace MobileGL { #endif } + void ReadPixels_State(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + void* pixels) { + TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); + TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); + + // Check width/height + if (width < 0 || height < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "Width and height must be non-negative")); + return; + } + + // Validate format + if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", "Invalid format")); + return; + } + + // Validate type + if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", "Invalid pixel data type")); + return; + } + + // Get bound framebuffer + auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read); + auto framebufferObject = bindingSlot.GetBoundObject(); + + if (!framebufferObject) { + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "No framebuffer bound to read target")); + return; + } + + // Check framebuffer completeness + if (!framebufferObject->CheckCompleteness()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidFramebufferOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", "Framebuffer is incomplete")); + return; + } + + // Check for required buffers + if (textureInputFormat == TextureInputFormat::StencilIndex) { + if (!framebufferObject->GetAttachment(FramebufferAttachmentType::Stencil).IsValid()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "No stencil buffer for stencil index format")); + return; + } + } else if (textureInputFormat == TextureInputFormat::DepthComponent) { + if (!framebufferObject->GetAttachment(FramebufferAttachmentType::Depth).IsValid()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "No depth buffer for depth component format")); + return; + } + } else if (textureInputFormat == TextureInputFormat::DepthStencil) { + if (!framebufferObject->GetAttachment(FramebufferAttachmentType::Depth).IsValid() || + !framebufferObject->GetAttachment(FramebufferAttachmentType::Stencil).IsValid()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "No depth/stencil buffer for depth-stencil format")); + return; + } + + // Validate type for depth/stencil + if (texturePixelDataType != TexturePixelDataType::UnsignedInt248 && + texturePixelDataType != TexturePixelDataType::Float32UnsignedInt248Rev) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "Invalid type for depth-stencil format")); + return; + } + } + + // Check PBO state + const auto& pixelPackBufferObject = + MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + + if (pixelPackBufferObject) { + // Check if PBO is mapped + if (pixelPackBufferObject->IsMapped()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "Pixel pack buffer is currently mapped")); + return; + } + + // Check alignment + const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(texturePixelDataType); + if (reinterpret_cast(pixels) % typeSize != 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "Pixel data not aligned for pixel pack buffer")); + return; + } + } + + // Check multisampling + if (framebufferObject->GetAttachment(FramebufferAttachmentType::Color0).IsRenderbuffer()) { + auto rbo = framebufferObject->GetAttachment(FramebufferAttachmentType::Color0).GetRenderbuffer(); + if (rbo && rbo->GetSamples() > 1) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "ReadPixels_State", + "ReadPixels not supported for multisampled framebuffers")); + return; + } + } + } + + void ReadPixels_Backend(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + void* pixels) { +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES + MG_Backend::DirectGLES::ReadPixels(x, y, width, height, format, type, pixels); +#endif + } + /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ + void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { + ReadPixels_State(x, y, width, height, format, type, pixels); + ReadPixels_Backend(x, y, width, height, format, type, pixels); + } + void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { ClearBufferfi_Backend(buffer, drawbuffer, depth, stencil); } diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h index b332608c..9b34e637 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h @@ -14,6 +14,7 @@ namespace MobileGL { namespace MG_Impl::GLImpl { /* @INSERTION_POINT:FUNCTION_DECLARATION@ */ + void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 3fc71b68..da14735f 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -1164,10 +1164,6 @@ namespace MobileGL { } } - void GetTexImage_State(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { - // TODO: implement - } - void GetCompressedTexImage_State(GLenum target, GLint level, void* img) { // TODO: implement } @@ -1378,7 +1374,136 @@ namespace MobileGL { MG_State::pGLContext->SetActiveTextureUnit(texture - GL_TEXTURE0); } + void GetTexImage_Backend(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES + MG_Backend::DirectGLES::GetTexImage(target, level, format, type, pixels); +#endif + } + + // Add to GL_Texture.cpp + void GetTexImage_State(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { + // ======================= Converting ================================ + TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); + TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); + + // ===================== Error Checking ============================== + // Validate target + if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", "Invalid texture target")); + return; + } + + // Validate level + if (level < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", "Level must be non-negative")); + return; + } + + // Validate format + if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", "Invalid format")); + return; + } + + // Validate type + if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", "Invalid pixel data type")); + return; + } + + // Get texture object + SharedPtr textureObject = nullptr; + if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { + textureObject = TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget); + } else { + auto activeUnit = + MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); + auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); + textureObject = bindingSlot.GetBoundObject(); + } + + if (!TextureImpl::ValidateTextureObject(textureObject)) { + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", + "No valid texture bound to target")); + return; + } + + // Check texture completeness + if (!textureObject->IsComplete()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", "Texture is incomplete")); + return; + } + + // Check PBO state + const auto& pixelPackBufferObject = + MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); + + if (pixelPackBufferObject) { + // Check if PBO is mapped + if (pixelPackBufferObject->IsMapped()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", + "Pixel pack buffer is currently mapped")); + return; + } + + // Check alignment + const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(texturePixelDataType); + if (reinterpret_cast(pixels) % typeSize != 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", + "Pixel data not aligned for pixel pack buffer")); + return; + } + } + + // Special case for depth/stencil + if (textureInputFormat == TextureInputFormat::StencilIndex) { + if (textureObject->GetFormat() != TextureInternalFormat::DepthStencil && + textureObject->GetFormat() != TextureInternalFormat::Depth24Stencil8 && + textureObject->GetFormat() != TextureInternalFormat::Depth32FStencil8) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", + "No stencil buffer for stencil index format")); + return; + } + } + + // Check for multisampling + if (textureObject->GetStorageType() == TextureStorageType::Mipmap) { + auto mipmapObject = static_cast(textureObject.get()); + if (mipmapObject->GetMipmapLevelCount() > 1) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", "GetTexImage_State", + "Multisampled textures not supported for GetTexImage")); + return; + } + } + } + /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ + void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { + GetTexImage_State(target, level, format, type, pixels); + GetTexImage_Backend(target, level, format, type, pixels); + } + void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { TexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); @@ -1475,10 +1600,6 @@ namespace MobileGL { GetTexLevelParameterfv_State(target, level, pname, params); } - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { - GetTexImage_State(target, level, format, type, pixels); - } - void GetCompressedTexImage(GLenum target, GLint level, void* img) { GetCompressedTexImage_State(target, level, img); } diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h index 97b4047d..2bd725bc 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h @@ -12,6 +12,7 @@ namespace MobileGL { namespace MG_Impl::GLImpl { /* @INSERTION_POINT:FUNCTION_DECLARATION@ */ + void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, @@ -43,7 +44,6 @@ namespace MobileGL { void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* params); void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params); void GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params); - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); void GetCompressedTexImage(GLenum target, GLint level, void* img); void GenTextures(GLsizei n, GLuint* textures); void DeleteTextures(GLsizei n, const GLuint* textures); diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index 5c33fec9..daaf5929 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -239,6 +239,12 @@ namespace MobileGL { } } + SizeT GetTexturePixelDataTypeSize(TexturePixelDataType type) { + SizeT sizedPixelFormatSize = GetSizedTexturePixelDataTypeSize(type); + if (sizedPixelFormatSize > 0) return sizedPixelFormatSize; + return GetBaseTexturePixelDataTypeSize(type); + } + SizeT GetInternalBytesPerPixel(TextureInternalFormat internalformat, TexturePixelDataType type) { SizeT sizedTextureFormatSize = GetSizedInternalFormatSizeInBytes(internalformat); if (sizedTextureFormatSize > 0) return sizedTextureFormatSize; diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.h b/MobileGL/MG_Util/Metrics/TextureMetrics.h index 85c23d55..cd99ee0d 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.h +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.h @@ -16,6 +16,7 @@ namespace MobileGL { SizeT GetBaseInternalFormatComponentCount(TextureInternalFormat format); SizeT GetSizedTexturePixelDataTypeSize(TexturePixelDataType type); SizeT GetBaseTexturePixelDataTypeSize(TexturePixelDataType type); + SizeT GetTexturePixelDataTypeSize(TexturePixelDataType type); // This should respect internal format more SizeT GetInternalBytesPerPixel(TextureInternalFormat internalformat, TexturePixelDataType type); // This should respect type more, representing data passed in