/* Independent, bounded GLES2 framebuffer test. No display/camera access. */
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void need(int ok, const char *what) {
if (!ok) {
fprintf(stderr, "%s failed: EGL=0x%x GL=0x%x\n", what,
eglGetError(), glGetError());
exit(3);
}
}
static GLuint shader(GLenum kind, const char *source) {
GLuint s = glCreateShader(kind);
glShaderSource(s, 1, &source, NULL);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[2048];
glGetShaderInfoLog(s, sizeof(log), NULL, log);
fprintf(stderr, "%s\n", log);
}
need(ok, "shader compile");
return s;
}
int main(int argc, char **argv) {
const char *mode = argc == 2 ? argv[1] : "clear";
need(!strcmp(mode, "clear") || !strcmp(mode, "upload") ||
!strcmp(mode, "draw"), "operation");
enum { W = 256, H = 256, N = W * H * 4, GUARD = 16 };
EGLDisplay d = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major, minor;
need(d != EGL_NO_DISPLAY && eglInitialize(d, &major, &minor), "eglInitialize");
need(eglBindAPI(EGL_OPENGL_ES_API), "eglBindAPI");
const EGLint ca[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_NONE};
EGLConfig cfg;
EGLint count;
need(eglChooseConfig(d, ca, &cfg, 1, &count) && count == 1, "eglChooseConfig");
const EGLint pa[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
EGLSurface surf = eglCreatePbufferSurface(d, cfg, pa);
need(surf != EGL_NO_SURFACE, "eglCreatePbufferSurface");
const EGLint ctxa[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGLContext ctx = eglCreateContext(d, cfg, EGL_NO_CONTEXT, ctxa);
need(ctx != EGL_NO_CONTEXT && eglMakeCurrent(d, surf, surf, ctx), "eglMakeCurrent");
printf("renderer=%s\nversion=%s\noperation=%s\n", glGetString(GL_RENDERER),
glGetString(GL_VERSION), mode);
fflush(stdout);
GLuint tex, fb;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
need(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE,
"framebuffer completeness");
glViewport(0, 0, W, H);
glDisable(GL_DITHER);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
glDisable(GL_SAMPLE_COVERAGE);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
unsigned char *upload = malloc(N);
unsigned char *buf = malloc(N + 2 * GUARD);
need(upload != NULL && buf != NULL, "allocation");
for (int i = 0; i < N; i += 4) {
upload[i] = upload[i+3] = 255;
upload[i+1] = upload[i+2] = 0;
}
memset(buf, 0xa5, N + 2 * GUARD);
if (!strcmp(mode, "upload")) {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA,
GL_UNSIGNED_BYTE, upload);
} else if (!strcmp(mode, "clear")) {
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
} else {
const char *vs = "attribute vec2 p; void main(){gl_Position=vec4(p,0.0,1.0);}";
const char *fs = "precision highp float; void main(){gl_FragColor=vec4(1.0,0.0,0.0,1.0);}";
GLuint program = glCreateProgram();
glAttachShader(program, shader(GL_VERTEX_SHADER, vs));
glAttachShader(program, shader(GL_FRAGMENT_SHADER, fs));
glBindAttribLocation(program, 0, "p");
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
need(linked, "program link");
glUseProgram(program);
const GLfloat vertices[] = {-1,-1, 3,-1, -1,3};
GLuint vb;
glGenBuffers(1, &vb);
glBindBuffer(GL_ARRAY_BUFFER, vb);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
glFinish();
need(glGetError() == GL_NO_ERROR, "render");
glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, buf + GUARD);
GLenum err = glGetError();
size_t bad = 0, alpha247 = 0;
int guards = 1;
for (int i = 0; i < GUARD; i++)
if (buf[i] != 0xa5 || buf[GUARD + N + i] != 0xa5) guards = 0;
for (int i = 0; i < N; i += 4) {
unsigned char *p = buf + GUARD + i;
if (memcmp(p, upload + i, 4)) {
if (bad < 16) printf("bad x=%d y=%d rgba=%u,%u,%u,%u\n",
(i/4)%W, (i/4)/W, p[0],p[1],p[2],p[3]);
bad++;
if (p[3] == 247) alpha247++;
}
}
printf("RESULT %s pixels=%d bad=%zu alpha247=%zu guards=%d gl_error=0x%x\n",
!bad && guards && !err ? "PASS" : "FAIL", W*H, bad, alpha247, guards, err);
fflush(stdout);
eglMakeCurrent(d, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(d, ctx);
eglDestroySurface(d, surf);
eglTerminate(d);
free(upload);
free(buf);
return bad || !guards || err ? 2 : 0;
}
Describe the bug
On a Compute Module 5 Lite, 8 GB, a small offscreen GLES test repeatedly reads some opaque-red pixels as RGBA
(255,0,0,247)instead of(255,0,0,255)after a GPU clear or draw. Buffer guards are intact andglGetError()returns zero. The native-V3D CPU-upload/readback control and matching llvmpipe controls pass.This also reproduces on a freshly imaged official Raspberry Pi OS Bookworm installation, with its own stock kernel/Mesa, NVMe absent, no DSI connector enumerated, and no desktop running. Independent Piglit tests on that installation reproduce incorrect pixels and GPU hangs. Selected Khronos CTS cases also reproduced failures/hangs on the original installation.
I am reporting the reproducible graphics failure, not asserting a known regression or defective module. I have no known-working hardware-accelerated baseline on this unit. I would appreciate independent reproduction or guidance on a targeted driver/firmware test before replacing hardware. If this belongs in the Mesa tracker, please advise and I can move the report.
Steps to reproduce the behaviour
Boot Raspberry Pi OS with
dtoverlay=vc4-kms-v3denabled and no other GPU workload. The test uses surfaceless EGL and does not require an active desktop or monitor.Save the exact
clear-check.csource included below. Build with a C compiler and EGL/GLES development headers available:Run the software control; verify that the printed renderer is llvmpipe:
Run the GPU comparison; verify that the renderer is V3D, not a software fallback:
Repeat each command separately with
uploadanddrawin place ofclear. Expected output isRESULT PASS, zero bad pixels, intact guards and zero GL error in every case. Stop if a GPU reset or kernel fault occurs; these comparisons do not require a long stress test.The program creates a 256 × 256 RGBA texture framebuffer.
clearfills it with opaque red usingglClear;uploadcopies opaque red from CPU memory;drawrenders an opaque-red triangle covering the framebuffer. It compares all 65,536 pixels after readback. Exit status 0 means pass, 2 means comparison/guard/GL failure, and 3 means setup/render failure.Device (s)
Raspberry Pi CM5 Lite, CM5108000, 8 GB RAM with wireless/Bluetooth, on the official CM5 IO Board. Official Raspberry Pi 45 W PSU and fitted heatsink; no overclock added for the tests.
Original setup: 512 GB Raspberry Pi NVMe and 10-inch Touch Display 2 on CAM/DISP0. The AI Camera is disconnected. The reduced-hardware fresh-SD comparison had no NVMe or DSI display enumerated and LightDM inactive.
System
Current original SSD installation:
Fresh full-OS comparison: official
2026-09-15-raspios-bookworm-arm64.img.xz, image SHA25628c432b8abbeea65365d957f8b1d4f9ebfcca42284044c5dfa149151a501d9c9; stock kernel 6.12.109+rpt-rpi-2712, stock Mesa 24.2.8-1~bpo12+rpt5. Graphics packages were not replaced or updated before that comparison. It used the earlier 12 September EEPROM,a86983925695a7e63166327d7c002d64040ed31d.Logs
Representative native GPU clear on the current kernel/Mesa, original 45 W PSU and updated EEPROM:
numa=fake=1boottotal_mem=4096boot, original command lineThe latest 18 baseline/memory cases had intact guards, zero GL errors,
get_throttled=0x0throughout, and no new GPU hang or monitor failure. Sampled 5 V readings were 5.10–5.13 V and temperatures 44–51°C. Counts vary by run; all incorrect pixels in these small C cases had alpha 247. Clean sampled values cannot exclude every electrical transient.Separately, the fresh-SD Piglit framebuffer-clear test passed 38/38 software subtests. Its GPU run returned alpha 247 instead of 255 in the first format, then triggered three GPU resets and was stopped. Kernel messages included:
That run reported
get_throttled=0x0, approximately 34–36°C, and sampled input voltage above 5.12 V. Linux/SSH remained available during reproduced GPU hangs; these observations do not establish a whole-board reset. The small C probe's pixel corruption and the other tests' hangs are not assumed to have a proven common cause.Additional context
glClear, and a supported sRGB framebuffer, also failed. These are not an exhaustive cross-product of all software versions.cff533aec2fa601846766b32ff57204e0a61bed7; the relevant unconditional-MMU-flush and pre-suspend-cache-clean fixes from PR of Maira's pmdomain/downstream/timeouts branch #7400 are already present. Holding V3D continuously active also failed to remove the corruption.All temporary boot overrides have been removed and normal 8 GB operation restored. The SSD desktop is currently kept usable with software rendering. I can provide additional selected logs or run a targeted patch/debug capture. Independent reproduction on another CM5 would be particularly useful.
Exact bounded reproducer: clear-check.c