qt6-base-fourier: GL_ALPHA → GL_R8 on OpenGL ES 3.x

Three small runtime checks in qtbase 6.11.0:

1. src/opengl/qopengltextureglyphcache.cpp createTextureData() and
   load_glyph_image_to_texture() — the `#else` branch (active when
   qtbase is built with QT_CONFIG(opengles2), every aarch64 distro)
   hard-codes GL_ALPHA without checking the runtime context's ES
   version. Replace with `useR8 = ctx->format().majorVersion() >= 3`
   so ES 3+ contexts get GL_R8 + GL_RED.

2. src/gui/rhi/qrhigles2.cpp toGlTextureFormat() RED_OR_ALPHA8 case
   — gated only on caps.coreProfile. Extend with
   `caps.gles && caps.ctxMajor >= 3`.

3. src/opengl/qopengltextureuploader.cpp Format_Alpha8 and
   Format_Grayscale8 cases — short-circuit on `context->isOpenGLES()`
   before reaching the swizzle fallback. Restrict to ES 2 only so
   ES 3+ falls through to the existing TextureSwizzle path.

Discovered while validating chromium-fourier patch 3/3 (NV12
EXTERNAL_OES) on ohm. The chrome stall, VLC vout init failure and
mpv "could not initialize video chain" all share this Qt 6 root
cause: every Qt application running on Mali / panfrost / panthor
under a KWin Wayland session emits glTexImage2D(GL_ALPHA) on every
text-glyph cache upload, mesa returns GL_INVALID_VALUE, the
compositor's frame-callback path stalls, and every wayland video
client deadlocks behind it.

PKGBUILD inherits from extra/qt6-base 6.11.0-2 with arch+=aarch64,
epoch=1 to dominate Arch's pkgrel until upstream lands the fix.
qt6-base-cflags.patch and qt6-base-nostrip.patch carried verbatim
from upstream packaging.

