diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index eb9789ef..430c4a69 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -79,6 +79,11 @@ namespace MobileGL { GLsizei height, GLint border); void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void (*CopyImageSubData)(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); void (*GenerateMipmap)(GLenum target); void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 98a643bc..473cc6d1 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -215,6 +215,7 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer; funcsTable.GL.CopyTexImage2D = CopyTexImage2D; funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D; + funcsTable.GL.CopyImageSubData = CopyImageSubData; funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; funcsTable.GL.GetTexImage = GetTexImage; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index dbc422a7..578bfa43 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1490,6 +1490,358 @@ namespace MobileGL::MG_Backend::DirectGLES { const Bool m_isRead = false; }; + static Bool IsDepthOnlyFormat(TextureInternalFormat format) { + return MG_Util::IsDepthFormatInternalFormat(format) && !MG_Util::IsStencilFormatInternalFormat(format); + } + + static Bool IsColorOnlyFormat(TextureInternalFormat format) { + return !MG_Util::IsDepthFormatInternalFormat(format) && !MG_Util::IsStencilFormatInternalFormat(format); + } + + static Bool IsIntegerColorFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::RGB10A2UI: + case TextureInternalFormat::R8I: + case TextureInternalFormat::R8UI: + case TextureInternalFormat::R16I: + case TextureInternalFormat::R16UI: + case TextureInternalFormat::R32I: + case TextureInternalFormat::R32UI: + case TextureInternalFormat::RG8I: + case TextureInternalFormat::RG8UI: + case TextureInternalFormat::RG16I: + case TextureInternalFormat::RG16UI: + case TextureInternalFormat::RG32I: + case TextureInternalFormat::RG32UI: + case TextureInternalFormat::RGB8I: + case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::RGB16I: + case TextureInternalFormat::RGB16UI: + case TextureInternalFormat::RGB32I: + case TextureInternalFormat::RGB32UI: + case TextureInternalFormat::RGBA8I: + case TextureInternalFormat::RGBA8UI: + case TextureInternalFormat::RGBA16I: + case TextureInternalFormat::RGBA16UI: + case TextureInternalFormat::RGBA32I: + case TextureInternalFormat::RGBA32UI: + return true; + default: + return false; + } + } + + static Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize) { + Int maxDimension = std::max( + baseTexelSize.x(), + std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); + Uint mipLevelCount = 1; + while (maxDimension > 1) { + maxDimension = std::max(maxDimension / 2, 1); + ++mipLevelCount; + } + return mipLevelCount; + } + + static IntVec3 ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel) { + return { + std::max(baseTexelSize.x() >> static_cast(relativeLevel), 1), + std::max(baseTexelSize.y() >> static_cast(relativeLevel), 1), + std::max(baseTexelSize.z() >> static_cast(relativeLevel), 1), + }; + } + + static Bool EnsureGenerateMipmapStorageAllocated(MG_State::GLState::TextureObjectMipmap& texture, + TextureUploadTarget uploadTarget, Bool& allocatedStorage) { + const Uint existingLevelCount = texture.GetMipmapLevelCount(); + if (existingLevelCount == 0) { + return false; + } + + const IntVec3 baseTexelSize = texture.GetMipmapTexelSize(uploadTarget, 0); + const SizeT baseByteSize = texture.GetMipmapByteSize(uploadTarget, 0); + const SizeT baseTexelCount = static_cast(baseTexelSize.x()) * + static_cast(baseTexelSize.y()) * + static_cast(baseTexelSize.z()); + if (baseTexelSize.x() <= 0 || baseTexelSize.y() <= 0 || baseTexelSize.z() <= 0 || + baseByteSize == 0 || baseTexelCount == 0 || (baseByteSize % baseTexelCount) != 0) { + return false; + } + + const SizeT bytesPerTexel = baseByteSize / baseTexelCount; + const Uint requiredLevelCount = ComputeFullMipmapLevelCount(baseTexelSize); + if (existingLevelCount < requiredLevelCount) { + allocatedStorage = true; + } + for (Uint level = existingLevelCount; level < requiredLevelCount; ++level) { + const IntVec3 levelTexelSize = ComputeMipmapTexelSize(baseTexelSize, level); + const SizeT levelByteSize = bytesPerTexel * static_cast(levelTexelSize.x()) * + static_cast(levelTexelSize.y()) * + static_cast(levelTexelSize.z()); + texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize}); + texture.MarkStorageDirty(uploadTarget, level, false); + } + return true; + } + + static Bool EnsureGenerateMipmapStorageAllocated(const SharedPtr& texture) { + auto* mipmapTexture = dynamic_cast(texture.get()); + MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage."); + Bool allocatedStorage = false; + for (const TextureUploadTarget uploadTarget : texture->GetUploadTargets()) { + MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, allocatedStorage), + "GenerateMipmap could not allocate generated mipmap storage."); + } + return allocatedStorage; + } + + static void AssertNoGLError(const char* operation) { + const GLenum err = g_GLESFuncs.glGetError(); + MOBILEGL_ASSERT(err == GL_NO_ERROR, "%s failed: %s", operation, + MG_Util::ConvertGLEnumToString(err).c_str()); + } + + static void ClearGLErrors() { + while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {} + } + + class ScopedDepthBlitState { + public: + ScopedDepthBlitState() { + g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, reinterpret_cast(&m_prevReadFBO)); + g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast(&m_prevDrawFBO)); + g_GLESFuncs.glGetBooleanv(GL_SCISSOR_TEST, &m_prevScissorEnabled); + g_GLESFuncs.glDisable(GL_SCISSOR_TEST); + + if (s_readFBO == 0) { + g_GLESFuncs.glGenFramebuffers(1, &s_readFBO); + } + if (s_drawFBO == 0) { + g_GLESFuncs.glGenFramebuffers(1, &s_drawFBO); + } + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, s_readFBO); + AssertNoGLError("bind depth blit read framebuffer"); + g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_drawFBO); + AssertNoGLError("bind depth blit draw framebuffer"); + } + + ~ScopedDepthBlitState() { + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, m_prevReadFBO); + g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_prevDrawFBO); + if (m_prevScissorEnabled == GL_TRUE) { + g_GLESFuncs.glEnable(GL_SCISSOR_TEST); + } else { + g_GLESFuncs.glDisable(GL_SCISSOR_TEST); + } + } + + private: + GLuint m_prevReadFBO = 0; + GLuint m_prevDrawFBO = 0; + GLboolean m_prevScissorEnabled = GL_FALSE; + static GLuint s_readFBO; + static GLuint s_drawFBO; + }; + + GLuint ScopedDepthBlitState::s_readFBO = 0; + GLuint ScopedDepthBlitState::s_drawFBO = 0; + + static void BlitDepthTexture2D(GLuint srcTexture, GLint srcLevel, GLint srcX, GLint srcY, GLsizei srcWidth, + GLsizei srcHeight, GLuint dstTexture, GLint dstLevel, GLint dstX, GLint dstY, + GLsizei dstWidth, GLsizei dstHeight) { + MOBILEGL_ASSERT(srcTexture != 0 && dstTexture != 0, "Depth blit requires valid backend textures."); + MOBILEGL_ASSERT(srcLevel >= 0 && dstLevel >= 0, "Depth blit mip levels must be non-negative."); + MOBILEGL_ASSERT(srcWidth > 0 && srcHeight > 0 && dstWidth > 0 && dstHeight > 0, + "Depth blit dimensions must be positive."); + + ClearGLErrors(); + ScopedDepthBlitState state; + g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, srcTexture, + srcLevel); + AssertNoGLError("attach depth blit source texture"); + g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dstTexture, + dstLevel); + AssertNoGLError("attach depth blit destination texture"); + MOBILEGL_ASSERT(g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "Depth blit read framebuffer is incomplete."); + AssertNoGLError("check depth blit read framebuffer"); + MOBILEGL_ASSERT(g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "Depth blit draw framebuffer is incomplete."); + AssertNoGLError("check depth blit draw framebuffer"); + + g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, + dstX, dstY, dstX + dstWidth, dstY + dstHeight, + GL_DEPTH_BUFFER_BIT, GL_NEAREST); + AssertNoGLError("depth texture blit"); + } + + static void BlitColorTexture2D(GLuint srcTexture, GLint srcLevel, GLint srcX, GLint srcY, GLsizei srcWidth, + GLsizei srcHeight, GLuint dstTexture, GLint dstLevel, GLint dstX, GLint dstY, + GLsizei dstWidth, GLsizei dstHeight, GLenum filter) { + MOBILEGL_ASSERT(srcTexture != 0 && dstTexture != 0, "Color blit requires valid backend textures."); + MOBILEGL_ASSERT(srcLevel >= 0 && dstLevel >= 0, "Color blit mip levels must be non-negative."); + MOBILEGL_ASSERT(srcWidth > 0 && srcHeight > 0 && dstWidth > 0 && dstHeight > 0, + "Color blit dimensions must be positive."); + MOBILEGL_ASSERT(filter == GL_NEAREST || filter == GL_LINEAR, "Color blit filter must be nearest or linear."); + + ClearGLErrors(); + ScopedDepthBlitState state; + g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, + srcLevel); + AssertNoGLError("attach color blit source texture"); + g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstTexture, + dstLevel); + AssertNoGLError("attach color blit destination texture"); + g_GLESFuncs.glReadBuffer(GL_COLOR_ATTACHMENT0); + AssertNoGLError("set color blit read buffer"); + const GLenum drawBuffer = GL_COLOR_ATTACHMENT0; + g_GLESFuncs.glDrawBuffers(1, &drawBuffer); + AssertNoGLError("set color blit draw buffer"); + MOBILEGL_ASSERT(g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "Color blit read framebuffer is incomplete."); + AssertNoGLError("check color blit read framebuffer"); + MOBILEGL_ASSERT(g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "Color blit draw framebuffer is incomplete."); + AssertNoGLError("check color blit draw framebuffer"); + + g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, + dstX, dstY, dstX + dstWidth, dstY + dstHeight, + GL_COLOR_BUFFER_BIT, filter); + AssertNoGLError("color texture blit"); + } + + static void CopyR32FTexture2D(GLuint srcTexture, GLint srcLevel, GLint srcX, GLint srcY, GLsizei width, + GLsizei height, GLuint dstTexture, GLenum dstTarget, GLint dstLevel, GLint dstX, + GLint dstY) { + MOBILEGL_ASSERT(srcTexture != 0 && dstTexture != 0, "R32F copy requires valid backend textures."); + MOBILEGL_ASSERT(dstTarget == GL_TEXTURE_2D, "R32F copy only supports GL_TEXTURE_2D destinations."); + MOBILEGL_ASSERT(srcLevel >= 0 && dstLevel >= 0, "R32F copy mip levels must be non-negative."); + MOBILEGL_ASSERT(width > 0 && height > 0, "R32F copy dimensions must be positive."); + + ClearGLErrors(); + ScopedDepthBlitState state; + g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, + srcLevel); + AssertNoGLError("attach R32F copy source texture"); + g_GLESFuncs.glReadBuffer(GL_COLOR_ATTACHMENT0); + AssertNoGLError("set R32F copy read buffer"); + MOBILEGL_ASSERT(g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "R32F copy read framebuffer is incomplete."); + AssertNoGLError("check R32F copy read framebuffer"); + + GLint prevPackBuffer = 0; + GLint prevUnpackBuffer = 0; + GLint prevPackAlignment = 4; + GLint prevUnpackAlignment = 4; + GLint prevPackRowLength = 0; + GLint prevUnpackRowLength = 0; + GLint prevPackSkipRows = 0; + GLint prevUnpackSkipRows = 0; + GLint prevPackSkipPixels = 0; + GLint prevUnpackSkipPixels = 0; + GLint prevActiveTexture = GL_TEXTURE0; + GLint prevBoundTexture = 0; + + g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &prevPackBuffer); + g_GLESFuncs.glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &prevUnpackBuffer); + g_GLESFuncs.glGetIntegerv(GL_PACK_ALIGNMENT, &prevPackAlignment); + g_GLESFuncs.glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment); + g_GLESFuncs.glGetIntegerv(GL_PACK_ROW_LENGTH, &prevPackRowLength); + g_GLESFuncs.glGetIntegerv(GL_UNPACK_ROW_LENGTH, &prevUnpackRowLength); + g_GLESFuncs.glGetIntegerv(GL_PACK_SKIP_ROWS, &prevPackSkipRows); + g_GLESFuncs.glGetIntegerv(GL_UNPACK_SKIP_ROWS, &prevUnpackSkipRows); + g_GLESFuncs.glGetIntegerv(GL_PACK_SKIP_PIXELS, &prevPackSkipPixels); + g_GLESFuncs.glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &prevUnpackSkipPixels); + g_GLESFuncs.glGetIntegerv(GL_ACTIVE_TEXTURE, &prevActiveTexture); + + g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + g_GLESFuncs.glPixelStorei(GL_PACK_ALIGNMENT, 4); + g_GLESFuncs.glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + g_GLESFuncs.glPixelStorei(GL_PACK_ROW_LENGTH, 0); + g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_ROWS, 0); + g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + + Vector pixels(static_cast(width) * static_cast(height)); + g_GLESFuncs.glReadPixels(srcX, srcY, width, height, GL_RED, GL_FLOAT, pixels.data()); + AssertNoGLError("read R32F copy pixels"); + + g_GLESFuncs.glActiveTexture(GL_TEXTURE0 + TextureImpl::TempTextureUnit); + g_GLESFuncs.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevBoundTexture); + g_GLESFuncs.glBindTexture(dstTarget, dstTexture); + g_GLESFuncs.glTexSubImage2D(dstTarget, dstLevel, dstX, dstY, width, height, GL_RED, GL_FLOAT, pixels.data()); + AssertNoGLError("upload R32F copy pixels"); + + g_GLESFuncs.glBindTexture(dstTarget, static_cast(prevBoundTexture)); + g_GLESFuncs.glActiveTexture(static_cast(prevActiveTexture)); + TextureImpl::g_activeTextureUnit = + static_cast(static_cast(prevActiveTexture) - GL_TEXTURE0); + g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(prevPackBuffer)); + g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast(prevUnpackBuffer)); + g_GLESFuncs.glPixelStorei(GL_PACK_ALIGNMENT, prevPackAlignment); + g_GLESFuncs.glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment); + g_GLESFuncs.glPixelStorei(GL_PACK_ROW_LENGTH, prevPackRowLength); + g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, prevUnpackRowLength); + g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_ROWS, prevPackSkipRows); + g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_ROWS, prevUnpackSkipRows); + g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_PIXELS, prevPackSkipPixels); + g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_PIXELS, prevUnpackSkipPixels); + } + + static void GenerateDepthTexture2DMipmap( + const SharedPtr& texture, + const SharedPtr& backendTexture) { + MOBILEGL_ASSERT(texture != nullptr && backendTexture != nullptr, "GenerateDepthTexture2DMipmap needs texture."); + MOBILEGL_ASSERT(texture->GetTarget() == TextureTarget::Texture2D, + "DirectGLES depth mipmap generation only supports GL_TEXTURE_2D."); + MOBILEGL_ASSERT(IsDepthOnlyFormat(texture->GetFormat()), + "DirectGLES depth mipmap generation requires a depth-only texture."); + + auto* mipmapTexture = dynamic_cast(texture.get()); + MOBILEGL_ASSERT(mipmapTexture != nullptr, "Depth mipmap generation requires mipmap storage."); + const Uint mipLevelCount = mipmapTexture->GetMipmapLevelCount(); + MOBILEGL_ASSERT(mipLevelCount > 0, "Depth mipmap generation requires allocated storage."); + + const GLuint textureId = backendTexture->GetBackendTextureId(); + for (Uint level = 1; level < mipLevelCount; ++level) { + const IntVec3 srcSize = mipmapTexture->GetMipmapTexelSize(TextureUploadTarget::Texture2D, level - 1); + const IntVec3 dstSize = mipmapTexture->GetMipmapTexelSize(TextureUploadTarget::Texture2D, level); + BlitDepthTexture2D(textureId, static_cast(level - 1), 0, 0, + static_cast(srcSize.x()), static_cast(srcSize.y()), + textureId, static_cast(level), 0, 0, + static_cast(dstSize.x()), static_cast(dstSize.y())); + } + } + + static void GenerateColorTexture2DMipmap( + const SharedPtr& texture, + const SharedPtr& backendTexture) { + MOBILEGL_ASSERT(texture != nullptr && backendTexture != nullptr, "GenerateColorTexture2DMipmap needs texture."); + MOBILEGL_ASSERT(texture->GetTarget() == TextureTarget::Texture2D, + "DirectGLES color mipmap generation only supports GL_TEXTURE_2D."); + MOBILEGL_ASSERT(IsColorOnlyFormat(texture->GetFormat()), + "DirectGLES color mipmap generation requires a color-only texture."); + + auto* mipmapTexture = dynamic_cast(texture.get()); + MOBILEGL_ASSERT(mipmapTexture != nullptr, "Color mipmap generation requires mipmap storage."); + const Uint mipLevelCount = mipmapTexture->GetMipmapLevelCount(); + MOBILEGL_ASSERT(mipLevelCount > 0, "Color mipmap generation requires allocated storage."); + + const GLenum filter = IsIntegerColorFormat(texture->GetFormat()) ? GL_NEAREST : GL_LINEAR; + const GLuint textureId = backendTexture->GetBackendTextureId(); + for (Uint level = 1; level < mipLevelCount; ++level) { + const IntVec3 srcSize = mipmapTexture->GetMipmapTexelSize(TextureUploadTarget::Texture2D, level - 1); + const IntVec3 dstSize = mipmapTexture->GetMipmapTexelSize(TextureUploadTarget::Texture2D, level); + BlitColorTexture2D(textureId, static_cast(level - 1), 0, 0, + static_cast(srcSize.x()), static_cast(srcSize.y()), + textureId, static_cast(level), 0, 0, + static_cast(dstSize.x()), static_cast(dstSize.y()), filter); + } + } + void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG @@ -1666,10 +2018,26 @@ namespace MobileGL::MG_Backend::DirectGLES { auto& unit = MG_State::pGLContext->GetTextureUnitObject(unitIndex); auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)); auto& texture = slot.GetBoundObject(); + MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture."); + if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat())) { + EnsureGenerateMipmapStorageAllocated(texture); + } auto& backendTexture = TextureImpl::SyncTextureObjectToBackend(texture); + if (IsDepthOnlyFormat(texture->GetFormat())) { + GenerateDepthTexture2DMipmap(texture, backendTexture); + return; + } + if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F && + texture->GetTarget() == TextureTarget::Texture2D) { + GenerateColorTexture2DMipmap(texture, backendTexture); + return; + } + backendTexture->Bind(target, unitIndex); + DebugImpl::ErrorLopper::Clear(); g_GLESFuncs.glGenerateMipmap(target); + AssertNoGLError("glGenerateMipmap"); } const GLubyte* GetString(GLenum name) { @@ -1700,6 +2068,61 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glMemoryBarrierByRegion(barriers); } + void CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + auto& srcBackendTexture = TextureImpl::SyncTextureObjectToBackend(srcTexture); + auto& dstBackendTexture = TextureImpl::SyncTextureObjectToBackend(dstTexture); + + const Bool srcIsDepth = MG_Util::IsDepthFormatInternalFormat(srcTexture->GetFormat()); + const Bool dstIsDepth = MG_Util::IsDepthFormatInternalFormat(dstTexture->GetFormat()); + const Bool srcStencil = MG_Util::IsStencilFormatInternalFormat(srcTexture->GetFormat()); + const Bool dstStencil = MG_Util::IsStencilFormatInternalFormat(dstTexture->GetFormat()); + if (srcIsDepth || dstIsDepth || srcStencil || dstStencil) { + MOBILEGL_ASSERT(srcIsDepth && dstIsDepth && !srcStencil && !dstStencil, + "DirectGLES CopyImageSubData only supports depth-only image copies."); + MOBILEGL_ASSERT(srcTarget == GL_TEXTURE_2D && dstTarget == GL_TEXTURE_2D, + "DirectGLES depth CopyImageSubData only supports GL_TEXTURE_2D."); + MOBILEGL_ASSERT(srcZ == 0 && dstZ == 0 && srcDepth == 1, + "DirectGLES depth CopyImageSubData only supports single-layer copies."); + BlitDepthTexture2D(srcBackendTexture->GetBackendTextureId(), srcLevel, srcX, srcY, srcWidth, srcHeight, + dstBackendTexture->GetBackendTextureId(), dstLevel, dstX, dstY, srcWidth, srcHeight); + return; + } + + if (srcTexture->GetFormat() == TextureInternalFormat::R32F || + dstTexture->GetFormat() == TextureInternalFormat::R32F) { + DebugImpl::ErrorLopper::Clear(); + g_GLESFuncs.glCopyImageSubData(srcBackendTexture->GetBackendTextureId(), srcTarget, srcLevel, srcX, srcY, srcZ, + dstBackendTexture->GetBackendTextureId(), dstTarget, dstLevel, dstX, dstY, dstZ, + srcWidth, srcHeight, srcDepth); + const GLenum copyImageError = g_GLESFuncs.glGetError(); + if (copyImageError == GL_NO_ERROR) { + return; + } + MOBILEGL_ASSERT(copyImageError == GL_INVALID_ENUM, + "glCopyImageSubData failed: %s", + MG_Util::ConvertGLEnumToString(copyImageError).c_str()); + MOBILEGL_ASSERT(IsColorOnlyFormat(srcTexture->GetFormat()) && IsColorOnlyFormat(dstTexture->GetFormat()), + "DirectGLES CopyImageSubData only supports color-only or depth-only copies."); + MOBILEGL_ASSERT(srcTarget == GL_TEXTURE_2D && dstTarget == GL_TEXTURE_2D, + "DirectGLES color CopyImageSubData only supports GL_TEXTURE_2D."); + MOBILEGL_ASSERT(srcZ == 0 && dstZ == 0 && srcDepth == 1, + "DirectGLES color CopyImageSubData only supports single-layer copies."); + CopyR32FTexture2D(srcBackendTexture->GetBackendTextureId(), srcLevel, srcX, srcY, srcWidth, srcHeight, + dstBackendTexture->GetBackendTextureId(), dstTarget, dstLevel, dstX, dstY); + return; + } + + DebugImpl::ErrorLopper::Clear(); + g_GLESFuncs.glCopyImageSubData(srcBackendTexture->GetBackendTextureId(), srcTarget, srcLevel, srcX, srcY, srcZ, + dstBackendTexture->GetBackendTextureId(), dstTarget, dstLevel, dstX, dstY, dstZ, + srcWidth, srcHeight, srcDepth); + AssertNoGLError("glCopyImageSubData"); + } + void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) { (void)texture; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 1ad94814..bd227a5d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -64,6 +64,11 @@ namespace MobileGL::MG_Backend::DirectGLES { GLsizei height, GLint border); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); 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); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 54cf7b45..942a048c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -223,6 +223,8 @@ namespace MobileGL::MG_Backend::DirectGLES { void UnbindTexture(Uint unit, GLenum target); extern StateBackendObjectRegistry g_backendTextureObjects; + SharedPtr& SyncTextureObjectToBackend( + const SharedPtr& textureObject); extern Array, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundTexturesCache; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 389ee36f..71745561 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -221,6 +221,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer; funcsTable.GL.CopyTexImage2D = CopyTexImage2D; funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D; + funcsTable.GL.CopyImageSubData = CopyImageSubData; funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; funcsTable.GL.GetTexImage = GetTexImage; diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 0651103a..1099f71a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -592,6 +592,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyTexSubImage2D called with null GL context"); pVulkanRenderer->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); } + void CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::CopyImageSubData called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyImageSubData called with null GL context"); + pVulkanRenderer->CopyImageSubData(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, + dstTexture, dstTarget, dstLevel, dstX, dstY, dstZ, + srcWidth, srcHeight, srcDepth); + } void GenerateMipmap(GLenum target) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GenerateMipmap called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GenerateMipmap called with null GL context"); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 55d1edf5..21635370 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -61,6 +61,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLsizei height, GLint border); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); void GenerateMipmap(GLenum target); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index d371ba41..96c7029a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -794,10 +794,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u; static constexpr Uint kHiddenBlitNearestSamplerId = 0xFFFFFFF3u; static constexpr Uint kHiddenBlitLinearSamplerId = 0xFFFFFFF4u; - static constexpr Uint kHiddenDepthMipmapProgramId = 0xFFFFFFF5u; - static constexpr Uint kHiddenDepthMipmapVertexShaderId = 0xFFFFFFF6u; - static constexpr Uint kHiddenDepthMipmapFragmentShaderId = 0xFFFFFFF7u; - static constexpr const char* kFullscreenTriangleVertexShaderSource = R"(#version 460 core uniform vec4 uSrcRect; uniform vec4 uDstRect; @@ -840,22 +836,6 @@ layout(location = 0) out vec4 outColor; void main() { outColor = texture(uSource, vTexCoord); } -)"; - - static constexpr const char* kDepthMipmapFragmentShaderSource = R"(#version 460 core -layout(binding = 0) uniform sampler2D uSource; -layout(location = 0) in vec2 vTexCoord; -uniform ivec2 uSrcTexelSize; - -void main() { - ivec2 srcBase = ivec2(gl_FragCoord.xy) * 2; - ivec2 srcMax = max(uSrcTexelSize - ivec2(1), ivec2(0)); - float depth0 = texelFetch(uSource, clamp(srcBase + ivec2(0, 0), ivec2(0), srcMax), 0).r; - float depth1 = texelFetch(uSource, clamp(srcBase + ivec2(1, 0), ivec2(0), srcMax), 0).r; - float depth2 = texelFetch(uSource, clamp(srcBase + ivec2(0, 1), ivec2(0), srcMax), 0).r; - float depth3 = texelFetch(uSource, clamp(srcBase + ivec2(1, 1), ivec2(0), srcMax), 0).r; - gl_FragDepth = 0.25 * (depth0 + depth1 + depth2 + depth3); -} )"; static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { @@ -1608,7 +1588,6 @@ void main() { VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), "CreateFrameContexts"); MGLOG_I("CreateFrameContexts completed"); - m_deferredDepthMipmapCleanup.assign(m_frameContext.GetFrameCount(), {}); auto succeeded = false; succeeded = m_bufferManager.Initialize({ .allocator = m_allocator, @@ -1651,8 +1630,6 @@ void main() { MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed."); succeeded = InitializeBlitResources(); MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed."); - succeeded = InitializeDepthMipmapResources(); - MOBILEGL_ASSERT(succeeded, "Depth mipmap pipeline resource initialization failed."); m_uniformManager = MakeUnique(); MOBILEGL_ASSERT(m_uniformManager != nullptr, "UniformDescriptorBinder creation failed."); @@ -1684,12 +1661,10 @@ void main() { void VulkanRenderer::Shutdown() { VK_VERIFY(vkDeviceWaitIdle(m_device)); - DestroyDeferredDepthMipmapCleanup(); DestroyComputePipelines(); m_pipelineFactory.reset(); ShutdownBlitResources(); - ShutdownDepthMipmapResources(); if (m_samplerManager) { m_samplerManager->Shutdown(); m_samplerManager.reset(); @@ -2074,123 +2049,6 @@ void main() { m_blitResources = {}; } - Bool VulkanRenderer::InitializeDepthMipmapResources() { - ShutdownDepthMipmapResources(); - - auto vertexShader = MakeShared(ShaderStage::Vertex, - kHiddenDepthMipmapVertexShaderId); - vertexShader->SetShaderSource(kFullscreenTriangleVertexShaderSource); - vertexShader->Compile(); - if (!vertexShader->GetCompileStatus()) { - MGLOG_E("InitializeDepthMipmapResources failed: vertex shader compile error: %s", - vertexShader->GetInfoLog().c_str()); - return false; - } - - auto fragmentShader = MakeShared(ShaderStage::Fragment, - kHiddenDepthMipmapFragmentShaderId); - fragmentShader->SetShaderSource(kDepthMipmapFragmentShaderSource); - fragmentShader->Compile(); - if (!fragmentShader->GetCompileStatus()) { - MGLOG_E("InitializeDepthMipmapResources failed: fragment shader compile error: %s", - fragmentShader->GetInfoLog().c_str()); - return false; - } - - m_depthMipmapResources.program = MakeShared(kHiddenDepthMipmapProgramId); - m_depthMipmapResources.program->AttachShader(vertexShader); - m_depthMipmapResources.program->AttachShader(fragmentShader); - m_depthMipmapResources.program->Link(false); - if (!m_depthMipmapResources.program->GetLinkStatus()) { - MGLOG_E("InitializeDepthMipmapResources failed: program link error: %s", - m_depthMipmapResources.program->GetInfoLog().c_str()); - return false; - } - - m_depthMipmapResources.srcRectLocation = m_depthMipmapResources.program->GetUniformLocation("uSrcRect"); - m_depthMipmapResources.dstRectLocation = m_depthMipmapResources.program->GetUniformLocation("uDstRect"); - m_depthMipmapResources.surfaceTransformLocation = - m_depthMipmapResources.program->GetUniformLocation("uSurfaceTransform"); - m_depthMipmapResources.srcTexelSizeLocation = - m_depthMipmapResources.program->GetUniformLocation("uSrcTexelSize"); - MOBILEGL_ASSERT(m_depthMipmapResources.srcRectLocation >= 0, - "InitializeDepthMipmapResources: missing uSrcRect"); - MOBILEGL_ASSERT(m_depthMipmapResources.dstRectLocation >= 0, - "InitializeDepthMipmapResources: missing uDstRect"); - MOBILEGL_ASSERT(m_depthMipmapResources.surfaceTransformLocation >= 0, - "InitializeDepthMipmapResources: missing uSurfaceTransform"); - MOBILEGL_ASSERT(m_depthMipmapResources.srcTexelSizeLocation >= 0, - "InitializeDepthMipmapResources: missing uSrcTexelSize"); - MOBILEGL_ASSERT(m_depthMipmapResources.program->GetUBOSize() > 0, - "InitializeDepthMipmapResources: depth mipmap program global UBO is empty"); - MOBILEGL_ASSERT(m_programFactory != nullptr, "InitializeDepthMipmapResources: program factory is null"); - - ProgramFactory::CompileOptionFlags depthMipmapTransformFlags = 0; - const auto& depthMipmapProgramObj = - m_programFactory->GetOrCreateProgram(*m_depthMipmapResources.program, depthMipmapTransformFlags); - Bool foundDepthMipmapSamplerBinding = false; - for (Uint32 binding = 0; binding < depthMipmapProgramObj.samplerNameByBinding.size(); ++binding) { - if (depthMipmapProgramObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { - continue; - } - if (depthMipmapProgramObj.samplerNameByBinding[binding] == "uSource") { - m_depthMipmapResources.samplerBinding = binding; - foundDepthMipmapSamplerBinding = true; - break; - } - } - MOBILEGL_ASSERT(foundDepthMipmapSamplerBinding, - "InitializeDepthMipmapResources: failed to resolve reflected binding for uSource"); - return true; - } - - void VulkanRenderer::ShutdownDepthMipmapResources() { - m_depthMipmapResources = {}; - } - - void VulkanRenderer::CollectDeferredDepthMipmapCleanup(Uint32 frameIndex) { - MOBILEGL_ASSERT(frameIndex < m_deferredDepthMipmapCleanup.size(), - "CollectDeferredDepthMipmapCleanup: frame index %u out of range (size=%zu)", - frameIndex, m_deferredDepthMipmapCleanup.size()); - if (m_device == VK_NULL_HANDLE) { - return; - } - - auto& cleanup = m_deferredDepthMipmapCleanup[frameIndex]; - for (auto framebuffer : cleanup.framebuffers) { - if (framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(m_device, framebuffer, nullptr); - } - } - for (auto pipeline : cleanup.pipelines) { - if (pipeline != VK_NULL_HANDLE) { - vkDestroyPipeline(m_device, pipeline, nullptr); - } - } - for (auto renderPass : cleanup.renderPasses) { - if (renderPass != VK_NULL_HANDLE) { - vkDestroyRenderPass(m_device, renderPass, nullptr); - } - } - for (auto imageView : cleanup.imageViews) { - if (imageView != VK_NULL_HANDLE) { - vkDestroyImageView(m_device, imageView, nullptr); - } - } - - cleanup.framebuffers.clear(); - cleanup.pipelines.clear(); - cleanup.renderPasses.clear(); - cleanup.imageViews.clear(); - } - - void VulkanRenderer::DestroyDeferredDepthMipmapCleanup() { - for (Uint32 frameIndex = 0; frameIndex < m_deferredDepthMipmapCleanup.size(); ++frameIndex) { - CollectDeferredDepthMipmapCleanup(frameIndex); - } - m_deferredDepthMipmapCleanup.clear(); - } - VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) { MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null"); MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null"); @@ -2238,297 +2096,6 @@ void main() { return m_pipelineFactory->GetOrCreatePipeline(payload); } - Bool VulkanRenderer::GenerateDepthMipmapWithShader(FrameContext::FrameData& frame, - MG_State::GLState::ITextureObject& texture, - VkTextureManager::TextureResource& resource, - Uint32 baseMipLevel, - Uint32 generateMipLevelCount, - const IntVec3& storageBaseTexelSize, - VkImageLayout originalLayout, - VkImageLayout finalLayout) { - MOBILEGL_ASSERT(m_depthMipmapResources.program != nullptr, - "GenerateDepthMipmapWithShader: depth mipmap program is null"); - MOBILEGL_ASSERT(m_blitResources.nearestSampler != nullptr, - "GenerateDepthMipmapWithShader: helper sampler is null"); - MOBILEGL_ASSERT(m_programFactory != nullptr, "GenerateDepthMipmapWithShader: program factory is null"); - MOBILEGL_ASSERT(m_uniformManager != nullptr, "GenerateDepthMipmapWithShader: uniform manager is null"); - MOBILEGL_ASSERT(texture.GetTarget() == TextureTarget::Texture2D, - "GenerateDepthMipmapWithShader only supports GL_TEXTURE_2D depth textures"); - MOBILEGL_ASSERT((resource.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0, - "GenerateDepthMipmapWithShader requires a depth aspect"); - MOBILEGL_ASSERT(resource.depth == 1 && resource.arrayLayers == 1, - "GenerateDepthMipmapWithShader only supports single-layer depth textures"); - MOBILEGL_ASSERT(m_frameContext.GetCurrentFrameIndex() < m_deferredDepthMipmapCleanup.size(), - "GenerateDepthMipmapWithShader: frame index %u out of range (cleanup slots=%zu)", - m_frameContext.GetCurrentFrameIndex(), m_deferredDepthMipmapCleanup.size()); - auto& deferredCleanup = m_deferredDepthMipmapCleanup[m_frameContext.GetCurrentFrameIndex()]; - - static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState { - VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO - }; - - ProgramFactory::CompileOptionFlags depthMipmapTransformFlags = 0; - const auto& programObj = m_programFactory->GetOrCreateProgram(*m_depthMipmapResources.program, - depthMipmapTransformFlags); - - VkAttachmentDescription depthAttachment{}; - depthAttachment.format = resource.format; - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthAttachmentRef{}; - depthAttachmentRef.attachment = 0; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpassDesc{}; - subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDesc.pDepthStencilAttachment = &depthAttachmentRef; - - VkRenderPassCreateInfo renderPassCreateInfo{}; - renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassCreateInfo.attachmentCount = 1; - renderPassCreateInfo.pAttachments = &depthAttachment; - renderPassCreateInfo.subpassCount = 1; - renderPassCreateInfo.pSubpasses = &subpassDesc; - - VkRenderPass renderPass = VK_NULL_HANDLE; - VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass), - "GenerateDepthMipmapWithShader: vkCreateRenderPass"); - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizationState{}; - rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; - rasterizationState.cullMode = VK_CULL_MODE_NONE; - rasterizationState.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizationState.lineWidth = 1.0f; - - VkPipelineMultisampleStateCreateInfo multisampleState{}; - multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineDepthStencilStateCreateInfo depthStencilState{}; - depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencilState.depthTestEnable = VK_TRUE; - depthStencilState.depthWriteEnable = VK_TRUE; - depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; - depthStencilState.minDepthBounds = 0.0f; - depthStencilState.maxDepthBounds = 1.0f; - - VkPipelineColorBlendStateCreateInfo colorBlendState{}; - colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - - const VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(std::size(dynamicStates)); - dynamicState.pDynamicStates = dynamicStates; - - VkGraphicsPipelineCreateInfo pipelineCreateInfo{}; - pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineCreateInfo.stageCount = static_cast(programObj.stages.size()); - pipelineCreateInfo.pStages = programObj.stages.data(); - pipelineCreateInfo.pVertexInputState = &kEmptyVertexInputState; - pipelineCreateInfo.pInputAssemblyState = &inputAssembly; - pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pRasterizationState = &rasterizationState; - pipelineCreateInfo.pMultisampleState = &multisampleState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.pColorBlendState = &colorBlendState; - pipelineCreateInfo.pDynamicState = &dynamicState; - pipelineCreateInfo.layout = programObj.pipelineLayout; - pipelineCreateInfo.renderPass = renderPass; - pipelineCreateInfo.subpass = 0; - - VkPipeline pipeline = VK_NULL_HANDLE; - VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline), - "GenerateDepthMipmapWithShader: vkCreateGraphicsPipelines"); - deferredCleanup.renderPasses.push_back(renderPass); - deferredCleanup.pipelines.push_back(pipeline); - - auto createMipView = [&](Uint32 mipLevel, VkImageAspectFlags aspectMask) { - VkImageViewCreateInfo viewCreateInfo{}; - viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewCreateInfo.image = resource.image; - viewCreateInfo.viewType = resource.viewType; - viewCreateInfo.format = resource.format; - viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - viewCreateInfo.subresourceRange.aspectMask = aspectMask; - viewCreateInfo.subresourceRange.baseMipLevel = mipLevel; - viewCreateInfo.subresourceRange.levelCount = 1; - viewCreateInfo.subresourceRange.baseArrayLayer = 0; - viewCreateInfo.subresourceRange.layerCount = resource.arrayLayers; - - VkImageView view = VK_NULL_HANDLE; - VK_VERIFY(vkCreateImageView(m_device, &viewCreateInfo, nullptr, &view), - "GenerateDepthMipmapWithShader: vkCreateImageView"); - return view; - }; - - VkPipelineStageFlags originalSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags originalSrcAccessMask = 0; - GetImageTransitionSourceState(originalLayout, originalSrcStageMask, originalSrcAccessMask); - - VkPipelineStageFlags finalDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags finalDstAccessMask = 0; - GetImageTransitionDestinationState(finalLayout, finalDstStageMask, finalDstAccessMask); - - VkPipelineStageFlags depthAttachmentStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags depthAttachmentAccessMask = 0; - GetImageTransitionDestinationState(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - depthAttachmentStageMask, depthAttachmentAccessMask); - - if (originalLayout != finalLayout) { - if (baseMipLevel > 0) { - VkImageLayout lowerMipLayout = originalLayout; - const Bool lowerReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource.image, lowerMipLayout, finalLayout, - originalSrcStageMask, finalDstStageMask, - originalSrcAccessMask, finalDstAccessMask, - resource.aspect, 0, baseMipLevel); - MOBILEGL_ASSERT(lowerReady, "%s: failed to transition lower untouched mip levels", __func__); - } - - if (generateMipLevelCount < resource.mipLevels) { - VkImageLayout upperMipLayout = originalLayout; - const Bool upperReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource.image, upperMipLayout, finalLayout, - originalSrcStageMask, finalDstStageMask, - originalSrcAccessMask, finalDstAccessMask, - resource.aspect, generateMipLevelCount, resource.mipLevels - generateMipLevelCount); - MOBILEGL_ASSERT(upperReady, "%s: failed to transition upper untouched mip levels", __func__); - } - - VkImageLayout srcMipLayout = originalLayout; - const Bool srcReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource.image, srcMipLayout, finalLayout, - originalSrcStageMask, finalDstStageMask, - originalSrcAccessMask, finalDstAccessMask, - resource.aspect, baseMipLevel, 1); - MOBILEGL_ASSERT(srcReady, "%s: failed to transition base mip level to sampled layout", __func__); - } - - resource.layout = finalLayout; - - auto* depthProgramData = static_cast(m_depthMipmapResources.program->MapUBO()); - MOBILEGL_ASSERT(depthProgramData != nullptr, "GenerateDepthMipmapWithShader: depth mipmap UBO is null"); - auto writeUniform = [&](Int location, const void* data, SizeT size) { - MOBILEGL_ASSERT(location >= 0, "GenerateDepthMipmapWithShader: invalid uniform location"); - const Uint offset = m_depthMipmapResources.program->GetUniformOffset(static_cast(location)); - MOBILEGL_ASSERT(offset + size <= m_depthMipmapResources.program->GetUBOSize(), - "GenerateDepthMipmapWithShader: uniform write out of bounds"); - memcpy(depthProgramData + offset, data, size); - }; - - for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) { - VkImageLayout dstMipLayout = originalLayout; - const Bool dstReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource.image, dstMipLayout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - originalSrcStageMask, depthAttachmentStageMask, - originalSrcAccessMask, depthAttachmentAccessMask, - resource.aspect, level, 1); - MOBILEGL_ASSERT(dstReady, "%s: failed to transition mip level %u to depth attachment layout", __func__, level); - - const IntVec3 srcTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level - 1); - const IntVec3 dstTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level); - const Int srcTexelSizeUniform[2] = {srcTexelSize.x(), srcTexelSize.y()}; - - const VkImageView sourceImageView = createMipView(level - 1, VK_IMAGE_ASPECT_DEPTH_BIT); - const VkImageView depthAttachmentView = createMipView(level, resource.aspect); - deferredCleanup.imageViews.push_back(sourceImageView); - deferredCleanup.imageViews.push_back(depthAttachmentView); - - VkFramebufferCreateInfo framebufferCreateInfo{}; - framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferCreateInfo.renderPass = renderPass; - framebufferCreateInfo.attachmentCount = 1; - framebufferCreateInfo.pAttachments = &depthAttachmentView; - framebufferCreateInfo.width = static_cast(dstTexelSize.x()); - framebufferCreateInfo.height = static_cast(dstTexelSize.y()); - framebufferCreateInfo.layers = 1; - - VkFramebuffer framebuffer = VK_NULL_HANDLE; - VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &framebuffer), - "GenerateDepthMipmapWithShader: vkCreateFramebuffer"); - deferredCleanup.framebuffers.push_back(framebuffer); - - VkRenderPassBeginInfo renderPassBeginInfo{}; - renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassBeginInfo.renderPass = renderPass; - renderPassBeginInfo.framebuffer = framebuffer; - renderPassBeginInfo.renderArea.offset = {0, 0}; - renderPassBeginInfo.renderArea.extent = { - static_cast(dstTexelSize.x()), static_cast(dstTexelSize.y()) - }; - - vkCmdBeginRenderPass(frame.commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - ApplyGLViewportState(frame.commandBuffer, dstTexelSize.xy()); - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = {static_cast(dstTexelSize.x()), static_cast(dstTexelSize.y())}; - vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); - - vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - - std::fill(depthProgramData, - depthProgramData + m_depthMipmapResources.program->GetUBOSize(), - Uint8{0}); - BlitUniformData blitUniformData{}; - writeUniform(m_depthMipmapResources.srcRectLocation, - blitUniformData.srcRect, - sizeof(blitUniformData.srcRect)); - writeUniform(m_depthMipmapResources.dstRectLocation, - blitUniformData.dstRect, - sizeof(blitUniformData.dstRect)); - writeUniform(m_depthMipmapResources.surfaceTransformLocation, - &blitUniformData.surfaceTransform, - sizeof(blitUniformData.surfaceTransform)); - writeUniform(m_depthMipmapResources.srcTexelSizeLocation, - srcTexelSizeUniform, - sizeof(srcTexelSizeUniform)); - - const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{ - .binding = m_depthMipmapResources.samplerBinding, - .texture = &texture, - .sampler = m_blitResources.nearestSampler.get(), - .imageView = sourceImageView, - }; - const Bool bound = m_uniformManager->BindProgramUniformBuffers( - frame.commandBuffer, *m_depthMipmapResources.program, programObj, - m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS, &samplerBindingOverride); - MOBILEGL_ASSERT(bound, "GenerateDepthMipmapWithShader: BindProgramUniformBuffers failed"); - vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); - vkCmdEndRenderPass(frame.commandBuffer); - - VkImageLayout finishedMipLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - const Bool finishedReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource.image, finishedMipLayout, finalLayout, - depthAttachmentStageMask, finalDstStageMask, - depthAttachmentAccessMask, finalDstAccessMask, - resource.aspect, level, 1); - MOBILEGL_ASSERT(finishedReady, "%s: failed to transition mip level %u to sampled layout", __func__, level); - } - return true; - } - VkPipeline VulkanRenderer::GetOrCreatePipeline( GLenum mode, const MG_State::GLState::ProgramObject& program, @@ -4132,6 +3699,152 @@ void main() { MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__); } + void VulkanRenderer::CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + MOBILEGL_ASSERT(srcWidth > 0 && srcHeight > 0 && srcDepth > 0, + "CopyImageSubData requires positive copy dimensions."); + MOBILEGL_ASSERT(srcTexture != nullptr && dstTexture != nullptr, + "CopyImageSubData requires valid source and destination textures."); + + const auto srcTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(srcTarget); + const auto dstTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(dstTarget); + MOBILEGL_ASSERT(srcTextureTarget == TextureTarget::Texture2D && dstTextureTarget == TextureTarget::Texture2D, + "CopyImageSubData currently only supports GL_TEXTURE_2D sources and destinations."); + MOBILEGL_ASSERT(srcDepth == 1 && srcZ == 0 && dstZ == 0, + "CopyImageSubData currently only supports single-layer 2D copies."); + MOBILEGL_ASSERT(srcTexture.get() != dstTexture.get(), + "CopyImageSubData does not support in-place texture copies yet."); + + auto& frame = m_frameContext.GetCurrent(); + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + + if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + + auto* srcResource = m_textureManager->SyncTextureAndGetDescriptor(*srcTexture); + auto* dstResource = m_textureManager->SyncTextureAndGetDescriptor(*dstTexture); + MOBILEGL_ASSERT(srcResource != nullptr && dstResource != nullptr, + "CopyImageSubData failed to sync source or destination texture."); + MOBILEGL_ASSERT(srcLevel >= 0 && dstLevel >= 0 && + static_cast(srcLevel) < srcResource->mipLevels && + static_cast(dstLevel) < dstResource->mipLevels, + "CopyImageSubData mip level is out of range."); + const VkImageAspectFlags copyAspectMask = + srcResource->aspect & dstResource->aspect & + (VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + MOBILEGL_ASSERT(copyAspectMask != 0 && + (srcResource->aspect & copyAspectMask) == srcResource->aspect && + (dstResource->aspect & copyAspectMask) == dstResource->aspect, + "CopyImageSubData source and destination aspects are incompatible."); + const Uint32 srcMipLevel = static_cast(srcLevel); + const Uint32 dstMipLevel = static_cast(dstLevel); + const Uint32 srcMipWidth = std::max(1u, srcResource->extent.width >> srcMipLevel); + const Uint32 srcMipHeight = std::max(1u, srcResource->extent.height >> srcMipLevel); + const Uint32 dstMipWidth = std::max(1u, dstResource->extent.width >> dstMipLevel); + const Uint32 dstMipHeight = std::max(1u, dstResource->extent.height >> dstMipLevel); + MOBILEGL_ASSERT(srcX >= 0 && srcY >= 0 && dstX >= 0 && dstY >= 0 && + static_cast(srcX + srcWidth) <= srcMipWidth && + static_cast(srcY + srcHeight) <= srcMipHeight && + static_cast(dstX + srcWidth) <= dstMipWidth && + static_cast(dstY + srcHeight) <= dstMipHeight, + "CopyImageSubData region is outside source or destination bounds."); + + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *srcTexture); + MOBILEGL_ASSERT(clearReady, "%s: failed to materialize pending clear for source textureId=%d", + __func__, srcTexture->GetExternalIndex()); + + const VkImageLayout srcOriginalLayout = srcResource->layout; + const VkImageLayout dstOriginalLayout = dstResource->layout; + MOBILEGL_ASSERT(srcOriginalLayout != VK_IMAGE_LAYOUT_UNDEFINED, + "CopyImageSubData source image has undefined layout."); + const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED + ? ((copyAspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0 + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + : dstOriginalLayout; + + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); + VkImageLayout srcCopyLayout = srcOriginalLayout; + Bool srcReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcResource->image, srcCopyLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, copyAspectMask, srcMipLevel, 1); + MOBILEGL_ASSERT(srcReady, "%s: failed to transition source image", __func__); + + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstAccessMask = 0; + GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask); + VkImageLayout dstCopyLayout = dstOriginalLayout; + if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + Bool dstReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstResource->image, dstResource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, + dstResource->aspect, 0, dstResource->mipLevels, dstResource->arrayLayers); + MOBILEGL_ASSERT(dstReady, "%s: failed to transition undefined destination image", __func__); + dstCopyLayout = dstResource->layout; + } else { + Bool dstReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstResource->image, dstCopyLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, copyAspectMask, dstMipLevel, 1); + MOBILEGL_ASSERT(dstReady, "%s: failed to transition destination image", __func__); + } + + VkImageCopy copyRegion{}; + copyRegion.srcSubresource.aspectMask = copyAspectMask; + copyRegion.srcSubresource.mipLevel = srcMipLevel; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = {srcX, srcY, 0}; + copyRegion.dstSubresource.aspectMask = copyAspectMask; + copyRegion.dstSubresource.mipLevel = dstMipLevel; + copyRegion.dstSubresource.baseArrayLayer = 0; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = {dstX, dstY, 0}; + copyRegion.extent = {static_cast(srcWidth), static_cast(srcHeight), 1}; + vkCmdCopyImage(frame.commandBuffer, + srcResource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstResource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©Region); + + VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcRestoreAccessMask = 0; + GetImageTransitionDestinationState(srcOriginalLayout, srcRestoreStageMask, srcRestoreAccessMask); + Bool srcRestored = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcResource->image, srcCopyLayout, srcOriginalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, + VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, copyAspectMask, srcMipLevel, 1); + MOBILEGL_ASSERT(srcRestored, "%s: failed to restore source image layout", __func__); + + VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstRestoreAccessMask = 0; + GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); + if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + Bool dstRestored = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstResource->image, dstResource->layout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, + dstResource->aspect, 0, dstResource->mipLevels, dstResource->arrayLayers); + MOBILEGL_ASSERT(dstRestored, "%s: failed to restore undefined destination image layout", __func__); + } else { + Bool dstRestored = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstResource->image, dstCopyLayout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, copyAspectMask, dstMipLevel, 1); + MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__); + } + } + Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) { if (frame.isCommandRecording) { m_frameContext.EndCommandRecording(); @@ -4483,13 +4196,21 @@ void main() { const VkFormatFeatureFlags optimalTilingFeatures = formatProperties.optimalTilingFeatures; const Bool isDepthOrStencilTexture = (resource->aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0; - if (!isDepthOrStencilTexture && - ((optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0 || - (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0)) { + const Bool supportsNativeBlit = + (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 && + (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0; + if (!isDepthOrStencilTexture && !supportsNativeBlit) { MGLOG_W("GenerateMipmap skipped for textureId=%d because Vulkan format %d does not support blit-based mip generation", texture->GetExternalIndex(), static_cast(resource->format)); return; } + if (isDepthOrStencilTexture) { + MOBILEGL_ASSERT((resource->aspect & VK_IMAGE_ASPECT_STENCIL_BIT) == 0, + "GenerateMipmap: depth-stencil mipmap generation is not supported yet."); + MOBILEGL_ASSERT(supportsNativeBlit, + "GenerateMipmap: depth texture format %d does not support native Vulkan blit.", + static_cast(resource->format)); + } MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, baseMipLevel), "GenerateMipmap could not allocate a full mip chain for this texture."); @@ -4522,20 +4243,11 @@ void main() { const VkImageLayout originalLayout = resource->layout; const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect); - if (isDepthOrStencilTexture) { - const Bool depthReady = GenerateDepthMipmapWithShader(frame, *texture, *resource, - baseMipLevel, generateMipLevelCount, - storageBaseTexelSize, originalLayout, finalLayout); - MOBILEGL_ASSERT(depthReady, - "GenerateMipmap: depth/stencil fallback failed for textureId=%d target=%d internalFormat=%d vkFormat=%d", - texture->GetExternalIndex(), static_cast(texture->GetTarget()), - static_cast(texture->GetFormat()), static_cast(resource->format)); - return; - } - - const VkFilter blitFilter = (optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 - ? VK_FILTER_LINEAR - : VK_FILTER_NEAREST; + const VkFilter blitFilter = isDepthOrStencilTexture + ? VK_FILTER_NEAREST + : ((optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST); VkPipelineStageFlags originalSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags originalSrcAccessMask = 0; @@ -4986,7 +4698,6 @@ void main() { m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired); } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); - CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex()); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_transientVertexIndexBufferSlicesThisFrame.clear(); @@ -5641,9 +5352,6 @@ void main() { vkDeviceWaitIdle(m_device); - DestroyDeferredDepthMipmapCleanup(); - m_deferredDepthMipmapCleanup.assign(m_frameContext.GetFrameCount(), {}); - ShutdownSwapchain(); CreateSwapchain(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index e2dae33d..3ed5b681 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -134,6 +134,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLbitfield mask, GLenum filter); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void CopyImageSubData(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); void GenerateMipmap(GLenum target); 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); @@ -175,22 +180,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 samplerBinding = 0; }; - struct DepthMipmapResources { - SharedPtr program; - Int srcRectLocation = -1; - Int dstRectLocation = -1; - Int surfaceTransformLocation = -1; - Int srcTexelSizeLocation = -1; - Uint32 samplerBinding = 0; - }; - - struct DeferredDepthMipmapCleanup { - Vector imageViews; - Vector framebuffers; - Vector renderPasses; - Vector pipelines; - }; - void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload); void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer, GLenum buffer, GLint drawbuffer, @@ -249,8 +238,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr m_textureManager; UniquePtr m_samplerManager; BlitResources m_blitResources; - DepthMipmapResources m_depthMipmapResources; - Vector m_deferredDepthMipmapCleanup; void CreateInstance(); VkResult SetupDebugMessenger(); @@ -280,11 +267,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::VertexArrayObject& vao, const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); - Bool InitializeDepthMipmapResources(); void ShutdownBlitResources(); - void ShutdownDepthMipmapResources(); - void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex); - void DestroyDeferredDepthMipmapCleanup(); Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, MG_State::GLState::FramebufferObject& readFbo, MG_State::GLState::FramebufferObject& drawFbo, @@ -294,14 +277,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry); - Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame, - MG_State::GLState::ITextureObject& texture, - VkTextureManager::TextureResource& resource, - Uint32 baseMipLevel, - Uint32 generateMipLevelCount, - const IntVec3& storageBaseTexelSize, - VkImageLayout originalLayout, - VkImageLayout finalLayout); Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame); void ShutdownSwapchain(); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 088a4ce1..912fed75 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -373,7 +373,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribIFormat, GLuint attribindex, GLi DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribBinding, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribBinding, attribindex, bindingindex) DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexBindingDivisor, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexBindingDivisor, bindingindex, divisor) DECLARE_GL_FUNCTION_STUB_HEAD(void, BlendBarrier) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlendBarrier) -DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyImageSubData, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyImageSubData, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth) +DECLARE_GL_FUNCTION_HEAD(void, CopyImageSubData, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyImageSubData, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth) DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageControl, GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageControl, source, type, severity, count, ids, enabled) DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageInsert, GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* buf) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageInsert, source, type, id, severity, length, buf) DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageCallback, GLDEBUGPROC callback, const void* userParam) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageCallback, callback, userParam) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index f3336d59..a31e3cd0 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -86,6 +86,57 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertGLEnumToTexturePixelDataType(realType)); } + Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize) { + Int maxDimension = std::max( + baseTexelSize.x(), + std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); + Uint mipLevelCount = 1; + while (maxDimension > 1) { + maxDimension = std::max(maxDimension / 2, 1); + ++mipLevelCount; + } + return mipLevelCount; + } + + IntVec3 ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel) { + return { + std::max(baseTexelSize.x() >> static_cast(relativeLevel), 1), + std::max(baseTexelSize.y() >> static_cast(relativeLevel), 1), + std::max(baseTexelSize.z() >> static_cast(relativeLevel), 1), + }; + } + + Bool EnsureGeneratedMipmapStorageAllocated( + MG_State::GLState::TextureObjectMipmap& texture, + TextureUploadTarget uploadTarget) { + const Uint existingLevelCount = texture.GetMipmapLevelCount(); + if (existingLevelCount == 0) { + return false; + } + + const IntVec3 baseTexelSize = texture.GetMipmapTexelSize(uploadTarget, 0); + const SizeT baseByteSize = texture.GetMipmapByteSize(uploadTarget, 0); + const SizeT baseTexelCount = static_cast(baseTexelSize.x()) * + static_cast(baseTexelSize.y()) * + static_cast(baseTexelSize.z()); + if (baseTexelSize.x() <= 0 || baseTexelSize.y() <= 0 || baseTexelSize.z() <= 0 || + baseByteSize == 0 || baseTexelCount == 0 || (baseByteSize % baseTexelCount) != 0) { + return false; + } + + const SizeT bytesPerTexel = baseByteSize / baseTexelCount; + const Uint requiredLevelCount = ComputeFullMipmapLevelCount(baseTexelSize); + for (Uint level = existingLevelCount; level < requiredLevelCount; ++level) { + const IntVec3 levelTexelSize = ComputeMipmapTexelSize(baseTexelSize, level); + const SizeT levelByteSize = bytesPerTexel * static_cast(levelTexelSize.x()) * + static_cast(levelTexelSize.y()) * + static_cast(levelTexelSize.z()); + texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize}); + texture.MarkStorageDirty(uploadTarget, level, false); + } + return true; + } + Bool IsMultisampleTextureTarget(TextureTarget target) { return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray; @@ -1875,6 +1926,61 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); } + void CopyImageSubData_Backend(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + auto copyImageSubData = MG_Backend::gBackendFunctionsTable.GL.CopyImageSubData; + if (!copyImageSubData) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Backend does not support image-to-image copies.")); + return; + } + copyImageSubData(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel, dstX, + dstY, dstZ, srcWidth, srcHeight, srcDepth); + } + + Bool ValidateCopyImageSubData_State(const SharedPtr& srcTexture, + GLenum srcTarget, GLint srcLevel, + const SharedPtr& dstTexture, + GLenum dstTarget, GLint dstLevel, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + if (!TextureImpl::ValidateTextureObject(srcTexture) || !TextureImpl::ValidateTextureObject(dstTexture)) { + return false; + } + const auto srcTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(srcTarget); + const auto dstTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(dstTarget); + if (!TextureImpl::ValidateTextureTarget(srcTextureTarget) || + !TextureImpl::ValidateTextureTarget(dstTextureTarget)) { + return false; + } + if (!TextureImpl::ValidateTextureTargetUniformity(srcTexture, srcTextureTarget) || + !TextureImpl::ValidateTextureTargetUniformity(dstTexture, dstTextureTarget)) { + return false; + } + if (!TextureImpl::ValidateTextureLevelNumber(srcLevel) || + !TextureImpl::ValidateTextureLevelNumber(dstLevel)) { + return false; + } + if (srcWidth < 0 || srcHeight < 0 || srcDepth < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "Copy dimensions must be non-negative.")); + return false; + } + if (srcWidth == 0 || srcHeight == 0 || srcDepth == 0) { + return false; + } + if (!TextureImpl::ValidateBaseInternalFormatMatch(srcTexture->GetFormat(), dstTexture->GetFormat())) { + return false; + } + return true; + } + void CopyTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { // TODO: implement } @@ -2725,6 +2831,17 @@ namespace MobileGL::MG_Impl::GLImpl { } void GenerateMipmap(GLenum target) { + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); + auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject(); + if (textureObject) { + auto* mipmapTexture = dynamic_cast(textureObject.get()); + MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage."); + for (const TextureUploadTarget uploadTarget : textureObject->GetUploadTargets()) { + MOBILEGL_ASSERT(EnsureGeneratedMipmapStorageAllocated(*mipmapTexture, uploadTarget), + "GenerateMipmap could not allocate generated mipmap state."); + } + } GenerateMipmap_Backend(target); } @@ -3001,6 +3118,19 @@ namespace MobileGL::MG_Impl::GLImpl { CopyTexImage1D_State(target, level, internalformat, x, y, width, border); } + void CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { + auto srcTexture = GetTextureObjectByName(srcName, __func__); + auto dstTexture = GetTextureObjectByName(dstName, __func__); + if (!ValidateCopyImageSubData_State(srcTexture, srcTarget, srcLevel, dstTexture, dstTarget, dstLevel, + srcWidth, srcHeight, srcDepth)) { + return; + } + CopyImageSubData_Backend(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel, + dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth); + } + void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) { CompressedTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h index febf458b..eed7fdeb 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h @@ -100,6 +100,9 @@ namespace MobileGL::MG_Impl::GLImpl { GLsizei height, GLint border); void CopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); + void CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, + GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data); void CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,