diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index c1d2cc92..7244d4c7 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3258,6 +3258,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", stateProgramObject->GetExternalIndex(), m_backendProgramId); + m_backendProgramUsable = true; m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask; m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask; m_fragColorBroadcastCount = g_fragColorBroadcastCount; @@ -3365,6 +3366,7 @@ namespace MobileGL::MG_Backend::DirectGLES { r.log += spvcSession.GetLastErrorString(); r.errc = -5; MGLOG_E("%s", r.log.c_str()); + m_backendProgramUsable = false; continue; } @@ -3406,6 +3408,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector log(logLength); g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data()); MGLOG_E("Shader compilation failed for backend ID %u: %s", backendShaderId, log.data()); + m_backendProgramUsable = false; continue; } @@ -3441,6 +3444,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLint linkStatus; g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus); + m_backendProgramUsable = m_backendProgramUsable && linkStatus == GL_TRUE; if (linkStatus != GL_TRUE) { GLint logLength; g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength); @@ -3566,12 +3570,18 @@ namespace MobileGL::MG_Backend::DirectGLES { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif - if (g_lastUsedBackendProgramId == m_backendProgramId) { + // glUseProgram on a program that did not link is an INVALID_OPERATION and + // leaves the *previous* program current, so the draw would silently render + // with an unrelated shader (KHR-GL3x.texture_size_promotion read another + // test case's alpha that way once a sampler2DRect stage failed to + // transpile). Bind nothing instead: the draw is then a visible no-op. + const Uint programToBind = m_backendProgramUsable ? m_backendProgramId : 0; + if (g_lastUsedBackendProgramId == programToBind) { return; } - MGLOG_D("Using program %u", m_backendProgramId); - g_GLESFuncs.glUseProgram(m_backendProgramId); - g_lastUsedBackendProgramId = m_backendProgramId; + MGLOG_D("Using program %u", programToBind); + g_GLESFuncs.glUseProgram(programToBind); + g_lastUsedBackendProgramId = programToBind; } void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const { diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index ccc53916..0c8101e2 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -600,6 +600,10 @@ namespace MobileGL::MG_Backend::DirectGLES { void SetDrawID(Uint32 drawId) const; Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; } Uint GetBackendProgramId() const { return m_backendProgramId; } + // False when the last SyncToBackend could not produce a usable program (a + // shader failed to transpile or compile, or the link itself failed). Use() + // must not leave the previously bound program current in that case. + Bool IsBackendProgramUsable() const { return m_backendProgramUsable; } Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; } Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; } Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; } @@ -634,6 +638,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // PrgramImpl::BroadcastLegacyFragColor); 1 keeps the plain single-output shader. Uint m_fragColorBroadcastCount = 1; Bool m_isInitialized = false; + Bool m_backendProgramUsable = false; Int m_globalUboBackendBlockIndex = -1; Int m_globalUboBackendBlockSize = 0;