glGetTexImage returns a black image when using a GBM-backed EGLDisplay

Full minimal reproducer
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <fcntl.h>
#include <unistd.h>

#include <gbm.h>

#include <epoxy/egl.h>
#include <epoxy/gl.h>

#define TARGET_SIZE 256

struct gpu_context {
    int fd;
    struct gbm_device *gbm;
    EGLDisplay dpy;
    EGLContext ctx;
};

void RenderTargetInit(const char *name, struct gpu_context *ctx)
{
    ctx->fd = open(name, O_RDWR);
    assert(ctx->fd >= 0);

    ctx->gbm = gbm_create_device(ctx->fd);
    assert(ctx->gbm != NULL);

    assert((ctx->dpy = eglGetDisplay(ctx->gbm)) != EGL_NO_DISPLAY);

    assert(eglInitialize(ctx->dpy, NULL, NULL) == EGL_TRUE);

    eglBindAPI(EGL_OPENGL_API);
  
    assert((ctx->ctx = eglCreateContext(ctx->dpy, NULL, EGL_NO_CONTEXT, NULL)) != EGL_NO_CONTEXT);

    assert(eglMakeCurrent(ctx->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx->ctx) == EGL_TRUE);
}

void Render(float r, float g, float b, float a) {
    glClearColor(r, g, b, a);
    glClear(GL_COLOR_BUFFER_BIT);

    glFinish();
}

void Check(GLuint tex_out, float r, float g, float b, float a) {
    uint32_t *data = calloc(1, TARGET_SIZE * TARGET_SIZE * 4);
    assert(data);

#if 1
    glBindTexture(GL_TEXTURE_2D, tex_out);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
#else
    GLuint fb_out;
    glGenFramebuffers(1, &fb_out);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_out, 0);
    glReadPixels(0, 0, TARGET_SIZE, TARGET_SIZE, GL_RGBA, GL_UNSIGNED_BYTE, data);
#endif

    uint32_t expect =
        (((uint32_t)(a * 255) & 0xff) << 24) |
        (((uint32_t)(b * 255) & 0xff) << 16) |
        (((uint32_t)(g * 255) & 0xff) << 8) |
        (((uint32_t)(r * 255) & 0xff) << 0);
    for (int i = 0; i < TARGET_SIZE * TARGET_SIZE; i++) {
        if (data[i] != expect) {
            printf("check buffer fail at %d: %x/%x\n", i, data[i], expect);
            return;
        }
    }
    printf("check buffer success\n");
    free(data);
}

int main(int argc, char **argv)
{
    char *f1 = "/dev/dri/renderD128";
    if (argc > 1)
        f1 = argv[1];

    struct gpu_context gpu;
    RenderTargetInit(f1, &gpu);

    GLuint fbid;
    glGenFramebuffers(1, &fbid);
    glBindFramebuffer(GL_FRAMEBUFFER, fbid);

    GLuint texid;
    glGenTextures(1, &texid);
    glBindTexture(GL_TEXTURE_2D, texid);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TARGET_SIZE, TARGET_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texid, 0);

    Render(1, 0, 1, 0);

    EGLImage image = eglCreateImage(gpu.dpy, gpu.ctx, EGL_GL_TEXTURE_2D, (EGLClientBuffer)(uint64_t)texid, NULL);
    assert(image != EGL_NO_IMAGE);

    GLuint out_tex = 0;
    glGenTextures(1, &out_tex);
    glBindTexture(GL_TEXTURE_2D, out_tex);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
    assert(glGetError() == GL_NO_ERROR);

    Check (out_tex, 1, 0, 1, 0);

    assert(glGetError() == GL_NO_ERROR);

    return 0;
}

Above is a code sample ready-to-compile with -lepoxy -lgbm.
Here are the highlight points:

  1. Allocate texture A
  2. Bind framebuffer F to texture A
  3. Draw to framebuffer F
  4. Create EGLImage I from texture A
  5. Create texture B from EGLImage I
  6. Read back texture B

You may also move step 3 down to after 5, and the result will be the same.

If you read back the texture using glGetTexImage you will get a black image.
If instead you bind the texture to a new framebuffer and use glReadPixels, you will correctly get the image you drew.

This only happens if the EGLDisplay is created with eglGetDisplay(gbm_dev), or with eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_dev, NULL), while using eglGetDisplay(EGL_DEFAULT_DISPLAY) gives the correct result.

Confirmed driver bug. Again, many thanks for finding this and providing a repro app. Fortunately the fix is fairly simple, should be able to get it into the next release.