gleglimage: cache EGL images per DmabufUpload

Do not store cached EGL images in GstMemory QData. Instead, use a
per-DmabufUpload GHashTable to store cache entries with a weak
reference to the GstMemory.

This allows two glupload elements on separate tee branches to have
their own EGL image cache. For this pipeline:

  gst-launch-1.0 v4l2src ! tee name=t \
      t. ! queue ! glupload ! fakesink
      t. ! queue ! glupload ! fakesink

this gets rid of the occasional critical error message:

  GStreamer-CRITICAL **: 08:26:33.194: gst_mini_object_unref: assertion 'GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) > 0' failed

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3880>
This commit is contained in:
Philipp Zabel 2023-01-30 07:59:34 +01:00 committed by GStreamer Marge Bot
parent 3943503fc1
commit 485c8ef4b5

View file

@ -486,11 +486,24 @@ static const UploadMethod _gl_memory_upload = {
};
#if GST_GL_HAVE_DMABUF
typedef struct _GstEGLImageCacheEntry
{
GstEGLImage *eglimage[GST_VIDEO_MAX_PLANES];
} GstEGLImageCacheEntry;
typedef struct _GstEGLImageCache
{
gint ref_count;
GHashTable *hash_table; /* for GstMemory -> GstEGLImageCacheEntry lookup */
GMutex lock; /* protects hash_table */
} GstEGLImageCache;
struct DmabufUpload
{
GstGLUpload *upload;
GstEGLImage *eglimage[GST_VIDEO_MAX_PLANES];
GstEGLImageCache *eglimage_cache;
GstGLFormat formats[GST_VIDEO_MAX_PLANES];
GstBuffer *outbuf;
GstGLVideoAllocationParams *params;
@ -503,6 +516,111 @@ struct DmabufUpload
gpointer out_caps;
};
static void
gst_egl_image_cache_ref (GstEGLImageCache * cache)
{
g_atomic_int_inc (&cache->ref_count);
}
static void
gst_egl_image_cache_unref (GstEGLImageCache * cache)
{
if (g_atomic_int_dec_and_test (&cache->ref_count)) {
g_hash_table_unref (cache->hash_table);
g_mutex_clear (&cache->lock);
}
}
static void
gst_egl_image_cache_entry_remove (GstEGLImageCache * cache, GstMiniObject * mem)
{
g_mutex_lock (&cache->lock);
g_hash_table_remove (cache->hash_table, mem);
g_mutex_unlock (&cache->lock);
gst_egl_image_cache_unref (cache);
}
static GstEGLImageCacheEntry *
gst_egl_image_cache_entry_new (GstEGLImageCache * cache, GstMemory * mem)
{
GstEGLImageCacheEntry *cache_entry;
cache_entry = g_new0 (GstEGLImageCacheEntry, 1);
gst_egl_image_cache_ref (cache);
gst_mini_object_weak_ref (GST_MINI_OBJECT (mem),
(GstMiniObjectNotify) gst_egl_image_cache_entry_remove, cache);
g_mutex_lock (&cache->lock);
g_hash_table_insert (cache->hash_table, mem, cache_entry);
g_mutex_unlock (&cache->lock);
return cache_entry;
}
static void
gst_egl_image_cache_entry_free (GstEGLImageCacheEntry * cache_entry)
{
gint i;
for (i = 0; i < GST_VIDEO_MAX_PLANES; i++) {
if (cache_entry->eglimage[i])
gst_egl_image_unref (cache_entry->eglimage[i]);
}
g_free (cache_entry);
}
/*
* Looks up a cache_entry for mem if mem is different from previous_mem.
* If mem is the same as previous_mem, the costly lookup is skipped and the
* provided (previous) cache_entry is used instead.
*
* Returns the cached eglimage for the given plane from the cache_entry, or
* NULL. previous_mem is set to mem.
*/
static GstEGLImage *
gst_egl_image_cache_lookup (GstEGLImageCache * cache, GstMemory * mem,
gint plane, GstMemory ** previous_mem, GstEGLImageCacheEntry ** cache_entry)
{
if (mem != *previous_mem) {
g_mutex_lock (&cache->lock);
*cache_entry = g_hash_table_lookup (cache->hash_table, mem);
g_mutex_unlock (&cache->lock);
*previous_mem = mem;
}
if (*cache_entry)
return (*cache_entry)->eglimage[plane];
return NULL;
}
/*
* Creates a new cache_entry for mem if no cache_entry is provided.
* Stores the eglimage for the given plane in the cache_entry.
*/
static void
gst_egl_image_cache_store (GstEGLImageCache * cache, GstMemory * mem,
gint plane, GstEGLImage * eglimage, GstEGLImageCacheEntry ** cache_entry)
{
if (!(*cache_entry))
*cache_entry = gst_egl_image_cache_entry_new (cache, mem);
(*cache_entry)->eglimage[plane] = eglimage;
}
static GstEGLImageCache *
gst_egl_image_cache_new (void)
{
GstEGLImageCache *cache;
cache = g_new0 (GstEGLImageCache, 1);
cache->ref_count = 1;
cache->hash_table = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) gst_egl_image_cache_entry_free);
g_mutex_init (&cache->lock);
return cache;
}
static GstStaticCaps _dma_buf_upload_caps =
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
(GST_CAPS_FEATURE_MEMORY_DMABUF,
@ -514,6 +632,7 @@ _dma_buf_upload_new (GstGLUpload * upload)
{
struct DmabufUpload *dmabuf = g_new0 (struct DmabufUpload, 1);
dmabuf->upload = upload;
dmabuf->eglimage_cache = gst_egl_image_cache_new ();
dmabuf->target = GST_GL_TEXTURE_TARGET_2D;
return dmabuf;
}
@ -576,38 +695,6 @@ _dma_buf_upload_transform_caps (gpointer impl, GstGLContext * context,
return ret;
}
static GQuark
_eglimage_quark (gint plane)
{
static GQuark quark[5] = { 0 };
static const gchar *quark_str[] = {
"GstGLDMABufEGLImage0",
"GstGLDMABufEGLImage1",
"GstGLDMABufEGLImage2",
"GstGLDMABufEGLImage3",
"GstGLDMABufEGLImage",
};
if (!quark[plane])
quark[plane] = g_quark_from_static_string (quark_str[plane]);
return quark[plane];
}
static GstEGLImage *
_get_cached_eglimage (GstMemory * mem, gint plane)
{
return gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
_eglimage_quark (plane));
}
static void
_set_cached_eglimage (GstMemory * mem, GstEGLImage * eglimage, gint plane)
{
return gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
_eglimage_quark (plane), eglimage, (GDestroyNotify) gst_egl_image_unref);
}
static gboolean
_dma_buf_upload_accept (gpointer impl, GstBuffer * buffer, GstCaps * in_caps,
GstCaps * out_caps)
@ -619,6 +706,8 @@ _dma_buf_upload_accept (gpointer impl, GstBuffer * buffer, GstCaps * in_caps,
GstVideoMeta *meta;
guint n_mem;
GstMemory *mems[GST_VIDEO_MAX_PLANES];
GstMemory *previous_mem = NULL;
GstEGLImageCacheEntry *cache_entry = NULL;
gsize offset[GST_VIDEO_MAX_PLANES];
gint fd[GST_VIDEO_MAX_PLANES];
guint i;
@ -745,12 +834,14 @@ _dma_buf_upload_accept (gpointer impl, GstBuffer * buffer, GstCaps * in_caps,
} else
dmabuf->n_mem = n_planes;
/* Now create an EGLImage for each dmabufs */
/* Now create an EGLImage for each dmabuf */
for (i = 0; i < dmabuf->n_mem; i++) {
gint cache_id = dmabuf->direct ? 4 : i;
/* check if one is cached */
dmabuf->eglimage[i] = _get_cached_eglimage (mems[i], cache_id);
/*
* Check if an EGLImage is cached. Remember the previous memory and cache
* entry to avoid repeated lookups if all mems[i] point to the same memory.
*/
dmabuf->eglimage[i] = gst_egl_image_cache_lookup (dmabuf->eglimage_cache,
mems[i], i, &previous_mem, &cache_entry);
if (dmabuf->eglimage[i]) {
dmabuf->formats[i] = dmabuf->eglimage[i]->format;
continue;
@ -770,7 +861,8 @@ _dma_buf_upload_accept (gpointer impl, GstBuffer * buffer, GstCaps * in_caps,
return FALSE;
}
_set_cached_eglimage (mems[i], dmabuf->eglimage[i], cache_id);
gst_egl_image_cache_store (dmabuf->eglimage_cache, mems[i], i,
dmabuf->eglimage[i], &cache_entry);
dmabuf->formats[i] = dmabuf->eglimage[i]->format;
}
@ -836,6 +928,7 @@ _dma_buf_upload_free (gpointer impl)
if (dmabuf->params)
gst_gl_allocation_params_free ((GstGLAllocationParams *) dmabuf->params);
gst_egl_image_cache_unref (dmabuf->eglimage_cache);
g_free (impl);
}