gstreamer/subprojects/gst-plugins-good/ext/qt/qtwindow.cc
Matthias Fuchs af71adf315 qmlglsrc: Fix missing depth & stencil buffer
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>
2022-04-06 09:18:16 +00:00

453 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);
}