From 93cf3559e1bd93e4748a44ba6ed2c453e0e74868 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 5 Jul 2026 18:41:32 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectGLES): fix Photon image replay --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 19 +- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 162 ++++++++++++++++++ MobileGL/MG_Backend/DirectGLES/Managers.h | 8 +- 3 files changed, 184 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 60bc6453..9d061ddd 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -325,7 +325,8 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace TextureImpl { SharedPtr& SyncTextureObjectToBackend( - const SharedPtr& textureObject) { + const SharedPtr& textureObject, + Bool imageBindableStorageRequired) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif @@ -335,6 +336,9 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!exist) { backendObj = MakeShared(); } + if (imageBindableStorageRequired) { + backendObj->RequireImageBindableStorage(); + } backendObj->SyncTextureParamsToBackend(textureObject); backendObj->SyncBuiltinSamplerToBackend(textureObject); backendObj->SyncMipmapsToBackend(textureObject); @@ -376,6 +380,12 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + static Bool SupportsLayeredImageBinding(TextureTarget target) { + return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap || + target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray || + target == TextureTarget::Texture2DMultisampleArray; + } + void SyncImageTextureBinding(Uint unit) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); @@ -386,10 +396,11 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } - auto& backendTexture = SyncTextureObjectToBackend(imageBinding.Texture); + auto& backendTexture = SyncTextureObjectToBackend(imageBinding.Texture, true); + const GLboolean layered = + SupportsLayeredImageBinding(imageBinding.Texture->GetTarget()) ? imageBinding.Layered : GL_FALSE; g_GLESFuncs.glBindImageTexture(unit, backendTexture->GetBackendTextureId(), imageBinding.Level, - imageBinding.Layered, imageBinding.Layer, imageBinding.Access, - imageBinding.Format); + layered, imageBinding.Layer, imageBinding.Access, imageBinding.Format); } void SyncImageTextureBindings() { diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 255fd34d..c9d555a6 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace MobileGL::MG_Backend::DirectGLES { constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = false; @@ -59,6 +60,46 @@ namespace MobileGL::MG_Backend::DirectGLES { return source; } + static String PackPhotonSharedVec3Memory(String source) { + constexpr const char* declaration = "shared vec3 shared_memory[256][9];"; + const SizeT declarationPos = source.find(declaration); + if (declarationPos == String::npos) { + return source; + } + + source.replace(declarationPos, String(declaration).size(), + "shared float shared_memory[256][9][3];\n" + "void StorePhotonSharedMemory(uint row, uint column, vec3 value)\n" + "{\n" + " shared_memory[row][column][0] = value.x;\n" + " shared_memory[row][column][1] = value.y;\n" + " shared_memory[row][column][2] = value.z;\n" + "}\n" + "vec3 LoadPhotonSharedMemory(uint row, uint column)\n" + "{\n" + " return vec3(shared_memory[row][column][0], shared_memory[row][column][1], " + "shared_memory[row][column][2]);\n" + "}\n"); + + source = std::regex_replace( + source, std::regex(R"(shared_memory\[([^\]]+)\]\[([^\]]+)\] \+= ([^;]+);)"), + "StorePhotonSharedMemory($1, $2, LoadPhotonSharedMemory($1, $2) + ($3));"); + source = std::regex_replace( + source, std::regex(R"(shared_memory\[([^\]]+)\]\[([^\]]+)\] = ([^;]+);)"), + "StorePhotonSharedMemory($1, $2, $3);"); + source = std::regex_replace(source, std::regex(R"(shared_memory\[([^\]]+)\]\[([^\]]+)\](?!\[))"), + "LoadPhotonSharedMemory($1, $2)"); + source = std::regex_replace(source, std::regex(R"(LoadPhotonSharedMemory\(0,)"), + "LoadPhotonSharedMemory(0u,"); + source = std::regex_replace(source, std::regex(R"(vec3 ([A-Za-z_][A-Za-z0-9_]*)\[9\] = shared_memory\[0\];)"), + "vec3 $1[9];\n" + " for (uint photon_band = 0u; photon_band < 9u; ++photon_band)\n" + " {\n" + " $1[photon_band] = LoadPhotonSharedMemory(0u, photon_band);\n" + " }"); + return source; + } + String InjectUniformAfterVersion(String source, const String& declaration) { const SizeT versionPos = source.find("#version"); if (versionPos == String::npos) { @@ -498,6 +539,38 @@ namespace MobileGL::MG_Backend::DirectGLES { return m_backendTextureId; } + void BackendTextureObject::RequireImageBindableStorage() { + if (m_imageBindableStorageRequired) { + return; + } + m_imageBindableStorageRequired = true; + m_isInitialized = false; + } + + void BackendTextureObject::RecreateBackendTexture() { + if (m_backendTextureId != 0) { + g_GLESFuncs.glDeleteTextures(1, &m_backendTextureId); + for (auto& unitCache : g_boundTexturesCache) { + for (auto& boundTexture : unitCache) { + if (boundTexture == this) { + boundTexture = nullptr; + } + } + } + } + + g_GLESFuncs.glGenTextures(1, &m_backendTextureId); + if (m_backendTextureId == 0) { + MGLOG_E("Failed to regenerate texture object."); + MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + } else { + MGLOG_D("Regenerated texture object with ID: %u.", m_backendTextureId); + } + m_isInitialized = false; + m_backendStorageImmutable = false; + m_prevTextureInfo = {}; + } + class ScopedDefaultUnpackState { public: ScopedDefaultUnpackState() { @@ -686,8 +759,15 @@ namespace MobileGL::MG_Backend::DirectGLES { currentTextureInfo.mipmapLevels = mipmapCount; Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo); + if (needsRegeneration && m_backendStorageImmutable) { + RecreateBackendTexture(); + Bind(target); + } + const Bool canAppendMipmaps = m_isInitialized && + !m_imageBindableStorageRequired && + !stateTextureObject->IsImmutable() && currentTextureInfo.internalFormat == m_prevTextureInfo.internalFormat && currentTextureInfo.width == m_prevTextureInfo.width && currentTextureInfo.height == m_prevTextureInfo.height && @@ -795,12 +875,93 @@ namespace MobileGL::MG_Backend::DirectGLES { MOBILEGL_ASSERT(false, "Unexpected multisample target: %d", static_cast(targetInternal)); break; } + m_backendStorageImmutable = true; for (const auto& uploadTarget : uploadTargets) { for (SizeT level = 0; level < mipmapCount; ++level) { textureMipmapObject->MarkStorageDirty(uploadTarget, level, false); } } + } else if (stateTextureObject->IsImmutable() || m_imageBindableStorageRequired) { + DebugImpl::ErrorLopper::Clear(); + g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + switch (targetInternal) { + case TextureTarget::Texture2D: + case TextureTarget::TextureCubeMap: + g_GLESFuncs.glTexStorage2D(target, static_cast(mipmapCount), glInternalFormat, + static_cast(baseSize.x()), + static_cast(baseSize.y())); + break; + case TextureTarget::Texture3D: + g_GLESFuncs.glTexStorage3D(target, static_cast(mipmapCount), glInternalFormat, + static_cast(baseSize.x()), + static_cast(baseSize.y()), + static_cast(baseSize.z())); + break; + default: + MGLOG_E("Unhandled immutable texture target %s", + MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); + break; + } + DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__, target, + glInternalFormat](GLenum err) { + MGLOG_D("%s(%s:%d) ES error: %s. glTexStorage*: target=%s, internalformat=%s", func, + file, line, MG_Util::ConvertGLEnumToString(err).c_str(), + MG_Util::ConvertGLEnumToString(target).c_str(), + MG_Util::ConvertGLEnumToString(glInternalFormat).c_str()); + }); + m_backendStorageImmutable = true; + + ScopedDefaultUnpackState unpackState; + for (auto& uploadTarget : uploadTargets) { + for (SizeT level = 0; level < mipmapCount; ++level) { + auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); + const bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level); + if (levelDirty && levelByteSize != 0) { + auto levelTexelSize = + textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); + auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget); + auto* pData = textureMipmapObject->MapMipmapData(uploadTarget, level); + Vector convertedUploadData; + const void* uploadData = PrepareNormFloatFallbackUpload( + textureMipmapObject->GetFormat(), levelTexelSize, pData, levelByteSize, glType, + convertedUploadData); + + DebugImpl::ErrorLopper::Clear(); + g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + switch (targetInternal) { + case TextureTarget::Texture2D: + case TextureTarget::TextureCubeMap: + g_GLESFuncs.glTexSubImage2D( + glUploadTarget, static_cast(level), 0, 0, + static_cast(levelTexelSize.x()), + static_cast(levelTexelSize.y()), glFormat, glType, uploadData); + break; + case TextureTarget::Texture3D: + g_GLESFuncs.glTexSubImage3D( + glUploadTarget, static_cast(level), 0, 0, 0, + static_cast(levelTexelSize.x()), + static_cast(levelTexelSize.y()), + static_cast(levelTexelSize.z()), glFormat, glType, uploadData); + break; + default: + break; + } + DebugImpl::ErrorLopper::Loop( + [file = __FILE__, line = __LINE__, func = __func__, glUploadTarget, + glFormat, glType, pData](GLenum err) { + MGLOG_D("%s(%s:%d) ES error: %s. glTexSubImage*: target=%s, format=%s, " + "type=%s, pixels=%p", + func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(), + MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(), + MG_Util::ConvertGLEnumToString(glFormat).c_str(), + MG_Util::ConvertGLEnumToString(glType).c_str(), pData); + }); + } + textureMipmapObject->MarkStorageDirty(uploadTarget, level, false); + } + } } else { + m_backendStorageImmutable = false; ScopedDefaultUnpackState unpackState; for (auto& uploadTarget : uploadTargets) { for (SizeT level = 0; level < mipmapCount; ++level) { @@ -1690,6 +1851,7 @@ namespace MobileGL::MG_Backend::DirectGLES { source = ClampNormFallbackOutputs(std::move(source), glShaderType, m_snormFallbackClampOutputMask, m_unormFallbackClampOutputMask); + source = PackPhotonSharedVec3Memory(std::move(source)); // Patch for Photon compiler precision issue String findStr = "1000000.0"; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 59ee09ff..19c04241 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -205,12 +205,17 @@ namespace MobileGL::MG_Backend::DirectGLES { void SyncMipmapsToBackend(const SharedPtr& stateTextureObject); void SyncBuiltinSamplerToBackend(const SharedPtr& stateTextureObject); void SyncTextureParamsToBackend(const SharedPtr& stateTextureObject); + void RequireImageBindableStorage(); void Bind(GLenum target, Uint unit = TempTextureUnit); Uint GetBackendTextureId() const; private: + void RecreateBackendTexture(); + Uint m_backendTextureId = 0; Bool m_isInitialized = false; + Bool m_imageBindableStorageRequired = false; + Bool m_backendStorageImmutable = false; StateTextureBasicInfo m_prevTextureInfo; SamplerParameters m_cacheSamplerParameters; UintVec2 m_cacheLodRange = {0, 1000}; @@ -226,7 +231,8 @@ namespace MobileGL::MG_Backend::DirectGLES { extern StateBackendObjectRegistry g_backendTextureObjects; SharedPtr& SyncTextureObjectToBackend( - const SharedPtr& textureObject); + const SharedPtr& textureObject, + Bool imageBindableStorageRequired = false); extern Array, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundTexturesCache;