/* * GStreamer * Copyright (C) 2022 Matthew Waters * * 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. */ #include "gstqsg6glnode.h" #include #include #include #include #define GST_CAT_DEFAULT gst_qsg_texture_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); GstQSG6OpenGLNode::GstQSG6OpenGLNode(QQuickItem * item) { static gsize _debug; if (g_once_init_enter (&_debug)) { GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtqsgtexture", 0, "Qt Scenegraph Texture"); g_once_init_leave (&_debug, 1); } gst_video_info_init (&this->v_info); this->buffer_ = NULL; this->sync_buffer_ = gst_buffer_new (); this->dummy_tex_ = nullptr; // TODO; handle windowChanged? this->window_ = item->window(); } GstQSG6OpenGLNode::~GstQSG6OpenGLNode() { gst_buffer_replace (&this->buffer_, NULL); gst_buffer_replace (&this->sync_buffer_, NULL); this->buffer_was_bound = FALSE; delete this->dummy_tex_; this->dummy_tex_ = nullptr; } QSGTexture * GstQSG6OpenGLNode::texture() const { return QSGSimpleTextureNode::texture(); } /* only called from the streaming thread with scene graph thread blocked */ void GstQSG6OpenGLNode::setCaps (GstCaps * caps) { GST_LOG ("%p setCaps %" GST_PTR_FORMAT, this, caps); if (caps) gst_video_info_from_caps (&this->v_info, caps); else gst_video_info_init (&this->v_info); } /* only called from the streaming thread with scene graph thread blocked */ GstBuffer * GstQSG6OpenGLNode::getBuffer () { GstBuffer *buffer = NULL; if (this->buffer_) buffer = gst_buffer_ref (this->buffer_); return buffer; } /* only called from the streaming thread with scene graph thread blocked */ void GstQSG6OpenGLNode::setBuffer (GstBuffer * buffer) { GstGLContext *qt_context = NULL; gboolean buffer_changed; GST_LOG ("%p setBuffer %" GST_PTR_FORMAT, this, buffer); /* FIXME: update more state here */ buffer_changed = gst_buffer_replace (&this->buffer_, buffer); if (buffer_changed) { GstGLContext *context; GstGLSyncMeta *sync_meta; GstMemory *mem; guint tex_id; QQuickWindow::CreateTextureOptions options = QQuickWindow::TextureHasAlphaChannel; QSGTexture *texture = nullptr; QSize texSize; qt_context = gst_gl_context_get_current(); if (!qt_context) goto use_dummy_tex; if (!this->buffer_) goto use_dummy_tex; if (GST_VIDEO_INFO_FORMAT (&this->v_info) == GST_VIDEO_FORMAT_UNKNOWN) goto use_dummy_tex; this->mem_ = gst_buffer_peek_memory (this->buffer_, 0); if (!this->mem_) goto use_dummy_tex; /* FIXME: should really lock the memory to prevent write access */ if (!gst_video_frame_map (&this->v_frame, &this->v_info, this->buffer_, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) { g_assert_not_reached (); goto use_dummy_tex; } mem = gst_buffer_peek_memory (this->buffer_, 0); g_assert (gst_is_gl_memory (mem)); context = ((GstGLBaseMemory *)mem)->context; sync_meta = gst_buffer_get_gl_sync_meta (this->sync_buffer_); if (!sync_meta) sync_meta = gst_buffer_add_gl_sync_meta (context, this->sync_buffer_); gst_gl_sync_meta_set_sync_point (sync_meta, context); gst_gl_sync_meta_wait (sync_meta, qt_context); tex_id = *(guint *) this->v_frame.data[0]; GST_LOG ("%p binding Qt texture %u", this, tex_id); texSize = QSize(GST_VIDEO_FRAME_WIDTH (&this->v_frame), GST_VIDEO_FRAME_HEIGHT (&this->v_frame)); // XXX: ideally, we would like to subclass the relevant texture object // ourselves but this is good enough for now texture = QNativeInterface::QSGOpenGLTexture::fromNative(tex_id, this->window_, texSize, options); setTexture(texture); setOwnsTexture(true); markDirty(QSGNode::DirtyMaterial); gst_video_frame_unmap (&this->v_frame); /* Texture was successfully bound, so we do not need * to use the dummy texture */ } if (!texture()) { use_dummy_tex: /* Create dummy texture if not already present. */ if (this->dummy_tex_ == nullptr) { /* Make this a black 64x64 pixel RGBA texture. * This size and format is supported pretty much everywhere, so these * are a safe pick. (64 pixel sidelength must be supported according * to the GLES2 spec, table 6.18.) * Set min/mag filters to GL_LINEAR to make sure no mipmapping is used. */ const int tex_sidelength = 64; QImage image(tex_sidelength, tex_sidelength, QImage::Format_ARGB32); image.fill(QColor(0, 0, 0, 255)); this->dummy_tex_ = this->window_->createTextureFromImage(image); } g_assert (this->dummy_tex_ != nullptr); if (texture() != this->dummy_tex_) { setTexture(this->dummy_tex_); setOwnsTexture(false); markDirty(QSGNode::DirtyMaterial); } GST_LOG ("%p binding fallback dummy Qt texture %p", this, this->dummy_tex_); } }