KWIN_PIVOT.md (in chromium-fourier/) carries the full diagnosis
thread.
This commit is contained in:
2026-04-28 12:35:08 +00:00
parent cd25d02e01
commit 1dada5122b
6 changed files with 378 additions and 0 deletions
@@ -0,0 +1,43 @@
diff --git a/src/opengl/qopengltextureglyphcache.cpp b/src/opengl/qopengltextureglyphcache.cpp
index 0bab710b..46bad551 100644
--- a/src/opengl/qopengltextureglyphcache.cpp
+++ b/src/opengl/qopengltextureglyphcache.cpp
@@ -108,14 +108,20 @@ void QOpenGLTextureGlyphCache::createTextureData(int width, int height)
for (int i = 0; i < data.size(); ++i)
data[i] = 0;
#if !QT_CONFIG(opengles2)
const GLint internalFormat = isCoreProfile() ? GL_R8 : GL_ALPHA;
const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA;
#else
- const GLint internalFormat = GL_ALPHA;
- const GLenum format = GL_ALPHA;
+ // qt6-base-fourier: OpenGL ES 3.x removed GL_ALPHA from
+ // glTexImage2D's valid internalFormat list (ES 3 spec
+ // section 3.8.3, table 3.13). Pick GL_R8 + matching format
+ // when the runtime context is ES 3 or newer; only legacy
+ // ES 2 contexts fall through to the historic GL_ALPHA path.
+ const bool useR8 = ctx->format().majorVersion() >= 3;
+ const GLint internalFormat = useR8 ? GL_R8 : GL_ALPHA;
+ const GLenum format = useR8 ? GL_RED : GL_ALPHA;
#endif
funcs->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, &data[0]);
}
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -210,13 +216,14 @@ static void load_glyph_image_to_texture(QOpenGLContext *ctx,
} else {
// The scanlines in image are 32-bit aligned, even for mono or 8-bit formats. This
// is good because it matches the default of 4 bytes for GL_UNPACK_ALIGNMENT.
#if !QT_CONFIG(opengles2)
const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA;
#else
- const GLenum format = GL_ALPHA;
+ // qt6-base-fourier: ES 3 path — see createTextureData() above.
+ const GLenum format = ctx->format().majorVersion() >= 3 ? GL_RED : GL_ALPHA;
#endif
funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, tx, ty, imgWidth, imgHeight, format, GL_UNSIGNED_BYTE, img.constBits());
}
}
static void load_glyph_image_region_to_texture(QOpenGLContext *ctx,
@@ -0,0 +1,33 @@
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 5208bd4d..a2625949 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -1367,18 +1367,25 @@ static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2
case QRhiTexture::RG8:
*glintformat = GL_RG8;
*glsizedintformat = *glintformat;
*glformat = GL_RG;
*gltype = GL_UNSIGNED_BYTE;
break;
- case QRhiTexture::RED_OR_ALPHA8:
- *glintformat = caps.coreProfile ? GL_R8 : GL_ALPHA;
+ case QRhiTexture::RED_OR_ALPHA8: {
+ // qt6-base-fourier: GL_ALPHA was removed from the valid
+ // glTexImage2D internalFormat list in OpenGL ES 3.0 (ES 3
+ // spec section 3.8.3). Pick GL_R8 + GL_RED for desktop GL
+ // Core profile and for any ES 3 or newer context; only
+ // legacy ES 2 contexts retain GL_ALPHA.
+ const bool useR8 = caps.coreProfile || (caps.gles && caps.ctxMajor >= 3);
+ *glintformat = useR8 ? GL_R8 : GL_ALPHA;
*glsizedintformat = *glintformat;
- *glformat = caps.coreProfile ? GL_RED : GL_ALPHA;
+ *glformat = useR8 ? GL_RED : GL_ALPHA;
*gltype = GL_UNSIGNED_BYTE;
break;
+ }
case QRhiTexture::RGBA16F:
*glintformat = GL_RGBA16F;
*glsizedintformat = *glintformat;
*glformat = GL_RGBA;
*gltype = GL_HALF_FLOAT;
break;
@@ -0,0 +1,51 @@
diff --git a/src/opengl/qopengltextureuploader.cpp b/src/opengl/qopengltextureuploader.cpp
index 3dca0a43..5d650523 100644
--- a/src/opengl/qopengltextureuploader.cpp
+++ b/src/opengl/qopengltextureuploader.cpp
@@ -246,17 +246,24 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
// Always needs conversion
break;
} else if (options & UseRedForAlphaAndLuminanceBindOption) {
externalFormat = internalFormat = GL_RED;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = image.format();
- } else if (context->isOpenGLES() || context->format().profile() != QSurfaceFormat::CoreProfile) {
+ } else if ((context->isOpenGLES() && context->format().majorVersion() < 3)
+ || (!context->isOpenGLES() && context->format().profile() != QSurfaceFormat::CoreProfile
+ && !funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle))) {
+ // qt6-base-fourier: only true ES 2 (or pre-3.3 desktop
+ // compat without the swizzle extension) reaches the
+ // GL_ALPHA fallback. ES 3+ falls through to the swizzle
+ // path below — TextureSwizzle is core in ES 3.0.
externalFormat = internalFormat = GL_ALPHA;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = image.format();
- } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)) {
+ } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)
+ || (context->isOpenGLES() && context->format().majorVersion() >= 3)) {
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ALPHA);
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ZERO);
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ZERO);
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ZERO);
externalFormat = internalFormat = GL_RED;
pixelType = GL_UNSIGNED_BYTE;
@@ -268,13 +275,18 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
// Always needs conversion
break;
} else if (options & UseRedForAlphaAndLuminanceBindOption) {
externalFormat = internalFormat = GL_RED;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = image.format();
- } else if (context->isOpenGLES() || context->format().profile() != QSurfaceFormat::CoreProfile) {
+ } else if ((context->isOpenGLES() && context->format().majorVersion() < 3)
+ || (!context->isOpenGLES() && context->format().profile() != QSurfaceFormat::CoreProfile
+ && !funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle))) {
+ // qt6-base-fourier: same reasoning as the Format_Alpha8
+ // branch above — GL_LUMINANCE is also gone from ES 3
+ // glTexImage2D internalFormats.
externalFormat = internalFormat = GL_LUMINANCE;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = image.format();
} else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)) {
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
@@ -0,0 +1,46 @@
diff --git a/mkspecs/common/g++-unix.conf b/mkspecs/common/g++-unix.conf
index a493cd5984..41342f5020 100644
--- a/mkspecs/common/g++-unix.conf
+++ b/mkspecs/common/g++-unix.conf
@@ -10,5 +10,6 @@
include(g++-base.conf)
-QMAKE_LFLAGS_RELEASE += -Wl,-O1
+SYSTEM_LDFLAGS = $$(LDFLAGS)
+!isEmpty(SYSTEM_LDFLAGS) { eval(QMAKE_LFLAGS_RELEASE += $$(LDFLAGS)) } else { QMAKE_LFLAGS_RELEASE += -Wl,-O1 }
QMAKE_LFLAGS_NOUNDEF += -Wl,--no-undefined
diff --git a/mkspecs/common/gcc-base.conf b/mkspecs/common/gcc-base.conf
index 1f919d270a..7ef6046326 100644
--- a/mkspecs/common/gcc-base.conf
+++ b/mkspecs/common/gcc-base.conf
@@ -40,9 +40,11 @@ QMAKE_CFLAGS_OPTIMIZE_SIZE = -Os
QMAKE_CFLAGS_DEPS += -M
QMAKE_CFLAGS_WARN_ON += -Wall -Wextra
QMAKE_CFLAGS_WARN_OFF += -w
-QMAKE_CFLAGS_RELEASE += $$QMAKE_CFLAGS_OPTIMIZE
-QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_OPTIMIZE -g
-QMAKE_CFLAGS_DEBUG += -g
+SYSTEM_CFLAGS = $$(CFLAGS)
+SYSTEM_DEBUG_CFLAGS = $$(DEBUG_CFLAGS)
+!isEmpty(SYSTEM_CFLAGS) { eval(QMAKE_CFLAGS_RELEASE += $$(CFLAGS)) } else { QMAKE_CFLAGS_RELEASE += $$QMAKE_CFLAGS_OPTIMIZE }
+!isEmpty(SYSTEM_CFLAGS) { eval(QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -g $$(CFLAGS)) } else { QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_OPTIMIZE -g }
+!isEmpty(SYSTEM_DEBUG_CFLAGS) { eval(QMAKE_CFLAGS_DEBUG += $$(DEBUG_CFLAGS)) } else { QMAKE_CFLAGS_DEBUG += -g }
QMAKE_CFLAGS_SHLIB += $$QMAKE_CFLAGS_PIC
QMAKE_CFLAGS_STATIC_LIB += $$QMAKE_CFLAGS_PIC
QMAKE_CFLAGS_APP += $$QMAKE_CFLAGS_PIC
@@ -59,9 +61,11 @@ QMAKE_CXXFLAGS += $$QMAKE_CFLAGS
QMAKE_CXXFLAGS_DEPS += $$QMAKE_CFLAGS_DEPS
QMAKE_CXXFLAGS_WARN_ON += $$QMAKE_CFLAGS_WARN_ON
QMAKE_CXXFLAGS_WARN_OFF += $$QMAKE_CFLAGS_WARN_OFF
-QMAKE_CXXFLAGS_RELEASE += $$QMAKE_CFLAGS_RELEASE
-QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
-QMAKE_CXXFLAGS_DEBUG += $$QMAKE_CFLAGS_DEBUG
+SYSTEM_CXXFLAGS = $$(CXXFLAGS)
+SYSTEM_DEBUG_CXXFLAGS = $$(DEBUG_CXXFLAGS)
+!isEmpty(SYSTEM_CXXFLAGS) { eval(QMAKE_CXXFLAGS_RELEASE += $$(CXXFLAGS)) } else { QMAKE_CXXFLAGS_RELEASE += $$QMAKE_CFLAGS_OPTIMIZE }
+!isEmpty(SYSTEM_CXXFLAGS) { eval(QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += -g $$(CXXFLAGS)) } else { QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_OPTIMIZE -g }
+!isEmpty(SYSTEM_DEBUG_CXXFLAGS) { eval(QMAKE_CXXFLAGS_DEBUG += $$(DEBUG_CXXFLAGS)) } else { QMAKE_CXXFLAGS_DEBUG += -g }
QMAKE_CXXFLAGS_SHLIB += $$QMAKE_CFLAGS_SHLIB
QMAKE_CXXFLAGS_STATIC_LIB += $$QMAKE_CFLAGS_STATIC_LIB
QMAKE_CXXFLAGS_APP += $$QMAKE_CFLAGS_APP
@@ -0,0 +1,13 @@
diff --git a/mkspecs/common/gcc-base.conf b/mkspecs/common/gcc-base.conf
index 99d77156fd..fc840fe9f6 100644
--- a/mkspecs/common/gcc-base.conf
+++ b/mkspecs/common/gcc-base.conf
@@ -31,6 +31,8 @@
# you can use the manual test in tests/manual/mkspecs.
#
+CONFIG += nostrip
+
QMAKE_CFLAGS_OPTIMIZE = -O2
QMAKE_CFLAGS_OPTIMIZE_FULL = -O3
QMAKE_CFLAGS_OPTIMIZE_DEBUG = -Og