Skip to content

CM5: V3D alpha corruption (255 -> 247) and GPU hangs across OS versions #7657

Description

@markoromteh

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 and glGetError() 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

  1. Boot Raspberry Pi OS with dtoverlay=vc4-kms-v3d enabled and no other GPU workload. The test uses surfaceless EGL and does not require an active desktop or monitor.

  2. Save the exact clear-check.c source included below. Build with a C compiler and EGL/GLES development headers available:

    cc -O2 -Wall -Wextra clear-check.c -o clear-check -lEGL -lGLESv2
  3. Run the software control; verify that the printed renderer is llvmpipe:

    env -u MESA_LOADER_DRIVER_OVERRIDE -u GALLIUM_DRIVER -u V3D_DEBUG \
      EGL_PLATFORM=surfaceless MESA_SHADER_CACHE_DISABLE=true \
      LIBGL_ALWAYS_SOFTWARE=true timeout -k 2s 15s ./clear-check clear
  4. Run the GPU comparison; verify that the renderer is V3D, not a software fallback:

    env -u LIBGL_ALWAYS_SOFTWARE -u MESA_LOADER_DRIVER_OVERRIDE \
      -u GALLIUM_DRIVER -u V3D_DEBUG EGL_PLATFORM=surfaceless \
      MESA_SHADER_CACHE_DISABLE=true timeout -k 2s 15s ./clear-check clear
  5. Repeat each command separately with upload and draw in place of clear. Expected output is RESULT 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. clear fills it with opaque red using glClear; upload copies opaque red from CPU memory; draw renders 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:

Raspberry Pi OS 64-bit / Debian 13 Trixie
Raspberry Pi reference 2026-09-15
pi-gen: 2c235fa703cacb65e0fe0b2ab2fcb23d44dfd268, stage4
Linux 6.18.50+rpt-rpi-2712 #1 SMP PREEMPT Debian 1:6.18.50-1+rpt1 (2026-09-11) aarch64
Mesa: 26.2.2-1~bpo13+0~rpt1
GPU renderer: V3D 7.1.10.2
vcgencmd version: 2026/09/25 15:59:09, version 1accd665 (release) (embedded)
EEPROM: 1accd6659983613cd104973ce2432da14f1a7682

Fresh full-OS comparison: official 2026-09-15-raspios-bookworm-arm64.img.xz, image SHA256 28c432b8abbeea65365d957f8b1d4f9ebfcca42284044c5dfa149151a501d9c9; 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:

renderer=V3D 7.1.10.2
version=OpenGL ES 3.1 Mesa 26.2.2-1~bpo13+0~rpt1
operation=clear
bad x=0 y=0 rgba=255,0,0,247
bad x=4 y=0 rgba=255,0,0,247
bad x=8 y=0 rgba=255,0,0,247
bad x=12 y=0 rgba=255,0,0,247
...
RESULT FAIL pixels=65536 bad=12 alpha247=12 guards=1 gl_error=0x0
Configuration Software clear/upload/draw Native GPU incorrect pixels: clear/upload/draw
6.18.50 / Mesa 26.2.2, normal 8 GB All pass 12 / 0 / 208
Same stack, verified one-node numa=fake=1 boot All pass 12 / 0 / 193
Same stack, separate total_mem=4096 boot, original command line All pass 13 / 0 / 195
Fresh full Bookworm SD, 6.12.109 / Mesa 24.2.8 All pass 13 / 0 / 225

The latest 18 baseline/memory cases had intact guards, zero GL errors, get_throttled=0x0 throughout, 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:

v3d 1002000000.v3d: [drm:v3d_reset [v3d]] *ERROR* Resetting GPU for hang.
v3d 1002000000.v3d: [drm:v3d_reset [v3d]] *ERROR* V3D_ERR_STAT: 0x00001000

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

  • Additional bounded comparisons did not resolve corruption: 6.18.50 with 4 KiB pages, 6.12.109 with the current userspace, privately extracted Mesa 25.0.7, PCIe Gen2, a temporary 500 MHz GPU cap, and held-active V3D runtime PM. The full-SD test also reproduced the fault without NVMe.
  • Zink over V3DV still produced incorrect pixels, including its upload case; true CPU-Zink controls passed. Native drawing without glClear, and a supported sRGB framebuffer, also failed. These are not an exhaustive cross-product of all software versions.
  • The original PSU, a second supply, and reseating the CM5 did not eliminate corruption. A boot on the second supply recorded historical undervoltage; the subsequent original-45 W repeat above had clear flags and reproduced the same failure. No module/carrier substitution or oscilloscope measurement has been performed.
  • The installed kernel source is 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.
  • One untested question is whether the cache-lock widening proposed in drm/v3d: Widen cache_clean_lock over the whole L2TCACTL sequence #7491 has any relevance here. That PR is closed without merging; its rationale cites an erratum whose pending-flush workaround is gated to hardware older than V3D 7.1. I have not applied it or treated it as a confirmed CM5 fix.

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
/* 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;
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions