diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index a361c2a0..b6f54e56 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -52,11 +52,15 @@ // that includes Defines.h without Log.h both tokens would silently evaluate to 0 in the // preprocessor conditional - enabling the assert in exactly the INFO-level builds it is // documented to be compiled out of. Log.h redefines them identically, which is legal. +// +// Severity order, ascending: DEBUG < INFO < WARN < ERROR < FATAL. MOBILEGL_LOG_ACTIVE_LEVEL +// names the lowest severity compiled in, so the production default INFO keeps I/W/E/F and +// drops only D. Any edit here must be mirrored in Log.h. #ifndef MOBILEGL_LOG_LEVEL_DEBUG #define MOBILEGL_LOG_LEVEL_DEBUG 0 -#define MOBILEGL_LOG_LEVEL_WARN 1 -#define MOBILEGL_LOG_LEVEL_ERROR 2 -#define MOBILEGL_LOG_LEVEL_INFO 3 +#define MOBILEGL_LOG_LEVEL_INFO 1 +#define MOBILEGL_LOG_LEVEL_WARN 2 +#define MOBILEGL_LOG_LEVEL_ERROR 3 #define MOBILEGL_LOG_LEVEL_FATAL 4 #endif @@ -91,6 +95,12 @@ #endif // =============================== Utils ================================ // +// Asserts are live in exactly the builds where MGLOG_D is live, i.e. DEBUG builds only; +// an INFO build (the production default) compiles them out. DEBUG is the lowest severity +// in the ordering above, so "ACTIVE <= DEBUG" is true only for ACTIVE == DEBUG - the same +// gate MGLOG_D uses in Log.h. That equivalence is what makes this gate survive the +// 2026-08-13 renumbering unchanged; the contract is and stays +// "INFO builds: asserts OFF; DEBUG builds: asserts ON". #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG #define MOBILEGL_ASSERT(condition, ...) \ do { \ diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f08c23d3..9e24e1bd 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -261,14 +261,14 @@ namespace MobileGL::MG_Backend::DirectGLES { drawBuffer->SyncPersistentMappedRange(); const SizeT commandOffset = reinterpret_cast(indirect); if (commandOffset + requiredBytes > drawBuffer->GetSize()) { - MGLOG_E("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); + MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); return nullptr; } return drawBuffer->MappedData() + commandOffset; } if (!indirect) { - MGLOG_E("%s skipped: indirect pointer is null", label); + MGLOG_E_ONCE("%s skipped: indirect pointer is null", label); return nullptr; } @@ -341,7 +341,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* backendResource = EnsureBufferResource(obj); if (!backendResource || backendResource->id == 0) { - MGLOG_E("No backend buffer found for %s binding point %zu.", + MGLOG_E_ONCE("No backend buffer found for %s binding point %zu.", MG_Util::ConvertGLEnumToString(glTarget).c_str(), i); continue; } @@ -385,7 +385,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* backendResource = EnsureBufferResource(bufferObject); if (!backendResource || backendResource->id == 0) { - MGLOG_E("No backend buffer found for %s.", MG_Util::ConvertGLEnumToString(glTarget).c_str()); + MGLOG_E_ONCE("No backend buffer found for %s.", MG_Util::ConvertGLEnumToString(glTarget).c_str()); return; } BindBufferId(glTarget, backendResource->id); @@ -410,7 +410,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // PBO is not needed since it should be handled in frontend if (!currentVAOObject) { - MGLOG_E("No VAO is currently bound, cannot sync necessary buffers."); + MGLOG_E_ONCE("No VAO is currently bound, cannot sync necessary buffers."); return; } @@ -643,7 +643,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(target.start), static_cast(size), GL_MAP_READ_BIT); if (mapped == nullptr) { - MGLOG_E("EndTransformFeedback: failed to map backend buffer %u for capture readback", + MGLOG_E_ONCE("EndTransformFeedback: failed to map backend buffer %u for capture readback", target.backendId); continue; } @@ -709,7 +709,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(packedStride * vertices), GL_MAP_READ_BIT); if (packed == nullptr) { - MGLOG_E("EndTransformFeedback: failed to map the scatter capture buffer"); + MGLOG_E_ONCE("EndTransformFeedback: failed to map the scatter capture buffer"); return; } @@ -935,7 +935,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_backendVertexArrayObjects.CollectGarbageIfNeeded(); if (!currentVAOObject || !vaoTwin) { - MGLOG_E("No VAO is currently bound, cannot sync current VAO."); + MGLOG_E_ONCE("No VAO is currently bound, cannot sync current VAO."); return; } @@ -999,7 +999,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glVertexAttribI4uiv(location, currentValue.uintValue.data()); break; case MG_State::GLState::VertexAttribBaseType::Unsupported: - MGLOG_E("SyncCurrentVertexAttributeValues: program=%u location=%u has no enabled array and its " + MGLOG_E_ONCE("SyncCurrentVertexAttributeValues: program=%u location=%u has no enabled array and its " "shader input type 0x%x is not supported as a current generic vertex attribute", program->GetExternalIndex(), location, program->GetAttribType(location)); break; @@ -1469,7 +1469,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } if (!currentFBO) { - MGLOG_E("No FBO is currently bound, cannot sync current FBO."); + MGLOG_E_ONCE("No FBO is currently bound, cannot sync current FBO."); continue; } @@ -2240,7 +2240,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (twin) { twin->Bind(target); } else { - MGLOG_E("No backend FBO found (maybe not synced) for current %s FBO, cannot bind FBO.", + MGLOG_E_ONCE("No backend FBO found (maybe not synced) for current %s FBO, cannot bind FBO.", (target == FramebufferTarget::Read ? "READ" : "DRAW")); } } else { @@ -2844,7 +2844,7 @@ namespace MobileGL::MG_Backend::DirectGLES { (GLintptr)range.start, (GLintptr)(range.end - range.start)); } } else { - MGLOG_E("No backend buffer found for UBO binding, cannot bind UBO."); + MGLOG_E_ONCE("No backend buffer found for UBO binding, cannot bind UBO."); } } } @@ -2976,7 +2976,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } else { g_GLESFuncs.glUseProgram(0); PrgramImpl::g_lastUsedBackendProgramId = 0; - MGLOG_E("No backend program found (maybe not synced) for current program, cannot use program."); + MGLOG_E_ONCE("No backend program found (maybe not synced) for current program, cannot use program."); } } } @@ -3213,13 +3213,13 @@ namespace MobileGL::MG_Backend::DirectGLES { GLuint GetBackendProgramId(GLuint program) { if (!MG_State::pGLContext->ValidateProgramName(program)) { - MGLOG_E("Invalid frontend program object: %u", program); + MGLOG_E_ONCE("Invalid frontend program object: %u", program); return 0; } auto& programObject = MG_State::pGLContext->GetProgramObject(program); if (!programObject) { - MGLOG_E("Program object %u is null.", program); + MGLOG_E_ONCE("Program object %u is null.", program); return 0; } @@ -3486,7 +3486,7 @@ namespace MobileGL::MG_Backend::DirectGLES { stride = sizeof(DrawElementsIndirectCommand); } if (stride < static_cast(sizeof(DrawElementsIndirectCommand))) { - MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawElementsIndirectCommand)); return; } @@ -3496,7 +3496,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); return; } @@ -3526,7 +3526,7 @@ namespace MobileGL::MG_Backend::DirectGLES { stride = sizeof(DrawElementsIndirectCommand); } if (stride < static_cast(sizeof(DrawElementsIndirectCommand))) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawElementsIndirectCommand)); return; } @@ -3536,18 +3536,18 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("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"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); return; } if (!parameterBuffer) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); return; } @@ -3558,11 +3558,11 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + sizeof(DrawElementsIndirectCommand); if (commandBytes > drawBuffer->GetSize()) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); return; } @@ -3570,7 +3570,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // have - MappedData() is null there and the reads below would be a null dereference, // not a wrong picture. The DirectVulkan twin declines the same way. if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or " + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or " "draw-indirect buffer"); return; } @@ -3594,7 +3594,7 @@ namespace MobileGL::MG_Backend::DirectGLES { stride = sizeof(DrawArraysIndirectCommand); } if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawArraysIndirectCommand)); return; } @@ -3634,7 +3634,7 @@ namespace MobileGL::MG_Backend::DirectGLES { stride = sizeof(DrawArraysIndirectCommand); } if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawArraysIndirectCommand)); return; } @@ -3645,11 +3645,11 @@ namespace MobileGL::MG_Backend::DirectGLES { auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); if (!drawBuffer) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); return; } if (!parameterBuffer) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); return; } @@ -3660,17 +3660,17 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + sizeof(DrawArraysIndirectCommand); if (commandBytes > drawBuffer->GetSize()) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); return; } // See the indexed twin: no CPU shadow means no count to read, not a wrong one. if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or " + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or " "draw-indirect buffer"); return; } @@ -3763,7 +3763,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("DrawElementsIndirect skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("DrawElementsIndirect skipped: unsupported index type 0x%x", type); return; } @@ -3960,7 +3960,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast(previousDraw)); FramebufferImpl::InvalidateFramebufferBindingCache(); if (!resolved) { - MGLOG_E("BlitFramebuffer: multisample resolve fallback failed"); + MGLOG_E_ONCE("BlitFramebuffer: multisample resolve fallback failed"); } return resolved; } @@ -4259,7 +4259,7 @@ namespace MobileGL::MG_Backend::DirectGLES { s_stencilProgram = BuildProgram(kStencilFragmentSource); if (s_depthProgram == 0 || s_stencilProgram == 0) { s_programsFailed = true; - MGLOG_E("BlitFramebuffer: could not build the multisample replicate programs"); + MGLOG_E_ONCE("BlitFramebuffer: could not build the multisample replicate programs"); return false; } s_depthUvTransform = g_GLESFuncs.glGetUniformLocation(s_depthProgram, "uUvTransform"); @@ -4463,7 +4463,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(previousRead)); FramebufferImpl::InvalidateFramebufferBindingCache(); if (!ok) { - MGLOG_E("BlitFramebuffer: could not stage the source for the multisample replicate"); + MGLOG_E_ONCE("BlitFramebuffer: could not stage the source for the multisample replicate"); return false; } @@ -4532,7 +4532,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Everything the pass disturbed goes back through emulationState's destructor. if (!replicated) { - MGLOG_E("BlitFramebuffer: multisample replicate fallback failed"); + MGLOG_E_ONCE("BlitFramebuffer: multisample replicate fallback failed"); } return replicated; } @@ -4567,7 +4567,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // outright, desktop GL replicates the source sample into every destination one. if (ReplicateBlitIntoMultisampleDraw(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask)) { if ((mask & GL_COLOR_BUFFER_BIT) != 0) { - MGLOG_E("BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated"); + MGLOG_E_ONCE("BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated"); } } return; @@ -4668,7 +4668,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); if (!TextureImpl::IsSupportedTextureTarget(textureTarget)) { - MGLOG_E(" Texture target %s is not supported, skipping.", + MGLOG_E_ONCE(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); return false; } @@ -4677,7 +4677,7 @@ namespace MobileGL::MG_Backend::DirectGLES { { const auto& textureObject = bindingSlot.GetBoundObject(); if (!textureObject) { - MGLOG_W("%s: Texture target %s does not have texture bound.", __func__, + MGLOG_D("%s: Texture target %s does not have texture bound.", __func__, MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); } @@ -4926,7 +4926,7 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } - MGLOG_E("%s failed: %s. target=%s, format=%s", operation, + MGLOG_E_ONCE("%s failed: %s. target=%s, format=%s", operation, MG_Util::ConvertGLEnumToString(err).c_str(), MG_Util::ConvertGLEnumToString(target).c_str(), MG_Util::ConvertTextureInternalFormatToString(format).c_str()); @@ -5262,7 +5262,7 @@ namespace MobileGL::MG_Backend::DirectGLES { .GetBoundObject(); auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E("CopyTexSubImage2D: No backend texture found for texture %u.", + MGLOG_E_ONCE("CopyTexSubImage2D: No backend texture found for texture %u.", textureObject ? textureObject->GetExternalIndex() : 0); return; } @@ -5313,7 +5313,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(currentTex), target, level, isStencilFormat); if (g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - MGLOG_E("ES glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE"); + MGLOG_E_ONCE("ES glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE"); return; } @@ -5357,7 +5357,7 @@ namespace MobileGL::MG_Backend::DirectGLES { .GetBoundObject(); auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E("CopyTexSubImage2D: No backend texture found for texture %u.", + MGLOG_E_ONCE("CopyTexSubImage2D: No backend texture found for texture %u.", textureObject ? textureObject->GetExternalIndex() : 0); return; } @@ -5395,7 +5395,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); }); if (g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - MGLOG_E("ES glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE"); + MGLOG_E_ONCE("ES glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE"); return; } @@ -6002,7 +6002,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); const SizeT pboOffset = reinterpret_cast(pixels); if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) { - MGLOG_E("ReadPixels: %s readback PBO is too small", what); + MGLOG_E_ONCE("ReadPixels: %s readback PBO is too small", what); return false; } Vector rowBuf(rowBytes); @@ -6156,7 +6156,7 @@ namespace MobileGL::MG_Backend::DirectGLES { s_stencilProgram = ReplicateBlitImpl::BuildProgram(kStencilFetchFragmentSource); if (s_depthProgram == 0 || s_stencilProgram == 0) { s_programsFailed = true; - MGLOG_E("ReadPixels: could not build the depth/stencil readback programs"); + MGLOG_E_ONCE("ReadPixels: could not build the depth/stencil readback programs"); return false; } s_depthUvTransform = g_GLESFuncs.glGetUniformLocation(s_depthProgram, "uUvTransform"); @@ -6414,7 +6414,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ClearGLErrors(); g_GLESFuncs.glDrawArrays(GL_TRIANGLES, 0, 3); if (g_GLESFuncs.glGetError() != GL_NO_ERROR) { - MGLOG_E("ReadPixels: the %s conversion pass failed", stencilAspect ? "stencil" : "depth"); + MGLOG_E_ONCE("ReadPixels: the %s conversion pass failed", stencilAspect ? "stencil" : "depth"); return false; } @@ -6430,7 +6430,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glReadPixels(0, 0, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, outWords.data()); const GLenum readError = g_GLESFuncs.glGetError(); if (readError != GL_NO_ERROR) { - MGLOG_E("ReadPixels: could not read the %s conversion target back: %s", + MGLOG_E_ONCE("ReadPixels: could not read the %s conversion target back: %s", stencilAspect ? "stencil" : "depth", MG_Util::ConvertGLEnumToString(readError).c_str()); return false; } @@ -6450,20 +6450,20 @@ namespace MobileGL::MG_Backend::DirectGLES { FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, slot.framebuffer); if (!StageAspect(slot, candidates, stencilAspect, isDefault, x, y, width, height)) { - MGLOG_E("ReadPixels: no ES-compatible scratch format for the %s source", + MGLOG_E_ONCE("ReadPixels: no ES-compatible scratch format for the %s source", stencilAspect ? "stencil" : "depth"); return false; } if (!EnsureColorTexture(width, height)) { - MGLOG_E("ReadPixels: could not allocate the depth/stencil conversion target"); + MGLOG_E_ONCE("ReadPixels: could not allocate the depth/stencil conversion target"); return false; } FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, s_colorFramebuffer); g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s_colorTexture, 0); if (g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - MGLOG_E("ReadPixels: the depth/stencil conversion target is not renderable"); + MGLOG_E_ONCE("ReadPixels: the depth/stencil conversion target is not renderable"); return false; } @@ -6568,7 +6568,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (DepthStencilSamplingReadImpl::Read(x, y, width, height, &outDepth, /*outStencil=*/nullptr)) { return true; } - MGLOG_E("ReadPixels: no depth readback path is available: native reads failed with %s and the " + MGLOG_E_ONCE("ReadPixels: no depth readback path is available: native reads failed with %s and the " "sampling emulation could not service the source", MG_Util::ConvertGLEnumToString(floatError).c_str()); return false; @@ -6672,7 +6672,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (DepthStencilSamplingReadImpl::Read(x, y, width, height, /*outDepth=*/nullptr, &outStencil)) { return true; } - MGLOG_E("ReadPixels: no stencil readback path is available: native reads failed with %s and the " + MGLOG_E_ONCE("ReadPixels: no stencil readback path is available: native reads failed with %s and the " "sampling emulation could not service the source", MG_Util::ConvertGLEnumToString(packedError).c_str()); return false; @@ -6977,7 +6977,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Bool integerAttachment = attachmentComponentType == GL_INT || attachmentComponentType == GL_UNSIGNED_INT; if (mapping.isInteger != integerAttachment) { - MGLOG_E("Readback conversion: integer-ness of format %s does not match the read buffer, skipping", + MGLOG_E_ONCE("Readback conversion: integer-ness of format %s does not match the read buffer, skipping", MG_Util::ConvertGLEnumToString(format).c_str()); return true; } @@ -7053,7 +7053,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } } if (wideType == GL_NONE) { - MGLOG_E("Readback conversion: ES accepted no wide read type for format %s type %s, skipping readback", + MGLOG_E_ONCE("Readback conversion: ES accepted no wide read type for format %s type %s, skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); return true; } @@ -7236,7 +7236,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Bool convertible = GetReadbackChannelMapping(format, conversionMapping) && GetReadbackDstPixelSize(conversionMapping, type) != 0; if (!useNativeReadback && !convertible) { - MGLOG_E("ReadPixels: format %s with type %s is not implemented yet, skipping readback", + MGLOG_E_ONCE("ReadPixels: format %s with type %s is not implemented yet, skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); return; } @@ -7257,7 +7257,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str()); if (fbStatus != GL_FRAMEBUFFER_COMPLETE) { - MGLOG_E("ReadPixels: bound READ FBO is not complete"); + MGLOG_E_ONCE("ReadPixels: bound READ FBO is not complete"); return; } // ES only guarantees GL_RGBA/GL_UNSIGNED_BYTE and GL_RGBA_INTEGER/GL_(UNSIGNED_)INT for the @@ -7282,7 +7282,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: finished via client-format conversion"); return; } - MGLOG_E("ReadPixels: format %s with type %s is not implemented yet, skipping readback", + MGLOG_E_ONCE("ReadPixels: format %s with type %s is not implemented yet, skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); return; } @@ -7315,7 +7315,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* backendResource = BufferImpl::EnsureBufferResource(pixelPackBufferObject); MGLOG_D("ReadPixels: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); if (!backendResource || backendResource->id == 0) { - MGLOG_E("ReadPixels: No backend buffer found for PBO %u.", + MGLOG_E_ONCE("ReadPixels: No backend buffer found for PBO %u.", pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); return; } @@ -7347,7 +7347,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: finished via client-format conversion after native failure"); return; } - MGLOG_E("ReadPixels: native read of %s/%s failed (%s) and no conversion path covers it, " + MGLOG_E_ONCE("ReadPixels: native read of %s/%s failed (%s) and no conversion path covers it, " "skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str(), MG_Util::ConvertGLEnumToString(nativeReadError).c_str()); @@ -7367,7 +7367,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: Unmapping PBO"); g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER); } else { - MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + MGLOG_E_ONCE("ReadPixels: glMapBufferRange returned nullptr"); } } MGLOG_D("ReadPixels: finished"); @@ -7403,7 +7403,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Bool convertible = GetReadbackChannelMapping(format, conversionMapping) && GetReadbackDstPixelSize(conversionMapping, type) != 0; if (!useNativeReadback && !convertible) { - MGLOG_E("GetTexImage: format %s with type %s is not implemented yet, skipping readback", + MGLOG_E_ONCE("GetTexImage: format %s with type %s is not implemented yet, skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); return; } @@ -7431,7 +7431,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E("GetTexImage: No backend texture found for texture %u.", + MGLOG_E_ONCE("GetTexImage: No backend texture found for texture %u.", textureObject ? textureObject->GetExternalIndex() : 0); return; } @@ -7499,7 +7499,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("GetTexImage: texture storage type = %d", (int)storageType); if (storageType == TextureStorageType::Buffer) { - MGLOG_E("GetTexImage: Texture storage type Buffer is not supported."); + MGLOG_E_ONCE("GetTexImage: Texture storage type Buffer is not supported."); return; } @@ -7511,7 +7511,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // levelRange.y() is GL_TEXTURE_MAX_LEVEL, an inclusive level index — a single-level // texture has range [0, 0] and level 0 must be readable. if (static_cast(level) < levelRange.x() || static_cast(level) > levelRange.y()) { - MGLOG_E("GetTexImage: Requested level %d is out of range (base level %u, max level %u), skipping readback", + MGLOG_E_ONCE("GetTexImage: Requested level %d is out of range (base level %u, max level %u), skipping readback", level, levelRange.x(), levelRange.y()); return; } @@ -7608,15 +7608,15 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } if (!tempFBOComplete) { - MGLOG_E("GetTexImage: READ FBO incomplete and no shadow copy available, skipping readback"); + MGLOG_E_ONCE("GetTexImage: READ FBO incomplete and no shadow copy available, skipping readback"); return; } - MGLOG_E("GetTexImage: format %s with type %s is not implemented yet, skipping readback", + MGLOG_E_ONCE("GetTexImage: format %s with type %s is not implemented yet, skipping readback", MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); return; } if (!tempFBOComplete) { - MGLOG_E("GetTexImage: bound READ FBO is not complete"); + MGLOG_E_ONCE("GetTexImage: bound READ FBO is not complete"); return; } @@ -7644,7 +7644,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* backendResource = BufferImpl::EnsureBufferResource(pixelPackBufferObject); MGLOG_D("GetTexImage: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); if (!backendResource || backendResource->id == 0) { - MGLOG_E("GetTexImage: No backend buffer found for PBO %u.", + MGLOG_E_ONCE("GetTexImage: No backend buffer found for PBO %u.", pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); return; } @@ -7680,7 +7680,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: Unmapping PBO"); g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER); } else { - MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); + MGLOG_E_ONCE("ReadPixels: glMapBufferRange returned nullptr"); } } @@ -8067,7 +8067,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (g_requestedSwapInterval < 0) return; if (!g_EGLFuncs.eglSwapInterval || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE) return; const EGLBoolean ok = g_EGLFuncs.eglSwapInterval(g_Display, g_requestedSwapInterval); - MGLOG_I("DirectGLES: applied native swap interval %d (%s)", g_requestedSwapInterval, + MGLOG_D("DirectGLES: applied native swap interval %d (%s)", g_requestedSwapInterval, ok ? "ok" : "failed"); } } // namespace @@ -8178,12 +8178,12 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool MakeCurrent() { if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE || g_Context == EGL_NO_CONTEXT) { - MGLOG_E("DirectGLES::MakeCurrent failed: EGL display/surface/context is not initialized"); + MGLOG_E_ONCE("DirectGLES::MakeCurrent failed: EGL display/surface/context is not initialized"); return false; } if (!g_EGLFuncs.eglMakeCurrent(g_Display, g_Surface, g_Surface, g_Context)) { const EGLint error = g_EGLFuncs.eglGetError ? g_EGLFuncs.eglGetError() : EGL_SUCCESS; - MGLOG_E("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error); + MGLOG_E_ONCE("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error); return false; } InvalidateEglVerifiedStamp(); @@ -8216,7 +8216,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } if (!g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { const EGLint error = g_EGLFuncs.eglGetError ? g_EGLFuncs.eglGetError() : EGL_SUCCESS; - MGLOG_E("DirectGLES::ReleaseCurrent failed: native eglMakeCurrent returned error 0x%04x", error); + MGLOG_E_ONCE("DirectGLES::ReleaseCurrent failed: native eglMakeCurrent returned error 0x%04x", error); return false; } // Clearing the global owner works from ANY thread (a release request can diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 67671e9c..31eb5d68 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -598,7 +598,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void* ptr = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(size), GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit); if (!ptr) { - MGLOG_E("Ops_AcquirePersistentMap: glMapBufferRange(persistent) failed for buffer %u", + MGLOG_E_ONCE("Ops_AcquirePersistentMap: glMapBufferRange(persistent) failed for buffer %u", resource->id); resource->persistentMapped = false; resource->persistentPtr = nullptr; @@ -716,7 +716,7 @@ namespace MobileGL::MG_Backend::DirectGLES { resource->syncedChangeSerial = bufferObject.GetChangeSerial(); return; } - MGLOG_E("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData", + MGLOG_E_ONCE("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData", resource->id); } UploadRangeNow(*resource, bufferObject, range.start, range.end); @@ -739,7 +739,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(size), GL_MAP_READ_BIT); if (mapped == nullptr) { - MGLOG_E("Ops_ReadbackFromGpu: glMapBufferRange(read) failed for buffer %u", resource->id); + MGLOG_E_ONCE("Ops_ReadbackFromGpu: glMapBufferRange(read) failed for buffer %u", resource->id); return; } bufferObject.WritebackFromBackend({mapped, size}, 0); @@ -993,8 +993,8 @@ namespace MobileGL::MG_Backend::DirectGLES { } else { g_GLESFuncs.glGenBuffers(1, &resource->id); if (resource->id == 0) { - MGLOG_E("Failed to generate buffer object."); - MGLOG_E("ES glGetError(): %s", + MGLOG_E_ONCE("Failed to generate buffer object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); return resource; } @@ -1224,7 +1224,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } } if (id == 0) { - MGLOG_E("Global-UBO ring: persistent storage creation failed (%zu bytes); " + MGLOG_E_ONCE("Global-UBO ring: persistent storage creation failed (%zu bytes); " "falling back to glBufferSubData uploads.", newSize); g_uboRing.creationFailed = true; @@ -1470,8 +1470,8 @@ namespace MobileGL::MG_Backend::DirectGLES { m_clientAttributeBufferIds.fill(0); g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId); if (m_backendVAOId == 0) { - MGLOG_E("Failed to generate vertex array object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to generate vertex array object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated vertex array object with ID: %u.", m_backendVAOId); } @@ -1529,13 +1529,13 @@ namespace MobileGL::MG_Backend::DirectGLES { inline Bool BindAttributeBuffer(const MG_State::GLState::VertexAttribute& attrib) { const auto& bufferObject = attrib.Buffer; if (!bufferObject) { - MGLOG_W("Attribute has no bound buffer, skipping."); + MGLOG_W_ONCE("Attribute has no bound buffer, skipping."); return false; } auto* backendResource = BufferImpl::EnsureBufferResource(bufferObject); if (!backendResource || backendResource->id == 0) { - MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute."); + MGLOG_E_ONCE("No backend buffer found for attribute's buffer, cannot bind attribute."); return false; } @@ -1586,12 +1586,12 @@ namespace MobileGL::MG_Backend::DirectGLES { inline Bool SyncZeroStrideAttribute(Uint attribIndex, const MG_State::GLState::VertexAttribute& attrib) { const auto& bufferObject = attrib.Buffer; if (!bufferObject) { - MGLOG_W("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex); + MGLOG_W_ONCE("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex); return false; } auto* backendResource = BufferImpl::EnsureBufferResource(bufferObject); if (!backendResource || backendResource->id == 0) { - MGLOG_E("No backend buffer for zero-stride attribute %u, cannot bind it.", attribIndex); + MGLOG_E_ONCE("No backend buffer for zero-stride attribute %u, cannot bind it.", attribIndex); return false; } @@ -1621,7 +1621,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateVAOObject) { - MGLOG_E("State VAO object is null, cannot sync to backend."); + MGLOG_E_ONCE("State VAO object is null, cannot sync to backend."); return; } @@ -1694,7 +1694,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // because the array stayed enabled with no pointer the failed call could set. // The type test therefore covers the storage, not the spelling. if (attrib.IsLong || attrib.Type == DataType::Float64) { - MGLOG_I("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array, which this " + MGLOG_W_ONCE("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array, which this " "backend cannot feed - disabling the array", attribIndex); g_GLESFuncs.glDisableVertexAttribArray(attribIndex); @@ -1761,7 +1761,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } if (formatMayBeRefused && g_GLESFuncs.glGetError() != GL_NO_ERROR) { - MGLOG_I("DirectGLES: the driver refused the vertex format of attribute %u " + MGLOG_W_ONCE("DirectGLES: the driver refused the vertex format of attribute %u " "(size=%d bgra=%d type=%s) - disabling the array so the draw cannot " "fetch through a pointer the driver never accepted", attribIndex, attrib.Size, attrib.IsBgra ? 1 : 0, @@ -1784,7 +1784,7 @@ namespace MobileGL::MG_Backend::DirectGLES { BufferImpl::BindBufferId(GL_ELEMENT_ARRAY_BUFFER, backendResource->id); indexBufferSynced = true; } else { - MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer."); + MGLOG_W_ONCE("No backend buffer found for index buffer binding, cannot bind index buffer."); } } else { g_GLESFuncs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -1842,7 +1842,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (bufferId == 0) { g_GLESFuncs.glGenBuffers(1, &bufferId); if (bufferId == 0) { - MGLOG_E("Failed to create client-side vertex attribute upload buffer."); + MGLOG_E_ONCE("Failed to create client-side vertex attribute upload buffer."); continue; } } @@ -1877,8 +1877,8 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glGenTextures(1, &m_backendTextureId); m_contextGeneration = g_backendContextGeneration; if (m_backendTextureId == 0) { - MGLOG_E("Failed to generate texture object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to generate texture object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated texture object with ID: %u.", m_backendTextureId); } @@ -1956,8 +1956,8 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glGenTextures(1, &m_backendTextureId); m_contextGeneration = g_backendContextGeneration; if (m_backendTextureId == 0) { - MGLOG_E("Failed to regenerate texture object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to regenerate texture object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Regenerated texture object with ID: %u.", m_backendTextureId); } @@ -2391,7 +2391,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void BackendTextureObject::SyncMipmapsToBackend( const SharedPtr& stateTextureObject) { if (!stateTextureObject) { - MGLOG_E("State texture object is null, cannot sync to backend."); + MGLOG_E_ONCE("State texture object is null, cannot sync to backend."); return; } @@ -2424,7 +2424,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { - MGLOG_E(" Texture target %s is not supported, skipping.", + MGLOG_E_ONCE(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } @@ -2576,7 +2576,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(uploadSize.z()), 0, glFormat, glType, uploadData); break; default: - MGLOG_E("Unhandled texture target %s", + MGLOG_E_ONCE("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(stateTextureObject->GetTarget()).c_str()); break; } @@ -2654,7 +2654,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(storageSize.z())); break; default: - MGLOG_E("Unhandled immutable texture target %s", + MGLOG_E_ONCE("Unhandled immutable texture target %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); break; } @@ -2776,7 +2776,7 @@ namespace MobileGL::MG_Backend::DirectGLES { break; } default: { - MGLOG_E("Unhandled texture target %s", + MGLOG_E_ONCE("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); } } @@ -2828,7 +2828,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto byteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); if (byteSize == 0) { - MGLOG_W("Mipmap level %d has no data, skipping update.", level); + MGLOG_D("Mipmap level %d has no data, skipping update.", level); continue; } @@ -2979,7 +2979,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } break; default: - MGLOG_E("Unhandled texture target %s", + MGLOG_E_ONCE("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(stateTextureObject->GetTarget()).c_str()); break; } @@ -3007,7 +3007,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Need to sync texture buffer if not synced yet auto* backendBufferResource = BufferImpl::EnsureBufferResource(buffer); if (!backendBufferResource || backendBufferResource->id == 0) { - MGLOG_E("Failed to sync backing buffer for texture buffer with ID: %u", + MGLOG_E_ONCE("Failed to sync backing buffer for texture buffer with ID: %u", stateTextureObject->GetExternalIndex()); return; } @@ -3026,15 +3026,15 @@ namespace MobileGL::MG_Backend::DirectGLES { // below that without EXT/OES_texture_buffer. Calling it was an unconditional // null dereference. There is no conformant way to refuse the call (it is valid // in the context MobileGL claims), so the texture is left unbacked and the - // reason is stated once per respecify at a level that survives the shipped - // INFO build - MGLOG_E is compiled out there, which is exactly how this class - // of defect stays invisible. + // reason is stated once per object, latched by the flag below. It was parked + // at MGLOG_I while the level ordering compiled MGLOG_W out of INFO builds; + // W is the correct level and now survives there. if (!AreBufferTexturesSupported()) { if (m_bufferTextureUnsupportedReported) { break; } m_bufferTextureUnsupportedReported = true; - MGLOG_I("Texture buffer %u cannot be backed: this ES driver has no buffer " + MGLOG_W("Texture buffer %u cannot be backed: this ES driver has no buffer " "textures (%s). Every draw sampling it will read zero and every " "shader declaring a samplerBuffer will fail to compile. MobileGL " "still advertises GL_MAX_TEXTURE_BUFFER_SIZE = %d because an " @@ -3062,7 +3062,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } else if (!CallTexBufferRange(GL_TEXTURE_BUFFER, glInternalFormat, backendId, static_cast(rangeOffset), static_cast(rangeSize))) { - MGLOG_I("Texture buffer %u names a sub-range but the driver has no " + MGLOG_W_ONCE("Texture buffer %u names a sub-range but the driver has no " "glTexBufferRange; binding the whole buffer instead", stateTextureObject->GetExternalIndex()); CallTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId); @@ -3080,7 +3080,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // TextureStorageType is {Mipmap, Buffer}, both handled above, so this is a // backstop for a state object that grew a new storage kind. Skipping the upload // renders wrong; throwing unwinds through the C GL ABI and kills the process. - MGLOG_I("DirectGLES texture sync: no upload path for storage type %d on texture %u; " + MGLOG_E_ONCE("DirectGLES texture sync: no upload path for storage type %d on texture %u; " "skipping this sync", static_cast(stateTextureObject->GetStorageType()), stateTextureObject->GetExternalIndex()); @@ -3115,7 +3115,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif if (!stateTextureObject) { - MGLOG_E("State texture object is null, cannot sync to backend."); + MGLOG_E_ONCE("State texture object is null, cannot sync to backend."); return; } @@ -3136,7 +3136,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { - MGLOG_E(" Texture target %s is not supported, skipping.", + MGLOG_E_ONCE(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } @@ -3225,7 +3225,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif if (!stateTextureObject) { - MGLOG_E("State texture object is null, cannot sync to backend."); + MGLOG_E_ONCE("State texture object is null, cannot sync to backend."); return; } @@ -3245,7 +3245,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D(" Texture target for syncing is %s", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); if (!IsSupportedTextureTarget(targetInternal)) { - MGLOG_E(" Texture target %s is not supported, skipping.", + MGLOG_E_ONCE(" Texture target %s is not supported, skipping.", MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); return; } @@ -3393,8 +3393,8 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glGenFramebuffers(1, &m_backendFBOId); m_contextGeneration = g_backendContextGeneration; if (m_backendFBOId == 0) { - MGLOG_E("Failed to generate framebuffer object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to generate framebuffer object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated framebuffer object with ID: %u.", m_backendFBOId); } @@ -3530,7 +3530,7 @@ namespace MobileGL::MG_Backend::DirectGLES { backendTextureObject = newTextureSlot; } if (!backendTextureObject) { - MGLOG_E("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__); + MGLOG_E_ONCE("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__); return false; } backendTextureObject->SyncMipmapsToBackend(textureObject); @@ -3899,7 +3899,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateFBOObject) { - MGLOG_E("State FBO object is null, cannot sync to backend."); + MGLOG_E_ONCE("State FBO object is null, cannot sync to backend."); return; } MGLOG_D("Syncing FBO with backend ID %u to backend for state ID %u, as %s FBO", m_backendFBOId, @@ -4418,8 +4418,8 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif m_backendProgramId = g_GLESFuncs.glCreateProgram(); if (m_backendProgramId == 0) { - MGLOG_E("Failed to create program object in backend."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to create program object in backend."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Created backend program object with ID: %u", m_backendProgramId); @@ -4658,7 +4658,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateProgramObject) { - MGLOG_E("State program object is null, skipping backend sync."); + MGLOG_E_ONCE("State program object is null, skipping backend sync."); return; } // Recorded before either early return below, so Use() can always name the GL @@ -4671,7 +4671,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // a LINK_STATUS it already reported true, so "linked but not drawable" is the // answer, and this is where the ES backend expresses it. if (!stateProgramObject->GetLinkStatus() || !stateProgramObject->GetSpirvStatus()) { - MGLOG_E("Program object is not linked or has no generated SPIR-V, skipping backend sync. State " + MGLOG_E_ONCE("Program object is not linked or has no generated SPIR-V, skipping backend sync. State " "program ID: %u", stateProgramObject->GetExternalIndex()); return; @@ -4763,7 +4763,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLuint backendShaderId = g_GLESFuncs.glCreateShader(glShaderType); if (backendShaderId == 0) { - MGLOG_E("Failed to create backend shader for attachment."); + MGLOG_E_ONCE("Failed to create backend shader for attachment."); continue; } String source; @@ -4773,12 +4773,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // ES 3.2 or EXT/OES_texture_buffer on the host. Without it SPIRV-Cross emits // `#extension GL_EXT_texture_buffer : require` and the driver rejects both that // and the isamplerBuffer keyword - the program never links and every draw using it - // becomes a silent no-op. Say so here, naming the stage, instead of leaving a - // driver info log the shipped INFO build compiles out (MGLOG_E is inactive there). + // becomes a silent no-op. Say so here, naming the stage. Deliberately unlatched: + // this is bounded by program count, and which stage failed is the whole point. // Gated on the capability so the module walk never runs on a healthy driver. if (!AreBufferTexturesSupported() && MG_Util::ShaderTranspiler::ShaderCompiler::ModuleDeclaresBufferTextureSampler(spirvCode)) { - MGLOG_I("Program %u stage %s samples a buffer texture, which this ES driver " + MGLOG_E("Program %u stage %s samples a buffer texture, which this ES driver " "cannot provide (%s). The shader will not compile and the program will " "not link; every draw using it is a no-op.", m_backendProgramId, @@ -4947,14 +4947,14 @@ namespace MobileGL::MG_Backend::DirectGLES { spvcSession.Compile(&result); if (!result) { - // MGLOG_I, for the same reason as the compile- and link-failure diagnostics - // below: every CI, retrace and release build compiles at - // MOBILEGL_LOG_LEVEL_INFO, where MGLOG_E expands to nothing. A stage that + // MGLOG_E, unlatched, like the compile- and link-failure diagnostics below: + // one line per failing stage is bounded by program count and naming the + // stage is the entire diagnostic value. A stage that // never reaches the driver leaves the program short of that stage, so the // link fails with an EMPTY driver info log - the least debuggable failure // MobileGL can produce, and what hid the whole // KHR-GL43.vertex_attrib_binding family behind "the draw captured zeros". - MGLOG_I("Shader transpilation to ESSL failed. State program ID: %u, stage: %s, " + MGLOG_E("Shader transpilation to ESSL failed. State program ID: %u, stage: %s, " "SPIRV-Cross error: %s", stateProgramObject->GetExternalIndex(), MG_Util::ConvertGLEnumToString(glShaderType).c_str(), @@ -5041,14 +5041,13 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector log(static_cast(logLength) + 1, '\0'); g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data()); log.back() = '\0'; - // MGLOG_I, deliberately. Every CI, retrace and release build compiles at - // MOBILEGL_LOG_LEVEL_INFO, where MGLOG_E and MGLOG_W expand to nothing - // (Log.h orders DEBUG < WARN < ERROR < INFO), so this diagnostic used to - // exist only in debug builds: the Android retrace artifact carried 294 - // INFO lines and zero ERROR lines while two generated shaders were being - // rejected outright, and the lane could not say why it was rendering an - // empty translucent layer. A shader the driver refuses is never noise. - MGLOG_I("Shader compilation failed. State program ID: %u, stage: %s, backend shader ID: " + // MGLOG_E, unlatched. This was parked at MGLOG_I while the level ordering + // compiled E and W out of every INFO build: the Android retrace artifact + // carried 294 INFO lines and zero ERROR lines while two generated shaders + // were being rejected outright, and the lane could not say why it was + // rendering an empty translucent layer. A shader the driver refuses is + // never noise, and one line per refused shader is bounded by program count. + MGLOG_E("Shader compilation failed. State program ID: %u, stage: %s, backend shader ID: " "%u, driver log: %s", stateProgramObject->GetExternalIndex(), MG_Util::ConvertGLEnumToString(glShaderType).c_str(), backendShaderId, @@ -5132,7 +5131,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // MGLOG_I for the same reason as the compile failure above: a program that // links nothing no-ops every draw that uses it, and that has to be readable // in an INFO-level artifact. - MGLOG_I("Program linking failed. State program ID: %u, backend program ID: %u, driver log: %s", + MGLOG_E("Program linking failed. State program ID: %u, backend program ID: %u, driver log: %s", stateProgramObject->GetExternalIndex(), m_backendProgramId, log.data()); } else { MGLOG_D("Program linked successfully. ID: %u", m_backendProgramId); @@ -5224,7 +5223,7 @@ namespace MobileGL::MG_Backend::DirectGLES { m_globalUboBackendBlockSize = static_cast(blockDataSize); } } else { - MGLOG_W("Program %u has frontend global UBO storage, but backend has no %s block.", + MGLOG_W_ONCE("Program %u has frontend global UBO storage, but backend has no %s block.", stateProgramObject->GetExternalIndex(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME); } } @@ -5300,13 +5299,12 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } if (!m_backendProgramUsable) { - // MGLOG_I, not MGLOG_W: at MOBILEGL_LOG_LEVEL_INFO - the level the shipped - // fordebug builds compile at - only I and F survive, and this is precisely the - // line those builds need. Every draw made with this program renders nothing and - // raises no GL error, so without it the only symptom is a framebuffer that kept - // its clear colour. The early return above keeps it to at most one line per - // program state change, not one per draw. - MGLOG_I("Backend program for GL program %u is unusable (a shader failed to transpile, " + // Every draw made with this program renders nothing and raises no GL error, so + // without this line the only symptom is a framebuffer that kept its clear + // colour. Latched: the early return above only dedupes CONSECUTIVE binds, so an + // app alternating a healthy and a broken program would otherwise log every + // single draw. Parked at MGLOG_I until the level ordering was fixed. + MGLOG_E_ONCE("Backend program for GL program %u is unusable (a shader failed to transpile, " "compile or link); binding program 0 - draws with it will render nothing", m_frontendProgramId); } @@ -5354,8 +5352,8 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glGenSamplers(1, &m_backendSamplerId); m_contextGeneration = g_backendContextGeneration; if (m_backendSamplerId == 0) { - MGLOG_E("Failed to generate sampler object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to generate sampler object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } else { MGLOG_D("Generated sampler object with ID: %u.", m_backendSamplerId); } @@ -5387,7 +5385,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateSamplerObject) { - MGLOG_E("State sampler object is null, cannot sync to backend."); + MGLOG_E_ONCE("State sampler object is null, cannot sync to backend."); return; } @@ -5498,8 +5496,8 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glGenRenderbuffers(1, &m_backendRBOId); m_contextGeneration = g_backendContextGeneration; if (m_backendRBOId == 0) { - MGLOG_E("Failed to generate renderbuffer object."); - MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); + MGLOG_E_ONCE("Failed to generate renderbuffer object."); + MGLOG_E_ONCE("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); } } @@ -5531,7 +5529,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif if (!stateRBOObject) { - MGLOG_E("State RBO object is null, cannot sync to backend."); + MGLOG_E_ONCE("State RBO object is null, cannot sync to backend."); return; } diff --git a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp index 5756c452..814da888 100644 --- a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp @@ -252,7 +252,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { g_resolvedTier = ResolveTier(g_GLESCapabilities, g_GLESFuncs, MG_Config::Features.EsprytMultiDrawMode, &g_tierResolution); - MGLOG_I("DirectGLES multi-draw: %s", g_tierResolution.c_str()); + MGLOG_D("DirectGLES multi-draw: %s", g_tierResolution.c_str()); } // Which tiers have already announced themselves, one bit per GLESMultiDrawMode. @@ -267,7 +267,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { const Uint32 bit = 1u << static_cast(tier); if (g_announcedTiers & bit) return; g_announcedTiers |= bit; - MGLOG_I("DirectGLES multi-draw: first batch executed via tier \"%s\"", TierName(tier)); + MGLOG_D("DirectGLES multi-draw: first batch executed via tier \"%s\"", TierName(tier)); } // The tier this particular batch can actually take. A tier is demoted here when @@ -490,7 +490,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { const Uint8* source = ResolveSubDrawIndices(indexBuffer, indexBufferBytes, indexBufferSize, indices[i], subDrawCount, indexSize); if (!source) { - MGLOG_E("DirectGLES multi-draw (drawelements tier): sub-draw %d reads outside the bound index " + MGLOG_E_ONCE("DirectGLES multi-draw (drawelements tier): sub-draw %d reads outside the bound index " "buffer; skipping the batch", i); return false; @@ -596,7 +596,7 @@ void main() { const GLuint shader = g_GLESFuncs.glCreateShader(GL_COMPUTE_SHADER); if (shader == 0) { - MGLOG_E("DirectGLES multi-draw (compute tier): glCreateShader(GL_COMPUTE_SHADER) failed"); + MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): glCreateShader(GL_COMPUTE_SHADER) failed"); return false; } const char* source = kFlattenComputeSource; @@ -607,14 +607,14 @@ void main() { if (status != GL_TRUE) { char log[1024] = {}; g_GLESFuncs.glGetShaderInfoLog(shader, sizeof(log) - 1, nullptr, log); - MGLOG_E("DirectGLES multi-draw (compute tier): index-flattening shader failed to compile: %s", log); + MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): index-flattening shader failed to compile: %s", log); g_GLESFuncs.glDeleteShader(shader); return false; } const GLuint program = g_GLESFuncs.glCreateProgram(); if (program == 0) { - MGLOG_E("DirectGLES multi-draw (compute tier): glCreateProgram failed"); + MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): glCreateProgram failed"); g_GLESFuncs.glDeleteShader(shader); return false; } @@ -625,7 +625,7 @@ void main() { if (status != GL_TRUE) { char log[1024] = {}; g_GLESFuncs.glGetProgramInfoLog(program, sizeof(log) - 1, nullptr, log); - MGLOG_E("DirectGLES multi-draw (compute tier): index-flattening program failed to link: %s", log); + MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): index-flattening program failed to link: %s", log); g_GLESFuncs.glDeleteProgram(program); return false; } @@ -635,7 +635,7 @@ void main() { g_uDrawCount = g_GLESFuncs.glGetUniformLocation(program, "uDrawCount"); g_uTotalIndices = g_GLESFuncs.glGetUniformLocation(program, "uTotalIndices"); g_computeProgramFailed = false; - MGLOG_I("DirectGLES multi-draw: index-flattening compute program ready (id %u)", program); + MGLOG_D("DirectGLES multi-draw: index-flattening compute program ready (id %u)", program); return true; } @@ -920,7 +920,7 @@ void main() { feedBaseVertex); } if (!drawn) { - MGLOG_E("DirectGLES multi-draw: no usable tier for a %d sub-draw batch (mode 0x%x, type 0x%x); " + MGLOG_E_ONCE("DirectGLES multi-draw: no usable tier for a %d sub-draw batch (mode 0x%x, type 0x%x); " "the batch was dropped", drawcount, mode, type); } diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 6f97ef4f..47c97902 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -1176,7 +1176,7 @@ namespace MobileGL::MG_Backend::DirectGLES { ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif for (GLenum err = g_GLESFuncs.glGetError(); err != GL_NO_ERROR; err = g_GLESFuncs.glGetError()) { - MGLOG_E("-> GLES Error: %s", MG_Util::ConvertGLEnumToString(err).c_str()); + MGLOG_D("-> GLES Error: %s", MG_Util::ConvertGLEnumToString(err).c_str()); } } @@ -1642,7 +1642,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(sliceCount - 1) * dstImageStride + static_cast(sliceHeight - 1) * dstRowStride + dstRowBytes; if (requiredSize > pixelPackBufferObject->GetSize()) { - MGLOG_E("Readback conversion: pixel pack buffer is too small"); + MGLOG_E_ONCE("Readback conversion: pixel pack buffer is too small"); return true; } } diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index a870db33..94470bfa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -269,14 +269,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { drawBuffer->SyncPersistentMappedRange(); const SizeT commandOffset = reinterpret_cast(indirect); if (drawBuffer->MappedData() == nullptr || commandOffset + requiredBytes > drawBuffer->GetSize()) { - MGLOG_E("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); + MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); return nullptr; } return drawBuffer->MappedData() + commandOffset; } if (!indirect) { - MGLOG_E("%s skipped: indirect pointer is null", label); + MGLOG_E_ONCE("%s skipped: indirect pointer is null", label); return nullptr; } @@ -398,7 +398,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { stride = sizeof(DrawArraysIndirectCommand); } if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawArraysIndirectCommand)); return; } @@ -446,20 +446,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { stride = sizeof(DrawArraysIndirectCommand); } if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", stride, sizeof(DrawArraysIndirectCommand)); return; } auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); if (!parameterBuffer || drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); return; } parameterBuffer->SyncPersistentMappedRange(); if (parameterBuffer->MappedData() == nullptr) { - MGLOG_E("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read parameter buffer"); + MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read parameter buffer"); return; } @@ -513,7 +513,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("DrawElementsIndirect skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("DrawElementsIndirect skipped: unsupported index type 0x%x", type); return; } @@ -1009,7 +1009,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // shift - the hardware divide was the hottest instruction of this loop. const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("MultiDrawElements skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("MultiDrawElements skipped: unsupported index type 0x%x", type); return; } const Uint32 indexSizeShift = static_cast(std::countr_zero(indexSize)); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index 145aa72b..1d86aac1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -205,7 +205,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // commands away. The device is gone on that path anyway - stay silent-safe // rather than trade a lost device for a barrier into a closed buffer. if (frame.hasCommandBufferRecorded) { - MGLOG_E("TransitionToPresent: command buffer already closed; skipping the present barrier"); + MGLOG_E_ONCE("TransitionToPresent: command buffer already closed; skipping the present barrier"); return false; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index de69915f..458f0734 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -259,7 +259,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // is the correct price for a broken pipeline and is bounded by the draw itself being // skipped. if (pipeline == VK_NULL_HANDLE) { - MGLOG_I("PipelineFactory::GetOrCreatePipeline: creation failed for hash=0x%llx " + // Unlatched, like the CreatePipeline report it accompanies: a pipeline MobileGL + // assembled and the driver refused is a broken invariant, not an expected failure, + // so it stays loud for as long as it is reachable. Raised from MGLOG_I once the + // Log.h ordering fix made MGLOG_E live in INFO builds. + MGLOG_E("PipelineFactory::GetOrCreatePipeline: creation failed for hash=0x%llx " "programHash=0x%llx; not caching the failure", static_cast(hash), static_cast(payload.programHash)); @@ -506,7 +510,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool s_warnedHalfTessellatedPipeline = false; if (!s_warnedHalfTessellatedPipeline) { s_warnedHalfTessellatedPipeline = true; - MGLOG_E("PipelineFactory::CreatePipeline: refusing a pipeline with %s tessellation stage and " + MGLOG_E_ONCE("PipelineFactory::CreatePipeline: refusing a pipeline with %s tessellation stage and " "no %s stage (VUID-VkGraphicsPipelineCreateInfo-pStages-00730). programHash=0x%llx " "patchControlPoints=%u. Its draws are skipped; logged once.", hasTessEval ? "an evaluation" : "a control", @@ -537,6 +541,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipeline pipeline = VK_NULL_HANDLE; const VkResult result = vkCreateGraphicsPipelines(m_device, m_pipelineCache, 1, &gpi, nullptr, &pipeline); + // Loud, at MGLOG_F, and deliberately NOT latched. vkCreateGraphicsPipelines refusing a + // pipeline MobileGL assembled is a should-never-happen state, and the driver's own + // answer is VK_ERROR_UNKNOWN - no information at all - so this dump is the entire + // diagnosis. It is not an expected failure mode, so the one-shot rule that quiets W/E + // does not apply: while this is reachable it should keep saying so on every draw. + // GetOrCreatePipeline deliberately does not cache the failure, which is what makes that + // repetition happen; if the repetition ever needs to stop, fix the pipeline, not the log. if (result != VK_SUCCESS) { MGLOG_F("PipelineFactory::CreatePipeline failed: result=%s (%d) programHash=0x%llx vertexInputHash=0x%llx stageCount=%u topology=%s(%d) colorAttachmentCount=%u samples=%s(%d) subpass=%u", VkResultToString(result), @@ -569,8 +580,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.vertexInputState->vertexAttributeDescriptionCount); // The driver's own answer is VK_ERROR_UNKNOWN, i.e. no information at all, so the only // way to work out WHICH shader it choked on (the open sampler-array-in-struct - // investigation) is to name the modules. MGLOG_I, not _D/_E: this must survive in the - // INFO-level builds that CTS actually runs against. + // investigation) is to name the modules. MGLOG_I, not _D: this is part of a + // should-never-happen report and must survive in the INFO-level builds that CTS + // actually runs against, alongside the MGLOG_F lines above. if (payload.stageSpirvDigests) { for (SizeT i = 0; i < payload.stageSpirvDigests->size(); ++i) { const auto& digest = (*payload.stageSpirvDigests)[i]; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index cfddc618..cff25d7e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -376,12 +376,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { spv_diagnostic diagnostic = nullptr; const spv_result_t result = spvValidateWithOptions(context, options, &binary, &diagnostic); if (result != SPV_SUCCESS) { - // MGLOG_I, not E: at the INFO compile level of the CI/test lanes that arm - // the validation switch, MGLOG_E is compiled out (Log.h orders - // DEBUG < WARN < ERROR < INFO) and the VUID would never reach a log. The - // latch is what a test harness asserts on. + // MGLOG_E, unlatched: reaching here already requires the validation switch to + // be armed, which bounds the volume, and each VUID names a different defect. + // (Parked at MGLOG_I until the Log.h level ordering was fixed, when E was + // compiled out of every INFO build.) The latch is what a test harness asserts on. MG_Util::ShaderTranspiler::ShaderCompiler::NoteSpirvValidationFailure(); - MGLOG_I( + MGLOG_E( "ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d index=%zu msg=%s", static_cast(shaderStage), programExternalIndex, @@ -1266,7 +1266,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (SizeT i = 1; i < group.offsets.size(); ++i) { if (group.elementBytes == 0 || group.offsets[i] != group.offsets[i - 1] + group.elementBytes) { - MGLOG_I("XfbCaptureDecoratePass: block member %u of type %%%u is captured with a " + MGLOG_D("XfbCaptureDecoratePass: block member %u of type %%%u is captured with a " "non-contiguous element set; the capture layout will differ from GL's", key.second, key.first); break; @@ -1855,16 +1855,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { // unification and the set->0 normalisation this function exists to do. A // program with an image array plus any second descriptor got aliased // bindings out of that, and a DEBUG build trapped on the same program. - // Which is also why the message below is MGLOG_I: MGLOG_E is compiled out - // of an INFO build, so a refusal that only said MGLOG_E said nothing at all - // in the builds that ship. + // The refusal below is MGLOG_E and per-program-compile, so it reports every + // program it declines. It spent time at MGLOG_I because the old level + // ordering compiled E out of the builds that ship. const Bool arraySupportedForKind = kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic || kind == ProgramFactory::DescriptorBindingKind::StorageBuffer || kind == ProgramFactory::DescriptorBindingKind::StorageImage || kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler; if (binding->count != 1 && !arraySupportedForKind) { - MGLOG_I("ProgramFactory: descriptor arrays are unsupported for this descriptor " + MGLOG_E("ProgramFactory: descriptor arrays are unsupported for this descriptor " "kind (name='%s' count=%u type=%d)", binding->name ? binding->name : "", binding->count, static_cast(binding->descriptor_type)); @@ -2468,7 +2468,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // inert; a device whose binding cap is smaller than a shader's array is not a // configuration MobileGL can serve at all. Needs a >maxBindings-element array to // reach (256 on desktop, ~16 on mobile). - MGLOG_I("ProgramFactory::ReflectLayout: %s array '%s' at binding %u has %u elements, past the %u " + MGLOG_D("ProgramFactory::ReflectLayout: %s array '%s' at binding %u has %u elements, past the %u " "this device can describe - declining the program", kindLabel, uniformName.c_str(), binding, count, maxBindings); outDeclined = true; @@ -2476,7 +2476,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, baseLocation + static_cast(count - 1u))) { - MGLOG_I("ProgramFactory::ReflectLayout: %s array '%s' at binding %u spans %u descriptors but the " + MGLOG_D("ProgramFactory::ReflectLayout: %s array '%s' at binding %u spans %u descriptors but the " "reflection reserved fewer uniform locations for it (base=%d) - a multi-dimensional array " "is the usual cause, and MobileGL declines it rather than resolve elements onto a " "neighbouring uniform", @@ -2713,7 +2713,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // a Uint16 on the way, where 65536 would silently become 0. const Uint32 storageArrayCount = std::max(1u, sampler->count); if (storageArrayCount > m_maxBindings) { - MGLOG_I("ProgramFactory::ReflectLayout: storage block array '%s' at binding %u has %u " + MGLOG_D("ProgramFactory::ReflectLayout: storage block array '%s' at binding %u has %u " "elements, past the %u this device can describe - declining the program", uniformName.c_str(), binding, storageArrayCount, m_maxBindings); entry.declinedDescriptors = true; @@ -2736,7 +2736,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // so at a level that survives a release build, because dropping the binding // leaves the shader reading a descriptor the layout never declared. if (sampler->count > 1) { - MGLOG_I("ProgramFactory::ReflectLayout: declining '%s' at binding %u - a %u-element " + MGLOG_E("ProgramFactory::ReflectLayout: declining '%s' at binding %u - a %u-element " "descriptor array with no frontend uniform location (a multi-dimensional array " "of samplers or images is the known cause)", uniformName.c_str(), binding, sampler->count); @@ -3201,7 +3201,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // "the layout and the shader disagree", so route it through that. Set AFTER ReflectLayout, // which clears the flag. if (!remapOk) { - MGLOG_I("ProgramFactory::GetOrCreateProgram: declining program %u - its descriptor bindings could not " + MGLOG_E("ProgramFactory::GetOrCreateProgram: declining program %u - its descriptor bindings could not " "be remapped, so the layout does not describe what the shader reads", program.GetExternalIndex()); entry.declinedDescriptors = true; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp index 0f5dc4d3..a2743a16 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -157,7 +157,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("Got %d surface formats:", swapchainCapabilities.surfaceFormats.size()); for (const auto& sf : swapchainCapabilities.surfaceFormats) { - MGLOG_I(" [%s, %s]", string_VkFormat(sf.format), string_VkColorSpaceKHR(sf.colorSpace)); + MGLOG_D(" [%s, %s]", string_VkFormat(sf.format), string_VkColorSpaceKHR(sf.colorSpace)); } const auto pickedSurfaceFormat = ChooseSwapchainSurfaceFormat(swapchainCapabilities.surfaceFormats); @@ -166,7 +166,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("Got %d present modes:", swapchainCapabilities.presentModes.size()); for (const auto& pm : swapchainCapabilities.presentModes) { - MGLOG_I(" %s", string_VkPresentModeKHR(pm)); + MGLOG_D(" %s", string_VkPresentModeKHR(pm)); } const auto presentMode = ChooseSwapchainPresentMode(swapchainCapabilities.presentModes); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 7dea4533..15a46c66 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -157,7 +157,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorPool initialPool = VK_NULL_HANDLE; if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) { - MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u", + MGLOG_E_ONCE("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u", frameIndex); Shutdown(); return false; @@ -345,13 +345,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { fallbackHolder = GetFallbackTexture(preferredTarget); texture = fallbackHolder.get(); if (texture == nullptr) { - MGLOG_E("ResolveSamplerDescriptor: no fallback texture available for binding=%u ('%s') " + MGLOG_E_ONCE("ResolveSamplerDescriptor: no fallback texture available for binding=%u ('%s') " "location=%d unit=%d target=%d", binding, programObj.samplerNameByBinding[binding].c_str(), location, unit, static_cast(preferredTarget)); return false; } - MGLOG_W( + MGLOG_W_ONCE( "ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d", binding, programObj.samplerNameByBinding[binding].c_str(), location, unit, static_cast(preferredTarget)); @@ -360,7 +360,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); if (samplerToUse == nullptr) { - MGLOG_E( + MGLOG_E_ONCE( "ResolveSamplerDescriptor: sampler binding %u ('%s') has no sampler object (textureId=%d location=%d unit=%d)", binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), location, unit); @@ -368,7 +368,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkTextureManager::TextureResource* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { - MGLOG_E( + MGLOG_E_ONCE( "ResolveSamplerDescriptor: sampler binding %u ('%s') failed to create/sync texture resource (textureId=%d target=%d location=%d unit=%d)", binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), static_cast(texture->GetTarget()), location, unit); @@ -380,7 +380,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Int attachmentLevel = 0; if (drawFbo && FindFramebufferAttachmentForTexture(*drawFbo, *texture, attachmentType, attachmentLevel)) { - MGLOG_W("ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound " + MGLOG_W_ONCE("ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound " "for sampling at binding=%u, but is also attached to drawFbo=%u as %s (level=%d, " "trackedLayout=%d)", texture->GetExternalIndex(), binding, drawFbo->GetExternalIndex(), @@ -390,7 +390,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool readyForSampling = m_textureManager->TransitionTextureForSampling(commandBuffer, *texture); if (!readyForSampling) { - MGLOG_E("ResolveSamplerDescriptor: failed to transition textureId=%d for sampler binding=%u", + MGLOG_E_ONCE("ResolveSamplerDescriptor: failed to transition textureId=%d for sampler binding=%u", texture->GetExternalIndex(), binding); return false; } @@ -432,7 +432,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } if (sampledViewFormat == VK_FORMAT_UNDEFINED) { - MGLOG_E("ResolveSamplerDescriptor: no compatible sampled view for binding=%u ('%s') " + MGLOG_E_ONCE("ResolveSamplerDescriptor: no compatible sampled view for binding=%u ('%s') " "textureId=%d imageFormat=%d numericDomain=%d", binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), static_cast(resource->format), static_cast(numericDomain)); @@ -445,7 +445,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { ? resource->sampledView : m_textureManager->GetOrCreateSampledImageView(*texture, sampledViewFormat); if (sampledImageView == VK_NULL_HANDLE) { - MGLOG_E("ResolveSamplerDescriptor: failed to resolve sampled view for binding=%u ('%s') " + MGLOG_E_ONCE("ResolveSamplerDescriptor: failed to resolve sampled view for binding=%u ('%s') " "textureId=%d imageFormat=%d viewFormat=%d numericDomain=%d", binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), static_cast(resource->format), static_cast(sampledViewFormat), @@ -671,14 +671,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { SharedPtr texture; if (!ResolveSamplerTexture(program, programObj, binding, texture) || texture == nullptr) { - MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') is unbound", binding, + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') is unbound", binding, programObj.samplerNameByBinding[binding].c_str()); return false; } if (texture->GetStorageType() != TextureStorageType::Buffer || texture->GetTarget() != TextureTarget::TextureBuffer) { - MGLOG_E( + MGLOG_E_ONCE( "ResolveTexelBufferDescriptor: binding %u ('%s') expected texture buffer, got textureId=%u target=%d storage=%d", binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), static_cast(texture->GetTarget()), static_cast(texture->GetStorageType())); @@ -688,14 +688,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* textureBuffer = static_cast(texture.get()); const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject(); if (bufferObject == nullptr) { - MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') has no GL buffer bound", + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') has no GL buffer bound", binding, programObj.samplerNameByBinding[binding].c_str()); return false; } BufferSlice slice{}; if (!m_bufferManager->AcquireResidentSlice(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) { - MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", bufferObject->GetExternalIndex(), texture->GetExternalIndex()); return false; } @@ -703,7 +703,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto internalFormat = textureBuffer->GetFormat(); const VkFormat vkFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat); if (vkFormat == VK_FORMAT_UNDEFINED) { - MGLOG_E("ResolveTexelBufferDescriptor: unsupported texture buffer internal format %d", + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: unsupported texture buffer internal format %d", static_cast(internalFormat)); return false; } @@ -719,7 +719,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { viewRange = (viewRange / texelSize) * texelSize; } if (viewRange == 0) { - MGLOG_E("ResolveTexelBufferDescriptor: texture buffer %u has empty view range", texture->GetExternalIndex()); + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: texture buffer %u has empty view range", texture->GetExternalIndex()); return false; } @@ -733,7 +733,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferView bufferView = VK_NULL_HANDLE; const VkResult result = vkCreateBufferView(m_device, &viewInfo, nullptr, &bufferView); if (result != VK_SUCCESS || bufferView == VK_NULL_HANDLE) { - MGLOG_E("ResolveTexelBufferDescriptor: vkCreateBufferView failed result=%d format=%d range=%zu", + MGLOG_E_ONCE("ResolveTexelBufferDescriptor: vkCreateBufferView failed result=%d format=%d range=%zu", result, static_cast(vkFormat), static_cast(viewRange)); return false; } @@ -769,13 +769,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int location = programObj.samplerUniformLocationByBinding[binding]; if (location < 0) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: binding %u ('%s') has no uniform location", binding, + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: binding %u ('%s') has no uniform location", binding, programObj.samplerNameByBinding[binding].c_str()); return false; } const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: image unit %d out of range for binding %u", imageUnit, + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: image unit %d out of range for binding %u", imageUnit, binding); return false; } @@ -783,13 +783,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& imageBinding = MG_State::pGLContext->GetImageTextureBinding(imageUnit); const auto& texture = imageBinding.Texture; if (texture == nullptr) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: image unit %d is unbound for binding %u", imageUnit, + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: image unit %d is unbound for binding %u", imageUnit, binding); return false; } if (texture->GetStorageType() != TextureStorageType::Buffer || texture->GetTarget() != TextureTarget::TextureBuffer) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: binding %u ('%s') expected a texture buffer on image " + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: binding %u ('%s') expected a texture buffer on image " "unit %d, got textureId=%u target=%d storage=%d", binding, programObj.samplerNameByBinding[binding].c_str(), imageUnit, texture->GetExternalIndex(), static_cast(texture->GetTarget()), @@ -800,7 +800,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* textureBuffer = static_cast(texture.get()); const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject(); if (bufferObject == nullptr) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: texture buffer on image unit %d has no GL buffer bound", + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: texture buffer on image unit %d has no GL buffer bound", imageUnit); return false; } @@ -819,7 +819,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { BufferSlice slice{}; if (!m_bufferManager->AcquireResidentSlice(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", bufferObject->GetExternalIndex(), texture->GetExternalIndex()); return false; } @@ -842,7 +842,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkFormat = resourceFormat; } if (vkFormat == VK_FORMAT_UNDEFINED) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: unsupported image buffer format (internal=%d bind=0x%x)", + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: unsupported image buffer format (internal=%d bind=0x%x)", static_cast(internalFormat), imageBinding.Format); return false; } @@ -862,7 +862,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { viewRange = (viewRange / texelSize) * texelSize; } if (viewRange == 0) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: texture buffer %u has empty view range", + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: texture buffer %u has empty view range", texture->GetExternalIndex()); return false; } @@ -877,7 +877,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferView bufferView = VK_NULL_HANDLE; const VkResult result = vkCreateBufferView(m_device, &viewInfo, nullptr, &bufferView); if (result != VK_SUCCESS || bufferView == VK_NULL_HANDLE) { - MGLOG_E("ResolveStorageTexelBufferDescriptor: vkCreateBufferView failed result=%d format=%d range=%zu", + MGLOG_E_ONCE("ResolveStorageTexelBufferDescriptor: vkCreateBufferView failed result=%d format=%d range=%zu", result, static_cast(vkFormat), static_cast(viewRange)); return false; } @@ -914,7 +914,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, frontendBinding); const auto& bufferObject = bindingPoint.GetBoundObject(); if (bufferObject == nullptr) { - MGLOG_E("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'", + MGLOG_E_ONCE("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'", frontendBinding, programObj.storageBlockNameByBinding[binding].c_str()); return false; } @@ -929,7 +929,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { BufferSlice slice{}; if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) { - MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'", + MGLOG_E_ONCE("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'", bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str()); return false; } @@ -943,7 +943,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { rangeEnd = bufferSize; } if (rangeEnd <= rangeStart) { - MGLOG_E("ResolveStorageBufferDescriptor: empty SSBO range for block '%s'", + MGLOG_E_ONCE("ResolveStorageBufferDescriptor: empty SSBO range for block '%s'", programObj.storageBlockNameByBinding[binding].c_str()); return false; } @@ -967,7 +967,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int baseLocation = programObj.samplerUniformLocationByBinding[binding]; if (baseLocation < 0) { - MGLOG_E("ResolveStorageImageDescriptor: storage image binding %u has no uniform location", binding); + MGLOG_E_ONCE("ResolveStorageImageDescriptor: storage image binding %u has no uniform location", binding); return false; } // Per ELEMENT, and this is where an image array differs from a storage-block array: GL @@ -979,26 +979,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { // uniform. const Int location = baseLocation + static_cast(element); if (!program.UniformLocationsAliasSameUniform(baseLocation, location)) { - MGLOG_E("ResolveStorageImageDescriptor: binding %u element %u is past the end of its image array", + MGLOG_E_ONCE("ResolveStorageImageDescriptor: binding %u element %u is past the end of its image array", binding, element); return false; } const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) { - MGLOG_E("ResolveStorageImageDescriptor: image unit %d out of range for binding %u", + MGLOG_E_ONCE("ResolveStorageImageDescriptor: image unit %d out of range for binding %u", imageUnit, binding); return false; } auto& imageBinding = MG_State::pGLContext->GetImageTextureBinding(imageUnit); if (imageBinding.Texture == nullptr) { - MGLOG_E("ResolveStorageImageDescriptor: image unit %d is unbound for binding %u", imageUnit, binding); + MGLOG_E_ONCE("ResolveStorageImageDescriptor: image unit %d is unbound for binding %u", imageUnit, binding); return false; } const Bool ready = m_textureManager->TransitionTextureForStorageImage(commandBuffer, *imageBinding.Texture); if (!ready) { - MGLOG_E("ResolveStorageImageDescriptor: failed to transition textureId=%d for image unit %d", + MGLOG_E_ONCE("ResolveStorageImageDescriptor: failed to transition textureId=%d for image unit %d", imageBinding.Texture->GetExternalIndex(), imageUnit); return false; } @@ -1018,7 +1018,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkFormat viewFormat = ResolveStorageImageViewFormat( reflectedFormat, imageBinding.Format, resource->format, useBindingFormat); if (viewFormat == VK_FORMAT_UNDEFINED) { - MGLOG_E("ResolveStorageImageDescriptor: unsupported glBindImageTexture format=0x%x " + MGLOG_E_ONCE("ResolveStorageImageDescriptor: unsupported glBindImageTexture format=0x%x " "for binding=%u imageUnit=%d textureId=%d bindingPolicy=%s", imageBinding.Format, binding, imageUnit, imageBinding.Texture->GetExternalIndex(), useBindingFormat ? "true" : "false"); @@ -1027,7 +1027,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkImageView view = m_textureManager->GetOrCreateStorageImageView( *imageBinding.Texture, mipLevel, viewFormat, imageBinding.Layered != GL_FALSE, imageBinding.Layer); if (view == VK_NULL_HANDLE) { - MGLOG_E("ResolveStorageImageDescriptor: failed to resolve storage view textureId=%d mip=%u " + MGLOG_E_ONCE("ResolveStorageImageDescriptor: failed to resolve storage view textureId=%d mip=%u " "bindingFormat=0x%x imageFormat=%d reflectedFormat=%d selectedFormat=%d bindingPolicy=%s", imageBinding.Texture->GetExternalIndex(), mipLevel, imageBinding.Format, static_cast(resource->format), static_cast(reflectedFormat), @@ -1048,7 +1048,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Report that there is no fallback and let the caller decline the draw - aborting the // process over an unbound sampler is never the right answer. if (target != TextureTarget::Texture2D && target != TextureTarget::TextureRectangle) { - MGLOG_E("UniformManager::GetFallbackTexture: no fallback exists for target=%d", + MGLOG_E_ONCE("UniformManager::GetFallbackTexture: no fallback exists for target=%d", static_cast(target)); return nullptr; } @@ -1224,13 +1224,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } if (binding >= programObj.samplerUniformLocationByBinding.size()) { - MGLOG_E("CollectStorageImageTextures: binding %u has no uniform-location mapping", binding); + MGLOG_E_ONCE("CollectStorageImageTextures: binding %u has no uniform-location mapping", binding); return false; } const Int baseLocation = programObj.samplerUniformLocationByBinding[binding]; if (baseLocation < 0) { - MGLOG_E("CollectStorageImageTextures: binding %u has no image uniform location", binding); + MGLOG_E_ONCE("CollectStorageImageTextures: binding %u has no image uniform location", binding); return false; } // Per ELEMENT, for the same reason the sampled walk above is: an image ARRAY is one @@ -1242,20 +1242,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (Uint32 element = 0; element < descriptorCount; ++element) { const Int location = ResolveDescriptorElementLocation(program, baseLocation, element); if (location < 0) { - MGLOG_E("CollectStorageImageTextures: binding %u element %u is past the end of its image array", + MGLOG_E_ONCE("CollectStorageImageTextures: binding %u element %u is past the end of its image array", binding, element); return false; } const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) { - MGLOG_E("CollectStorageImageTextures: image unit %d is invalid for binding %u element %u", + MGLOG_E_ONCE("CollectStorageImageTextures: image unit %d is invalid for binding %u element %u", imageUnit, binding, element); return false; } auto* texture = MG_State::pGLContext->GetImageTextureBinding(imageUnit).Texture.get(); if (texture == nullptr) { - MGLOG_E("CollectStorageImageTextures: image unit %d is unbound for binding %u element %u", + MGLOG_E_ONCE("CollectStorageImageTextures: image unit %d is unbound for binding %u element %u", imageUnit, binding, element); return false; } @@ -1406,7 +1406,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint64 descriptorCount64 = static_cast(maxSets) * static_cast(std::min(m_maxBindings, kEstimatedBindingsPerSet)); if (descriptorCount64 > static_cast(std::numeric_limits::max())) { - MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow"); + MGLOG_E_ONCE("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow"); return false; } @@ -1438,7 +1438,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool); if (result != VK_SUCCESS) { - MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", + MGLOG_E_ONCE("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", result); return false; } @@ -1457,7 +1457,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorPool grownPool = VK_NULL_HANDLE; if (!CreateDescriptorPool(grownMaxSets, grownPool)) { - MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)", + MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)", currentMaxSets, grownMaxSets); return false; } @@ -1516,7 +1516,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet); if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) { if (!GrowFrameDescriptorPool(frame, frameIndex)) { - MGLOG_E("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed"); + MGLOG_E_ONCE("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed"); return allocResult; } allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet); @@ -1647,7 +1647,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } auto& frame = m_frames[frameIndex]; if (frame.descriptorPools.empty()) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid"); + MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid"); return false; } if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) { @@ -1784,7 +1784,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferView bufferView = VK_NULL_HANDLE; if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) || bufferView == VK_NULL_HANDLE) { - MGLOG_E( + MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: texture buffer binding %u has no valid descriptor", binding); return false; @@ -1803,7 +1803,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferView bufferView = VK_NULL_HANDLE; if (!ResolveStorageTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) || bufferView == VK_NULL_HANDLE) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: image buffer binding %u " + MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: image buffer binding %u " "has no valid descriptor", binding); return false; @@ -1823,7 +1823,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (Uint32 element = 0; element < descriptorCount; ++element) { VkDescriptorBufferInfo bufferInfo{}; if (!ResolveStorageBufferDescriptor(program, programObj, binding, element, bufferInfo)) { - MGLOG_E( + MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: storage buffer binding %u " "element %u has no valid descriptor", binding, element); @@ -1850,7 +1850,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorImageInfo imageInfo{}; if (!ResolveStorageImageDescriptor(commandBuffer, program, programObj, binding, element, imageInfo)) { - MGLOG_E( + MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: storage image binding %u " "element %u has no valid descriptor", binding, element); @@ -1892,14 +1892,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo, samplerDescriptorsUnchangedHint); } if (!hasImage) { - MGLOG_E( + MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u " "has no valid texture descriptor", binding, element); return false; } if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) { - MGLOG_E( + MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u " "has null sampler or imageView", binding, element); @@ -1973,7 +1973,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else { VkResult allocResult = AcquireDescriptorSet(frameIndex, programObj, descriptorSet); if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d", + MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d", allocResult); return false; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index b55b06a0..47ddbde9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -110,7 +110,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkFormat sourceVkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra, attr.IsLong); if (sourceVkFormat == VK_FORMAT_UNDEFINED) { - MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is " + MGLOG_E_ONCE("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is " "enabled but cannot be mapped to a VkFormat", location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size); unsupportedAttribMask |= (1u << location); @@ -125,7 +125,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (fallbackFormat != VK_FORMAT_UNDEFINED && SupportsVertexBufferFormat(fallbackFormat)) { vkFormat = fallbackFormat; conversion = VertexStreamConversion::ScaledIntegerToFloat32; - MGLOG_W("Vertex attribute location=%u format=%d lacks " + MGLOG_W_ONCE("Vertex attribute location=%u format=%d lacks " "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; using float32 stream format=%d " "(type=%s size=%d normalized=%s integer=%s)", location, static_cast(sourceVkFormat), static_cast(vkFormat), @@ -135,7 +135,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (conversion == VertexStreamConversion::None) { - MGLOG_E("Unsupported Vulkan vertex format (location=%u, format=%d, type=%s, size=%d): " + MGLOG_E_ONCE("Unsupported Vulkan vertex format (location=%u, format=%d, type=%s, size=%d): " "VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT is unavailable and no semantic fallback exists", location, static_cast(sourceVkFormat), MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size); @@ -146,7 +146,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra); if (attribByteSize == 0) { - MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is " + MGLOG_E_ONCE("Vertex attribute with unknown component size (location=%u, type=%s): the array is " "enabled but cannot be sized", location, MG_Util::ConvertDataTypeToString(attr.Type).c_str()); unsupportedAttribMask |= (1u << location); @@ -175,7 +175,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // unless VK_EXT_legacy_vertex_attributes is available, so deinterleave this one // attribute into a tightly packed transient stream without changing its format. conversion = VertexStreamConversion::Repack; - MGLOG_W("Vertex attribute location=%u uses Vulkan-incompatible alignment " + MGLOG_W_ONCE("Vertex attribute location=%u uses Vulkan-incompatible alignment " "(offset=%zu stride=%u required=%zu); using a tightly packed stream", location, attr.Offset, sourceStride, requiredAlignment); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index da1d9d68..e8b72084 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -302,7 +302,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { .requiredFlags = requiredFlags, }); if (!created || resource.buffer.Map() == nullptr) { - MGLOG_E("VkBufferManager::CreateResidentStorage failed (size=%llu)", + MGLOG_E_ONCE("VkBufferManager::CreateResidentStorage failed (size=%llu)", static_cast(size)); resource.buffer.Destroy(); resource.storageSize = 0; @@ -324,7 +324,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } if (!resource.buffer.Upload(bufferObject.MappedData(), size, 0)) { - MGLOG_E("VkBufferManager::SwapStorageAndUploadAll: upload failed"); + MGLOG_E_ONCE("VkBufferManager::SwapStorageAndUploadAll: upload failed"); resource.pendingFullUpload = true; return false; } @@ -409,7 +409,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (!resource->buffer.Upload(bufferObject.MappedData(), size, 0)) { - MGLOG_E("VkBufferManager::OnRespecify: in-place upload failed"); + MGLOG_E_ONCE("VkBufferManager::OnRespecify: in-place upload failed"); resource->pendingFullUpload = true; } } @@ -434,7 +434,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!IsResourceBusy(*resource)) { if (!resource->buffer.Upload(bufferObject.MappedData() + offset, static_cast(size), static_cast(offset))) { - MGLOG_E("VkBufferManager::OnSubData: host upload failed"); + MGLOG_E_ONCE("VkBufferManager::OnSubData: host upload failed"); resource->pendingFullUpload = true; } return; @@ -471,7 +471,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if ((appAccess & BufferMappingAccessBit::Unsynchronized) || !IsResourceBusy(*resource)) { if (!resource->buffer.Upload(bufferObject.MappedData() + offset, static_cast(size), static_cast(offset))) { - MGLOG_E("VkBufferManager::OnFlushMappedRange: host upload failed"); + MGLOG_E_ONCE("VkBufferManager::OnFlushMappedRange: host upload failed"); resource->pendingFullUpload = true; } return; @@ -563,7 +563,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkDeviceSize size = static_cast(bufferObject->GetSize()); if (size == 0) { - MGLOG_E("VkBufferManager::AcquireResidentSlice failed: buffer size is zero"); + MGLOG_E_ONCE("VkBufferManager::AcquireResidentSlice failed: buffer size is zero"); return false; } @@ -585,7 +585,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } if (!resource->buffer.Upload(bufferObject->MappedData(), size, 0)) { - MGLOG_E("VkBufferManager::AcquireResidentSlice failed: initial upload failed"); + MGLOG_E_ONCE("VkBufferManager::AcquireResidentSlice failed: initial upload failed"); resource->buffer.Destroy(); resource->storageSize = 0; resource->usageFlags = 0; @@ -620,7 +620,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkDeviceSize size = static_cast(bufferObject->GetSize()); if (size == 0) { - MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero"); + MGLOG_E_ONCE("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero"); return false; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp index 930c0241..292c938a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp @@ -76,7 +76,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult result = vmaCreateBuffer(m_allocator, &bufferInfo, &allocationInfo, &m_buffer, &m_allocation, nullptr); if (result != VK_SUCCESS) { - MGLOG_E("VkBufferObject::Create failed: vmaCreateBuffer returned %d", result); + MGLOG_E_ONCE("VkBufferObject::Create failed: vmaCreateBuffer returned %d", result); m_allocator = nullptr; m_buffer = VK_NULL_HANDLE; m_allocation = nullptr; @@ -108,7 +108,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &m_mappedData); if (mapResult != VK_SUCCESS || m_mappedData == nullptr) { - MGLOG_E("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult); + MGLOG_E_ONCE("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult); m_mappedData = nullptr; return nullptr; } @@ -138,14 +138,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool wasMapped = IsMapped(); void* mapped = wasMapped ? m_mappedData : Map(); if (mapped == nullptr) { - MGLOG_E("VkBufferObject::Upload failed: unable to map buffer"); + MGLOG_E_ONCE("VkBufferObject::Upload failed: unable to map buffer"); return false; } Memcpy(static_cast(mapped) + offset, data, static_cast(size)); const VkResult flushResult = vmaFlushAllocation(m_allocator, m_allocation, offset, size); if (flushResult != VK_SUCCESS) { - MGLOG_E("VkBufferObject::Upload failed: vmaFlushAllocation returned %d", flushResult); + MGLOG_E_ONCE("VkBufferObject::Upload failed: vmaFlushAllocation returned %d", flushResult); if (!wasMapped) { Unmap(); } @@ -170,7 +170,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult result = vmaInvalidateAllocation(m_allocator, m_allocation, offset, resolvedSize); if (result != VK_SUCCESS) { - MGLOG_E("VkBufferObject::Invalidate failed: vmaInvalidateAllocation returned %d", result); + MGLOG_E_ONCE("VkBufferObject::Invalidate failed: vmaInvalidateAllocation returned %d", result); return false; } return true; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index dee01658..6203b443 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -123,7 +123,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (!attachment.IsComplete()) { - MGLOG_W("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has an incomplete texture attachment; using VK_ATTACHMENT_UNUSED", + MGLOG_W_ONCE("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has an incomplete texture attachment; using VK_ATTACHMENT_UNUSED", drawBufferIndex, MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), fbo.GetExternalIndex()); @@ -132,7 +132,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* texture = attachment.GetTexture().get(); if (texture == nullptr) { - MGLOG_W("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u resolved to a null texture; using VK_ATTACHMENT_UNUSED", + MGLOG_W_ONCE("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u resolved to a null texture; using VK_ATTACHMENT_UNUSED", drawBufferIndex, MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), fbo.GetExternalIndex()); @@ -311,7 +311,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; if (!TryResolveSampleCountFlagBits(renderbuffer->GetSamples(), sampleCount)) { - MGLOG_E("GetOrCreateRenderbufferResource: unsupported renderbuffer sample count %d for renderbuffer %u", + MGLOG_E_ONCE("GetOrCreateRenderbufferResource: unsupported renderbuffer sample count %d for renderbuffer %u", renderbuffer->GetSamples(), renderbuffer->GetExternalIndex()); return nullptr; @@ -457,7 +457,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, &imageFormatProperties); if (imageFormatResult != VK_SUCCESS || (imageFormatProperties.sampleCounts & sampleCount) == 0) { - MGLOG_E("GetOrCreateRenderbufferResource: unsupported renderbuffer format=%d samples=%d for renderbuffer %u", + MGLOG_E_ONCE("GetOrCreateRenderbufferResource: unsupported renderbuffer format=%d samples=%d for renderbuffer %u", static_cast(format), static_cast(sampleCount), renderbuffer->GetExternalIndex()); @@ -929,7 +929,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto& renderbuffer = rbAtt.GetRenderbuffer(); auto* rbResource = GetOrCreateRenderbufferResource(renderbuffer); if (rbResource == nullptr || (rbResource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E("GetOrCreateRenderPass: draw buffer slot %u on FBO %u has an unsupported color " + MGLOG_E_ONCE("GetOrCreateRenderPass: draw buffer slot %u on FBO %u has an unsupported color " "renderbuffer %u; using VK_ATTACHMENT_UNUSED", i, fbo.GetExternalIndex(), renderbuffer->GetExternalIndex()); continue; @@ -1105,7 +1105,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { adoptRenderPassSampleCount(attachmentSampleCount, "color", texture->GetExternalIndex()); if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_W("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; " + MGLOG_W_ONCE("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; " "using LOAD_OP_DONT_CARE", texture->GetExternalIndex()); desc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; @@ -1161,7 +1161,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { isUsableDepthStencilAttachment(depthAtt) && isUsableDepthStencilAttachment(stencilAtt) && !sameDepthStencilAttachmentObject(depthAtt, stencilAtt); if (hasDistinctDepthAndStencilAttachments) { - MGLOG_E("GetOrCreateRenderPass: separate depth/stencil attachments are not supported yet; using the depth attachment and ignoring the standalone stencil attachment for framebuffer %u", + MGLOG_E_ONCE("GetOrCreateRenderPass: separate depth/stencil attachments are not supported yet; using the depth attachment and ignoring the standalone stencil attachment for framebuffer %u", fbo.GetExternalIndex()); } if (selectedDepthStencilAttachment != nullptr) { @@ -1223,7 +1223,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthAttachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; depthAttachmentDescription.initialLayout = loadInfo.initialLayout; if (trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED && (!clearDepth || !clearStencil)) { - MGLOG_W("GetOrCreateRenderPass: depth/stencil attachment id=%d starts with undefined layout " + MGLOG_W_ONCE("GetOrCreateRenderPass: depth/stencil attachment id=%d starts with undefined layout " "and partial/no clear; using DONT_CARE for uncleared aspects", depthAttachmentId); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 180fa530..749f353e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -975,13 +975,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { return resource->sampledView; } if (!AreSampledImageViewFormatsCompatible(resource->format, format)) { - MGLOG_E("%s: incompatible sampled image view format=%d for textureId=%d imageFormat=%d", + MGLOG_E_ONCE("%s: incompatible sampled image view format=%d for textureId=%d imageFormat=%d", __func__, static_cast(format), texture.GetExternalIndex(), static_cast(resource->format)); return VK_NULL_HANDLE; } if ((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) { - MGLOG_E("%s: textureId=%d needs mutable image format=%d for sampled view format=%d", + MGLOG_E_ONCE("%s: textureId=%d needs mutable image format=%d for sampled view format=%d", __func__, texture.GetExternalIndex(), static_cast(resource->format), static_cast(format)); return VK_NULL_HANDLE; @@ -1001,7 +1001,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); if ((formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0) { - MGLOG_E("%s: sampled image view format=%d lacks VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT " + MGLOG_E_ONCE("%s: sampled image view format=%d lacks VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT " "for textureId=%d (available=0x%x)", __func__, static_cast(format), texture.GetExternalIndex(), static_cast(formatProperties.optimalTilingFeatures)); @@ -1015,7 +1015,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource->sampledBaseMipLevel, resource->sampledLevelCount, 0, resource->arrayLayers, &sampledComponents, VK_IMAGE_USAGE_SAMPLED_BIT); if (view == VK_NULL_HANDLE) { - MGLOG_E("%s: failed to create sampled image view textureId=%d imageFormat=%d viewFormat=%d", + MGLOG_E_ONCE("%s: failed to create sampled image view textureId=%d imageFormat=%d viewFormat=%d", __func__, texture.GetExternalIndex(), static_cast(resource->format), static_cast(format)); return VK_NULL_HANDLE; @@ -1043,14 +1043,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { format = resource->format; } if (!AreStorageImageViewFormatsCompatible(resource->format, format)) { - MGLOG_E("%s: incompatible storage image view format=%d for textureId=%d imageFormat=%d", + MGLOG_E_ONCE("%s: incompatible storage image view format=%d for textureId=%d imageFormat=%d", __func__, static_cast(format), texture.GetExternalIndex(), static_cast(resource->format)); return VK_NULL_HANDLE; } if (format != resource->format && (resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) { - MGLOG_E("%s: textureId=%d needs mutable image format=%d for storage view format=%d", + MGLOG_E_ONCE("%s: textureId=%d needs mutable image format=%d for storage view format=%d", __func__, texture.GetExternalIndex(), static_cast(resource->format), static_cast(format)); return VK_NULL_HANDLE; @@ -1070,7 +1070,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { viewType = VK_IMAGE_VIEW_TYPE_2D; break; case VK_IMAGE_VIEW_TYPE_3D: - MGLOG_E("%s: non-layered 3D storage views are unsupported for textureId=%d", + MGLOG_E_ONCE("%s: non-layered 3D storage views are unsupported for textureId=%d", __func__, texture.GetExternalIndex()); return VK_NULL_HANDLE; default: @@ -1079,7 +1079,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (viewType != resource->viewType) { if (layer < 0 || static_cast(layer) >= resource->arrayLayers) { - MGLOG_E("%s: storage image layer=%d is out of range for textureId=%d arrayLayers=%u", + MGLOG_E_ONCE("%s: storage image layer=%d is out of range for textureId=%d arrayLayers=%u", __func__, layer, texture.GetExternalIndex(), resource->arrayLayers); return VK_NULL_HANDLE; } @@ -1114,7 +1114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); if ((formatProperties.optimalTilingFeatures & requiredFormatFeatures) != requiredFormatFeatures) { - MGLOG_E("%s: storage image view format=%d lacks required features=0x%x for textureId=%d " + MGLOG_E_ONCE("%s: storage image view format=%d lacks required features=0x%x for textureId=%d " "(available=0x%x)", __func__, static_cast(format), static_cast(requiredFormatFeatures), texture.GetExternalIndex(), static_cast(formatProperties.optimalTilingFeatures)); @@ -1125,7 +1125,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { mipLevel, 1, baseArrayLayer, layerCount, nullptr, VK_IMAGE_USAGE_STORAGE_BIT); if (view == VK_NULL_HANDLE) { - MGLOG_E("%s: failed to create storage image view for textureId=%d mip=%u imageFormat=%d viewFormat=%d", + MGLOG_E_ONCE("%s: failed to create storage image view for textureId=%d mip=%u imageFormat=%d viewFormat=%d", __func__, texture.GetExternalIndex(), mipLevel, static_cast(resource->format), static_cast(format)); return VK_NULL_HANDLE; @@ -1223,7 +1223,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } if (resource->layout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_W("TransitionTextureForSampling: textureId=%d is still in VK_IMAGE_LAYOUT_UNDEFINED before sampling", + MGLOG_W_ONCE("TransitionTextureForSampling: textureId=%d is still in VK_IMAGE_LAYOUT_UNDEFINED before sampling", texture.GetExternalIndex()); } @@ -1574,7 +1574,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // targets this manager has no Vulkan image shape for yet (cube map arrays above all). // Declining the sync leaves the texture unbacked - wrong, but recoverable - where an // assertion would take the whole process down instead. - MGLOG_W("SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) " + MGLOG_W_ONCE("SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) " "mipLevels=%u vkViewType=%d", MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(), texture.GetExternalIndex(), @@ -1803,7 +1803,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Losing reinterpreted views only degrades the formatless-image feature for // this texture; failing creation would lose the texture entirely, so retry // as a plain immutable-format image. - MGLOG_W("%s: mutable image format=%d is unsupported for textureId=%d; creating " + MGLOG_W_ONCE("%s: mutable image format=%d is unsupported for textureId=%d; creating " "without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT (format reinterpretation " "will be unavailable for it)", __func__, static_cast(format), texture.GetExternalIndex()); @@ -1821,7 +1821,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Losing 2D-array compatibility only costs per-slice framebuffer attachment for this // format; failing creation would lose the texture entirely. Remembered so later syncs // neither reprobe nor flag-mismatch against this image and recreate it. - MGLOG_W("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d " + MGLOG_W_ONCE("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d " "textureId=%d; creating without it (per-slice framebuffer attachment will be " "unavailable for it)", __func__, static_cast(format), texture.GetExternalIndex()); @@ -1853,7 +1853,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult createImageResult = vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr); if (createImageResult != VK_SUCCESS) { - MGLOG_F("SyncTextureResource: vmaCreateImage failed (%d) textureId=%d extent=%ux%u depth=%u layers=%u " + // E_ONCE, not F: the comment above says it - this is a soft failure the caller + // recovers from, and it re-fires on every sync of every texture the driver refuses. + MGLOG_E_ONCE("SyncTextureResource: vmaCreateImage failed (%d) textureId=%d extent=%ux%u depth=%u layers=%u " "mips=%u samples=%d format=%d", createImageResult, texture.GetExternalIndex(), imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth, imageInfo.arrayLayers, imageInfo.mipLevels, @@ -2426,7 +2428,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool srcIsD24S8 = outResource.format == VK_FORMAT_D24_UNORM_S8_UINT; const Bool srcIsD32FS8 = outResource.format == VK_FORMAT_D32_SFLOAT_S8_UINT; if (!srcIsD24S8 && !srcIsD32FS8) { - MGLOG_E("UploadDirtyMipLevels: unsupported combined depth-stencil format %d for textureId=%d", + MGLOG_E_ONCE("UploadDirtyMipLevels: unsupported combined depth-stencil format %d for textureId=%d", static_cast(outResource.format), mipmapTexture.GetExternalIndex()); for (const auto& item : uploadItems) { mipmapTexture.MarkStorageDirty(item.target, item.level, false); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp index abbbba48..b812e89d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp @@ -15,7 +15,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(initInfo.device != VK_NULL_HANDLE, "VkTimerQueryManager::Initialize requires valid VkDevice"); MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTimerQueryManager::Initialize requires non-zero frame count"); if (initInfo.timestampValidBits == 0 || initInfo.timestampPeriodNs <= 0.0f || initInfo.slotsPerPool == 0) { - MGLOG_W("VkTimerQueryManager: timestamps unsupported (validBits=%u, period=%f, slots=%u)", + MGLOG_W_ONCE("VkTimerQueryManager: timestamps unsupported (validBits=%u, period=%f, slots=%u)", initInfo.timestampValidBits, initInfo.timestampPeriodNs, initInfo.slotsPerPool); return false; } @@ -35,7 +35,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& poolState : m_pools) { const VkResult result = vkCreateQueryPool(m_device, &poolInfo, nullptr, &poolState.pool); if (result != VK_SUCCESS) { - MGLOG_E("VkTimerQueryManager: vkCreateQueryPool failed with %s", VkResultToString(result)); + MGLOG_E_ONCE("VkTimerQueryManager: vkCreateQueryPool failed with %s", VkResultToString(result)); Shutdown(); return false; } @@ -90,7 +90,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& poolState = m_pools[frameIndex]; if (poolState.cursor >= m_slotsPerPool) { if (!poolState.exhaustionWarned) { - MGLOG_W("VkTimerQueryManager: frame %u timestamp pool exhausted (%u slots); further timer queries " + MGLOG_W_ONCE("VkTimerQueryManager: frame %u timestamp pool exhausted (%u slots); further timer queries " "this frame fall back to the frontend path", frameIndex, m_slotsPerPool); poolState.exhaustionWarned = true; @@ -120,7 +120,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_device, m_pools[record.poolIndex].pool, record.slot, 1, sizeof(resultWithAvailability), resultWithAvailability, sizeof(Uint64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT); if (result != VK_SUCCESS && result != VK_NOT_READY) { - MGLOG_E("VkTimerQueryManager: vkGetQueryPoolResults failed with %s", VkResultToString(result)); + MGLOG_E_ONCE("VkTimerQueryManager: vkGetQueryPoolResults failed with %s", VkResultToString(result)); return false; } if (resultWithAvailability[1] == 0) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 14ccdbcb..bdb32b72 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1602,7 +1602,7 @@ void main() { (attachmentType >= FramebufferAttachmentType::FrontLeft && attachmentType <= FramebufferAttachmentType::BackRight); if (!defaultColorAttachment) { - MGLOG_E("BlitFramebuffer skipped: default framebuffer color attachment %d is not supported", + MGLOG_E_ONCE("BlitFramebuffer skipped: default framebuffer color attachment %d is not supported", static_cast(attachmentType)); return false; } @@ -1620,14 +1620,14 @@ void main() { } if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) { - MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)", + MGLOG_E_ONCE("BlitFramebuffer only supports color attachments right now (attachment=%d)", static_cast(attachmentType)); return false; } const auto& attachment = fbo.GetAttachment(attachmentType); if (!attachment.IsComplete()) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer color attachment is incomplete", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer color attachment is incomplete", isReadFramebuffer ? "read" : "draw"); return false; } @@ -1635,7 +1635,7 @@ void main() { const auto& renderbuffer = attachment.GetRenderbuffer(); auto* rbResource = renderPassManager.GetOrCreateRenderbufferResource(renderbuffer); if (rbResource == nullptr || (rbResource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer color renderbuffer %u is unsupported", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer color renderbuffer %u is unsupported", outBinding.label, renderbuffer->GetExternalIndex()); return false; } @@ -1653,7 +1653,7 @@ void main() { return true; } if (!attachment.IsTexture()) { - MGLOG_E("BlitFramebuffer skipped: unsupported framebuffer attachment type"); + MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported framebuffer attachment type"); return false; } @@ -1662,12 +1662,12 @@ void main() { auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { - MGLOG_E("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", + MGLOG_E_ONCE("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", outBinding.label, texture->GetExternalIndex()); return false; } if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is not a color image", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is not a color image", outBinding.label, texture->GetExternalIndex()); return false; } @@ -1700,7 +1700,7 @@ void main() { const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask); if (attachmentType == FramebufferAttachmentType::None) { - MGLOG_E("BlitFramebuffer skipped: unsupported aspect mask=0x%x", + MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported aspect mask=0x%x", static_cast(requiredAspectMask)); return false; } @@ -1722,7 +1722,7 @@ void main() { const VkImageAspectFlags swapchainAspectMask = GetSwapchainDepthStencilAspectMask(swapchainObject); if ((swapchainAspectMask & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("BlitFramebuffer skipped: swapchain depth image missing required aspect mask=0x%x", + MGLOG_E_ONCE("BlitFramebuffer skipped: swapchain depth image missing required aspect mask=0x%x", static_cast(requiredAspectMask)); return false; } @@ -1735,7 +1735,7 @@ void main() { const auto& attachment = fbo.GetAttachment(attachmentType); if (!attachment.IsComplete()) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment is incomplete (fbo=%u attachmentType=%d " + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment is incomplete (fbo=%u attachmentType=%d " "isTexture=%d isRenderbuffer=%d texId=%d)", outBinding.label, fbo.GetExternalIndex(), static_cast(attachmentType), attachment.IsTexture() ? 1 : 0, attachment.IsRenderbuffer() ? 1 : 0, @@ -1746,12 +1746,12 @@ void main() { const auto& renderbuffer = attachment.GetRenderbuffer(); auto* rbResource = renderPassManager.GetOrCreateRenderbufferResource(renderbuffer); if (rbResource == nullptr) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is unsupported", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is unsupported", outBinding.label, renderbuffer->GetExternalIndex()); return false; } if ((rbResource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is missing aspect mask=0x%x", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is missing aspect mask=0x%x", outBinding.label, renderbuffer->GetExternalIndex(), static_cast(requiredAspectMask)); return false; @@ -1770,7 +1770,7 @@ void main() { return true; } if (!attachment.IsTexture()) { - MGLOG_E("BlitFramebuffer skipped: unsupported framebuffer attachment type"); + MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported framebuffer attachment type"); return false; } @@ -1778,12 +1778,12 @@ void main() { MOBILEGL_ASSERT(texture != nullptr, "ResolveFramebufferBlitBinding: texture attachment is null"); auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { - MGLOG_E("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", + MGLOG_E_ONCE("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", outBinding.label, texture->GetExternalIndex()); return false; } if ((resource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is missing aspect mask=0x%x", + MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is missing aspect mask=0x%x", outBinding.label, texture->GetExternalIndex(), static_cast(requiredAspectMask)); return false; } @@ -1811,19 +1811,19 @@ void main() { VkTextureManager& textureManager, BlitImageBinding& outBinding) { auto* resource = textureManager.SyncTextureAndGetDescriptor(texture); if (resource == nullptr) { - MGLOG_E("CopyTexSubImage2D skipped: failed to sync destination textureId=%d", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: failed to sync destination textureId=%d", texture.GetExternalIndex()); return false; } const VkImageAspectFlags copyAspectMask = resource->aspect & (VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); if (copyAspectMask == 0) { - MGLOG_E("CopyTexSubImage2D skipped: destination textureId=%d uses unsupported aspect mask=0x%x", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: destination textureId=%d uses unsupported aspect mask=0x%x", texture.GetExternalIndex()); return false; } if (mipLevel >= resource->mipLevels) { - MGLOG_E("CopyTexSubImage2D skipped: destination textureId=%d mip=%u out of range (mips=%u)", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: destination textureId=%d mip=%u out of range (mips=%u)", texture.GetExternalIndex(), mipLevel, resource->mipLevels); return false; } @@ -1851,7 +1851,7 @@ void main() { const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, true, requiredAspectMask); if (attachmentType == FramebufferAttachmentType::None) { - MGLOG_E("CopyTexSubImage2D skipped: unsupported source aspect mask=0x%x", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: unsupported source aspect mask=0x%x", static_cast(requiredAspectMask)); return false; } @@ -1873,7 +1873,7 @@ void main() { const VkImageAspectFlags swapchainAspectMask = GetSwapchainDepthStencilAspectMask(swapchainObject); if ((swapchainAspectMask & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("CopyTexSubImage2D skipped: swapchain depth image missing required aspect mask=0x%x", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: swapchain depth image missing required aspect mask=0x%x", static_cast(requiredAspectMask)); return false; } @@ -1885,7 +1885,7 @@ void main() { const auto& attachment = fbo.GetAttachment(attachmentType); if (!attachment.IsComplete()) { - MGLOG_E("CopyTexSubImage2D skipped: read framebuffer attachment %d is incomplete", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: read framebuffer attachment %d is incomplete", static_cast(attachmentType)); return false; } @@ -1893,12 +1893,12 @@ void main() { const auto& renderbuffer = attachment.GetRenderbuffer(); auto* rbResource = renderPassManager.GetOrCreateRenderbufferResource(renderbuffer); if (rbResource == nullptr) { - MGLOG_E("CopyTexSubImage2D skipped: read framebuffer renderbuffer %u is unsupported", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: read framebuffer renderbuffer %u is unsupported", renderbuffer->GetExternalIndex()); return false; } if ((rbResource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("CopyTexSubImage2D skipped: read framebuffer renderbuffer %u aspect mask=0x%x " + MGLOG_E_ONCE("CopyTexSubImage2D skipped: read framebuffer renderbuffer %u aspect mask=0x%x " "does not satisfy requested mask=0x%x", renderbuffer->GetExternalIndex(), static_cast(rbResource->aspect), static_cast(requiredAspectMask)); @@ -1918,7 +1918,7 @@ void main() { return true; } if (!attachment.IsTexture()) { - MGLOG_E("CopyTexSubImage2D skipped: unsupported read framebuffer attachment type"); + MGLOG_E_ONCE("CopyTexSubImage2D skipped: unsupported read framebuffer attachment type"); return false; } @@ -1926,12 +1926,12 @@ void main() { MOBILEGL_ASSERT(texture != nullptr, "ResolveTextureCopySourceBinding: source texture attachment is null"); auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { - MGLOG_E("CopyTexSubImage2D skipped: failed to sync read framebuffer textureId=%d", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: failed to sync read framebuffer textureId=%d", texture->GetExternalIndex()); return false; } if ((resource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E("CopyTexSubImage2D skipped: read framebuffer textureId=%d aspect mask=0x%x does not satisfy requested mask=0x%x", + MGLOG_E_ONCE("CopyTexSubImage2D skipped: read framebuffer textureId=%d aspect mask=0x%x does not satisfy requested mask=0x%x", texture->GetExternalIndex(), static_cast(resource->aspect), static_cast(requiredAspectMask)); return false; @@ -2632,7 +2632,7 @@ void main() { DirectGLES::ReadbackImpl::ReadbackChannelMapping mapping{}; if (!DirectGLES::ReadbackImpl::GetReadbackChannelMapping(format, mapping) || DirectGLES::ReadbackImpl::GetReadbackDstPixelSize(mapping, type) == 0) { - MGLOG_E("DirectVulkan readback skipped: unsupported format=0x%x type=0x%x", format, type); + MGLOG_E_ONCE("DirectVulkan readback skipped: unsupported format=0x%x type=0x%x", format, type); return false; } @@ -2640,7 +2640,7 @@ void main() { GLenum wideType = GL_FLOAT; if (!DecodeReadbackRowsToWide(srcPixels, srcFormat, width, sliceHeight * sliceCount, wide, wideType)) { - MGLOG_E("DirectVulkan readback skipped: unsupported source format=%d", + MGLOG_E_ONCE("DirectVulkan readback skipped: unsupported source format=%d", static_cast(srcFormat)); return false; } @@ -2662,7 +2662,7 @@ void main() { const Bool sourceIsInteger = wideType == GL_INT || wideType == GL_UNSIGNED_INT; if (sourceIsInteger != mapping.isInteger) { - MGLOG_E("DirectVulkan readback skipped: integerness mismatch (format=0x%x source=%d)", + MGLOG_E_ONCE("DirectVulkan readback skipped: integerness mismatch (format=0x%x source=%d)", format, static_cast(srcFormat)); return false; } @@ -2747,13 +2747,13 @@ void main() { switch (messageSeverity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - MGLOG_E("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); + MGLOG_E_ONCE("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - MGLOG_W("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); + MGLOG_W_ONCE("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - MGLOG_I("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); + MGLOG_D("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: MGLOG_D("Vulkan Debug: [%s] %s", typeToString(messageType), pCallbackData->pMessage); @@ -3477,7 +3477,7 @@ void main() { const SizeT stride = attr.Stride > 0 ? static_cast(attr.Stride) : elementSize; const auto* clientData = reinterpret_cast(attr.Offset); if (!clientData || elementSize == 0 || stride == 0) { - MGLOG_E("UploadAndBindVertexStreams skipped: invalid client vertex attribute at location %u", location); + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: invalid client vertex attribute at location %u", location); return false; } @@ -3504,7 +3504,7 @@ void main() { // Indirect/multi indexed draws have no CPU-visible index range and a // client array has no size to fall back to; a guessed range could // truncate the converted stream, so skip the draw loudly. - MGLOG_E("UploadAndBindVertexStreams skipped: converted client-memory attribute " + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: converted client-memory attribute " "location=%u has no computable vertex range", location); return false; } @@ -3552,7 +3552,7 @@ void main() { const SizeT sourceStride = static_cast(attr.Stride); if (sourceBufferShared->MappedData() == nullptr || elementSize == 0 || baseOffset > sourceSize || elementSize > sourceSize - baseOffset) { - MGLOG_E("UploadAndBindVertexStreams skipped: invalid converted source binding=%zu " + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: invalid converted source binding=%zu " "location=%u base=%zu size=%zu element=%zu stride=%zu", binding, bindingLocation, baseOffset, sourceSize, elementSize, sourceStride); return false; @@ -3604,7 +3604,7 @@ void main() { const Uint8* sourceData = sourceBufferShared->MappedData() + baseOffset; if (!uploadConvertedStream(conversion, attr, sourceData, sourceStride, elementSize, elementCount, slice)) { - MGLOG_E("UploadAndBindVertexStreams skipped: failed to convert binding=%zu location=%u", + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: failed to convert binding=%zu location=%u", binding, bindingLocation); return false; } @@ -3625,7 +3625,7 @@ void main() { } } else { if (!m_bufferManager.AcquireResidentSlice(BufferKind::Vertex, sourceBufferShared, slice)) { - MGLOG_E("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding); + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding); return false; } } @@ -3664,7 +3664,7 @@ void main() { sourceData, sourceSize); if (!supported) { // SetupDraw's pre-flight should have rejected this already; never upload a null payload. - MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: " + MGLOG_E_ONCE("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: " "programHash=%llu location=%u type=0x%x", static_cast(programObj.hash), location, glType); return false; @@ -3805,7 +3805,7 @@ void main() { // this even in core contexts). Snapshot the data into a transient slice. const auto* clientIndices = reinterpret_cast(pIndexBufferView->indexByteOffset); if (clientIndices == nullptr || pIndexBufferView->indexByteSize == 0) { - MGLOG_E("DrawElements skipped: no element array buffer bound and no client index data"); + MGLOG_E_ONCE("DrawElements skipped: no element array buffer bound and no client index data"); return false; } Vector rewrittenIndices; @@ -3818,7 +3818,7 @@ void main() { BufferSlice slice{}; if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), uploadSource, pIndexBufferView->indexByteSize, 4, slice)) { - MGLOG_E("DrawElements skipped: failed to upload client index data"); + MGLOG_E_ONCE("DrawElements skipped: failed to upload client index data"); return false; } auto& shadow = g_dynamicStateShadow; @@ -3887,7 +3887,7 @@ void main() { substituteRestartIndex, rewrittenIndices); if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), rewrittenIndices.data(), rewrittenIndices.size(), 4, slice)) { - MGLOG_E("DrawElements skipped: failed to upload restart-substituted index data"); + MGLOG_E_ONCE("DrawElements skipped: failed to upload restart-substituted index data"); return false; } } else if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared)) { @@ -3897,7 +3897,7 @@ void main() { return false; } } else if (!m_bufferManager.AcquireResidentSlice(BufferKind::Index, indexBufferShared, slice)) { - MGLOG_E("DrawElements skipped: failed to sync resident index buffer"); + MGLOG_E_ONCE("DrawElements skipped: failed to sync resident index buffer"); return false; } else if (indexMemo != nullptr && !substituteRestart) { // Resident acquire succeeded: record the slice for the next draw of this VAO. @@ -4638,17 +4638,18 @@ void main() { // it can produce are named and refused here. Same philosophy as the VK_NULL_HANDLE gate // in SetupDraw: hostile input degrades to a broken draw, never to a dead process. GL // leaves all three undefined for a draw, so nothing legal is being turned away. - // MGLOG_I because the INFO builds CTS runs against keep only I and F. + // MGLOG_E, latched: a refused program is never memoized, so the refusal is re-derived + // on every draw that uses it. Parked at MGLOG_I until the Log.h ordering was fixed. { Bool hasVertexStage = false; for (const auto& stage : programObj.stages) { if (stage.module == VK_NULL_HANDLE) { - MGLOG_I("GetOrCreatePipeline skipped: program=%u has a null shader module for stage 0x%x", + MGLOG_E_ONCE("GetOrCreatePipeline skipped: program=%u has a null shader module for stage 0x%x", program.GetExternalIndex(), static_cast(stage.stage)); return VK_NULL_HANDLE; } if (stage.stage == VK_SHADER_STAGE_COMPUTE_BIT) { - MGLOG_I("GetOrCreatePipeline skipped: program=%u carries a compute stage, which no graphics " + MGLOG_E_ONCE("GetOrCreatePipeline skipped: program=%u carries a compute stage, which no graphics " "pipeline may contain", program.GetExternalIndex()); return VK_NULL_HANDLE; @@ -4658,7 +4659,7 @@ void main() { } } if (!hasVertexStage) { - MGLOG_I("GetOrCreatePipeline skipped: program=%u has no vertex stage", program.GetExternalIndex()); + MGLOG_E_ONCE("GetOrCreatePipeline skipped: program=%u has no vertex stage", program.GetExternalIndex()); return VK_NULL_HANDLE; } } @@ -4730,7 +4731,7 @@ void main() { static_cast(shaderInputType), program.GetExternalIndex()); - MGLOG_W("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input type=%u for program=%u", + MGLOG_W_ONCE("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input type=%u for program=%u", attribute.location, static_cast(attribute.format), static_cast(patchedFormat), @@ -5076,7 +5077,7 @@ void main() { const VkColorComponentFlags supportedColorWriteMask = GetSupportedColorWriteMaskForComponentCount(componentCount); if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) { - MGLOG_W( + MGLOG_W_ONCE( "GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)", static_cast(attachmentColorWriteMask), static_cast(attachmentColorWriteMask & supportedColorWriteMask), @@ -5131,7 +5132,7 @@ void main() { blendSupportIt = formatBlendSupport.emplace(static_cast(colorAttachmentFormat), blendable).first; if (!blendable) { - MGLOG_E("GetOrCreatePipeline: format=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; " + MGLOG_E_ONCE("GetOrCreatePipeline: format=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; " "disabling blending on attachments with this format (first hit: attachment %u textureId=%d program=%u)", static_cast(colorAttachmentFormat), i, textureExternalIndex, program.GetExternalIndex()); @@ -5140,7 +5141,7 @@ void main() { // never fire for pipelines on this format, so a depth-equality // chain that accumulates into it (MC 26.3 OIT depth_bounds on // RGBA32F) keeps its depth writes and may flicker on this driver. - MGLOG_W("GetOrCreatePipeline: format=%d is not blendable, so the blended " + MGLOG_W_ONCE("GetOrCreatePipeline: format=%d is not blendable, so the blended " "depth-write quirk cannot apply to it; depth-equality chains " "accumulating into this format may flicker", static_cast(colorAttachmentFormat)); @@ -5200,7 +5201,7 @@ void main() { } auto& storageTextures = m_storageImageTexturesScratch; if (!m_uniformManager->CollectStorageImageTextures(program, programObj, storageTextures)) { - MGLOG_E("%s: failed to collect storage images for program=%u", + MGLOG_E_ONCE("%s: failed to collect storage images for program=%u", __func__, program.GetExternalIndex()); return false; } @@ -5257,7 +5258,7 @@ void main() { } else { // Best effort: the upgrade still produces a correct image, only its preserved // contents may predate this frame's writes. Dropping the draw would be worse. - MGLOG_E("%s: flush before a storage-usage image upgrade failed; preserved contents " + MGLOG_E_ONCE("%s: flush before a storage-usage image upgrade failed; preserved contents " "may be stale for one frame", __func__); } } @@ -5275,12 +5276,12 @@ void main() { for (auto* texture : storageTextures) { if (!MaterializePendingClearForTexture(frame.commandBuffer, *texture)) { - MGLOG_E("%s: failed to materialize pending clear for storage textureId=%d", + MGLOG_E_ONCE("%s: failed to materialize pending clear for storage textureId=%d", __func__, texture->GetExternalIndex()); return false; } if (!m_textureManager->TransitionTextureForStorageImage(frame.commandBuffer, *texture)) { - MGLOG_E("%s: failed to prepare storage textureId=%d", + MGLOG_E_ONCE("%s: failed to prepare storage textureId=%d", __func__, texture->GetExternalIndex()); return false; } @@ -5902,7 +5903,7 @@ void main() { } if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E("SetupDraw skipped: storage image preparation failed"); + MGLOG_E_ONCE("SetupDraw skipped: storage image preparation failed"); return false; } @@ -6093,7 +6094,7 @@ void main() { // vertex data. Fail loudly rather than render wrong pixels. const Uint32 brokenAttribMask = vertexInputState.unsupportedAttribMask & activeAttribMask; if (brokenAttribMask != 0) { - MGLOG_E("SetupDraw skipped: program=%u reads vertex attribute location mask 0x%x whose enabled " + MGLOG_E_ONCE("SetupDraw skipped: program=%u reads vertex attribute location mask 0x%x whose enabled " "array has no supported vertex format", program.GetExternalIndex(), brokenAttribMask); return false; @@ -6109,7 +6110,7 @@ void main() { const GLenum glType = programObj.vertexInputTypes[location]; if (MG_State::GLState::ClassifyVertexAttribType(glType).baseType == MG_State::GLState::VertexAttribBaseType::Unsupported) { - MGLOG_E("SetupDraw skipped: program=%u location=%u has no enabled array and its shader input " + MGLOG_E_ONCE("SetupDraw skipped: program=%u location=%u has no enabled array and its shader input " "type 0x%x is not supported as a current generic vertex attribute", program.GetExternalIndex(), location, glType); return false; @@ -6122,9 +6123,10 @@ void main() { // rejected vkCreateGraphicsPipelines). Binding it dereferences null inside the driver - // 9 of the 15 CTS process deaths were exactly this vkCmdBindPipeline. A draw that has no // pipeline is a skipped draw, which is what every other failure below already does. - // MGLOG_I so the skip is visible in the INFO builds CTS runs against. + // MGLOG_E, latched: the condition is a property of the program, so an unlatched line + // here is one per draw forever. Parked at MGLOG_I until the Log.h ordering was fixed. if (pipeline == VK_NULL_HANDLE) { - MGLOG_I("SetupDraw skipped: no graphics pipeline for program=%u (creation failed or the " + MGLOG_E_ONCE("SetupDraw skipped: no graphics pipeline for program=%u (creation failed or the " "program has no shader stages)", program.GetExternalIndex()); return false; @@ -6150,14 +6152,14 @@ void main() { const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex()); if (!boundUniforms) { - MGLOG_E("SetupDraw skipped: BindProgramUniformBuffers failed"); + MGLOG_E_ONCE("SetupDraw skipped: BindProgramUniformBuffers failed"); return false; } auto vtxUploadOk = UploadAndBindVertexBuffers( frame.commandBuffer, vao, programObj, drawParams, pIndexBufferView); if (!vtxUploadOk) { - MGLOG_E("SetupDraw skipped: failed to upload vertex buffers"); + MGLOG_E_ONCE("SetupDraw skipped: failed to upload vertex buffers"); return false; } @@ -6250,7 +6252,7 @@ void main() { // itself, never the graphics composite (which carries no compute stage at all). const auto& program = *MG_State::pGLContext->GetProgramForDispatch(); if (!program.GetLinkStatus() || !program.GetSpirvStatus()) { - MGLOG_E("DispatchCompute skipped: program=%u has no optimized SPIR-V", + MGLOG_E_ONCE("DispatchCompute skipped: program=%u has no optimized SPIR-V", program.GetExternalIndex()); return; } @@ -6266,13 +6268,13 @@ void main() { } if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E("DispatchCompute skipped: storage image preparation failed"); + MGLOG_E_ONCE("DispatchCompute skipped: storage image preparation failed"); return; } const VkPipeline pipeline = GetOrCreateComputePipeline(programObj); if (pipeline == VK_NULL_HANDLE) { - MGLOG_E("DispatchCompute skipped: compute pipeline creation failed for program=%u", + MGLOG_E_ONCE("DispatchCompute skipped: compute pipeline creation failed for program=%u", program.GetExternalIndex()); return; } @@ -6282,7 +6284,7 @@ void main() { frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE); if (!boundUniforms) { - MGLOG_E("DispatchCompute skipped: BindProgramUniformBuffers failed"); + MGLOG_E_ONCE("DispatchCompute skipped: BindProgramUniformBuffers failed"); return; } @@ -6296,7 +6298,7 @@ void main() { // See DispatchCompute: the dispatch accessor, not the draw one. const auto& program = *MG_State::pGLContext->GetProgramForDispatch(); if (!program.GetLinkStatus() || !program.GetSpirvStatus()) { - MGLOG_E("DispatchComputeIndirect skipped: program=%u has no optimized SPIR-V", + MGLOG_E_ONCE("DispatchComputeIndirect skipped: program=%u has no optimized SPIR-V", program.GetExternalIndex()); return; } @@ -6312,13 +6314,13 @@ void main() { } if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E("DispatchComputeIndirect skipped: storage image preparation failed"); + MGLOG_E_ONCE("DispatchComputeIndirect skipped: storage image preparation failed"); return; } const VkPipeline pipeline = GetOrCreateComputePipeline(programObj); if (pipeline == VK_NULL_HANDLE) { - MGLOG_E("DispatchComputeIndirect skipped: compute pipeline creation failed for program=%u", + MGLOG_E_ONCE("DispatchComputeIndirect skipped: compute pipeline creation failed for program=%u", program.GetExternalIndex()); return; } @@ -6328,20 +6330,20 @@ void main() { frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE); if (!boundUniforms) { - MGLOG_E("DispatchComputeIndirect skipped: BindProgramUniformBuffers failed"); + MGLOG_E_ONCE("DispatchComputeIndirect skipped: BindProgramUniformBuffers failed"); return; } auto indirectBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DispatchIndirect).GetBoundObject(); if (!indirectBuffer) { - MGLOG_E("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound"); + MGLOG_E_ONCE("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound"); return; } indirectBuffer->SyncPersistentMappedRange(); BufferSlice slice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, indirectBuffer, slice)) { - MGLOG_E("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer"); + MGLOG_E_ONCE("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer"); return; } @@ -6500,7 +6502,7 @@ void main() { continue; } if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) { - MGLOG_W("DirectVulkan: scissored glClear with a partial color mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: scissored glClear with a partial color mask is not supported"); continue; } @@ -6539,7 +6541,7 @@ void main() { if ((stencilWriteMask & 0xFFu) == 0xFFu) { depthStencilAspects |= VK_IMAGE_ASPECT_STENCIL_BIT; } else if (stencilWriteMask != 0) { - MGLOG_W("DirectVulkan: scissored glClear with a partial stencil write mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: scissored glClear with a partial stencil write mask is not supported"); } } } @@ -6571,7 +6573,7 @@ void main() { const Uint32 stencilWriteMask = MG_State::pGLContext->GetStencilState(StencilFace::Front).WriteMask; if ((stencilWriteMask & 0xFFu) != 0xFFu) { if (stencilWriteMask != 0) { - MGLOG_W("DirectVulkan: deferred glClear with a partial stencil write mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: deferred glClear with a partial stencil write mask is not supported"); } deferredMask &= ~static_cast(GL_STENCIL_BUFFER_BIT); } @@ -6591,7 +6593,7 @@ void main() { } else { anyRestrictedMask = true; if (colorMask.r() || colorMask.g() || colorMask.b() || colorMask.a()) { - MGLOG_W("DirectVulkan: deferred glClear with a partial color mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: deferred glClear with a partial color mask is not supported"); } } } @@ -6714,7 +6716,7 @@ void main() { return true; } if (stencilWriteMask != 0) { - MGLOG_W("DirectVulkan: deferred glClearBuffer with a partial stencil write mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: deferred glClearBuffer with a partial stencil write mask is not supported"); } return false; }; @@ -6726,7 +6728,7 @@ void main() { return; } if (!(colorMask.r() && colorMask.g() && colorMask.b() && colorMask.a())) { - MGLOG_W("DirectVulkan: deferred glClearBuffer with a partial color mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: deferred glClearBuffer with a partial color mask is not supported"); return; } queueAttachmentClear(framebuffer.GetDrawBuffers()[drawbuffer], clearPayload); @@ -6783,7 +6785,7 @@ void main() { return; } if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) { - MGLOG_W("DirectVulkan: scissored glClearBuffer with a partial color mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: scissored glClearBuffer with a partial color mask is not supported"); return; } MG_State::GLState::ITextureObject* colorTexture = nullptr; @@ -6808,7 +6810,7 @@ void main() { if ((stencilWriteMask & 0xFFu) == 0xFFu) { aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; } else if (stencilWriteMask != 0) { - MGLOG_W("DirectVulkan: scissored glClearBuffer with a partial stencil write mask is not supported"); + MGLOG_W_ONCE("DirectVulkan: scissored glClearBuffer with a partial stencil write mask is not supported"); } } if (aspects == 0) { @@ -7147,7 +7149,7 @@ void main() { // The device or the format refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, so // there is no way to name this slice. Leaving it uncleared is wrong pixels; // asserting would abort a process that glFramebufferTextureLayer can reach at will. - MGLOG_W("MaterializePendingClearForTexture: textureId=%d slice %u could not be cleared " + MGLOG_W_ONCE("MaterializePendingClearForTexture: textureId=%d slice %u could not be cleared " "(no 2D-array-compatible view)", texture.GetExternalIndex(), pendingClear.key.baseArrayLayer); } @@ -7225,7 +7227,7 @@ void main() { auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(renderbuffer); if (resource == nullptr) { - MGLOG_E("MaterializePendingClearForRenderbuffer: no resource for renderbuffer %u", + MGLOG_E_ONCE("MaterializePendingClearForRenderbuffer: no resource for renderbuffer %u", renderbuffer->GetExternalIndex()); return false; } @@ -7339,7 +7341,7 @@ void main() { if (vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &m_msResolveScratch.image, &m_msResolveScratch.allocation, nullptr) != VK_SUCCESS) { // Soft failure: the caller keeps the direct resolve, which is what shipped before. - MGLOG_E("AcquireMultisampleResolveScratchImage: vmaCreateImage failed (format=%d %ux%u)", + MGLOG_E_ONCE("AcquireMultisampleResolveScratchImage: vmaCreateImage failed (format=%d %ux%u)", static_cast(format), grown.width, grown.height); m_msResolveScratch = {}; return false; @@ -7558,7 +7560,7 @@ void main() { return false; } if (srcBinding.trackedLayout == nullptr) { - MGLOG_E("BlitFramebuffer skipped: shader blit to default framebuffer requires a texture-backed source framebuffer"); + MGLOG_E_ONCE("BlitFramebuffer skipped: shader blit to default framebuffer requires a texture-backed source framebuffer"); return false; } @@ -7576,12 +7578,12 @@ void main() { sourceTexture->GetExternalIndex()); const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sourceTexture); if (!ready) { - MGLOG_E("BlitFramebuffer skipped: failed to transition source textureId=%d for sampling", + MGLOG_E_ONCE("BlitFramebuffer skipped: failed to transition source textureId=%d for sampling", sourceTexture->GetExternalIndex()); return false; } if (m_textureManager->SyncTextureAndGetDescriptor(*sourceTexture) == nullptr) { - MGLOG_E("BlitFramebuffer skipped: failed to resolve source textureId=%d after sampling transition", + MGLOG_E_ONCE("BlitFramebuffer skipped: failed to resolve source textureId=%d after sampling transition", sourceTexture->GetExternalIndex()); return false; } @@ -7696,7 +7698,7 @@ void main() { static constexpr GLbitfield kSupportedBlitMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; if ((mask & ~kSupportedBlitMask) != 0) { - MGLOG_E("BlitFramebuffer skipped: unsupported mask bits=0x%x", static_cast(mask)); + MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported mask bits=0x%x", static_cast(mask)); return; } const Bool isColorBlit = (mask & GL_COLOR_BUFFER_BIT) != 0; @@ -7706,11 +7708,11 @@ void main() { return; } if (filter != GL_NEAREST && filter != GL_LINEAR) { - MGLOG_E("BlitFramebuffer skipped: unsupported filter=0x%x", static_cast(filter)); + MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported filter=0x%x", static_cast(filter)); return; } if ((isDepthBlit || isStencilBlit) && filter != GL_NEAREST) { - MGLOG_E("BlitFramebuffer skipped: depth/stencil blits require GL_NEAREST"); + MGLOG_E_ONCE("BlitFramebuffer skipped: depth/stencil blits require GL_NEAREST"); return; } @@ -7772,7 +7774,7 @@ void main() { dstX0, dstY0, dstX1, dstY1, filter)) { return; } - MGLOG_E("BlitFramebuffer skipped: rotated blit to default framebuffer requires a texture-backed source framebuffer"); + MGLOG_E_ONCE("BlitFramebuffer skipped: rotated blit to default framebuffer requires a texture-backed source framebuffer"); return; } @@ -7794,7 +7796,7 @@ void main() { } if (srcX1 < srcX0 || srcY1 < srcY0 || dstX1 < dstX0 || dstY1 < dstY0) { - MGLOG_E("BlitFramebuffer skipped: depth blits with flipped rectangles are not supported yet"); + MGLOG_E_ONCE("BlitFramebuffer skipped: depth blits with flipped rectangles are not supported yet"); continue; } @@ -7803,7 +7805,7 @@ void main() { const Int dstWidth = dstX1 - dstX0; const Int dstHeight = dstY1 - dstY0; if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) { - MGLOG_E("BlitFramebuffer skipped: degenerate depth blit rectangle"); + MGLOG_E_ONCE("BlitFramebuffer skipped: degenerate depth blit rectangle"); continue; } // A scaling depth blit is legal GL and vkCmdBlitImage scales natively; only a same-size @@ -7861,7 +7863,7 @@ void main() { ? m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired) : *srcBinding.trackedLayout; if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E("BlitFramebuffer skipped: depth source image layout is undefined"); + MGLOG_E_ONCE("BlitFramebuffer skipped: depth source image layout is undefined"); continue; } @@ -7878,7 +7880,7 @@ void main() { // per-texel re-encode instead. if (srcBinding.format != dstBinding.format) { if (readIsDefaultFbo || drawIsDefaultFbo) { - MGLOG_E("BlitFramebuffer skipped: cross-format depth/stencil blit with the default framebuffer"); + MGLOG_E_ONCE("BlitFramebuffer skipped: cross-format depth/stencil blit with the default framebuffer"); continue; } if (!BlitDepthAcrossFormats(frame, srcBinding.image, srcBinding.format, srcBinding.trackedLayout, @@ -8106,11 +8108,11 @@ void main() { : dstOriginalLayout; if (readIsDefaultFbo && srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E("BlitFramebuffer skipped: swapchain source image layout is undefined"); + MGLOG_E_ONCE("BlitFramebuffer skipped: swapchain source image layout is undefined"); return; } if (srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E("BlitFramebuffer skipped: source image layout is undefined"); + MGLOG_E_ONCE("BlitFramebuffer skipped: source image layout is undefined"); return; } @@ -8443,7 +8445,7 @@ void main() { // separately; the four sites behind the 1,759-case orientation defect are the viewport, // the scissor, the ReadPixels copy offset and the readback remap. if (readIsDefaultFbo) { - MGLOG_I("DirectVulkan::CopyTexSubImage2D: copying from the DEFAULT framebuffer still uses the raw GL " + MGLOG_D("DirectVulkan::CopyTexSubImage2D: copying from the DEFAULT framebuffer still uses the raw GL " "Y origin (x=%d y=%d w=%d h=%d); the result is the mirrored band, stored flipped", x, y, width, height); } @@ -8675,13 +8677,13 @@ void main() { VkResult result = vkWaitForFences(m_device, 1, &frame.imageInFlightFence, VK_TRUE, UINT64_MAX); if (result != VK_SUCCESS) { - MGLOG_E("DirectVulkan readback: vkWaitForFences returned %d", result); + MGLOG_E_ONCE("DirectVulkan readback: vkWaitForFences returned %d", result); return false; } OnSubmitsCompletedUpTo(frame.lastSubmitIndex); result = vkResetFences(m_device, 1, &frame.imageInFlightFence); if (result != VK_SUCCESS) { - MGLOG_E("DirectVulkan readback: vkResetFences returned %d", result); + MGLOG_E_ONCE("DirectVulkan readback: vkResetFences returned %d", result); return false; } @@ -8704,7 +8706,7 @@ void main() { auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); if (readFbo == nullptr) { - MGLOG_E("DirectVulkan::ReadPixels skipped: no read framebuffer is bound"); + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: no read framebuffer is bound"); return; } @@ -8768,14 +8770,14 @@ void main() { ? m_swapchainObject.GetImageLayout(m_imageIndexAcquired) : *srcBinding.trackedLayout; if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E("DirectVulkan::ReadPixels skipped: source image layout is undefined"); + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: source image layout is undefined"); return; } const VkFormat srcFormat = srcBinding.format; const SizeT sourceTexelSize = GetReadbackTexelSize(srcFormat); if (sourceTexelSize == 0) { - MGLOG_E("DirectVulkan::ReadPixels skipped: unsupported source format=%d", + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: unsupported source format=%d", static_cast(srcFormat)); return; } @@ -8789,7 +8791,7 @@ void main() { .memoryUsage = VMA_MEMORY_USAGE_AUTO, .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, })) { - MGLOG_E("DirectVulkan::ReadPixels skipped: failed to create readback buffer"); + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: failed to create readback buffer"); return; } @@ -8862,11 +8864,11 @@ void main() { } const auto* mapped = static_cast(readback.Map()); if (mapped == nullptr) { - MGLOG_E("DirectVulkan::ReadPixels skipped: failed to map readback buffer"); + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: failed to map readback buffer"); return; } if (!readback.Invalidate(readbackSize)) { - MGLOG_E("DirectVulkan::ReadPixels skipped: failed to invalidate readback buffer"); + MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: failed to invalidate readback buffer"); return; } if (readIsDefaultFbo) { @@ -8884,7 +8886,7 @@ void main() { } // Only a quarter-turn pre-transform reaches this, and nothing in this renderer models // one. MGLOG_I because the INFO builds are the ones that run conformance. - MGLOG_I("DirectVulkan::ReadPixels: default-FBO remap declined (w=%d h=%d preTransform=%d); falling back " + MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap declined (w=%d h=%d preTransform=%d); falling back " "to raw readback", width, height, static_cast(preTransform)); } @@ -8931,7 +8933,7 @@ void main() { const SizeT srcTexel = stencilAspect ? 1 : depthTexelSize(srcFormat); const SizeT dstTexel = stencilAspect ? 1 : depthTexelSize(dstFormat); if (srcTexel == 0 || dstTexel == 0 || width <= 0 || height <= 0) { - MGLOG_E("BlitDepthAcrossFormats skipped: unsupported formats src=%d dst=%d", + MGLOG_E_ONCE("BlitDepthAcrossFormats skipped: unsupported formats src=%d dst=%d", static_cast(srcFormat), static_cast(dstFormat)); return false; } @@ -9039,7 +9041,7 @@ void main() { BufferSlice slice{}; if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), encoded.data(), encoded.size(), 4, slice)) { - MGLOG_E("BlitDepthAcrossFormats: staging upload failed"); + MGLOG_E_ONCE("BlitDepthAcrossFormats: staging upload failed"); return false; } @@ -9092,7 +9094,7 @@ void main() { if (!readIsDefaultFbo) { const auto& attachment = readFbo.GetAttachment(attachmentType); if (!attachment.IsValid() || attachment.IsEmpty()) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: no depth/stencil attachment image"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: no depth/stencil attachment image"); return; } } @@ -9114,7 +9116,7 @@ void main() { if (readIsDefaultFbo) { const VkImage swapchainDepthImage = m_swapchainObject.GetDepthStencilImage(m_imageIndexAcquired); if (swapchainDepthImage == VK_NULL_HANDLE) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: the default framebuffer has no " + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: the default framebuffer has no " "depth/stencil image"); return; } @@ -9159,7 +9161,7 @@ void main() { textureObject->GetExternalIndex()); auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*textureObject); if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: failed to sync depth textureId=%u", + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: failed to sync depth textureId=%u", textureObject->GetExternalIndex()); return; } @@ -9177,7 +9179,7 @@ void main() { renderbufferObject->GetExternalIndex()); auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(renderbufferObject); if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: failed to resolve renderbuffer %u", + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: failed to resolve renderbuffer %u", renderbufferObject->GetExternalIndex()); return; } @@ -9203,15 +9205,15 @@ void main() { auto& frame = m_frameContext.GetCurrent(); if (*trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: source layout is undefined"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: source layout is undefined"); return; } if (wantDepth && (imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) == 0) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: attachment has no depth aspect"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: attachment has no depth aspect"); return; } if (wantStencil && (imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) == 0) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: attachment has no stencil aspect"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: attachment has no stencil aspect"); return; } @@ -9231,7 +9233,7 @@ void main() { case VK_FORMAT_S8_UINT: break; default: - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: unsupported source format=%d", + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: unsupported source format=%d", static_cast(vkFormat)); return; } @@ -9249,7 +9251,7 @@ void main() { .memoryUsage = VMA_MEMORY_USAGE_AUTO, .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, })) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: failed to create readback buffer"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: failed to create readback buffer"); return; } @@ -9315,7 +9317,7 @@ void main() { } const auto* mapped = static_cast(readback.Map()); if (mapped == nullptr || !readback.Invalidate(stencilOffset + stencilBytes)) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: failed to map readback buffer"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: failed to map readback buffer"); return; } const Uint8* depthSrc = mapped; @@ -9347,7 +9349,7 @@ void main() { } else { // Only a quarter-turn pre-transform reaches this, and nothing in this renderer // models one. MGLOG_I because the INFO builds are the ones that run conformance. - MGLOG_I("DirectVulkan::ReadDepthStencilPixels: default-FBO remap declined (w=%d h=%d " + MGLOG_D("DirectVulkan::ReadDepthStencilPixels: default-FBO remap declined (w=%d h=%d " "preTransform=%d); falling back to raw readback", width, height, static_cast(preTransform)); } @@ -9394,7 +9396,7 @@ void main() { dstPixelBytes = 8; break; default: - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: unsupported type=0x%x", type); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: unsupported type=0x%x", type); return; } @@ -9469,7 +9471,7 @@ void main() { const SizeT requiredSize = pboBaseOffset + dstSkipOffset + static_cast(height - 1) * dstRowStride + dstRowBytes; if (requiredSize > pixelPackBufferObject->GetSize()) { - MGLOG_E("DirectVulkan::ReadDepthStencilPixels skipped: pixel pack buffer is too small"); + MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: pixel pack buffer is too small"); return; } } @@ -9501,13 +9503,13 @@ void main() { auto* textureMipmapObject = static_cast(textureObject.get()); if (level < 0 || static_cast(level) >= textureMipmapObject->GetMipmapLevelCount()) { - MGLOG_E("DirectVulkan::GetTexImage skipped: level %d is out of range", level); + MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: level %d is out of range", level); return; } auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*textureObject); if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - MGLOG_E("DirectVulkan::GetTexImage skipped: failed to sync textureId=%u", + MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: failed to sync textureId=%u", textureObject->GetExternalIndex()); return; } @@ -9538,7 +9540,7 @@ void main() { static_cast(level), arrayLayer, 0, 0, levelSize.x(), levelSize.y(), format, type, pixels); } else { - MGLOG_E("DirectVulkan::GetTexImage skipped: color query of a non-color texture"); + MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: color query of a non-color texture"); } return; } @@ -9567,7 +9569,7 @@ void main() { const SizeT minSize = static_cast(width) * static_cast(height) * static_cast(dstChannels) * dstComponentSize; if (static_cast(bufSize) < minSize) { - MGLOG_E("DirectVulkan::GetTextureImage skipped: destination buffer is too small"); + MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: destination buffer is too small"); return; } } @@ -9575,7 +9577,7 @@ void main() { const SizeT sourceTexelSize = GetReadbackTexelSize(resource->format); if (sourceTexelSize == 0) { - MGLOG_E("DirectVulkan::GetTexImage skipped: unsupported source format=%d", + MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: unsupported source format=%d", static_cast(resource->format)); return; } @@ -9590,7 +9592,7 @@ void main() { .memoryUsage = VMA_MEMORY_USAGE_AUTO, .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, })) { - MGLOG_E("DirectVulkan::GetTexImage skipped: failed to create readback buffer"); + MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: failed to create readback buffer"); return; } @@ -9630,11 +9632,11 @@ void main() { } const auto* mapped = static_cast(readback.Map()); if (mapped == nullptr) { - MGLOG_E("DirectVulkan::GetTextureImage skipped: failed to map readback buffer"); + MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: failed to map readback buffer"); return; } if (!readback.Invalidate(readbackSize)) { - MGLOG_E("DirectVulkan::GetTextureImage skipped: failed to invalidate readback buffer"); + MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: failed to invalidate readback buffer"); return; } PackReadbackToClientOrPbo(mapped, resource->format, width, height, sliceCount, format, type, pixels, @@ -9652,7 +9654,7 @@ void main() { // A 1D texture needs nothing special: its storage extent is {width, 1, 1}, so the blit // loop below already emits the y and z offsets of 0 and 1 that a 1D image requires. textureTarget != TextureTarget::Texture1D) { - MGLOG_W("GenerateMipmap: unsupported target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); + MGLOG_W_ONCE("GenerateMipmap: unsupported target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); return; } @@ -9710,7 +9712,7 @@ void main() { (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", + MGLOG_W_ONCE("GenerateMipmap skipped for textureId=%d because Vulkan format %d does not support blit-based mip generation", texture->GetExternalIndex(), static_cast(resource->format)); return; } @@ -9937,7 +9939,7 @@ void main() { VK_BUFFER_USAGE_TRANSFER_DST_BIT, .memoryUsage = VMA_MEMORY_USAGE_AUTO, })) { - MGLOG_E("BeginXfbCaptureForDraw: failed to create the counter buffer"); + MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to create the counter buffer"); return false; } } @@ -9960,7 +9962,7 @@ void main() { bufferObject->MarkGpuWritten(); BufferSlice slice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Vertex, bufferObject, slice)) { - MGLOG_E("BeginXfbCaptureForDraw: failed to acquire capture buffer %zu", i); + MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to acquire capture buffer %zu", i); return false; } const Range1D range = point.GetRange(); @@ -10083,7 +10085,7 @@ void main() { poolInfo.queryType = VK_QUERY_TYPE_OCCLUSION; poolInfo.queryCount = kOcclusionQuerySlots; if (vkCreateQueryPool(m_device, &poolInfo, nullptr, &m_occlusionQueryPool) != VK_SUCCESS) { - MGLOG_E("StartOcclusionQueryCapture: vkCreateQueryPool failed"); + MGLOG_E_ONCE("StartOcclusionQueryCapture: vkCreateQueryPool failed"); m_occlusionQueryPool = VK_NULL_HANDLE; return false; } @@ -10141,7 +10143,7 @@ void main() { poolInfo.queryType = VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT; poolInfo.queryCount = kXfbQuerySlots; if (vkCreateQueryPool(m_device, &poolInfo, nullptr, &m_xfbQueryPool) != VK_SUCCESS) { - MGLOG_E("StartXfbQueryCapture: vkCreateQueryPool failed"); + MGLOG_E_ONCE("StartXfbQueryCapture: vkCreateQueryPool failed"); m_xfbQueryPool = VK_NULL_HANDLE; return false; } @@ -10593,21 +10595,21 @@ void main() { stride = kGLDrawElementsIndirectCommandBytes; } if (stride < static_cast(kGLDrawElementsIndirectCommandBytes)) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", stride, kGLDrawElementsIndirectCommandBytes); return; } const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: unsupported index type 0x%x", type); return; } const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); if (!indexBuffer) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: no element array buffer is bound"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: no element array buffer is bound"); return; } @@ -10616,13 +10618,13 @@ void main() { static_cast(stride) * static_cast(maxdrawcount - 1) + kGLDrawElementsIndirectCommandBytes; auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); if (!parameterBuffer || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); return; } @@ -10645,12 +10647,12 @@ void main() { BufferSlice drawSlice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync draw indirect buffer"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: failed to sync draw indirect buffer"); return; } BufferSlice parameterSlice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, parameterBuffer, parameterSlice)) { - MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync parameter buffer"); + MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: failed to sync parameter buffer"); return; } @@ -10694,21 +10696,21 @@ void main() { stride = kGLDrawElementsIndirectCommandBytes; } if (stride < static_cast(kGLDrawElementsIndirectCommandBytes)) { - MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", stride, kGLDrawElementsIndirectCommandBytes); return; } const SizeT indexSize = MG_Util::GetGLTypeSize(type); if (indexSize == 0) { - MGLOG_E("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: unsupported index type 0x%x", type); return; } const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); if (!indexBuffer) { - MGLOG_E("MultiDrawElementsIndirect skipped: no element array buffer is bound"); + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: no element array buffer is bound"); return; } @@ -10717,7 +10719,7 @@ void main() { static_cast(stride) * static_cast(drawcount - 1) + kGLDrawElementsIndirectCommandBytes; auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { - MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } @@ -10739,7 +10741,7 @@ void main() { BufferSlice drawSlice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) { - MGLOG_E("MultiDrawElementsIndirect skipped: failed to sync draw indirect buffer"); + MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: failed to sync draw indirect buffer"); return; } @@ -10777,7 +10779,7 @@ void main() { stride = kGLDrawArraysIndirectCommandBytes; } if (stride < static_cast(kGLDrawArraysIndirectCommandBytes)) { - MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", stride, kGLDrawArraysIndirectCommandBytes); return; } @@ -10787,7 +10789,7 @@ void main() { static_cast(stride) * static_cast(drawcount - 1) + kGLDrawArraysIndirectCommandBytes; auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { - MGLOG_E("MultiDrawArraysIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); return; } @@ -10803,7 +10805,7 @@ void main() { BufferSlice drawSlice{}; if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) { - MGLOG_E("MultiDrawArraysIndirect skipped: failed to sync draw indirect buffer"); + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: failed to sync draw indirect buffer"); return; } @@ -10884,7 +10886,7 @@ void main() { // the in-flight frame count) but never deadlocks. const VkResult result = vkQueueWaitIdle(m_graphicsQueue); if (result != VK_SUCCESS) { - MGLOG_E("WaitForFrameSerial: vkQueueWaitIdle returned %d", result); + MGLOG_E_ONCE("WaitForFrameSerial: vkQueueWaitIdle returned %d", result); return false; } m_bufferManager.NotifyDeviceIdle(); @@ -11050,7 +11052,7 @@ void main() { VkFence fence = VK_NULL_HANDLE; const VkResult result = vkCreateFence(m_device, &fenceInfo, nullptr, &fence); if (result != VK_SUCCESS) { - MGLOG_E("AcquirePooledSubmitFence: vkCreateFence returned %d", result); + MGLOG_E_ONCE("AcquirePooledSubmitFence: vkCreateFence returned %d", result); return VK_NULL_HANDLE; } return fence; @@ -11102,7 +11104,7 @@ void main() { submitInfo.pCommandBuffers = commandBuffers; const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, fence); if (result != VK_SUCCESS) { - MGLOG_E("SubmitPendingCommandBuffer: vkQueueSubmit returned %d", result); + MGLOG_E_ONCE("SubmitPendingCommandBuffer: vkQueueSubmit returned %d", result); return false; } frame.imageAvailableSemaphoreConsumed = true; @@ -11164,7 +11166,7 @@ void main() { // draining this submission so reusing the buffer stays legal. const VkResult retireResult = m_frameContext.RetireCurrentCommandBuffer(submittingPreCommandBuffer); if (retireResult != VK_SUCCESS) { - MGLOG_E("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission", retireResult); + MGLOG_E_ONCE("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission", retireResult); if (vkWaitForFences(m_device, 1, &fence, VK_TRUE, UINT64_MAX) == VK_SUCCESS) { OnSubmitsCompletedUpTo(m_submitCounter); } else if (vkQueueWaitIdle(m_graphicsQueue) == VK_SUCCESS) { @@ -11173,7 +11175,7 @@ void main() { } else { // Device is effectively lost; the command buffer may still be // pending, but no recovery can make reuse legal. - MGLOG_E("FlushPendingCommands: drain failed; command buffer reuse is unsafe"); + MGLOG_E_ONCE("FlushPendingCommands: drain failed; command buffer reuse is unsafe"); } } return true; @@ -11217,7 +11219,7 @@ void main() { return true; } if (result != VK_TIMEOUT) { - MGLOG_E("WaitForSubmitIndex: vkWaitForFences returned %d", result); + MGLOG_E_ONCE("WaitForSubmitIndex: vkWaitForFences returned %d", result); } return false; } @@ -11704,6 +11706,11 @@ void main() { const char* pMessage, void*) { if ((flags & (VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) != 0) { + // MGLOG_F, unlatched, on purpose: a validation-layer report means MobileGL fed + // Vulkan something illegal, which is a broken invariant rather than an expected + // failure mode. It stays loud and keeps repeating - the quietness rules that latch + // W/E are for expected failures, not for this. The callback is only installed when + // a build arms the debug report extension, so it costs shipping builds nothing. MGLOG_F("[Vulkan %s %d] %s", pLayerPrefix ? pLayerPrefix : "?", messageCode, pMessage ? pMessage : ""); } return VK_FALSE; @@ -11773,10 +11780,12 @@ void main() { // GPU-less machine with the vendor ICDs installed (RADV/ANV/NVK on a CI runner) // is exactly that. It has to be a bring-up failure the caller can report. // - // It used to be MGLOG_E + MOBILEGL_ASSERT, and BOTH are compiled out at the INFO - // log level every shipping and CI build uses (Log.h orders DEBUG < WARN < ERROR - // < INFO), so the count-zero case fell through in silence to `devices[0]` on an - // EMPTY vector below and segfaulted in vkGetPhysicalDeviceProperties. + // It used to be MGLOG_E + MOBILEGL_ASSERT, and back then BOTH were compiled out at + // the INFO log level every shipping and CI build uses - the ordering bug that made + // MGLOG_E dead at INFO was only fixed in 2026-08. The count-zero case therefore fell + // through in silence to `devices[0]` on an EMPTY vector below and segfaulted in + // vkGetPhysicalDeviceProperties. MGLOG_F stays: E is live now, but MOBILEGL_ASSERT + // is still DEBUG-only and this is a genuine bring-up abort, not a recoverable error. MGLOG_F("No Vulkan physical devices found: the instance loaded ICDs but none of them exposes a " "device. Cannot bring up DirectVulkan. (A software ICD such as lavapipe provides one; " "pin it with VK_ICD_FILENAMES if the machine has no GPU.)"); @@ -12992,7 +13001,7 @@ void main() { if (!extentChanged && !transformChanged) { return false; } - MGLOG_I("Swapchain out of date: surface %ux%u transform %u -> %ux%u transform %u", + MGLOG_D("Swapchain out of date: surface %ux%u transform %u -> %ux%u transform %u", builtFrom.width, builtFrom.height, static_cast(m_swapchainObject.GetPreTransform()), surfaceCaps.currentExtent.width, surfaceCaps.currentExtent.height, static_cast(surfaceCaps.currentTransform)); diff --git a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h index e3d1f983..38a8df4b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h +++ b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h @@ -74,6 +74,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { // The context line (__VA_ARGS__ = its own format string + args) must be a SEPARATE log // call: appending its format to the base format while its arguments precede the base // arguments makes every conversion read the wrong slot (a %s pulling an int crashes). +// +// MGLOG_F and deliberately NOT latched. VK_VERIFY is the invariant-check macro: a Vulkan call +// MobileGL believes it has already made legal came back non-success, which is a +// should-never-happen state, not an expected failure mode a user hits. Those fast-fail loudly +// and keep saying so - the log-quietness rules that latch W/E cover expected failures (driver +// capability gaps, app misuse), not broken internal invariants. MOBILEGL_ASSERT below traps in +// a DEBUG build; MGLOG_F is what makes the same condition visible in an INFO test run, where +// the assert is compiled out by contract. +// +// A soft, recoverable failure must therefore NOT be routed through VK_VERIFY. Check the +// VkResult directly and report it with MGLOG_E_ONCE - see VkTextureManager::SyncTextureResource, +// where a driver legitimately refuses an image the format pre-check accepted. #define VK_VERIFY(expr, ...) \ do { \ VkResult _vk_verify_result = (expr); \ diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index cb3af392..636ff229 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -21,7 +21,7 @@ namespace MobileGL::MG_Impl::EGLImpl { EGLStateContext* GetState() { if (!MG_State::pEGLContext) { - MGLOG_E("pEGLContext is null. MG_State may not be initialized."); + MGLOG_E_ONCE("pEGLContext is null. MG_State may not be initialized."); } return MG_State::pEGLContext.get(); } @@ -146,7 +146,7 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); state->DestroySurface(dpy, surface); return EGL_NO_SURFACE; } @@ -172,11 +172,11 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); return EGL_FALSE; } if (!backendObject->SwapEGLBuffers(dpy, draw)) { - MGLOG_E("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw); + MGLOG_E_ONCE("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw); state->SetError(EGL_BAD_SURFACE); return EGL_FALSE; } @@ -211,7 +211,7 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); return EGL_FALSE; } if (!backendObject->InitializeEGLDisplay(dpy, major, minor)) { @@ -265,7 +265,7 @@ namespace MobileGL::MG_Impl::EGLImpl { if (releaseCurrentRequest) { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) { - MGLOG_E("eglMakeCurrent release failed in backend thread=%s", threadId.c_str()); + MGLOG_E_ONCE("eglMakeCurrent release failed in backend thread=%s", threadId.c_str()); state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext); state->SetError(EGL_BAD_ACCESS); return EGL_FALSE; @@ -277,12 +277,12 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext); return EGL_FALSE; } if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) { - MGLOG_E("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), + MGLOG_E_ONCE("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), dpy, draw, read, ctx); state->SetError(EGL_BAD_ACCESS); state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext); @@ -703,7 +703,7 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); state->DestroySurface(dpy, surface); return EGL_NO_SURFACE; } @@ -726,7 +726,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } auto* backendObject = GetBackendObject(state); if (!backendObject) { - MGLOG_E("activeBackendObject not initialized!"); + MGLOG_E_ONCE("activeBackendObject not initialized!"); return EGL_FALSE; } width = std::max(width, 1); @@ -764,7 +764,7 @@ namespace MobileGL::MG_Impl::EGLImpl { MGLOG_D("eglGetProcAddress(%s)", name); void* proc = MG_Impl::GetProcAddress(name); if (!proc) { - MGLOG_W("Failed to get function: %s", name); + MGLOG_D("Failed to get function: %s", name); return nullptr; } return (__eglMustCastToProperFunctionPointerType)proc; diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index 5a58d640..110cb5d6 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -149,7 +149,7 @@ namespace MobileGL::MG_Impl::GLImpl { // quietly writing a differently-sized pattern. const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType); if (sourceSize != elementSize) { - MGLOG_W("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; " + MGLOG_W_ONCE("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; " "converting between them is not implemented", GetBufferOpName(op), sourceSize, internalformat, elementSize); } diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index bee83f4c..06434bde 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -25,12 +25,12 @@ #define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) { #define DECLARE_GL_FUNCTION_STUB_END(type, name, ...) \ - MGLOG_W("Stub function: %s(...)", __FUNCTION__); \ + MGLOG_W_ONCE("Stub function: %s(...)", __FUNCTION__); \ return (type)1; \ } #define DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(type, name, ...) \ - MGLOG_W("Stub function: %s(...)", __FUNCTION__); \ + MGLOG_W_ONCE("Stub function: %s(...)", __FUNCTION__); \ } #define DECLARE_GL_FUNCTION_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) { @@ -2585,7 +2585,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedbackNV, GLenum target, GLui DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacksNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacksNV, n, ids) DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacksNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacksNV, n, ids) MOBILEGL_GL_API GLboolean glIsTransformFeedbackNV(GLuint id) { - MGLOG_W("Stub function: %s(...)", __FUNCTION__); + MGLOG_W_ONCE("Stub function: %s(...)", __FUNCTION__); return GL_FALSE; } DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedbackNV, ) @@ -3181,5 +3181,5 @@ MOBILEGL_GL_API void glVertexAttribDivisorARB(GLuint index, GLuint divisor) { } MOBILEGL_GL_API void glWindowRectanglesEXT(GLenum mode, GLsizei count, const GLint* box) { - MGLOG_W("Stub function: %s(...)", __FUNCTION__); + MGLOG_W_ONCE("Stub function: %s(...)", __FUNCTION__); } diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index ba5a1dbe..2b2701fc 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -547,7 +547,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { auto blitNamedFramebuffer = MG_Backend::gBackendFunctionsTable.GL.BlitNamedFramebuffer; if (!blitNamedFramebuffer) { - MGLOG_E("glBlitNamedFramebuffer skipped: backend does not implement explicit framebuffer blit."); + MGLOG_E_ONCE("glBlitNamedFramebuffer skipped: backend does not implement explicit framebuffer blit."); return; } blitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, @@ -558,7 +558,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum buffer, GLint drawbuffer, const GLfloat* value) { auto clearNamedFramebufferfv = MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfv; if (!clearNamedFramebufferfv) { - MGLOG_E("glClearNamedFramebufferfv skipped: backend does not implement explicit framebuffer clear."); + MGLOG_E_ONCE("glClearNamedFramebufferfv skipped: backend does not implement explicit framebuffer clear."); return; } clearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value); @@ -568,7 +568,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { auto clearNamedFramebufferfi = MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfi; if (!clearNamedFramebufferfi) { - MGLOG_E("glClearNamedFramebufferfi skipped: backend does not implement explicit framebuffer clear."); + MGLOG_E_ONCE("glClearNamedFramebufferfi skipped: backend does not implement explicit framebuffer clear."); return; } clearNamedFramebufferfi(framebuffer, buffer, drawbuffer, depth, stencil); @@ -578,7 +578,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum buffer, GLint drawbuffer, const GLint* value) { auto clearNamedFramebufferiv = MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferiv; if (!clearNamedFramebufferiv) { - MGLOG_E("glClearNamedFramebufferiv skipped: backend does not implement explicit framebuffer clear."); + MGLOG_E_ONCE("glClearNamedFramebufferiv skipped: backend does not implement explicit framebuffer clear."); return; } clearNamedFramebufferiv(framebuffer, buffer, drawbuffer, value); @@ -588,7 +588,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum buffer, GLint drawbuffer, const GLuint* value) { auto clearNamedFramebufferuiv = MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferuiv; if (!clearNamedFramebufferuiv) { - MGLOG_E("glClearNamedFramebufferuiv skipped: backend does not implement explicit framebuffer clear."); + MGLOG_E_ONCE("glClearNamedFramebufferuiv skipped: backend does not implement explicit framebuffer clear."); return; } clearNamedFramebufferuiv(framebuffer, buffer, drawbuffer, value); diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 66f0d9c9..9d6baf93 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -383,7 +383,7 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("glGetString, name: %s", MG_Util::ConvertGLEnumToString(name).c_str()); if (!activeBackendObject) { - MGLOG_E("activeBackendObject is not initialized!"); + MGLOG_E_ONCE("activeBackendObject is not initialized!"); return (GLubyte*)"Unknown"; } @@ -442,7 +442,7 @@ namespace MobileGL::MG_Impl::GLImpl { const auto& activeBackendObject = MG_Backend::pActiveBackendObject; if (!activeBackendObject) { - MGLOG_E("activeBackendObject is not initialized!"); + MGLOG_E_ONCE("activeBackendObject is not initialized!"); return (GLubyte*)"Unknown"; } const auto& rendererInfo = activeBackendObject->GetRendererInfo(); @@ -1954,7 +1954,7 @@ namespace MobileGL::MG_Impl::GLImpl { const auto& activeBackendObject = MG_Backend::pActiveBackendObject; if (!activeBackendObject) { - MGLOG_E("activeBackendObject is not initialized!"); + MGLOG_E_ONCE("activeBackendObject is not initialized!"); return; } const auto& rendererInfo = activeBackendObject->GetRendererInfo(); @@ -2248,7 +2248,7 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(std::lround(dynamicParameters.MaxTextureMaxAnisotropy)); break; default: - MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname); + MGLOG_D("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname); MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetIntegerv", std::format("Invalid enum: 0x{:X}", pname))); diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 91da6f17..61199e89 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -908,7 +908,7 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT span = UniformStorageSpanInBytes(ttype, size); if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || offset + span > programObject->GetUBOSize()) { - MGLOG_E("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, + MGLOG_E_ONCE("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, program, location); return; } @@ -962,7 +962,7 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT span = UniformStorageSpanInBytes(ttype, size); if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || offset + span > programObject->GetUBOSize()) { - MGLOG_E("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, + MGLOG_E_ONCE("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, program, location); return; } @@ -1062,7 +1062,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!initialized) { const auto& activeBackendObject = MG_Backend::pActiveBackendObject; if (!activeBackendObject) { - MGLOG_E("activeBackendObject is not initialized!"); + MGLOG_E_ONCE("activeBackendObject is not initialized!"); return; } const auto& rendererInfo = activeBackendObject->GetRendererInfo(); @@ -1152,7 +1152,7 @@ namespace MobileGL::MG_Impl::GLImpl { SizeT writeSize = ItemCount * sizeof(T); if (size < writeSize) { // Metadata bug: degrade to a clamped copy instead of killing the process. - MGLOG_E("%s: uniform size mismatch at program %u location %u: expected at least %zu bytes, got %zu " + MGLOG_E_ONCE("%s: uniform size mismatch at program %u location %u: expected at least %zu bytes, got %zu " "bytes; clamping", __func__, programObject.GetExternalIndex(), location, ItemCount * sizeof(T), size); writeSize = size; @@ -1173,7 +1173,7 @@ namespace MobileGL::MG_Impl::GLImpl { offset + byteOffsetInsideUniform + writeSize > uboSize) { // Should not happen: linking gives every settable uniform backing // storage. Log and drop the write instead of faulting. - MGLOG_E("%s: uniform at program %u location %u has no backing storage (ubo=%p offset=%u size=%zu " + MGLOG_E_ONCE("%s: uniform at program %u location %u has no backing storage (ubo=%p offset=%u size=%zu " "uboSize=%zu); dropping write", __func__, programObject.GetExternalIndex(), location, static_cast(pUBO), offset, writeSize, uboSize); @@ -1807,7 +1807,7 @@ namespace MobileGL::MG_Impl::GLImpl { break; } default: - MGLOG_E("%s: unknown pname = %p %s", __func__, pname, MG_Util::ConvertGLEnumToString(pname).c_str()); + MGLOG_D("%s: unknown pname = %p %s", __func__, pname, MG_Util::ConvertGLEnumToString(pname).c_str()); MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 490278e7..ba2c2f6f 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -621,7 +621,7 @@ namespace MobileGL::MG_Impl::GLImpl { // the process down, which is never an acceptable answer to a query - see the same reasoning // above for the compressed-format path. void RecordUnsupportedLevelQueryStorage(const char* caller, GLenum pname) { - MGLOG_I("%s: glGetTexLevelParameter(pname=%s) is not implemented for texture-buffer " + MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for texture-buffer " "storage; recording GL_INVALID_OPERATION instead of terminating", caller, MG_Util::ConvertGLEnumToString(pname).c_str()); MG_State::pGLContext->RecordError( @@ -870,7 +870,7 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format), MG_Util::ConvertGLEnumToTexturePixelDataType(type)); if (readBytesPerTexel != bytesPerTexel) { - MGLOG_I("%s: cannot copy into a %zu-byte texel from a %zu-byte readback layout", caller, + MGLOG_W_ONCE("%s: cannot copy into a %zu-byte texel from a %zu-byte readback layout", caller, bytesPerTexel, readBytesPerTexel); return false; } @@ -1490,7 +1490,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (xoffset + width > static_cast(texelSize.x()) || yoffset + height > static_cast(texelSize.y()) || zoffset + depth > static_cast(texelSize.z())) { - MGLOG_E("TexSubImage3D_State: Specified region exceeds texture level dimensions"); + MGLOG_E_ONCE("TexSubImage3D_State: Specified region exceeds texture level dimensions"); free(processedPixels); return; } @@ -1599,7 +1599,7 @@ namespace MobileGL::MG_Impl::GLImpl { {width, height, 1}, false, inputSize); if (!processedPixels || inputSize == 0) { - MGLOG_E("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", width, + MGLOG_E_ONCE("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", width, height); if (processedPixels) free(processedPixels); return; @@ -1613,7 +1613,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (xoffset + width > static_cast(texelSize.x()) || yoffset + height > static_cast(texelSize.y())) { - MGLOG_E("TexSubImage2D_State: Specified region exceeds texture dimensions"); + MGLOG_E_ONCE("TexSubImage2D_State: Specified region exceeds texture dimensions"); free(processedPixels); return; } @@ -2164,7 +2164,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { - MGLOG_W("%s: Processed pixel data size (%zu) does not match expected size (%zu). " + MGLOG_W_ONCE("%s: Processed pixel data size (%zu) does not match expected size (%zu). " "This may indicate an alignment or processing issue.", __func__, imageSize, internalBytes); } @@ -2310,7 +2310,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { - MGLOG_W("TexImage2D_State: Processed pixel data size (%zu) does not match expected size (%zu). " + MGLOG_W_ONCE("TexImage2D_State: Processed pixel data size (%zu) does not match expected size (%zu). " "This may indicate an alignment or processing issue.", imageSize, internalBytes); } @@ -3643,10 +3643,11 @@ namespace MobileGL::MG_Impl::GLImpl { // an application has every right to expect from it. Before this existed the call answered // GL_INVALID_ENUM, which was wrong but at least visible; a silent success that leaves the // sampled texels untouched is the kind of thing that costs a day to find from the other - // end. MGLOG_I, not _W: warnings are compiled out at the level everything ships at. + // end. MGLOG_W is the right level and now survives at INFO; it sat at MGLOG_I only + // while the Log.h ordering compiled warnings out of the builds that ship. static std::atomic announcedNoCodec{false}; if (!announcedNoCodec.exchange(true)) { - MGLOG_I("%s: the compressed blocks are stored verbatim and returned by " + MGLOG_W("%s: the compressed blocks are stored verbatim and returned by " "glGetCompressedTexImage, but there is no BC/ETC decoder here, so they do not " "reach the texels this level SAMPLES as. Upload through glTexSubImage2D for " "that.", diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp index f9b616e9..6fd7ad63 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp @@ -527,7 +527,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!MG_Backend::pActiveBackendObject || !MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) { - MGLOG_I("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this " + MGLOG_W_ONCE("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this " "backend has no double-precision vertex attribute support - see the " "\"64-bit vertex attributes\" / \"shaderFloat64\" POST row for what that costs", attribindex); diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp index 778abe8e..bc4cee2c 100644 --- a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp @@ -166,32 +166,32 @@ MOBILEGL_GLX_API int glXSwapIntervalSGI(int interval) { // Legacy entry points some loaders probe for; harmless no-op stubs. MOBILEGL_GLX_API void glXCopyContext(Display*, void*, void*, unsigned long) { - MGLOG_W("glx: glXCopyContext is not supported"); + MGLOG_W_ONCE("glx: glXCopyContext is not supported"); } MOBILEGL_GLX_API unsigned long glXCreateGLXPixmap(Display*, void*, unsigned long) { - MGLOG_W("glx: glXCreateGLXPixmap is not supported"); + MGLOG_W_ONCE("glx: glXCreateGLXPixmap is not supported"); return 0; } MOBILEGL_GLX_API void glXDestroyGLXPixmap(Display*, unsigned long) {} MOBILEGL_GLX_API unsigned long glXCreatePixmap(Display*, void*, unsigned long, const int*) { - MGLOG_W("glx: glXCreatePixmap is not supported"); + MGLOG_W_ONCE("glx: glXCreatePixmap is not supported"); return 0; } MOBILEGL_GLX_API void glXDestroyPixmap(Display*, unsigned long) {} MOBILEGL_GLX_API unsigned long glXCreatePbuffer(Display*, void*, const int*) { - MGLOG_W("glx: glXCreatePbuffer is not supported"); + MGLOG_W_ONCE("glx: glXCreatePbuffer is not supported"); return 0; } MOBILEGL_GLX_API void glXDestroyPbuffer(Display*, unsigned long) {} MOBILEGL_GLX_API void glXUseXFont(unsigned long, int, int, int) { - MGLOG_W("glx: glXUseXFont is not supported"); + MGLOG_W_ONCE("glx: glXUseXFont is not supported"); } MOBILEGL_GLX_API void glXSelectEvent(Display*, unsigned long, unsigned long) {} diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp index c8737775..c7f73ab8 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -149,7 +149,7 @@ namespace MobileGL::MG_Impl::GLXImpl { fns->Sync = reinterpret_castSync)>(dlsym(fns->Library, "XSync")); } if (!fns->Valid()) { - MGLOG_E("glx: failed to load libX11 entry points"); + MGLOG_E_ONCE("glx: failed to load libX11 entry points"); } return fns; }(); @@ -314,7 +314,7 @@ namespace MobileGL::MG_Impl::GLXImpl { Uint32 width = 0; Uint32 height = 0; if (!QueryDrawableSize(dpy, drawable, width, height)) { - MGLOG_E("glx: XGetGeometry failed for drawable 0x%lx", drawable); + MGLOG_E_ONCE("glx: XGetGeometry failed for drawable 0x%lx", drawable); return nullptr; } @@ -326,7 +326,7 @@ namespace MobileGL::MG_Impl::GLXImpl { EGLSurface surface = EGLImpl::CreatePlatformWindowSurface( context.Display, context.Config, reinterpret_cast(drawable), attribs); if (surface == EGL_NO_SURFACE) { - MGLOG_E("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable, + MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable, width, height); return nullptr; } @@ -347,7 +347,7 @@ namespace MobileGL::MG_Impl::GLXImpl { const std::lock_guard lock(RegistryMutex()); EGLDisplay display = EnsureDisplay(); if (display == EGL_NO_DISPLAY) { - MGLOG_E("glx: no EGL display"); + MGLOG_E_ONCE("glx: no EGL display"); return nullptr; } EGLImpl::BindAPI(EGL_OPENGL_API); @@ -376,13 +376,13 @@ namespace MobileGL::MG_Impl::GLXImpl { EGLint configCount = 0; if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) || configCount <= 0) { - MGLOG_E("glx: eglChooseConfig failed"); + MGLOG_E_ONCE("glx: eglChooseConfig failed"); return nullptr; } EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); if (eglContext == EGL_NO_CONTEXT) { - MGLOG_E("glx: eglCreateContext failed"); + MGLOG_E_ONCE("glx: eglCreateContext failed"); return nullptr; } @@ -931,7 +931,7 @@ namespace MobileGL::MG_Impl::GLXImpl { if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, object->Context)) { - MGLOG_E("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context); + MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context); return 0; } t_current = {dpy, drawable, drawable, context}; @@ -943,7 +943,7 @@ namespace MobileGL::MG_Impl::GLXImpl { if (context && draw != read) { // MobileGL's backends reject split draw/read surfaces; bind the draw // drawable for both, which is what every real caller here needs. - MGLOG_W("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw, + MGLOG_W_ONCE("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw, read); } const int result = MakeCurrent(dpy, draw, context); @@ -958,7 +958,7 @@ namespace MobileGL::MG_Impl::GLXImpl { auto& surfaces = DrawableSurfaces(); auto it = surfaces.find(drawable); if (it == surfaces.end()) { - MGLOG_W("glx: glXSwapBuffers with no surface for drawable 0x%lx", drawable); + MGLOG_W_ONCE("glx: glXSwapBuffers with no surface for drawable 0x%lx", drawable); return; } SyncSurfaceSize(dpy, drawable, it->second); diff --git a/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp b/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp index 924ea2f8..65e05ed2 100644 --- a/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp +++ b/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp @@ -31,7 +31,7 @@ namespace MG_Impl::GLXImpl { #endif void* proc = MobileGL::MG_Impl::GetProcAddress(name); if (!proc) { - MGLOG_W("Failed to get function: %s", (const char*)name); + MGLOG_D("Failed to get function: %s", (const char*)name); return nullptr; } diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index a38458ee..fe94f063 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -1403,7 +1403,7 @@ namespace MobileGL::MG_Impl { GETPROC(glFramebufferTextureMultiviewOVR, name); // GETPROC(glNamedFramebufferTextureMultiviewOVR, name); - MGLOG_W("GetProcAddress(%s) = nullptr!", name); + MGLOG_D("GetProcAddress(%s) = nullptr!", name); return nullptr; } } // namespace MobileGL::MG_Impl diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp index 5907ff31..a6f0c219 100644 --- a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp @@ -269,7 +269,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } id metalLayerClass = reinterpret_cast(objc_getClass("CAMetalLayer")); if (!metalLayerClass) { - MGLOG_E("NSOpenGLImpl: CAMetalLayer class not found"); + MGLOG_E_ONCE("NSOpenGLImpl: CAMetalLayer class not found"); return nil; } @@ -310,7 +310,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { static_cast(geometry.DrawableSize.width), static_cast(geometry.DrawableSize.height)); if (error != kCGLNoError) { - MGLOG_E("NSOpenGLImpl: failed to attach drawable: %s", CGLImpl::ErrorString(error)); + MGLOG_E_ONCE("NSOpenGLImpl: failed to attach drawable: %s", CGLImpl::ErrorString(error)); } } @@ -325,7 +325,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } const auto error = CGLImpl::SetCurrentContext(context); if (error != kCGLNoError) { - MGLOG_E("NSOpenGLImpl: makeCurrentContext failed: %s", CGLImpl::ErrorString(error)); + MGLOG_E_ONCE("NSOpenGLImpl: makeCurrentContext failed: %s", CGLImpl::ErrorString(error)); } } @@ -345,7 +345,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } const auto error = CGLImpl::FlushDrawable(context); if (error != kCGLNoError) { - MGLOG_E("NSOpenGLImpl: flushBuffer failed: %s", CGLImpl::ErrorString(error)); + MGLOG_E_ONCE("NSOpenGLImpl: flushBuffer failed: %s", CGLImpl::ErrorString(error)); } } @@ -377,7 +377,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { static_cast(geometry.DrawableSize.width), static_cast(geometry.DrawableSize.height)); if (error != kCGLNoError) { - MGLOG_E("NSOpenGLImpl: update failed to attach drawable: %s", CGLImpl::ErrorString(error)); + MGLOG_E_ONCE("NSOpenGLImpl: update failed to attach drawable: %s", CGLImpl::ErrorString(error)); return; } CGLImpl::UpdateContext(context); @@ -421,7 +421,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { SEL selector = sel_registerName(selectorName); Method method = class_getInstanceMethod(cls, selector); if (!method) { - MGLOG_W("NSOpenGLImpl: missing instance method %s", selectorName); + MGLOG_W_ONCE("NSOpenGLImpl: missing instance method %s", selectorName); return; } if (original) { @@ -434,7 +434,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { SEL selector = sel_registerName(selectorName); Method method = class_getClassMethod(cls, selector); if (!method) { - MGLOG_W("NSOpenGLImpl: missing class method %s", selectorName); + MGLOG_W_ONCE("NSOpenGLImpl: missing class method %s", selectorName); return; } method_setImplementation(method, replacement); @@ -444,7 +444,7 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { Class pixelFormatClass = objc_getClass("NSOpenGLPixelFormat"); Class contextClass = objc_getClass("NSOpenGLContext"); if (!pixelFormatClass || !contextClass) { - MGLOG_W("NSOpenGLImpl: NSOpenGL classes are not loaded; hooks not installed"); + MGLOG_W_ONCE("NSOpenGLImpl: NSOpenGL classes are not loaded; hooks not installed"); return false; } diff --git a/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp index 8fadd48f..7e96eea4 100644 --- a/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp @@ -56,7 +56,7 @@ extern "C" HGLRC WINAPI wglCreateLayerContext(HDC hdc, int iLayerPlane) { } extern "C" BOOL WINAPI wglCopyContext(HGLRC, HGLRC, UINT) { - MGLOG_W("wglCopyContext is not supported"); + MGLOG_W_ONCE("wglCopyContext is not supported"); SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } @@ -132,24 +132,24 @@ extern "C" DWORD WINAPI wglSwapMultipleBuffers(UINT n, CONST WGLSWAP* ps) { // ---- Font rendering (legacy immediate-mode feature; not supported) ---- extern "C" BOOL WINAPI wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD) { - MGLOG_W("wglUseFontBitmapsA is not supported"); + MGLOG_W_ONCE("wglUseFontBitmapsA is not supported"); return FALSE; } extern "C" BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD) { - MGLOG_W("wglUseFontBitmapsW is not supported"); + MGLOG_W_ONCE("wglUseFontBitmapsW is not supported"); return FALSE; } extern "C" BOOL WINAPI wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT) { - MGLOG_W("wglUseFontOutlinesA is not supported"); + MGLOG_W_ONCE("wglUseFontOutlinesA is not supported"); return FALSE; } extern "C" BOOL WINAPI wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT) { - MGLOG_W("wglUseFontOutlinesW is not supported"); + MGLOG_W_ONCE("wglUseFontOutlinesW is not supported"); return FALSE; } diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp index 605b53de..59093cb9 100644 --- a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp @@ -215,7 +215,7 @@ namespace MobileGL::MG_Impl::WGLImpl { Uint32 width = 0; Uint32 height = 0; if (!QueryClientSize(hwnd, width, height)) { - MGLOG_E("wgl: GetClientRect failed for HWND %p", hwnd); + MGLOG_E_ONCE("wgl: GetClientRect failed for HWND %p", hwnd); return nullptr; } @@ -227,7 +227,7 @@ namespace MobileGL::MG_Impl::WGLImpl { EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(context.Display, context.Config, hwnd, attribs); if (surface == EGL_NO_SURFACE) { - MGLOG_E("wgl: failed to create window surface for HWND %p (%ux%u)", hwnd, width, height); + MGLOG_E_ONCE("wgl: failed to create window surface for HWND %p (%ux%u)", hwnd, width, height); return nullptr; } @@ -244,7 +244,7 @@ namespace MobileGL::MG_Impl::WGLImpl { const std::lock_guard lock(RegistryMutex()); EGLDisplay display = EnsureDisplay(); if (display == EGL_NO_DISPLAY) { - MGLOG_E("wgl: no EGL display"); + MGLOG_E_ONCE("wgl: no EGL display"); return nullptr; } EGLImpl::BindAPI(EGL_OPENGL_API); @@ -275,13 +275,13 @@ namespace MobileGL::MG_Impl::WGLImpl { EGLConfig config = nullptr; EGLint configCount = 0; if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) || configCount <= 0) { - MGLOG_E("wgl: eglChooseConfig failed"); + MGLOG_E_ONCE("wgl: eglChooseConfig failed"); return nullptr; } EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); if (eglContext == EGL_NO_CONTEXT) { - MGLOG_E("wgl: eglCreateContext failed"); + MGLOG_E_ONCE("wgl: eglCreateContext failed"); return nullptr; } @@ -612,7 +612,7 @@ namespace MobileGL::MG_Impl::WGLImpl { auto& surfaces = WindowSurfaces(); auto it = surfaces.find(hwnd); if (it == surfaces.end()) { - MGLOG_W("wglSwapBuffers: no surface for HWND %p", hwnd); + MGLOG_W_ONCE("wglSwapBuffers: no surface for HWND %p", hwnd); return FALSE; } SyncSurfaceSize(hwnd, it->second); @@ -685,7 +685,7 @@ namespace MobileGL::MG_Impl::WGLImpl { } if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, object->Context)) { - MGLOG_E("wglMakeCurrent: eglMakeCurrent failed (hdc=%p, hglrc=%p)", hdc, hglrc); + MGLOG_E_ONCE("wglMakeCurrent: eglMakeCurrent failed (hdc=%p, hglrc=%p)", hdc, hglrc); return FALSE; } t_current = {hdc, hglrc}; diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index d4b4852b..3d8cd5e9 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -196,7 +196,7 @@ namespace MobileGL::MG_State { // (which expands to nothing outside debug builds). void GLContext::SetCurrentVertexAttributeFloat(Uint index, const Array& value) { if (index >= m_currentVertexAttributes.size()) { - MGLOG_E("SetCurrentVertexAttributeFloat: index %u is out of range", index); + MGLOG_E_ONCE("SetCurrentVertexAttributeFloat: index %u is out of range", index); return; } @@ -210,7 +210,7 @@ namespace MobileGL::MG_State { void GLContext::SetCurrentVertexAttributeInt(Uint index, const Array& value) { if (index >= m_currentVertexAttributes.size()) { - MGLOG_E("SetCurrentVertexAttributeInt: index %u is out of range", index); + MGLOG_E_ONCE("SetCurrentVertexAttributeInt: index %u is out of range", index); return; } @@ -224,7 +224,7 @@ namespace MobileGL::MG_State { void GLContext::SetCurrentVertexAttributeUint(Uint index, const Array& value) { if (index >= m_currentVertexAttributes.size()) { - MGLOG_E("SetCurrentVertexAttributeUint: index %u is out of range", index); + MGLOG_E_ONCE("SetCurrentVertexAttributeUint: index %u is out of range", index); return; } @@ -239,7 +239,7 @@ namespace MobileGL::MG_State { const CurrentVertexAttributeValue& GLContext::GetCurrentVertexAttribute(Uint index) const { static const CurrentVertexAttributeValue defaultValue{}; if (index >= m_currentVertexAttributes.size()) { - MGLOG_E("GetCurrentVertexAttribute: index %u is out of range", index); + MGLOG_E_ONCE("GetCurrentVertexAttribute: index %u is out of range", index); return defaultValue; } return m_currentVertexAttributes[index]; diff --git a/MobileGL/MG_State/GLState/ErrorState/Error.cpp b/MobileGL/MG_State/GLState/ErrorState/Error.cpp index 5c0afc85..a18617c7 100644 --- a/MobileGL/MG_State/GLState/ErrorState/Error.cpp +++ b/MobileGL/MG_State/GLState/ErrorState/Error.cpp @@ -14,10 +14,10 @@ namespace MobileGL::MG_State::GLState { void ErrorState::RecordError(ErrorCode code, UniquePtr info) { if (code == ErrorCode::NoError) { - MGLOG_E("Recording Non-OpenGL error:\n%s", info->toString().c_str()); + MGLOG_D("Recording Non-OpenGL error:\n%s", info->toString().c_str()); m_nonGLErrors.push_back(MakeUnique(code, Move(info))); } else { - MGLOG_E("Recording OpenGL error (%s):\n%s", + MGLOG_D("Recording OpenGL error (%s):\n%s", MG_Util::ConvertGLEnumToString(MG_Util::ConvertErrorCodeToGLEnum(code)).c_str(), info->toString().c_str()); // GL error semantics are sticky flags, not a queue (GL 3.3 core §2.5): with multiple diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index f2844f08..a3235809 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -255,7 +255,7 @@ namespace MobileGL::MG_State::GLState { static_cast(offset) + write.byteOffsetInUniform + write.byteSize > uboSize) { // Same verdict the live write path reaches for a uniform without backing // storage: log and drop, rather than fault. - MGLOG_E("ProgramObject %u: buffered uniform write at location %u has no backing storage " + MGLOG_E_ONCE("ProgramObject %u: buffered uniform write at location %u has no backing storage " "(offset=%u size=%u uboSize=%zu); dropping write", m_externalIndex, write.location, offset, write.byteSize, uboSize); continue; @@ -436,7 +436,7 @@ namespace MobileGL::MG_State::GLState { defaultFS->Compile(); // TODO: use a global default FS object. auto status = defaultFS->GetCompileStatus(); if (!status) { - MGLOG_E("ProgramObject %u: Failed to compile default fragment shader. InfoLog:\n%s", m_externalIndex, + MGLOG_E_ONCE("ProgramObject %u: Failed to compile default fragment shader. InfoLog:\n%s", m_externalIndex, defaultFS->GetInfoLog().c_str()); return; } diff --git a/MobileGL/MG_Test/Util/CMakeLists.txt b/MobileGL/MG_Test/Util/CMakeLists.txt index 2bc31b7d..ffaf927a 100644 --- a/MobileGL/MG_Test/Util/CMakeLists.txt +++ b/MobileGL/MG_Test/Util/CMakeLists.txt @@ -16,5 +16,25 @@ target_link_libraries( ${LINK_LIBRARIES} ) +# The log-severity ordering and the MOBILEGL_ASSERT gate keyed to it. Links gtest +# (not gtest_main): the suite needs its own main() to point MOBILEGL_LOG_FILE_PATH at a +# temp file before anything in the process logs and latches the sink's FILE*. +add_executable( + LogLevelTest + LogLevelTest.cpp +) + +target_include_directories(LogLevelTest PRIVATE + ${MGL_ROOT}/include + ${MGL_ROOT}/MobileGL +) + +target_link_libraries( + LogLevelTest PRIVATE + GTest::gtest + ${LINK_LIBRARIES} +) + include(GoogleTest) gtest_discover_tests(JobNodeTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) +gtest_discover_tests(LogLevelTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) diff --git a/MobileGL/MG_Test/Util/LogLevelTest.cpp b/MobileGL/MG_Test/Util/LogLevelTest.cpp new file mode 100644 index 00000000..eae11ff3 --- /dev/null +++ b/MobileGL/MG_Test/Util/LogLevelTest.cpp @@ -0,0 +1,204 @@ +// MobileGL - MobileGL/MG_Test/Util/LogLevelTest.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// Guards the log-severity ordering and the MOBILEGL_ASSERT gate that hangs off it. +// +// Until 2026-08-13 the numeric order was DEBUG < WARN < ERROR < INFO < FATAL, so the +// production gate `#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_X` compiled +// MGLOG_W and MGLOG_E out of every INFO build. Failures logged with MGLOG_E were +// invisible in exactly the builds that shipped. Nothing in the suite noticed, which is +// why this file exists. +// +// This test is written to be meaningful in BOTH configurations - build it at +// MOBILEGL_LOG_LEVEL_INFO and at MOBILEGL_LOG_LEVEL_DEBUG and it checks the contract +// appropriate to each. It runs headless: no GL context, no device, just the file sink. + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + + // --------------------------------------------------------------------------- + // Compile-time contract + // --------------------------------------------------------------------------- + + // The ordering itself. A renumbering that re-inverts the scale fails here. + static_assert(MOBILEGL_LOG_LEVEL_DEBUG < MOBILEGL_LOG_LEVEL_INFO, "DEBUG must be below INFO"); + static_assert(MOBILEGL_LOG_LEVEL_INFO < MOBILEGL_LOG_LEVEL_WARN, "INFO must be below WARN"); + static_assert(MOBILEGL_LOG_LEVEL_WARN < MOBILEGL_LOG_LEVEL_ERROR, "WARN must be below ERROR"); + static_assert(MOBILEGL_LOG_LEVEL_ERROR < MOBILEGL_LOG_LEVEL_FATAL, "ERROR must be below FATAL"); + + // DEBUG must stay the floor: the MOBILEGL_ASSERT gate in Defines.h is spelled + // `ACTIVE <= MOBILEGL_LOG_LEVEL_DEBUG` and means "only in a DEBUG build". That + // reading is only correct while DEBUG is the minimum. + static_assert(MOBILEGL_LOG_LEVEL_DEBUG == 0, "DEBUG must be the lowest level"); + + // Defines.h and Log.h each define the five constants. Log.h's copy wins when both + // are included; if the two ever drift, the duplicate-definition warning is not + // guaranteed to be an error, so pin the values a second time from this TU's view. + static_assert(MOBILEGL_LOG_LEVEL_INFO == 1, "INFO must be 1 in both Defines.h and Log.h"); + static_assert(MOBILEGL_LOG_LEVEL_WARN == 2, "WARN must be 2 in both Defines.h and Log.h"); + static_assert(MOBILEGL_LOG_LEVEL_ERROR == 3, "ERROR must be 3 in both Defines.h and Log.h"); + static_assert(MOBILEGL_LOG_LEVEL_FATAL == 4, "FATAL must be 4 in both Defines.h and Log.h"); + + // Whether this translation unit was compiled with asserts live. This is a literal + // copy of the Defines.h gate - the point of the test is to prove it agrees with + // MGLOG_D's liveness, observed at runtime below. +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG + constexpr bool kAssertsLive = true; +#else + constexpr bool kAssertsLive = false; +#endif + + // Whether the build is the production INFO configuration. + constexpr bool kBuiltAtInfo = (MOBILEGL_LOG_ACTIVE_LEVEL == MOBILEGL_LOG_LEVEL_INFO); + + // --------------------------------------------------------------------------- + // Runtime observation of the sink + // --------------------------------------------------------------------------- + + // The log file path is latched by MG_Util::Debug::InitFile() on the first write and + // never reopened, so the whole process gets one file. main() below points + // MOBILEGL_LOG_FILE_PATH at a temp file before gtest runs; this fixture emits one + // line per level and then reads the file back. + std::string g_logPath; + + struct Emitted { + bool debug = false; + bool info = false; + bool warn = false; + bool error = false; + bool fatal = false; + }; + + Emitted EmitAndRead() { + // Distinctive markers so a substring search cannot collide with unrelated output. + MGLOG_D("MGLOGTEST_MARKER_DEBUG_5f3a"); + MGLOG_I("MGLOGTEST_MARKER_INFO_5f3a"); + MGLOG_W("MGLOGTEST_MARKER_WARN_5f3a"); + MGLOG_E("MGLOGTEST_MARKER_ERROR_5f3a"); + MGLOG_F("MGLOGTEST_MARKER_FATAL_5f3a"); + + std::ifstream in(g_logPath, std::ios::binary); + std::ostringstream ss; + ss << in.rdbuf(); + const std::string text = ss.str(); + + Emitted e; + e.debug = text.find("MGLOGTEST_MARKER_DEBUG_5f3a") != std::string::npos; + e.info = text.find("MGLOGTEST_MARKER_INFO_5f3a") != std::string::npos; + e.warn = text.find("MGLOGTEST_MARKER_WARN_5f3a") != std::string::npos; + e.error = text.find("MGLOGTEST_MARKER_ERROR_5f3a") != std::string::npos; + e.fatal = text.find("MGLOGTEST_MARKER_FATAL_5f3a") != std::string::npos; + return e; + } + + TEST(LogLevel, SinkIsReachableAtAll) { + // Guards the test itself: if the file sink were disabled or the path override + // ignored, every "level X is suppressed" assertion below would pass vacuously. + ASSERT_FALSE(g_logPath.empty()) << "test harness did not set MOBILEGL_LOG_FILE_PATH"; + const Emitted e = EmitAndRead(); + EXPECT_TRUE(e.fatal) << "FATAL is compiled in at every level; an empty log means the " + "file sink never opened and this suite proves nothing"; + } + + TEST(LogLevel, ProductionBuildKeepsErrorAndWarn) { + if constexpr (!kBuiltAtInfo) { + GTEST_SKIP() << "only meaningful when built at MOBILEGL_LOG_LEVEL_INFO"; + } else { + const Emitted e = EmitAndRead(); + // The regression this file exists for. + EXPECT_TRUE(e.error) << "MGLOG_E must be live in an INFO build"; + EXPECT_TRUE(e.warn) << "MGLOG_W must be live in an INFO build"; + EXPECT_TRUE(e.info) << "MGLOG_I must be live in an INFO build"; + EXPECT_TRUE(e.fatal) << "MGLOG_F must be live in an INFO build"; + // ...and the other half: D must still be compiled out, or production pays + // for every dev-only diagnostic in the tree. + EXPECT_FALSE(e.debug) << "MGLOG_D must be compiled out of an INFO build"; + } + } + + TEST(LogLevel, DebugBuildKeepsEverything) { + if constexpr (MOBILEGL_LOG_ACTIVE_LEVEL != MOBILEGL_LOG_LEVEL_DEBUG) { + GTEST_SKIP() << "only meaningful when built at MOBILEGL_LOG_LEVEL_DEBUG"; + } else { + const Emitted e = EmitAndRead(); + EXPECT_TRUE(e.debug); + EXPECT_TRUE(e.info); + EXPECT_TRUE(e.warn); + EXPECT_TRUE(e.error); + EXPECT_TRUE(e.fatal); + } + } + + // --------------------------------------------------------------------------- + // The assert contract + // --------------------------------------------------------------------------- + + TEST(LogLevel, AssertGateTracksDebugLiveness) { + // The contract: "INFO builds: asserts OFF; DEBUG builds: asserts ON". Stated + // without naming a level, that is exactly "asserts are live iff MGLOG_D is + // live" - which is checkable in whichever configuration this was built in, + // and is what makes the renumbering safe. + const Emitted e = EmitAndRead(); + EXPECT_EQ(kAssertsLive, e.debug) + << "MOBILEGL_ASSERT liveness (" << kAssertsLive << ") disagrees with MGLOG_D liveness (" + << e.debug << "). The Defines.h assert gate and the Log.h MGLOG_D gate have drifted."; + } + + TEST(LogLevel, AssertIsCompiledOutOfProductionBuilds) { + if constexpr (kAssertsLive) { + GTEST_SKIP() << "asserts are live in this configuration; see AssertIsLiveInDebugBuilds"; + } else { + // If MOBILEGL_ASSERT were live here this would TRAP and take the process + // down, which is the behavioural half of the contract. + MOBILEGL_ASSERT(false, "this assert must be compiled out at %s", "INFO"); + SUCCEED(); + } + } + + TEST(LogLevel, AssertIsLiveInDebugBuilds) { + if constexpr (!kAssertsLive) { + GTEST_SKIP() << "asserts are compiled out in this configuration"; + } else { + // A satisfied assert must be a no-op rather than a trap; that it expands to + // real code at all is what kAssertsLive already established. + MOBILEGL_ASSERT(true, "a satisfied assert must not trap"); + SUCCEED(); + } + } + +} // namespace + +int main(int argc, char** argv) { + // Must happen before anything logs: MG_Util::Debug::InitFile() reads the variable + // once, on the first write, and caches the FILE*. + namespace fs = std::filesystem; + const fs::path path = fs::temp_directory_path() / "mobilegl-loglevel-test.log"; + std::error_code ec; + fs::remove(path, ec); + g_logPath = path.string(); + +#if defined(_WIN32) + _putenv_s("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str()); +#else + setenv("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str(), 1); +#endif + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/MobileGL/MG_Util/Async/JobNode.cpp b/MobileGL/MG_Util/Async/JobNode.cpp index 1329ea97..2cdd1f30 100644 --- a/MobileGL/MG_Util/Async/JobNode.cpp +++ b/MobileGL/MG_Util/Async/JobNode.cpp @@ -32,11 +32,11 @@ namespace MobileGL::MG_Util::Async { try { continuation(); } catch (const std::exception& e) { - MGLOG_E("JobNode: a terminal continuation threw (%s); it has been contained, but whatever it " + MGLOG_E_ONCE("JobNode: a terminal continuation threw (%s); it has been contained, but whatever it " "was going to do did not happen", e.what()); } catch (...) { - MGLOG_E("JobNode: a terminal continuation threw a non-std exception; it has been contained, " + MGLOG_E_ONCE("JobNode: a terminal continuation threw a non-std exception; it has been contained, " "but whatever it was going to do did not happen"); } } @@ -165,7 +165,7 @@ namespace MobileGL::MG_Util::Async { Vector lines; lines.swap(node.diagnostics.logLines); for (const String& line : lines) { - MGLOG_W("%s", line.c_str()); + MGLOG_D("%s", line.c_str()); } } diff --git a/MobileGL/MG_Util/Async/ShaderCompilePool.cpp b/MobileGL/MG_Util/Async/ShaderCompilePool.cpp index 315f1734..cbbc863a 100644 --- a/MobileGL/MG_Util/Async/ShaderCompilePool.cpp +++ b/MobileGL/MG_Util/Async/ShaderCompilePool.cpp @@ -281,7 +281,7 @@ namespace MobileGL::MG_Util::Async { enqueued = true; } } catch (...) { - MGLOG_E("ShaderCompilePool::Post: enqueue failed; cancelling the job so its joiner " + MGLOG_E_ONCE("ShaderCompilePool::Post: enqueue failed; cancelling the job so its joiner " "cannot block forever"); if (node) node->Cancel(); return; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 212af1ed..b615e76a 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -594,9 +594,10 @@ namespace MobileGL::MG_Util::BackendLoader { #endif // !_WIN32 if (!eglLib) { - // MGLOG_F, not MGLOG_E: at the INFO log level every shipping and CI build - // uses, MGLOG_E is compiled out (Log.h orders DEBUG < WARN < ERROR < INFO), - // so this diagnosis was invisible in precisely the builds that needed it. + // MGLOG_F, not MGLOG_E: with no EGL there is no rendering at all, so this is a + // bring-up abort rather than a recoverable error. It was forced to F while the + // Log.h ordering compiled MGLOG_E out of every shipping and CI build; F is still + // the right level on its own merits, so it stays. MGLOG_F("Failed to open EGL library: none of libEGL.so.1 / libEGL.so could be " "dlopened; every EGL entry point will be null"); return; @@ -986,6 +987,12 @@ namespace MobileGL::MG_Util::BackendLoader { caps.SupportsBaseInstance ? "yes" : "no"); MGLOG_I(" clip distances (EXT_clip_cull_distance): %s", caps.SupportsClipDistance ? "yes" : "no"); + // LOAD-BEARING STRING, not just a banner. android-plugin/trace-replay-ci.sh's + // is_angle_surface_lost() greps mobilegl.log for exactly "OpenGL ES capabilities:" to + // decide whether MobileGL got far enough to have a working context: if the probe ran, + // a later surface loss is a real defect rather than an emulator fault worth retrying. + // Demoting this line, renaming it, or moving it before the context is usable silently + // inverts that retry logic. It is init-phase, so MGLOG_I is correct and it stays. MGLOG_I("OpenGL ES capabilities:"); glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment); MGLOG_I(" GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: %d", caps.UniformBufferOffsetAlignment); diff --git a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp index 3fe452b2..4564d155 100644 --- a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp @@ -135,7 +135,7 @@ namespace MobileGL { case TexturePixelDataType::UnsignedShort: return TextureInternalFormat::RGBA16; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -148,7 +148,7 @@ namespace MobileGL { case TexturePixelDataType::UnsignedByte: return TextureInternalFormat::RGB8; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -163,7 +163,7 @@ namespace MobileGL { case TexturePixelDataType::UnsignedShort: return TextureInternalFormat::RG16; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -178,7 +178,7 @@ namespace MobileGL { case TexturePixelDataType::UnsignedShort: return TextureInternalFormat::R16; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -195,7 +195,7 @@ namespace MobileGL { case TexturePixelDataType::Float: return TextureInternalFormat::DepthComponent32F; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -208,7 +208,7 @@ namespace MobileGL { case TexturePixelDataType::UnsignedInt248: return TextureInternalFormat::Depth24Stencil8; default: - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " "returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -217,7 +217,7 @@ namespace MobileGL { } } default: { - MGLOG_W("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, returning " + MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, returning " "original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), MG_Util::ConvertTextureInputFormatToString(format).c_str(), @@ -310,7 +310,7 @@ namespace MobileGL { case TextureInternalFormat::DepthStencil: return TextureInternalFormat::DepthStencil; default: - MGLOG_W("%s: Unknown or unhandled internal format %s, returning original.", __func__, + MGLOG_W_ONCE("%s: Unknown or unhandled internal format %s, returning original.", __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str()); return internalformat; } diff --git a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp index 2493606e..23806611 100644 --- a/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp @@ -28,7 +28,7 @@ namespace MobileGL { // DrawArrays/DrawElements rewrite line loops into closed indexed // strips; entry points without that rewrite (instanced/indirect) // degrade to an open strip, which only misses the closing segment. - MGLOG_W("GL_LINE_LOOP without index rewrite; drawing as LINE_STRIP"); + MGLOG_W_ONCE("GL_LINE_LOOP without index rewrite; drawing as LINE_STRIP"); return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; case GL_LINES_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY; @@ -43,7 +43,7 @@ namespace MobileGL { // state (patchControlPoints), not part of the topology. return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; default: - MGLOG_W("Unrecognized primitive topology"); + MGLOG_W_ONCE("Unrecognized primitive topology"); return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; } } @@ -57,7 +57,7 @@ namespace MobileGL { case GL_POINT: return VK_POLYGON_MODE_POINT; default: - MGLOG_W("Unrecognized polygon mode"); + MGLOG_W_ONCE("Unrecognized polygon mode"); return VK_POLYGON_MODE_FILL; } } @@ -73,7 +73,7 @@ namespace MobileGL { case CullFaceMode::Unknown: case CullFaceMode::CullFaceModeCount: default: - MGLOG_W("Unrecognized cull face mode"); + MGLOG_W_ONCE("Unrecognized cull face mode"); return VK_CULL_MODE_BACK_BIT; } } diff --git a/MobileGL/MG_Util/Debug/Log.cpp b/MobileGL/MG_Util/Debug/Log.cpp index c55f0fb5..c8598f69 100644 --- a/MobileGL/MG_Util/Debug/Log.cpp +++ b/MobileGL/MG_Util/Debug/Log.cpp @@ -115,7 +115,13 @@ namespace MobileGL { #endif #if MOBILEGL_LOG_ENABLE_ANDROID && defined(__ANDROID__) - __android_log_print(androidLogLevel, "MobileGL", "%s", out.c_str()); + // Without the trailing newline that the file sink needs: logcat terminates + // records itself, so handing it an already-newline-terminated string made + // every MobileGL log occupy TWO logcat records, the second one empty. That + // halved the useful depth of every `adb logcat -t N` window the CI + // diagnostics read (android-plugin/trace-replay-ci.sh). + __android_log_print(androidLogLevel, "MobileGL", "%.*s", static_cast(out.size() - 1), + out.c_str()); #endif WriteToFile(out.c_str()); diff --git a/MobileGL/MG_Util/Debug/Log.h b/MobileGL/MG_Util/Debug/Log.h index 16b82b5b..299e63a4 100644 --- a/MobileGL/MG_Util/Debug/Log.h +++ b/MobileGL/MG_Util/Debug/Log.h @@ -9,10 +9,24 @@ #pragma once #include +#include + +// Severity order, ascending. MOBILEGL_LOG_ACTIVE_LEVEL names the LOWEST severity that is +// compiled in, so every level at or above it survives and everything below it becomes a +// no-op: the production default INFO admits I/W/E/F and drops only D. +// +// This ordering was inverted until 2026-08-13 (DEBUG < WARN < ERROR < INFO < FATAL), which +// silently compiled MGLOG_W and MGLOG_E out of every production and CI build and cost +// several real diagnostic blackouts. Do not reorder without re-reading every +// `#if MOBILEGL_LOG_ACTIVE_LEVEL <= ...` in the tree. +// +// These five constants are duplicated verbatim in Defines.h, which needs them for the +// MOBILEGL_ASSERT gate in translation units that do not include Log.h. Keep both copies +// in sync; the values are load-bearing, not cosmetic. #define MOBILEGL_LOG_LEVEL_DEBUG 0 -#define MOBILEGL_LOG_LEVEL_WARN 1 -#define MOBILEGL_LOG_LEVEL_ERROR 2 -#define MOBILEGL_LOG_LEVEL_INFO 3 +#define MOBILEGL_LOG_LEVEL_INFO 1 +#define MOBILEGL_LOG_LEVEL_WARN 2 +#define MOBILEGL_LOG_LEVEL_ERROR 3 #define MOBILEGL_LOG_LEVEL_FATAL 4 #define MOBILEGL_LOG_INTERNAL(levelTag, androidLogLevel, fmt, ...) \ @@ -20,6 +34,28 @@ MobileGL::MG_Util::Debug::Log(levelTag, androidLogLevel, fmt, ##__VA_ARGS__); \ } while (0) +// Emit `inner` at most once per call site, for the life of the process. +// +// Production logging is not allowed to repeat: a diagnostic on a per-draw or per-frame +// path costs frame time on every occurrence and buries the rest of the log. Anything at +// W or E that sits on such a path must either be latched with one of the _ONCE forms +// below or be demoted to MGLOG_D, which production compiles out entirely. +// +// The latch is a function-local atomic - zero-initialised before any dynamic +// initialisation runs, so it is safe from any thread at any time, needs no guard +// variable, and costs one relaxed test-and-set on the already-cold failure path. Note +// that the latch is per CALL SITE, not per subject: a site that reports "texture %u is +// unsupported" reports only the first such texture. That is the intended trade - the +// first occurrence is what a user shares for troubleshooting, and MGLOG_D still shows +// every occurrence in a dev build. +#define MOBILEGL_LOG_ONCE_INTERNAL(inner, fmt, ...) \ + do { \ + static ::std::atomic_flag mobileglLogOnceLatch; \ + if (!mobileglLogOnceLatch.test_and_set(::std::memory_order_relaxed)) { \ + inner(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG #define MGLOG_D(fmt, ...) MOBILEGL_LOG_INTERNAL("DEBUG", ANDROID_LOG_DEBUG, fmt, ##__VA_ARGS__) #else @@ -55,6 +91,37 @@ {} #endif +// One-shot forms. Each is gated on its own level so that a suppressed level leaves no +// latch behind - MGLOG_D_ONCE in a production build is nothing at all, not a byte of +// state plus a test-and-set. +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG +#define MGLOG_D_ONCE(fmt, ...) MOBILEGL_LOG_ONCE_INTERNAL(MGLOG_D, fmt, ##__VA_ARGS__) +#else +#define MGLOG_D_ONCE(fmt, ...) \ + {} +#endif + +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_INFO +#define MGLOG_I_ONCE(fmt, ...) MOBILEGL_LOG_ONCE_INTERNAL(MGLOG_I, fmt, ##__VA_ARGS__) +#else +#define MGLOG_I_ONCE(fmt, ...) \ + {} +#endif + +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_WARN +#define MGLOG_W_ONCE(fmt, ...) MOBILEGL_LOG_ONCE_INTERNAL(MGLOG_W, fmt, ##__VA_ARGS__) +#else +#define MGLOG_W_ONCE(fmt, ...) \ + {} +#endif + +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_ERROR +#define MGLOG_E_ONCE(fmt, ...) MOBILEGL_LOG_ONCE_INTERNAL(MGLOG_E, fmt, ##__VA_ARGS__) +#else +#define MGLOG_E_ONCE(fmt, ...) \ + {} +#endif + namespace MobileGL { namespace MG_Util { namespace Debug { diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index 2c9f8b08..3ca3a3fa 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -517,7 +517,7 @@ namespace MobileGL { // parameter queries on the initial state); every size stays 0. break; default: - MGLOG_W("Unimplemented internal format in GetComponentSizesForInternalFormat: %d", + MGLOG_W_ONCE("Unimplemented internal format in GetComponentSizesForInternalFormat: %d", static_cast(internal)); break; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 7a69df68..17271264 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -463,11 +463,10 @@ namespace MobileGL { case SPV_MSG_FATAL: case SPV_MSG_INTERNAL_ERROR: case SPV_MSG_ERROR: - // MGLOG_I, deliberately: at the INFO compile level of every - // CI/WSL/retrace build, MGLOG_E and MGLOG_W are compiled out - // (Log.h orders DEBUG < WARN < ERROR < INFO) and the VUID - // would never reach a log. - MGLOG_I("[spirv] %s: %s (word index %zu)", site, text, position.index); + // Unlatched: only reachable with the validation switch armed, + // and every VUID names a different defect. (Parked at MGLOG_I + // until the Log.h ordering fix made E live at INFO.) + MGLOG_E("[spirv] %s: %s (word index %zu)", site, text, position.index); break; default: MGLOG_D("[spirv] %s: %s", site, text); @@ -486,7 +485,7 @@ namespace MobileGL { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); tools.SetMessageConsumer(MakeSpirvMessageConsumer(site)); if (!tools.Validate(binary)) { - MGLOG_I("[spirv] %s: produced a module that fails validation (failure #%llu)", + MGLOG_E("[spirv] %s: produced a module that fails validation (failure #%llu)", site, static_cast( ShaderCompiler::NoteSpirvValidationFailure())); @@ -837,10 +836,10 @@ namespace MobileGL { } if (LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(outputBinary)) { - // MGLOG_I, deliberately: MGLOG_E/W are compiled out at the INFO level every - // CI and retrace build uses, and this is precisely the diagnostic that has - // to survive to explain a shader the driver is about to reject. - MGLOG_I("[spirv] LegalizeFragmentOutputIndexingForEssl: a fragment output is still " + // MGLOG_W, latched: this runs per shader compile, and shader packs compile + // lazily mid-session, so an unlatched line here is unbounded runtime noise. + // (Parked at MGLOG_I until the Log.h ordering fix made W live at INFO.) + MGLOG_W_ONCE("[spirv] LegalizeFragmentOutputIndexingForEssl: a fragment output is still " "indexed dynamically; a strict ES driver will reject this shader"); } return true; @@ -866,9 +865,8 @@ namespace MobileGL { // access path there is no correct answer to substitute, because the ES texture // genuinely has a height the GL one does not. // - // MGLOG_I, deliberately: MGLOG_E/W are compiled out at the INFO level every CI, - // retrace and release build uses, and this is exactly the diagnostic that has to - // survive to explain the shader the driver is about to reject. + // MGLOG_W, latched: per shader compile, and shader packs compile lazily + // mid-session. (Parked at MGLOG_I until the Log.h ordering fix made W live.) const auto traits = Lower1DArrayImagesPass::InspectBinary(inputBinary); // The overwhelmingly common answer, and the reason the inspection exists: no // 1D-array storage image, so the module is handed back byte for byte without an @@ -879,7 +877,7 @@ namespace MobileGL { return true; } if (traits.queriesImageSize) { - MGLOG_I("[spirv] Lower1DArrayImagesForEssl: the module queries the size of a 1D-array " + MGLOG_W_ONCE("[spirv] Lower1DArrayImagesForEssl: the module queries the size of a 1D-array " "storage image, which cannot be answered in the 2D-array shape ES stores it in; " "leaving the module alone, and a strict ES driver will reject it"); outputBinary = inputBinary; diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index bab0f43d..213d2de9 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -1275,7 +1275,7 @@ namespace MobileGL { // matches (e.g. the pack shipped a new shader revision), the affected device // silently falls back to the driver's miscompiled path. Make that visible. if (CountToken(tokens, "subgroupInclusiveAdd") > 0) { - MGLOG_W("%s: subgroupInclusiveAdd present but the linear prefix-scan template " + MGLOG_W_ONCE("%s: subgroupInclusiveAdd present but the linear prefix-scan template " "did not match; the wide-subgroup rewrite was NOT applied", __func__); } @@ -1357,7 +1357,7 @@ namespace MobileGL { continue; } if (quirk.Apply(quirkContext, source)) { - MGLOG_I("ApplyShaderSourceQuirks: applied '%s'%s", quirk.name, + MGLOG_D("ApplyShaderSourceQuirks: applied '%s'%s", quirk.name, quirkOverride == MG_Config::QuirkOverride::ForceOn ? " (forced on)" : ""); } } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp index 8d252255..8b7ba0de 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp @@ -203,7 +203,7 @@ namespace MobileGL { static_cast(entryPoint->GetSingleWordInOperand(0)); if (executionModel == spv::ExecutionModel::Geometry || executionModel == spv::ExecutionModel::TessellationControl) { - MGLOG_I("FlattenXfbInterfaceBlocksPass: execution model %u publishes outputs outside " + MGLOG_D("FlattenXfbInterfaceBlocksPass: execution model %u publishes outputs outside " "the entry point's return; leaving its blocks declared as blocks", static_cast(executionModel)); return Status::SuccessWithoutChange; @@ -302,7 +302,7 @@ namespace MobileGL { target.members.push_back(member); } if (!usable || target.members.empty()) { - MGLOG_I("FlattenXfbInterfaceBlocksPass: block '%s' has a member this pass cannot " + MGLOG_D("FlattenXfbInterfaceBlocksPass: block '%s' has a member this pass cannot " "place; leaving it declared as a block", blockName.c_str()); continue; @@ -370,7 +370,7 @@ namespace MobileGL { continue; } if (derivedPointers.count(operand.words[0]) == 0) continue; - MGLOG_I("FlattenXfbInterfaceBlocksPass: interface block %%%u reaches a " + MGLOG_D("FlattenXfbInterfaceBlocksPass: interface block %%%u reaches a " "SPIR-V opcode %u that this pass cannot follow; leaving it " "declared as a block", operand.words[0], static_cast(opcode)); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp index 9afff299..77a19ebe 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp @@ -82,7 +82,7 @@ namespace MobileGL { // 6 or 8 uint32 components has no single vertex format, and GL spreads such // an input over two attribute locations. Left alone; the vertex-input // factory declines the matching attribute for the same reason. - MGLOG_E("PackDoubleVertexInputsPass: vertex input %%%u is a %u-component 64-bit " + MGLOG_E_ONCE("PackDoubleVertexInputsPass: vertex input %%%u is a %u-component 64-bit " "float; only double and dvec2 inputs can be packed", inst.result_id(), components); continue; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp index c97753b6..329edff5 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp @@ -128,7 +128,7 @@ namespace MobileGL { const Uint32 elementTypeId = typeMgr->GetId(arrayType->element_type()); const Uint32 locationsPerElement = LocationsPerElement(arrayType->element_type()); if (elementTypeId == 0 || locationsPerElement == 0) { - MGLOG_I("SplitArrayVertexInputsPass: vertex input %%%u is an array whose element " + MGLOG_D("SplitArrayVertexInputsPass: vertex input %%%u is an array whose element " "type has no single-location mapping; leaving it declared as an array", inst.result_id()); continue; @@ -195,7 +195,7 @@ namespace MobileGL { continue; } if (derivedPointers.count(operand.words[0]) == 0) continue; - MGLOG_I("SplitArrayVertexInputsPass: array vertex input %%%u reaches a " + MGLOG_D("SplitArrayVertexInputsPass: array vertex input %%%u reaches a " "SPIR-V opcode %u that this pass cannot follow; leaving it " "declared as an " "array", diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 41e43a0d..27ae0380 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -95,7 +95,7 @@ namespace MobileGL { if (member.array.dims_count > 1) { // Arrays of arrays of structs cannot be declared in the GL 3.3-era GLSL // MobileGL ingests; record the base so at least element 0 resolves. - MGLOG_W("FlattenGlobalUboMember: multi-dimensional struct array '%s' is not supported, " + MGLOG_W_ONCE("FlattenGlobalUboMember: multi-dimensional struct array '%s' is not supported, " "flattening element 0 only", name.c_str()); } diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp index c56dd35d..a5797d36 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp @@ -455,7 +455,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { break; default: - MGLOG_E("NormalizePixelFormat: outFormat: unhandled internalFormat: %s", + MGLOG_E_ONCE("NormalizePixelFormat: outFormat: unhandled internalFormat: %s", MG_Util::ConvertGLEnumToString(internalFormat).c_str()); // Fallback handling for other formats // Try to infer format from internal format name @@ -676,7 +676,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { break; default: - MGLOG_E("NormalizePixelFormat: outType: unhandled internalFormat: %s", + MGLOG_E_ONCE("NormalizePixelFormat: outType: unhandled internalFormat: %s", MG_Util::ConvertGLEnumToString(internalFormat).c_str()); // Fallback handling for other formats *outType = GL_UNSIGNED_BYTE; diff --git a/android-plugin/trace-replay-ci.sh b/android-plugin/trace-replay-ci.sh index 9e9be025..07ca235d 100644 --- a/android-plugin/trace-replay-ci.sh +++ b/android-plugin/trace-replay-ci.sh @@ -187,7 +187,11 @@ collect_run_diagnostics() { if [ "${adb_state}" != "device" ]; then return fi - "${ADB}" logcat -d -t 2000 > "${diagnostics_dir}/logcat.txt" || true + # Depth matters, not just content: is_infrastructure_failure below decides "the emulator + # broke, retry" by finding a system_server crash in this window, and MobileGL logs into the + # same buffer under its own tag. A tail that is too short lets routine MobileGL output evict + # the crash line and charges an infrastructure fault to the trace under test. + "${ADB}" logcat -d -t 20000 > "${diagnostics_dir}/logcat.txt" || true adb_device_path shell pidof "${package_name}" > "${diagnostics_dir}/pidof.txt" 2>&1 || true adb_device_path shell dumpsys activity activities > "${diagnostics_dir}/activity.txt" 2>&1 || true adb_device_path shell run-as "${package_name}" ls -laR "${app_dir}" > "${diagnostics_dir}/app-files.txt" 2>&1 || true