From 3a9e52017015a075405f9ca0a0b65cba5cbc2de9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 26 Jul 2026 10:43:48 -0400 Subject: [PATCH] [Test] (CTS): isolate the DirectVulkan renderbuffer-FBO readback defect so the rest of KHR-GL33 can be measured --- .../0001-fbo-color-texture-attachment.patch | 109 ++++++++++++++++++ tools/cts/probe/mgprobe.c | 47 +++++++- tools/cts/scripts/run_cts.py | 5 +- tools/cts/skills/gl-cts-on-mobilegl/SKILL.md | 24 +++- 4 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 tools/cts/patches/0001-fbo-color-texture-attachment.patch diff --git a/tools/cts/patches/0001-fbo-color-texture-attachment.patch b/tools/cts/patches/0001-fbo-color-texture-attachment.patch new file mode 100644 index 00000000..b98c5ae3 --- /dev/null +++ b/tools/cts/patches/0001-fbo-color-texture-attachment.patch @@ -0,0 +1,109 @@ +diff --git a/framework/opengl/gluFboRenderContext.cpp b/framework/opengl/gluFboRenderContext.cpp +index 588cf7d2a..0721ffee7 100644 +--- a/framework/opengl/gluFboRenderContext.cpp ++++ b/framework/opengl/gluFboRenderContext.cpp +@@ -132,6 +132,7 @@ FboRenderContext::FboRenderContext(RenderContext *context, const RenderConfig &c + : m_context(context) + , m_framebuffer(0) + , m_colorBuffer(0) ++ , m_colorIsTexture(false) + , m_depthStencilBuffer(0) + , m_renderTarget() + { +@@ -151,6 +152,7 @@ FboRenderContext::FboRenderContext(const ContextFactory &factory, const RenderCo + : m_context(nullptr) + , m_framebuffer(0) + , m_colorBuffer(0) ++ , m_colorIsTexture(false) + , m_depthStencilBuffer(0) + , m_renderTarget() + { +@@ -215,19 +217,41 @@ void FboRenderContext::createFramebuffer(const RenderConfig &config) + height = (height == glu::RenderConfig::DONT_CARE) ? maxSize : height; + } + ++ // MOBILEGL: allow the colour attachment to be a texture instead of a ++ // renderbuffer. MobileGL's DirectVulkan backend returns zeros when reading ++ // back a renderbuffer-attached FBO, which makes every image comparison fail ++ // for one reason and hides everything else. Setting ++ // MOBILEGL_CTS_FBO_COLOR_TEXTURE=1 isolates that single defect so the rest ++ // of the suite can be measured. Off by default: stock behaviour. + { +- pixelFormat = getPixelFormat(colorFormat); ++ const char *useTexEnv = getenv("MOBILEGL_CTS_FBO_COLOR_TEXTURE"); ++ m_colorIsTexture = (useTexEnv && useTexEnv[0] == '1' && config.numSamples <= 0); + +- gl.genRenderbuffers(1, &m_colorBuffer); +- gl.bindRenderbuffer(GL_RENDERBUFFER, m_colorBuffer); ++ pixelFormat = getPixelFormat(colorFormat); + +- if (config.numSamples > 0) +- gl.renderbufferStorageMultisample(GL_RENDERBUFFER, config.numSamples, colorFormat, width, height); ++ if (m_colorIsTexture) ++ { ++ gl.genTextures(1, &m_colorBuffer); ++ gl.bindTexture(GL_TEXTURE_2D, m_colorBuffer); ++ gl.texStorage2D(GL_TEXTURE_2D, 1, colorFormat, width, height); ++ gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); ++ gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); ++ gl.bindTexture(GL_TEXTURE_2D, 0); ++ GLU_EXPECT_NO_ERROR(gl.getError(), "Creating color texture"); ++ } + else +- gl.renderbufferStorage(GL_RENDERBUFFER, colorFormat, width, height); +- +- gl.bindRenderbuffer(GL_RENDERBUFFER, 0); +- GLU_EXPECT_NO_ERROR(gl.getError(), "Creating color renderbuffer"); ++ { ++ gl.genRenderbuffers(1, &m_colorBuffer); ++ gl.bindRenderbuffer(GL_RENDERBUFFER, m_colorBuffer); ++ ++ if (config.numSamples > 0) ++ gl.renderbufferStorageMultisample(GL_RENDERBUFFER, config.numSamples, colorFormat, width, height); ++ else ++ gl.renderbufferStorage(GL_RENDERBUFFER, colorFormat, width, height); ++ ++ gl.bindRenderbuffer(GL_RENDERBUFFER, 0); ++ GLU_EXPECT_NO_ERROR(gl.getError(), "Creating color renderbuffer"); ++ } + } + + if (depthStencilFormat != GL_NONE) +@@ -250,7 +274,12 @@ void FboRenderContext::createFramebuffer(const RenderConfig &config) + gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + + if (m_colorBuffer) +- gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorBuffer); ++ { ++ if (m_colorIsTexture) ++ gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0); ++ else ++ gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorBuffer); ++ } + + if (m_depthStencilBuffer) + { +@@ -290,7 +319,10 @@ void FboRenderContext::destroyFramebuffer(void) + + if (m_colorBuffer) + { +- gl.deleteRenderbuffers(1, &m_colorBuffer); ++ if (m_colorIsTexture) ++ gl.deleteTextures(1, &m_colorBuffer); ++ else ++ gl.deleteRenderbuffers(1, &m_colorBuffer); + m_colorBuffer = 0; + } + } +diff --git a/framework/opengl/gluFboRenderContext.hpp b/framework/opengl/gluFboRenderContext.hpp +index 75a0ff6b7..09ff1e7a9 100644 +--- a/framework/opengl/gluFboRenderContext.hpp ++++ b/framework/opengl/gluFboRenderContext.hpp +@@ -80,6 +80,7 @@ private: + RenderContext *m_context; + uint32_t m_framebuffer; + uint32_t m_colorBuffer; ++ bool m_colorIsTexture; + uint32_t m_depthStencilBuffer; + tcu::RenderTarget m_renderTarget; + }; diff --git a/tools/cts/probe/mgprobe.c b/tools/cts/probe/mgprobe.c index c2bbe123..915129ec 100644 --- a/tools/cts/probe/mgprobe.c +++ b/tools/cts/probe/mgprobe.c @@ -77,6 +77,7 @@ typedef void *EGLNativeWindowType; #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_NEAREST 0x2600 +#define GL_RENDERBUFFER 0x8D41 typedef EGLDisplay (*P_getDisplay)(EGLNativeDisplayType); typedef EGLBoolean (*P_initialize)(EGLDisplay, EGLint *, EGLint *); @@ -105,6 +106,10 @@ typedef void (*P_glBindFramebuffer)(unsigned int, unsigned int); typedef void (*P_glFramebufferTexture2D)(unsigned int, unsigned int, unsigned int, unsigned int, int); typedef unsigned int (*P_glCheckFramebufferStatus)(unsigned int); typedef void (*P_glViewport)(int, int, int, int); +typedef void (*P_glGenRenderbuffers)(int, unsigned int *); +typedef void (*P_glBindRenderbuffer)(unsigned int, unsigned int); +typedef void (*P_glRenderbufferStorage)(unsigned int, unsigned int, int, int); +typedef void (*P_glFramebufferRenderbuffer)(unsigned int, unsigned int, unsigned int, unsigned int); static void *g_lib; static void *S(const char *n) { return dlsym(g_lib, n); } @@ -256,6 +261,10 @@ int main(int argc, char **argv) { P_glFramebufferTexture2D glFramebufferTexture2D_ = (P_glFramebufferTexture2D)S("glFramebufferTexture2D"); P_glCheckFramebufferStatus glCheckFramebufferStatus_ = (P_glCheckFramebufferStatus)S("glCheckFramebufferStatus"); P_glViewport glViewport_ = (P_glViewport)S("glViewport"); + P_glGenRenderbuffers glGenRenderbuffers_ = (P_glGenRenderbuffers)S("glGenRenderbuffers"); + P_glBindRenderbuffer glBindRenderbuffer_ = (P_glBindRenderbuffer)S("glBindRenderbuffer"); + P_glRenderbufferStorage glRenderbufferStorage_ = (P_glRenderbufferStorage)S("glRenderbufferStorage"); + P_glFramebufferRenderbuffer glFramebufferRenderbuffer_ = (P_glFramebufferRenderbuffer)S("glFramebufferRenderbuffer"); int major = -1, minor = -1, profile = -1; glGetIntegerv_(GL_MAJOR_VERSION, &major); @@ -306,11 +315,43 @@ int main(int argc, char **argv) { printf(" user-FBO incomplete status=0x%x\n", fbst); } + /* FBO with a RENDERBUFFER colour attachment. This is what dEQP's + * FboRenderContext allocates for --deqp-surface-type=fbo, so it is the path + * that actually decides a conformance run - a texture-attached FBO working + * says nothing about it. */ + unsigned int rbo = 0, rfbo = 0; + int rboOk = 0; + if (glGenRenderbuffers_ && glBindRenderbuffer_ && glRenderbufferStorage_ && glFramebufferRenderbuffer_) { + glGenRenderbuffers_(1, &rbo); + glBindRenderbuffer_(GL_RENDERBUFFER, rbo); + glRenderbufferStorage_(GL_RENDERBUFFER, GL_RGBA8, DIM, DIM); + glBindRenderbuffer_(GL_RENDERBUFFER, 0); + glGenFramebuffers_(1, &rfbo); + glBindFramebuffer_(GL_FRAMEBUFFER, rfbo); + glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); + unsigned int rst = glCheckFramebufferStatus_(GL_FRAMEBUFFER); + if (rst == GL_FRAMEBUFFER_COMPLETE) { + glViewport_(0, 0, DIM, DIM); + glClearColor_(0.1f, 0.7f, 0.3f, 1.0f); + glClear_(GL_COLOR_BUFFER_BIT); + if (glFinish_) glFinish_(); + memset(px, 0, sizeof px); + glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); + rboOk = near8(px[0], 26, 10) && near8(px[1], 179, 10) && near8(px[2], 77, 10); + printf(" rbo-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], + rboOk ? "ok" : "BROKEN"); + } else { + printf(" rbo-FBO incomplete status=0x%x\n", rst); + } + } else { + printf(" rbo-FBO skipped (renderbuffer entry points unavailable)\n"); + } + unsigned glerr = glGetError_ ? glGetError_() : 0; - int ok = fboOk && (major > 3 || (major == 3 && minor >= 3)) && (profile & 1) && glerr == 0; - printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s glerr=0x%x\n", + int ok = fboOk && rboOk && (major > 3 || (major == 3 && minor >= 3)) && (profile & 1) && glerr == 0; + printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s rbo_fbo=%s glerr=0x%x\n", ok ? "PASS" : "FAIL", backend, surface, defOk ? "ok" : "broken", - fboOk ? "ok" : "broken", glerr); + fboOk ? "ok" : "broken", rboOk ? "ok" : "broken", glerr); fflush(stdout); /* MobileGL aborts in static teardown; leave before that runs. */ diff --git a/tools/cts/scripts/run_cts.py b/tools/cts/scripts/run_cts.py index f827f9fb..df10d1a7 100644 --- a/tools/cts/scripts/run_cts.py +++ b/tools/cts/scripts/run_cts.py @@ -95,6 +95,8 @@ def main(): help="seconds before giving up on one glcts invocation (a GPU hang never returns)") ap.add_argument("--skip-file", default=None, help="file of case names to exclude, e.g. cases known to hang the device") + ap.add_argument("--env", action="append", default=[], metavar="K=V", + help="extra environment variable for glcts (repeatable)") args = ap.parse_args() os.makedirs(args.outdir, exist_ok=True) @@ -148,9 +150,10 @@ def main(): continue adb(args.serial, "shell", f"rm -f {dev_qpa}", timeout=60) + extra_env = "".join(f"{kv} " for kv in args.env) cmd = ( f"cd {args.device_dir} && " - f"MOBILEGL_BACKEND_TYPE={args.backend} LD_LIBRARY_PATH=. " + f"MOBILEGL_BACKEND_TYPE={args.backend} LD_LIBRARY_PATH=. {extra_env}" f"./glcts --deqp-caselist-file={dev_list} " f"--deqp-surface-type={args.surface} " f"--deqp-terminate-on-device-lost=disable " diff --git a/tools/cts/skills/gl-cts-on-mobilegl/SKILL.md b/tools/cts/skills/gl-cts-on-mobilegl/SKILL.md index 3a40b805..7b829833 100644 --- a/tools/cts/skills/gl-cts-on-mobilegl/SKILL.md +++ b/tools/cts/skills/gl-cts-on-mobilegl/SKILL.md @@ -141,7 +141,8 @@ complete one. | Flag | Why it is not optional | | --- | --- | -| `--deqp-surface-type=fbo` | On DirectVulkan, `glReadPixels` from the **default framebuffer returns all zeros** with no GL error. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores DirectVulkan near zero for a reason unrelated to conformance. FBO readback is correct on both backends. Use it for **both** backends so the two numbers stay comparable. | +| `--deqp-surface-type=fbo` | On DirectVulkan, `glReadPixels` from the **default framebuffer returns all zeros** with no GL error. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores DirectVulkan near zero for a reason unrelated to conformance. Use it for **both** backends so the two numbers stay comparable. | +| `MOBILEGL_CTS_FBO_COLOR_TEXTURE=1` | **`--deqp-surface-type=fbo` alone is not enough.** dEQP's `FboRenderContext` allocates a *renderbuffer* colour attachment, and DirectVulkan returns zeros from a renderbuffer-attached FBO too — only a *texture*-attached FBO reads back correctly. This env var (a patch to `framework/opengl/gluFboRenderContext.cpp`, off by default) switches the attachment to a texture and isolates that single defect. Measured effect: `KHR-GL33.shaders.loops.for_constant_iterations.*` goes 0/62 → 62/62, and the whole-suite DirectVulkan conformance rate goes 46.15% → 72.74%. DirectGLES is bit-identical either way (93.05%), which is the control proving the switch is neutral where readback works. | | `--deqp-terminate-on-device-lost=disable` | Defaults to *enable*, which calls `glGetGraphicsResetStatus()` after every case. That is GL 4.5 / `KHR_robustness`, absent from GL 3.3 core, so the pointer is null and the process segfaults on the first case. Desktop drivers expose the extension, which is why upstream never trips on it. | ## Cases that take the device down @@ -153,11 +154,32 @@ adb entirely. Keep them in a `--skip-file`, and expect to find more: and then stopped responding to adb altogether. - `KHR-GL33.framebuffer_blit.multisampled_to_singlesampled_blit_color_config_test` — rebooted an Adreno 830 phone after 862 cases, on DirectGLES. +- `KHR-GL33.framebuffer_blit.multisampled_to_singlesampled_blit_depth_config_test` + — same, on both backends (found and quarantined automatically by the runner). +- `KHR-GL33.texture_repeat_mode.rgb565_11x131_0_clamp_to_edge` — on DirectVulkan. + +The whole `framebuffer_blit.multisampled_to_singlesampled_*` family is suspect; +treat a new variant as a device-hang candidate rather than a normal failure. When a run dies, pull `/data/local/tmp/mgcts/chunk.qpa` — it survives the reboot, and the last `#beginTestCaseResult` with no matching `#endTestCaseResult` names the case that did it. +## Reference results + +`opengl-cts-4.6.8.1`, KHR-GL33 mustpass (`gl33-main.txt`, 9886 cases), Adreno 830 +/ Android 15, MobileGL `dev`@199164c2, 9884 measured / 0 unrun / 2 quarantined. +Conformance rate = Pass + NotSupported, as Khronos scores a submission. + +| backend | conformance | strict Pass | Fail | Crash | InternalError | DeviceHang | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| DirectGLES | **93.05%** | 85.94% | 679 | 1 | 6 | 1 | +| DirectVulkan (texture FBO) | **72.74%** | 65.71% | 2394 | 294 | 5 | 1 | +| DirectVulkan (stock renderbuffer FBO) | 46.15% | 39.11% | 5024 | 292 | 5 | 2 | + +The third row is what stock dEQP reports; the gap to the second row is entirely +the renderbuffer-FBO readback defect. + ## MobileGL constraints the port works around - **DirectVulkan cannot use an EGL pbuffer.** That path needs