Wrapped gl context gst_gl_context_check_feature does not delegate to gst_gl_context_egl_check_feature

Hello,

I’m using gstreamer on imx8mp with NXP patches, but I’ve checked upstream code and I don’t think it’s different – I’ll try to reproduce on my PC next week.

The problem we have is that using a context created with gst_gl_context_new_wrapped()the features check don’t work, which in turn made dmabuf break since this change https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2925/diffs#diff-content-4885cc453da9f66e3581af312f02697342c4db3b (yes that’s a bit old, we’ve been on debian oldoldstable and are only upgrading now…)

The real use case is this: flutter-elinux-plugins/packages/video_player/elinux/gst_video_player.cc at main · sony/flutter-elinux-plugins · GitHub (gst_egl_image_from_dmabuf() returns null)

Below is a minimal reproducer that fails on our platform, the check feature check is false when it should be true, and in turn we’re not getting any image because it’s not even trying due to the feature check:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <wayland-client.h>
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <gst/video/video.h>
#include <gst/allocators/gstdmabuf.h>
#include <gst/gl/gl.h>
#include <gst/gl/egl/egl.h>

/* --- Wayland globals --- */
static struct wl_display *wl_dpy;
static EGLDisplay egl_dpy;
static EGLContext egl_ctx;

/* -------------------------------------------------------- */

static void init_egl(void)
{
    wl_dpy = wl_display_connect(NULL);
    if (!wl_dpy) {
        fprintf(stderr, "wl_display_connect failed\n");
        exit(1);
    }

    egl_dpy = eglGetDisplay((EGLNativeDisplayType)wl_dpy);
    eglInitialize(egl_dpy, NULL, NULL);

    EGLint cfg_attr[] = {
        EGL_SURFACE_TYPE, EGL_DONT_CARE,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };

    EGLConfig cfg;
    EGLint n;
    eglChooseConfig(egl_dpy, cfg_attr, &cfg, 1, &n);

    EGLint ctx_attr[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };

    egl_ctx = eglCreateContext(
        egl_dpy, cfg, EGL_NO_CONTEXT, ctx_attr);

    eglMakeCurrent(
        egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_ctx);
}

/* -------------------------------------------------------- */

int main(int argc, char **argv)
{
    gst_init(&argc, &argv);
    init_egl();

    /* --- GStreamer pipeline --- */
    GstElement *pipeline = gst_parse_launch(
        "videotestsrc is-live=true ! "
        "video/x-raw,format=RGBA,width=640,height=480 ! "
        "vpuenc_h264 ! h264parse ! vpudec ! imxvideoconvert_g2d ! "
        "appsink name=sink sync=false",
        NULL);

    GstElement *appsink =
        gst_bin_get_by_name(GST_BIN(pipeline), "sink");

    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* --- pull one frame --- */
    GstSample *sample =
        gst_app_sink_pull_sample(GST_APP_SINK(appsink));

    GstBuffer *buffer = gst_sample_get_buffer(sample);
    GstCaps *caps = gst_sample_get_caps(sample);

    GstVideoInfo vinfo;
    gst_video_info_from_caps(&vinfo, caps);

    GstMemory *mem = gst_buffer_peek_memory(buffer, 0);
    if (!gst_is_dmabuf_memory(mem)) {
        fprintf(stderr, "not dmabuf\n");
        return 1;
    }

    int fd = gst_dmabuf_memory_get_fd(mem);

    GstGLDisplayEGL *gst_gl_display_egl =
        gst_gl_display_egl_new_with_egl_display((gpointer) egl_dpy);
    GstGLContext *gst_gl_ctx = gst_gl_context_new_wrapped(
        GST_GL_DISPLAY_CAST(gst_gl_display_egl), (guintptr) egl_ctx,
        GST_GL_PLATFORM_EGL, GST_GL_API_GLES2);
    printf("gst_gl_display_egl = %p, gst_gl_ctx = %p\n", gst_gl_display_egl, gst_gl_ctx);

    gst_gl_context_activate(gst_gl_ctx, TRUE);

    printf("has feature %d\n", gst_gl_context_check_feature (gst_gl_ctx, "EGL_KHR_image_base"));

    GstEGLImage* gst_egl_image = 
        gst_egl_image_from_dmabuf(gst_gl_ctx, fd, &vinfo, 0, 0);
    printf("gst_egl_image = %p\n", gst_egl_image);

    return 0;
}

what I don’t get is that gst_gl_context_new_wrapped() properly sees context_type == GST_GL_PLATFORM_EGL but that only initializes a few of the methods, in particular context_class->check_feature is not set – forcing it to gst_gl_context_egl_check_feature() doesn’t work because it’s then passing a wrapped context to something that expects an egl context and context_egl->egl_exts is bogus and everything crashes…

I assume that check would work from a context created with gst_gl_context_new() (which calls the proper egl init underneath)?

So, should that plugin be updated to use gst_gl_context_new()instead? why doesn’t it work with the wrapped type?

Thanks!

Ok, I couldn’t figure how to make a pipeline that uses dmabuf on PC (and obviously can’t use NXP’s vpuenc/vpudec/imxvideoconvert plugins there), but just focusing on the feature check this can be reproduced (on debian trixie):


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <gst/allocators/gstdmabuf.h>
#include <gst/app/gstappsink.h>
#include <gst/gl/egl/egl.h>
#include <gst/gl/gl.h>
#include <gst/gl/wayland/gstgldisplay_wayland.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <wayland-client.h>

