From 85bd0613ca07f9c3d1ffaa110ebcd06a23248370 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 9 Jun 2026 17:20:23 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectGLES): support Voxy rendering Implemented: - Advertise Voxy-required DirectGLES extensions without raising the reported OpenGL version. - Add DirectGLES multi draw indirect count emulation and preserve GL draw indirect baseInstance semantics on GLES. - Add DirectGLES DSA framebuffer clear/blit paths used by Minecraft and Voxy presentation. Fixed: - Rewrite gl_BaseInstance in DirectGLES vertex shaders and provide a backend uniform for indirect draw emulation. - Materialize framebuffer attachment textures during DirectGLES FBO sync so named framebuffer operations do not desync backend attachment state. - Avoid redundant texture buffer rebinding and handle texture buffers without bound storage during backend sync. Tests: - Add MG_Test coverage for DirectGLES Voxy extension advertising, baseInstance shader rewriting, and DSA named framebuffer clear/blit backend wiring. --- .../DirectGLES/BackendObject_DirectGLES.cpp | 9 +- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 237 +++++++++++++++++- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 12 + MobileGL/MG_Backend/DirectGLES/Managers.cpp | 102 +++++++- MobileGL/MG_Backend/DirectGLES/Managers.h | 5 + MobileGL/MG_Test/SanityTest.cpp | 66 +++++ 6 files changed, 418 insertions(+), 13 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 3bb3f6bf..5d7a764f 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -132,7 +132,10 @@ namespace MobileGL::MG_Backend::DirectGLES { V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, - E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage}, + E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, + E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, + E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, + E_GL_ARB_shader_draw_parameters}, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability @@ -166,6 +169,7 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.MultiDrawElements = MultiDrawElements; funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex; funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect; + funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount; funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect; funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex; funcsTable.GL.DrawRangeElements = DrawRangeElements; @@ -197,7 +201,10 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.ClearBufferfv = ClearBufferfv; funcsTable.GL.ClearBufferuiv = ClearBufferuiv; funcsTable.GL.ClearBufferiv = ClearBufferiv; + funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv; + funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi; funcsTable.GL.BlitFramebuffer = BlitFramebuffer; + funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer; funcsTable.GL.CopyTexImage2D = CopyTexImage2D; funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D; funcsTable.GL.GenerateMipmap = GenerateMipmap; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 11438aaa..91a6cea1 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,14 @@ namespace MobileGL::MG_Backend::DirectGLES { return a; } + struct DrawElementsIndirectCommand { + Uint32 count = 0; + Uint32 instanceCount = 0; + Uint32 firstIndex = 0; + Int32 baseVertex = 0; + Uint32 baseInstance = 0; + }; + namespace DebugImpl { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG void ErrorLopper::Loop(const std::function& func) { @@ -623,6 +632,41 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + void SyncAndBindFramebufferObject(const SharedPtr& framebuffer, + FramebufferTarget target, Bool forceSync = false) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + if (!framebuffer || framebuffer == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { + g_GLESFuncs.glBindFramebuffer(target == FramebufferTarget::Draw ? GL_DRAW_FRAMEBUFFER : GL_READ_FRAMEBUFFER, + 0); + return; + } + + auto& registry = FramebufferImpl::g_backendFramebufferObjects; + const auto& backendFBOIt = registry.find(framebuffer.get()); + const Bool exists = backendFBOIt != registry.end(); + auto& backendObj = exists ? backendFBOIt->second : registry.GetOrCreate(framebuffer); + if (!exists) { + backendObj = MakeShared(); + } + if (forceSync) { + backendObj->InvalidateSyncedState(); + } + + backendObj->SyncToBackend(framebuffer, target); + backendObj->Bind(target); + } + + void ForceBindCurrentFBO(FramebufferTarget target) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(target); + SyncAndBindFramebufferObject(slot.GetBoundObject(), target); + FramebufferImpl::g_fboBindVersions[(SizeT)target] = slot.GetVersion(); + } + void PrepareForDraw(DrawSyncBit syncBit) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); @@ -804,6 +848,17 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + void SetCurrentBaseInstance(Uint32 baseInstance) { + const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + if (!currentProgram || !currentProgram->GetLinkStatus()) { + return; + } + const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get()); + if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) { + backendProgramIt->second->SetBaseInstance(baseInstance); + } + } + void PrepareForCompute(Bool includeDispatchIndirectBuffer) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); @@ -930,14 +985,127 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER DebugImpl::OpenGLScopeMarker marker(__func__); #endif - DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer; + if (drawcount <= 0) { + return; + } + if (stride == 0) { + stride = sizeof(DrawElementsIndirectCommand); + } + if (stride < static_cast(sizeof(DrawElementsIndirectCommand))) { + MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", + stride, sizeof(DrawElementsIndirectCommand)); + return; + } + + DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer | DrawSyncBit::Instancing; PrepareForDraw(syncBit); - for (GLsizei i = 0; i < drawcount; ++i) { - const GLvoid* cmd = reinterpret_cast(reinterpret_cast(indirect) + - i * (stride ? stride : sizeof(GLsizei) * 4)); - g_GLESFuncs.glDrawElementsIndirect(mode, type, cmd); + const SizeT indexSize = MG_Util::GetGLTypeSize(type); + if (indexSize == 0) { + MGLOG_E("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); + return; } + + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + if (!drawBuffer) { + MGLOG_E("MultiDrawElementsIndirect skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); + return; + } + drawBuffer->MarkPersistentMappedRangeDirty(); + const auto drawData = drawBuffer->GetDataReadOnly(); + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(drawcount - 1) + + sizeof(DrawElementsIndirectCommand); + if (!drawData || commandBytes > drawData->size()) { + MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + return; + } + + for (GLsizei i = 0; i < drawcount; ++i) { + DrawElementsIndirectCommand cmd{}; + std::memcpy(&cmd, drawData->data() + commandOffset + static_cast(i) * stride, sizeof(cmd)); + if (cmd.count == 0 || cmd.instanceCount == 0) { + continue; + } + SetCurrentBaseInstance(cmd.baseInstance); + const auto indexByteOffset = static_cast(cmd.firstIndex) * indexSize; + g_GLESFuncs.glDrawElementsInstancedBaseVertex( + mode, static_cast(cmd.count), type, reinterpret_cast(indexByteOffset), + static_cast(cmd.instanceCount), cmd.baseVertex); + } + SetCurrentBaseInstance(0); + } + + void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, + GLsizei maxdrawcount, GLsizei stride) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + if (maxdrawcount <= 0) { + return; + } + if (stride == 0) { + stride = sizeof(DrawElementsIndirectCommand); + } + if (stride < static_cast(sizeof(DrawElementsIndirectCommand))) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", + stride, sizeof(DrawElementsIndirectCommand)); + return; + } + + DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer | DrawSyncBit::Instancing; + PrepareForDraw(syncBit); + + const SizeT indexSize = MG_Util::GetGLTypeSize(type); + if (indexSize == 0) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: unsupported index type 0x%x", type); + return; + } + + auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); + auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); + if (!drawBuffer) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); + return; + } + if (!parameterBuffer) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); + return; + } + + drawBuffer->MarkPersistentMappedRangeDirty(); + parameterBuffer->MarkPersistentMappedRangeDirty(); + const auto drawData = drawBuffer->GetDataReadOnly(); + const auto parameterData = parameterBuffer->GetDataReadOnly(); + + const SizeT commandOffset = reinterpret_cast(indirect); + const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + + sizeof(DrawElementsIndirectCommand); + if (!drawData || commandBytes > drawData->size()) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + return; + } + if (!parameterData || drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterData->size()) { + MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + return; + } + + Uint32 actualDrawCount = 0; + std::memcpy(&actualDrawCount, parameterData->data() + drawcount, sizeof(actualDrawCount)); + actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); + for (Uint32 i = 0; i < actualDrawCount; ++i) { + DrawElementsIndirectCommand cmd{}; + std::memcpy(&cmd, drawData->data() + commandOffset + static_cast(i) * stride, sizeof(cmd)); + if (cmd.count == 0 || cmd.instanceCount == 0) { + continue; + } + SetCurrentBaseInstance(cmd.baseInstance); + const auto indexByteOffset = static_cast(cmd.firstIndex) * indexSize; + g_GLESFuncs.glDrawElementsInstancedBaseVertex( + mode, static_cast(cmd.count), type, reinterpret_cast(indexByteOffset), + static_cast(cmd.instanceCount), cmd.baseVertex); + } + SetCurrentBaseInstance(0); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { @@ -1048,6 +1216,31 @@ namespace MobileGL::MG_Backend::DirectGLES { }); } + void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, + const SharedPtr& drawFramebuffer, + GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + TextureImpl::SyncNeccessaryTextures(); + RenderStateImpl::SyncRenderState(); + + SyncAndBindFramebufferObject(readFramebuffer, FramebufferTarget::Read, true); + SyncAndBindFramebufferObject(drawFramebuffer, FramebufferTarget::Draw, true); + + MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, + dstX0, dstY0, dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str()); + g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { + MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + + ForceBindCurrentFBO(FramebufferTarget::Read); + ForceBindCurrentFBO(FramebufferTarget::Draw); + } + Bool UpdateTextureBindingAtTarget(GLenum target) { #ifdef TRACY_ENABLE ZoneScopedNC(__func__, TRACY_ZONECOLOR_BACKEND); @@ -1558,6 +1751,40 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glClearBufferuiv(buffer, drawbuffer, value); } + void ClearNamedFramebufferfv(const SharedPtr& framebuffer, + GLenum buffer, GLint drawbuffer, const GLfloat* value) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + TextureImpl::SyncNeccessaryTextures(); + RenderStateImpl::SyncRenderState(); + + SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); + g_GLESFuncs.glClearBufferfv(buffer, drawbuffer, value); + DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { + MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + + ForceBindCurrentFBO(FramebufferTarget::Draw); + } + + void ClearNamedFramebufferfi(const SharedPtr& framebuffer, + GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + TextureImpl::SyncNeccessaryTextures(); + RenderStateImpl::SyncRenderState(); + + SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); + g_GLESFuncs.glClearBufferfi(buffer, drawbuffer, depth, stencil); + DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { + MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + + ForceBindCurrentFBO(FramebufferTarget::Draw); + } + class TempPixelStoreParameterSync { public: TempPixelStoreParameterSync(Bool isUnpack) : m_isUnpack(isUnpack) { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 271689d6..1ad94814 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -30,6 +31,8 @@ namespace MobileGL::MG_Backend::DirectGLES { void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); + void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, + GLsizei maxdrawcount, GLsizei stride); void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex); @@ -46,8 +49,17 @@ namespace MobileGL::MG_Backend::DirectGLES { GLuint baseinstance); void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); void DrawArraysIndirect(GLenum mode, const void* indirect); + void ClearNamedFramebufferfv(const SharedPtr& framebuffer, + GLenum buffer, GLint drawbuffer, const GLfloat* value); + void ClearNamedFramebufferfi(const SharedPtr& framebuffer, + GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, + const SharedPtr& drawFramebuffer, + GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter); void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index c8d77b6b..e36d239d 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -23,9 +23,11 @@ #include #include #include +#include namespace MobileGL::MG_Backend::DirectGLES { constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = true; + constexpr const char* BASE_INSTANCE_UNIFORM_NAME = "mg_BaseInstance"; static Uint ResolveBackendEsslVersion() { const auto& version = g_GLESCapabilities.GLESVersion; @@ -38,6 +40,47 @@ namespace MobileGL::MG_Backend::DirectGLES { return 300; } + String ReplaceIdentifier(String source, const String& from, const String& to) { + SizeT pos = 0; + while ((pos = source.find(from, pos)) != String::npos) { + const Bool leftIsIdent = pos > 0 && + (std::isalnum(static_cast(source[pos - 1])) || source[pos - 1] == '_'); + const SizeT end = pos + from.size(); + const Bool rightIsIdent = end < source.size() && + (std::isalnum(static_cast(source[end])) || source[end] == '_'); + if (!leftIsIdent && !rightIsIdent) { + source.replace(pos, from.size(), to); + pos += to.size(); + } else { + pos = end; + } + } + return source; + } + + String InjectUniformAfterVersion(String source, const String& declaration) { + const SizeT versionPos = source.find("#version"); + if (versionPos == String::npos) { + return declaration + "\n" + source; + } + + const SizeT lineEnd = source.find('\n', versionPos); + if (lineEnd == String::npos) { + return source + "\n" + declaration + "\n"; + } + source.insert(lineEnd + 1, declaration + "\n"); + return source; + } + + String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType) { + if (shaderType != GL_VERTEX_SHADER || source.find("gl_BaseInstance") == String::npos) { + return source; + } + source = ReplaceIdentifier(std::move(source), "gl_BaseInstance", BASE_INSTANCE_UNIFORM_NAME); + return InjectUniformAfterVersion(std::move(source), + String("uniform highp int ") + BASE_INSTANCE_UNIFORM_NAME + ";"); + } + namespace BufferImpl { BackendBufferObject::BackendBufferObject() { #ifdef TRACY_ENABLE @@ -629,13 +672,15 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(stateTextureObject.get()); auto& slot = textureBufferObject->GetBufferBindingSlot(); auto& buffer = slot.GetBoundObject(); + if (!buffer) { + MGLOG_D("Texture buffer object with ID: %u has no bound buffer, skipping sync.", + stateTextureObject->GetExternalIndex()); + return; + } auto bufferIndex = buffer->GetExternalIndex(); currentTextureInfo.bufferExternalIndex = bufferIndex; Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo); - MGLOG_D("Texture state changed significantly or not initialized, regenerating texture (tex buffer) " - "with ID: %u", - m_backendTextureId); // Need to sync texture buffer if not synced yet auto& backendBuffers = BufferImpl::g_backendBufferObjects; @@ -659,7 +704,19 @@ namespace MobileGL::MG_Backend::DirectGLES { TextureImpl::GenerateTextureFormatInfo(textureBufferObject->GetFormat(), &glInternalFormat, &glFormat, &glType); - g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId); + if (needsRegeneration) { + MGLOG_D("Texture state changed significantly or not initialized, regenerating texture buffer with " + "ID: %u, buffer ID: %u, buffer size: %zu, format: %s", + m_backendTextureId, backendId, buffer->GetSize(), + MG_Util::ConvertGLEnumToString(glInternalFormat).c_str()); + g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId); + DebugImpl::ErrorLopper::Loop( + [file = __FILE__, line = __LINE__, func = __func__, glInternalFormat, backendId](GLenum err) { + MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s", + func, file, line, MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(), + backendId, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + } break; } default: @@ -905,17 +962,37 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_backendFBOId); } + void BackendFramebufferObject::InvalidateSyncedState() { + std::fill(std::begin(m_frontendDrawBuffers), std::end(m_frontendDrawBuffers), + FramebufferAttachmentType::Unknown); + std::fill(std::begin(m_backendDrawBuffers), std::end(m_backendDrawBuffers), GL_NONE); + m_frontendReadBuffer = FramebufferAttachmentType::Unknown; + m_backendReadBuffer = GL_NONE; + std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(), + static_cast(~0u)); + } + static Bool SyncAttachmentObject(GLenum glFBOTarget, const MG_State::GLState::FramebufferAttachmentObject& attachmentObject, GLenum glBackendAttachment) { if (attachmentObject.IsTexture()) { const auto& textureObject = attachmentObject.GetTexture(); + SharedPtr backendTextureObject; const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get()); if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) { + auto& backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject); + if (!backendTextureSlot) { + backendTextureSlot = MakeShared(); + } + backendTextureObject = backendTextureSlot; + } else { + backendTextureObject = backendTextureIt->second; + } + if (!backendTextureObject) { MGLOG_E("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__); return false; } - const auto& backendTextureObject = backendTextureIt->second; + backendTextureObject->SyncMipmapsToBackend(textureObject); auto glTextureTarget = MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()); backendTextureObject->Bind(glTextureTarget); g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget, @@ -1023,8 +1100,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // relevant FRONTEND!!! version should be checked and updated if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) { - SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment); - m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i]; + if (SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment)) { + m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i]; + } } #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG else { @@ -1223,6 +1301,7 @@ namespace MobileGL::MG_Backend::DirectGLES { source = RemoveLayoutBinding(source); source = ProcessOutColorLocations(source); source = ForceFlatIntegerVaryings(source, glShaderType); + source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType); source = ForceSupporterOutput(source); // Patch for Photon compiler precision issue @@ -1273,6 +1352,8 @@ namespace MobileGL::MG_Backend::DirectGLES { } else { MGLOG_D("Program linked successfully. ID: %u", m_backendProgramId); } + m_baseInstanceUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId, + BASE_INSTANCE_UNIFORM_NAME); // Create global UBO if (stateProgramObject->GetUBOSize() > 0) { @@ -1295,6 +1376,13 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("Using program %u", m_backendProgramId); g_GLESFuncs.glUseProgram(m_backendProgramId); } + + void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const { + if (m_baseInstanceUniformLocation < 0) { + return; + } + g_GLESFuncs.glUniform1i(m_baseInstanceUniformLocation, static_cast(baseInstance)); + } } // namespace PrgramImpl namespace SamplerImpl { diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 17876da6..455bf88a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -15,6 +15,8 @@ #include namespace MobileGL::MG_Backend::DirectGLES { + String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType); + template class StateBackendObjectRegistry { public: @@ -226,6 +228,7 @@ namespace MobileGL::MG_Backend::DirectGLES { BackendFramebufferObject(); void SyncToBackend(const SharedPtr& stateFBOObject, FramebufferTarget asTarget); + void InvalidateSyncedState(); Uint GetBackendFramebufferId() const { return m_backendFBOId; } void Bind(FramebufferTarget target) const; // FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index); @@ -267,12 +270,14 @@ namespace MobileGL::MG_Backend::DirectGLES { ~BackendProgramObjectImpl(); void SyncToBackend(const SharedPtr& stateProgramObject); void Use() const; + void SetBaseInstance(Uint32 baseInstance) const; Uint GetBackendProgramId() const { return m_backendProgramId; } Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; } private: Uint m_backendProgramId = 0; Uint m_backendGlobalUBOId = 0; + Int m_baseInstanceUniformLocation = -1; Bool m_isInitialized = false; }; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 5825444a..4b506476 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -90,6 +91,71 @@ TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) { EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end()); } +TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) { + MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; + const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo; + const auto& extensions = rendererInfo.Extensions; + + EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 3); + EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 3); + EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0); + + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_storage_buffer_object), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_direct_state_access), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_multi_draw_indirect), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_indirect_parameters), + extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_draw_parameters), + extensions.end()); + EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64), + extensions.end()); + EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_KHR_shader_subgroup), + extensions.end()); +} + +TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) { + MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; + const auto& funcs = backend.GetBackendFunctions().GL; + + EXPECT_NE(funcs.ClearNamedFramebufferfv, nullptr); + EXPECT_NE(funcs.ClearNamedFramebufferfi, nullptr); + EXPECT_NE(funcs.BlitFramebuffer, nullptr); + EXPECT_NE(funcs.BlitNamedFramebuffer, nullptr); +} + +TEST(DirectGLESSanity, RewritesBaseInstanceBuiltinForEsslVertexShaders) { + const MobileGL::String source = R"(#version 320 es +void main() { + uint drawId = gl_BaseInstance; + uint untouched = my_gl_BaseInstance_value; +} +)"; + + const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader( + source, GL_VERTEX_SHADER); + + EXPECT_NE(rewritten.find("uniform highp int mg_BaseInstance;"), MobileGL::String::npos); + EXPECT_NE(rewritten.find("uint drawId = mg_BaseInstance;"), MobileGL::String::npos); + EXPECT_NE(rewritten.find("my_gl_BaseInstance_value"), MobileGL::String::npos); + EXPECT_EQ(rewritten.find("uint drawId = gl_BaseInstance;"), MobileGL::String::npos); +} + +TEST(DirectGLESSanity, LeavesBaseInstanceBuiltinAloneOutsideVertexShaders) { + const MobileGL::String source = "#version 320 es\nuint value = gl_BaseInstance;\n"; + + const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader( + source, GL_FRAGMENT_SHADER); + + EXPECT_EQ(rewritten, source); +} + TEST(DirectVulkanSanity, AdvertisesTextureStorageForDirectStateAccess) { MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend; const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions;