mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-10-18 00:13:49 +00:00
af71adf315
Qt Quick primitives which have some kind of alpha blending (transparency, rounded corners) are z-sorted by Qt and rendered in the correct order. For opaque primitives Qt relies on the OpenGL depth buffer to correctly determine the visibility of stacked elements. This change enables the depth buffer to make sure that opaque primitives are correctly z-stacked. https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph-renderer.html#opaque-primitives Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2114>
452 lines
12 KiB
C++
452 lines
12 KiB
C++
/*
|
|
* GStreamer
|
|
* Copyright (C) 2016 Freescale Semiconductor, Inc. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <gst/video/video.h>
|
|
#include <gst/gl/gstglfuncs.h>
|
|
#include "qtwindow.h"
|
|
#include "gstqsgtexture.h"
|
|
#include "gstqtglutility.h"
|
|
|
|
#include <QtCore/QDateTime>
|
|
#include <QtGui/QGuiApplication>
|
|
#include <QtQuick/QQuickWindow>
|
|
#include <QOpenGLFramebufferObject>
|
|
|
|
/* compatibility definitions... */
|
|
#ifndef GL_READ_FRAMEBUFFER
|
|
#define GL_READ_FRAMEBUFFER 0x8CA8
|
|
#endif
|
|
#ifndef GL_DRAW_FRAMEBUFFER
|
|
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
|
#endif
|
|
|
|
/**
|
|
* SECTION:
|
|
*
|
|
* #QtGLWindow is an #QQuickWindow that grab QtQuick view to GStreamer OpenGL video buffers.
|
|
*/
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (qt_window_debug);
|
|
#define GST_CAT_DEFAULT qt_window_debug
|
|
|
|
struct _QtGLWindowPrivate
|
|
{
|
|
GMutex lock;
|
|
GCond update_cond;
|
|
|
|
GstBuffer *buffer;
|
|
GstCaps *caps;
|
|
GstVideoInfo v_info;
|
|
|
|
gboolean initted;
|
|
gboolean updated;
|
|
gboolean quit;
|
|
gboolean result;
|
|
gboolean useDefaultFbo;
|
|
|
|
GstGLDisplay *display;
|
|
GstGLContext *other_context;
|
|
GstGLContext *context;
|
|
|
|
GLuint fbo;
|
|
|
|
/* frames that qmlview rendered in its gl thread */
|
|
quint64 frames_rendered;
|
|
quint64 start;
|
|
quint64 stop;
|
|
};
|
|
|
|
QtGLWindow::QtGLWindow ( QWindow * parent, QQuickWindow *src ) :
|
|
QQuickWindow( parent ), source (src)
|
|
{
|
|
QGuiApplication *app = static_cast<QGuiApplication *> (QCoreApplication::instance ());
|
|
static gsize _debug;
|
|
|
|
g_assert (app != NULL);
|
|
|
|
if (g_once_init_enter (&_debug)) {
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglwindow", 0, "Qt GL QuickWindow");
|
|
g_once_init_leave (&_debug, 1);
|
|
}
|
|
|
|
this->priv = g_new0 (QtGLWindowPrivate, 1);
|
|
|
|
g_mutex_init (&this->priv->lock);
|
|
g_cond_init (&this->priv->update_cond);
|
|
|
|
this->priv->display = gst_qt_get_gl_display(FALSE);
|
|
|
|
connect (source, SIGNAL(beforeRendering()), this, SLOT(beforeRendering()), Qt::DirectConnection);
|
|
connect (source, SIGNAL(afterRendering()), this, SLOT(afterRendering()), Qt::DirectConnection);
|
|
connect (app, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()), Qt::DirectConnection);
|
|
if (source->isSceneGraphInitialized())
|
|
source->scheduleRenderJob(new RenderJob(std::bind(&QtGLWindow::onSceneGraphInitialized, this)), QQuickWindow::BeforeSynchronizingStage);
|
|
else
|
|
connect (source, SIGNAL(sceneGraphInitialized()), this, SLOT(onSceneGraphInitialized()), Qt::DirectConnection);
|
|
|
|
connect (source, SIGNAL(sceneGraphInvalidated()), this, SLOT(onSceneGraphInvalidated()), Qt::DirectConnection);
|
|
|
|
GST_DEBUG ("%p init Qt Window", this->priv->display);
|
|
}
|
|
|
|
QtGLWindow::~QtGLWindow()
|
|
{
|
|
GST_DEBUG ("deinit Qt Window");
|
|
g_mutex_clear (&this->priv->lock);
|
|
g_cond_clear (&this->priv->update_cond);
|
|
if (this->priv->other_context)
|
|
gst_object_unref(this->priv->other_context);
|
|
if (this->priv->display)
|
|
gst_object_unref(this->priv->display);
|
|
if (this->priv->context)
|
|
gst_object_unref(this->priv->context);
|
|
g_free (this->priv);
|
|
this->priv = NULL;
|
|
}
|
|
|
|
void
|
|
QtGLWindow::beforeRendering()
|
|
{
|
|
unsigned int width, height;
|
|
|
|
g_mutex_lock (&this->priv->lock);
|
|
|
|
static gsize once = 0;
|
|
if (g_once_init_enter(&once)) {
|
|
this->priv->start = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
|
g_once_init_leave(&once,1);
|
|
}
|
|
|
|
if (!fbo && !this->priv->useDefaultFbo) {
|
|
|
|
width = source->width();
|
|
height = source->height();
|
|
|
|
GST_DEBUG ("create new framebuffer object %dX%d", width, height);
|
|
|
|
fbo.reset(new QOpenGLFramebufferObject (width, height,
|
|
QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_2D, GL_RGBA));
|
|
|
|
source->setRenderTarget(fbo.data());
|
|
} else if (this->priv->useDefaultFbo) {
|
|
GST_DEBUG ("use default fbo for render target");
|
|
fbo.reset(NULL);
|
|
source->setRenderTarget(NULL);
|
|
}
|
|
|
|
g_mutex_unlock (&this->priv->lock);
|
|
}
|
|
|
|
|
|
void
|
|
QtGLWindow::afterRendering()
|
|
{
|
|
GstVideoFrame gl_frame;
|
|
GstVideoInfo *info;
|
|
GstGLContext *context;
|
|
gboolean ret;
|
|
guint width, height;
|
|
const GstGLFuncs *gl;
|
|
GLuint dst_tex;
|
|
GstGLSyncMeta *sync_meta;
|
|
|
|
g_mutex_lock (&this->priv->lock);
|
|
|
|
this->priv->frames_rendered++;
|
|
|
|
if(!this->priv->buffer || this->priv->updated == TRUE) {
|
|
GST_DEBUG ("skip this frame");
|
|
g_mutex_unlock (&this->priv->lock);
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG ("copy buffer %p",this->priv->buffer);
|
|
|
|
width = GST_VIDEO_INFO_WIDTH (&this->priv->v_info);
|
|
height = GST_VIDEO_INFO_HEIGHT (&this->priv->v_info);
|
|
info = &this->priv->v_info;
|
|
context = this->priv->other_context;
|
|
|
|
gst_gl_context_activate (context, TRUE);
|
|
gl = context->gl_vtable;
|
|
|
|
ret = gst_video_frame_map (&gl_frame, info, this->priv->buffer,
|
|
(GstMapFlags) (GST_MAP_WRITE | GST_MAP_GL));
|
|
|
|
if (!ret) {
|
|
this->priv->buffer = NULL;
|
|
GST_ERROR ("Failed to map video frame");
|
|
goto errors;
|
|
}
|
|
|
|
gl->BindFramebuffer (GL_READ_FRAMEBUFFER, this->source->renderTargetId());
|
|
|
|
ret = gst_gl_context_check_framebuffer_status (context, GL_READ_FRAMEBUFFER);
|
|
if (!ret) {
|
|
GST_ERROR ("FBO errors");
|
|
goto errors;
|
|
}
|
|
|
|
dst_tex = *(guint *) gl_frame.data[0];
|
|
GST_DEBUG ("qml render target id %d, render to tex %d %dX%d",
|
|
this->source->renderTargetId(), dst_tex, width,height);
|
|
|
|
gl->BindTexture (GL_TEXTURE_2D, dst_tex);
|
|
if (gl->BlitFramebuffer) {
|
|
gl->BindFramebuffer (GL_DRAW_FRAMEBUFFER, this->priv->fbo);
|
|
gl->FramebufferTexture2D (GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_TEXTURE_2D, dst_tex, 0);
|
|
|
|
ret = gst_gl_context_check_framebuffer_status (context, GL_DRAW_FRAMEBUFFER);
|
|
if (!ret) {
|
|
GST_ERROR ("FBO errors");
|
|
goto errors;
|
|
}
|
|
if (this->priv->useDefaultFbo)
|
|
gl->ReadBuffer (GL_BACK);
|
|
else
|
|
gl->ReadBuffer (GL_COLOR_ATTACHMENT0);
|
|
gl->BlitFramebuffer (0, 0, width, height,
|
|
0, 0, width, height,
|
|
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
} else {
|
|
gl->CopyTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0);
|
|
}
|
|
|
|
if (this->priv->context) {
|
|
sync_meta = gst_buffer_get_gl_sync_meta (this->priv->buffer);
|
|
if (!sync_meta) {
|
|
sync_meta = gst_buffer_add_gl_sync_meta (this->priv->context, this->priv->buffer);
|
|
}
|
|
gst_gl_sync_meta_set_sync_point (sync_meta, context);
|
|
}
|
|
|
|
GST_DEBUG ("rendering finished");
|
|
|
|
errors:
|
|
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
|
|
gst_video_frame_unmap (&gl_frame);
|
|
|
|
gst_gl_context_activate (context, FALSE);
|
|
|
|
this->priv->result = ret;
|
|
this->priv->updated = TRUE;
|
|
g_cond_signal (&this->priv->update_cond);
|
|
g_mutex_unlock (&this->priv->lock);
|
|
}
|
|
|
|
void
|
|
QtGLWindow::aboutToQuit()
|
|
{
|
|
g_mutex_lock (&this->priv->lock);
|
|
|
|
this->priv->updated = TRUE;
|
|
this->priv->quit = TRUE;
|
|
g_cond_signal (&this->priv->update_cond);
|
|
|
|
this->priv->stop = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
|
qint64 duration = this->priv->stop - this->priv->start;
|
|
float fps = ((float)this->priv->frames_rendered / duration * 1000);
|
|
|
|
GST_DEBUG("about to quit, total refresh frames (%lld) in (%0.3f) seconds, fps: %0.3f",
|
|
this->priv->frames_rendered, (float)duration / 1000, fps);
|
|
|
|
g_mutex_unlock (&this->priv->lock);
|
|
}
|
|
|
|
void
|
|
QtGLWindow::onSceneGraphInitialized()
|
|
{
|
|
GST_DEBUG ("scene graph initialization with Qt GL context %p",
|
|
this->source->openglContext ());
|
|
|
|
this->priv->initted = gst_qt_get_gl_wrapcontext (this->priv->display,
|
|
&this->priv->other_context, &this->priv->context);
|
|
|
|
if (this->priv->initted && this->priv->other_context) {
|
|
const GstGLFuncs *gl;
|
|
|
|
gst_gl_context_activate (this->priv->other_context, TRUE);
|
|
gl = this->priv->other_context->gl_vtable;
|
|
|
|
gl->GenFramebuffers (1, &this->priv->fbo);
|
|
|
|
gst_gl_context_activate (this->priv->other_context, FALSE);
|
|
}
|
|
|
|
GST_DEBUG ("%p created wrapped GL context %" GST_PTR_FORMAT, this,
|
|
this->priv->other_context);
|
|
}
|
|
|
|
void
|
|
QtGLWindow::onSceneGraphInvalidated()
|
|
{
|
|
GST_DEBUG ("scene graph invalidated");
|
|
|
|
if (this->priv->fbo && this->priv->other_context) {
|
|
const GstGLFuncs *gl;
|
|
|
|
gst_gl_context_activate (this->priv->other_context, TRUE);
|
|
gl = this->priv->other_context->gl_vtable;
|
|
|
|
gl->DeleteFramebuffers (1, &this->priv->fbo);
|
|
|
|
gst_gl_context_activate (this->priv->other_context, FALSE);
|
|
}
|
|
}
|
|
|
|
bool
|
|
QtGLWindow::getGeometry(int * width, int * height)
|
|
{
|
|
if (width == NULL || height == NULL)
|
|
return FALSE;
|
|
|
|
*width = this->source->width();
|
|
*height = this->source->height();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstGLContext *
|
|
qt_window_get_qt_context (QtGLWindow * qt_window)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, NULL);
|
|
|
|
if (!qt_window->priv->other_context)
|
|
return NULL;
|
|
|
|
return (GstGLContext *) gst_object_ref (qt_window->priv->other_context);
|
|
}
|
|
|
|
GstGLDisplay *
|
|
qt_window_get_display (QtGLWindow * qt_window)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, NULL);
|
|
|
|
if (!qt_window->priv->display)
|
|
return NULL;
|
|
|
|
return (GstGLDisplay *) gst_object_ref (qt_window->priv->display);
|
|
}
|
|
|
|
GstGLContext *
|
|
qt_window_get_context (QtGLWindow * qt_window)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, NULL);
|
|
|
|
if (!qt_window->priv->context)
|
|
return NULL;
|
|
|
|
return (GstGLContext *) gst_object_ref (qt_window->priv->context);
|
|
}
|
|
|
|
gboolean
|
|
qt_window_set_context (QtGLWindow * qt_window, GstGLContext * context)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, FALSE);
|
|
|
|
if (qt_window->priv->context && qt_window->priv->context != context)
|
|
return FALSE;
|
|
|
|
gst_object_replace ((GstObject **) &qt_window->priv->context, (GstObject *) context);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
qt_window_is_scenegraph_initialized (QtGLWindow * qt_window)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, FALSE);
|
|
|
|
return qt_window->priv->initted;
|
|
}
|
|
|
|
gboolean
|
|
qt_window_set_caps (QtGLWindow * qt_window, GstCaps * caps)
|
|
{
|
|
GstVideoInfo v_info;
|
|
|
|
g_return_val_if_fail (qt_window != NULL, FALSE);
|
|
g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
|
|
g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
|
|
|
|
if (qt_window->priv->caps && gst_caps_is_equal_fixed (qt_window->priv->caps, caps))
|
|
return TRUE;
|
|
|
|
if (!gst_video_info_from_caps (&v_info, caps))
|
|
return FALSE;
|
|
|
|
g_mutex_lock (&qt_window->priv->lock);
|
|
|
|
gst_caps_replace (&qt_window->priv->caps, caps);
|
|
|
|
qt_window->priv->v_info = v_info;
|
|
|
|
g_mutex_unlock (&qt_window->priv->lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
qt_window_set_buffer (QtGLWindow * qt_window, GstBuffer * buffer)
|
|
{
|
|
g_return_val_if_fail (qt_window != NULL, FALSE);
|
|
g_return_val_if_fail (qt_window->priv->initted, FALSE);
|
|
gboolean ret;
|
|
|
|
g_mutex_lock (&qt_window->priv->lock);
|
|
|
|
if (qt_window->priv->quit){
|
|
GST_DEBUG("about to quit, drop this buffer");
|
|
g_mutex_unlock (&qt_window->priv->lock);
|
|
return TRUE;
|
|
}
|
|
|
|
qt_window->priv->updated = FALSE;
|
|
qt_window->priv->buffer = buffer;
|
|
|
|
while (!qt_window->priv->updated)
|
|
g_cond_wait (&qt_window->priv->update_cond, &qt_window->priv->lock);
|
|
|
|
ret = qt_window->priv->result;
|
|
|
|
g_mutex_unlock (&qt_window->priv->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
qt_window_use_default_fbo (QtGLWindow * qt_window, gboolean useDefaultFbo)
|
|
{
|
|
g_return_if_fail (qt_window != NULL);
|
|
|
|
g_mutex_lock (&qt_window->priv->lock);
|
|
|
|
GST_DEBUG ("set to use default fbo %d", useDefaultFbo);
|
|
qt_window->priv->useDefaultFbo = useDefaultFbo;
|
|
|
|
g_mutex_unlock (&qt_window->priv->lock);
|
|
}
|