// #define USE_WRAPPED

/* --- Wayland globals --- */
static struct wl_display *wl_dpy;

#ifdef USE_WRAPPED
static EGLDisplay egl_dpy;
static EGLContext egl_ctx;

static void init_egl(void) {
  egl_dpy = eglGetDisplay((EGLNativeDisplayType)wl_dpy);
  eglInitialize(egl_dpy, NULL, NULL);

  EGLint cfg_attr[] = {EGL_SURFACE_TYPE, EGL_DONT_CARE, EGL_RENDERABLE_TYPE,
                       EGL_OPENGL_ES2_BIT, EGL_NONE};

  EGLConfig cfg;
  EGLint n;
  eglChooseConfig(egl_dpy, cfg_attr, &cfg, 1, &n);

  EGLint ctx_attr[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};

  egl_ctx = eglCreateContext(egl_dpy, cfg, EGL_NO_CONTEXT, ctx_attr);

  eglMakeCurrent(egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_ctx);
}
#endif

/* -------------------------------------------------------- */

int main(int argc, char **argv) {
  gst_init(&argc, &argv);

  wl_dpy = wl_display_connect(NULL);
  if (!wl_dpy) {
    fprintf(stderr, "wl_display_connect failed\n");
    exit(1);
  }

#ifdef USE_WRAPPED
  init_egl();

  /* uncomment to check the extension is really present */
  // const char *ext = eglQueryString(egl_dpy, EGL_EXTENSIONS);
  // printf("EGL extensions:\n%s\n", ext);

  GstGLDisplayEGL *gst_gl_display_egl =
      gst_gl_display_egl_new_with_egl_display((gpointer)egl_dpy);
  GstGLContext *gst_gl_ctx = gst_gl_context_new_wrapped(
      GST_GL_DISPLAY_CAST(gst_gl_display_egl), (guintptr)egl_ctx,
      GST_GL_PLATFORM_EGL, GST_GL_API_GLES2);
  printf("gst_gl_display_egl = %p, gst_gl_ctx = %p\n", gst_gl_display_egl,
         gst_gl_ctx);

#else

  GstGLDisplayWayland *gl_display =
      gst_gl_display_wayland_new_with_display(wl_dpy);
  GstGLContext *gst_gl_ctx = gst_gl_context_new((GstGLDisplay *)gl_display);
  if (!gst_gl_ctx) {
    fprintf(stderr, "gst_gl_display_create_context failed\n");
    exit(1);
  }
#endif

  gst_gl_context_activate(gst_gl_ctx, TRUE);

  printf("has feature %d\n",
         gst_gl_context_check_feature(gst_gl_ctx, "EGL_KHR_image_base"));
  return 0;
}


built with gcc repro.c $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 gstreamer-allocators-1.0 gstreamer-plugins-base-1.0 gstreamer-gl-egl-1.0 wayland-client) -o repro -g

With this program, both paths fail to find the feature, but for different reasons:

  • in the USE_WRAPPED case, context_class->check_feature is not set as I had observed
  • in the gst_gl_context_new path, it is set and gst_gl_check_extension() is called… but context_egl->egl_exts is NULL because gst_gl_context_egl_create_context() is not called in this code, I assume I’m missing something there as well, but it’s good enough to illustrate the difference.

Now the reason we’re using gst_gl_context_new_wrapped() is because we already have an egl context created from eglCreateContext(), so it feels wasteful to create a new one with a GstGLContextEGL’s create_context… the egl context is created in a different part of the code base (in a different repo) so it’s difficult to change at this point :confused:

Anyway, back to the original question: how would I go around making gst_egl_image_from_dmabuf() work again with the wrapped context?

I thought this was a usage problem, so I started a discussion, but it looks heavy enough – should I open an issue instead?

Thanks

In the Wayland case, if you don’t actually call gst_gl_context_create, then there is no GL context and therefore check_feature for any feature will fail.

A wrapped GL contexts main purpose is to wrap an application provided OpenGL context so that GStreamer can create it’s own OpenGL context shared with the application’s GL context. Doing anything above and beyond that may or may not work and is not guaranteed in any way.

1 Like

Thanks, this makes sense with what I found out, adding a call to gst_gl_context_create(gst_gl_ctx, NULL, &error); works as expected

Ok, so using the wrapped context in as “other_context” in gst_gl_context_create()is how it’s meant to be used?

e.g.


    GstGLDisplayEGL *gst_gl_display_egl =
        gst_gl_display_egl_new_with_egl_display((gpointer)egl_dpy);
    // create wrapped context
    GstGLContext *wrapped_ctx = gst_gl_context_new_wrapped(
        GST_GL_DISPLAY_CAST(gst_gl_display_egl), (guintptr)egl_ctx,
        GST_GL_PLATFORM_EGL, GST_GL_API_GLES2);
  
    // create normal context
    GstGLContext *gst_gl_ctx = gst_gl_context_new(GST_GL_DISPLAY_CAST(gst_gl_display_egl));
    if (!gst_gl_ctx) {
      fprintf(stderr, "gst_gl_display_create_context failed\n");
      exit(1);
    }

    // initialize gst_gl_ctx from wrapped ctx
    GError *error;
    gst_gl_context_create(gst_gl_ctx, wrapped_ctx, &error);

Yes. Some situations can support using a wrapped GL context (e.g. GstGLSyncMeta) but general usage of a wrapped GL context is not recommended.

1 Like

Thank you, will give updating the flutter plugin to do that a shot