diff options
Diffstat (limited to 'src/multimedia/video/qvideowindow.cpp')
-rw-r--r-- | src/multimedia/video/qvideowindow.cpp | 224 |
1 files changed, 109 insertions, 115 deletions
diff --git a/src/multimedia/video/qvideowindow.cpp b/src/multimedia/video/qvideowindow.cpp index f0ae26b98..9cab23f5f 100644 --- a/src/multimedia/video/qvideowindow.cpp +++ b/src/multimedia/video/qvideowindow.cpp @@ -1,47 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qvideowindow_p.h" #include <QPlatformSurfaceEvent> #include <qfile.h> #include <qpainter.h> #include <private/qguiapplication_p.h> +#include <private/qmemoryvideobuffer_p.h> +#include <private/qmultimediautils_p.h> #include <qpa/qplatformintegration.h> QT_BEGIN_NAMESPACE @@ -54,13 +20,18 @@ static QSurface::SurfaceType platformSurfaceType() return QSurface::Direct3DSurface; #endif - if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) + auto *integration = QGuiApplicationPrivate::platformIntegration(); + + if (!integration->hasCapability(QPlatformIntegration::OpenGL)) return QSurface::RasterSurface; - if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface) - || QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets)) + + if (QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets)) return QSurface::RasterSurface; - return QSurface::RasterGLSurface; + if (integration->hasCapability(QPlatformIntegration::RasterGLSurface)) + return QSurface::RasterGLSurface; + + return QSurface::OpenGLSurface; } QVideoWindowPrivate::QVideoWindowPrivate(QVideoWindow *q) @@ -99,18 +70,36 @@ QVideoWindowPrivate::QVideoWindowPrivate(QVideoWindow *q) QVideoWindowPrivate::~QVideoWindowPrivate() { - freeTextures(); + QObject::disconnect(m_sink.get(), &QVideoSink::videoFrameChanged, + q, &QVideoWindow::setVideoFrame); } - -static const float g_quad[] = { +static const float g_vw_quad[] = { + // 4 clockwise rotation of texture vertexes (the second pair) + // Rotation 0 -1.f, -1.f, 0.f, 0.f, -1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f, + // Rotation 90 + -1.f, -1.f, 0.f, 1.f, + -1.f, 1.f, 1.f, 1.f, + 1.f, -1.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f, + + // Rotation 180 + -1.f, -1.f, 1.f, 1.f, + -1.f, 1.f, 1.f, 0.f, + 1.f, -1.f, 0.f, 1.f, + 1.f, 1.f, 0.f, 0.f, + // Rotation 270 + -1.f, -1.f, 1.f, 0.f, + -1.f, 1.f, 0.f, 0.f, + 1.f, -1.f, 1.f, 1.f, + 1.f, 1.f, 0.f, 1.f }; -static QShader getShader(const QString &name) +static QShader vwGetShader(const QString &name) { QFile f(name); if (f.open(QIODevice::ReadOnly)) @@ -168,11 +157,11 @@ void QVideoWindowPrivate::initRhi() m_renderPass.reset(m_swapChain->newCompatibleRenderPassDescriptor()); m_swapChain->setRenderPassDescriptor(m_renderPass.get()); - m_vertexBuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_quad))); + m_vertexBuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_vw_quad))); m_vertexBuf->create(); m_vertexBufReady = false; - m_uniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4)); + m_uniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(QVideoTextureHelper::UniformData))); m_uniformBuf->create(); m_textureSampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, @@ -182,17 +171,19 @@ void QVideoWindowPrivate::initRhi() m_shaderResourceBindings.reset(m_rhi->newShaderResourceBindings()); m_subtitleResourceBindings.reset(m_rhi->newShaderResourceBindings()); - m_subtitleUniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4)); + m_subtitleUniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(QVideoTextureHelper::UniformData))); m_subtitleUniformBuf->create(); + + Q_ASSERT(NVideoFrameSlots >= m_rhi->resourceLimit(QRhi::FramesInFlight)); } -void QVideoWindowPrivate::setupGraphicsPipeline(QRhiGraphicsPipeline *pipeline, QRhiShaderResourceBindings *bindings, QVideoFrameFormat::PixelFormat fmt) +void QVideoWindowPrivate::setupGraphicsPipeline(QRhiGraphicsPipeline *pipeline, QRhiShaderResourceBindings *bindings, const QVideoFrameFormat &fmt) { pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); - QShader vs = getShader(QVideoTextureHelper::vertexShaderFileName(fmt)); + QShader vs = vwGetShader(QVideoTextureHelper::vertexShaderFileName(fmt)); Q_ASSERT(vs.isValid()); - QShader fs = getShader(QVideoTextureHelper::fragmentShaderFileName(fmt)); + QShader fs = vwGetShader(QVideoTextureHelper::fragmentShaderFileName(fmt, m_swapChain->format())); Q_ASSERT(fs.isValid()); pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, @@ -216,34 +207,26 @@ void QVideoWindowPrivate::updateTextures(QRhiResourceUpdateBatch *rub) { m_texturesDirty = false; - auto fmt = m_currentFrame.pixelFormat(); - if (fmt == QVideoFrameFormat::Format_Invalid) - // We render a 1x1 black pixel when we don't have a video - fmt = QVideoFrameFormat::Format_RGBA8888; - - auto textureDesc = QVideoTextureHelper::textureDescription(fmt); - - m_frameSize = m_currentFrame.isValid() ? m_currentFrame.size() : QSize(1, 1); - - freeTextures(); - if (m_currentFrame.isValid()) { - QVideoTextureHelper::updateRhiTextures(m_currentFrame, m_rhi.get(), rub, m_frameTextures); - } else { - Q_ASSERT(fmt == QVideoFrameFormat::Format_RGBA8888); - QImage img(QSize(1, 1), QImage::Format_RGBA8888); - img.fill(Qt::black); - m_frameTextures[0] = m_rhi->newTexture(QRhiTexture::RGBA8, m_frameSize, 1); - m_frameTextures[0]->create(); - rub->uploadTexture(m_frameTextures[0], img); - } + // We render a 1x1 black pixel when we don't have a video + if (!m_currentFrame.isValid()) + m_currentFrame = QVideoFrame(new QMemoryVideoBuffer(QByteArray{4, 0}, 4), + QVideoFrameFormat(QSize(1,1), QVideoFrameFormat::Format_RGBA8888)); + + m_frameTextures = QVideoTextureHelper::createTextures(m_currentFrame, m_rhi.get(), rub, std::move(m_frameTextures)); + if (!m_frameTextures) + return; QRhiShaderResourceBinding bindings[4]; auto *b = bindings; *(b++) = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_uniformBuf.get()); + + auto fmt = m_currentFrame.surfaceFormat(); + auto textureDesc = QVideoTextureHelper::textureDescription(fmt.pixelFormat()); + for (int i = 0; i < textureDesc->nplanes; ++i) (*b++) = QRhiShaderResourceBinding::sampledTexture(i + 1, QRhiShaderResourceBinding::FragmentStage, - m_frameTextures[i], m_textureSampler.get()); + m_frameTextures->texture(i), m_textureSampler.get()); m_shaderResourceBindings->setBindings(bindings, b); m_shaderResourceBindings->create(); @@ -256,14 +239,14 @@ void QVideoWindowPrivate::updateTextures(QRhiResourceUpdateBatch *rub) } } -void QVideoWindowPrivate::updateSubtitle(QRhiResourceUpdateBatch *rub) +void QVideoWindowPrivate::updateSubtitle(QRhiResourceUpdateBatch *rub, const QSize &frameSize) { m_subtitleDirty = false; m_hasSubtitle = !m_currentFrame.subtitleText().isEmpty(); if (!m_hasSubtitle) return; - m_subtitleLayout.updateFromVideoFrame(m_currentFrame); + m_subtitleLayout.update(frameSize, m_currentFrame.subtitleText()); QSize size = m_subtitleLayout.bounds.size().toSize(); QImage img = m_subtitleLayout.toImage(); @@ -288,16 +271,7 @@ void QVideoWindowPrivate::updateSubtitle(QRhiResourceUpdateBatch *rub) QRhiGraphicsPipeline::TargetBlend blend; blend.enable = true; m_subtitlePipeline->setTargetBlends({ blend }); - setupGraphicsPipeline(m_subtitlePipeline.get(), m_subtitleResourceBindings.get(), QVideoFrameFormat::Format_RGBA8888); - } -} - -void QVideoWindowPrivate::freeTextures() -{ - for (int i = 0; i < 3; ++i) { - if (m_frameTextures[i]) - delete m_frameTextures[i]; - m_frameTextures[i] = nullptr; + setupGraphicsPipeline(m_subtitlePipeline.get(), m_subtitleResourceBindings.get(), QVideoFrameFormat(QSize(1, 1), QVideoFrameFormat::Format_RGBA8888)); } } @@ -357,17 +331,34 @@ void QVideoWindowPrivate::render() return; } - QSize scaled = m_currentFrame.size().scaled(rect.size(), aspectRatioMode); + const int frameRotationIndex = (static_cast<int>(m_currentFrame.rotation()) / 90) % 4; + QSize frameSize = m_currentFrame.size(); + if (frameRotationIndex % 2) + frameSize.transpose(); + QSize scaled = frameSize.scaled(rect.size(), aspectRatioMode); QRect videoRect = QRect(QPoint(0, 0), scaled); videoRect.moveCenter(rect.center()); + QRect subtitleRect = videoRect.intersected(rect); + + if (!m_hasSwapChain || (m_swapChain->currentPixelSize() != m_swapChain->surfacePixelSize())) + resizeSwapChain(); - if (m_swapChain->currentPixelSize() != m_swapChain->surfacePixelSize()) + const auto requiredSwapChainFormat = + qGetRequiredSwapChainFormat(m_currentFrame.surfaceFormat()); + if (qShouldUpdateSwapChainFormat(m_swapChain.get(), requiredSwapChainFormat)) { + releaseSwapChain(); + m_swapChain->setFormat(requiredSwapChainFormat); resizeSwapChain(); + } if (!m_hasSwapChain) return; QRhi::FrameOpResult r = m_rhi->beginFrame(m_swapChain.get()); + + // keep the video frames alive until we know that they are not needed anymore + m_videoFrameSlots[m_rhi->currentFrameSlot()] = m_currentFrame; + if (r == QRhi::FrameOpSwapChainOutOfDate) { resizeSwapChain(); if (!m_hasSwapChain) @@ -384,44 +375,44 @@ void QVideoWindowPrivate::render() if (!m_vertexBufReady) { m_vertexBufReady = true; - rub->uploadStaticBuffer(m_vertexBuf.get(), g_quad); + rub->uploadStaticBuffer(m_vertexBuf.get(), g_vw_quad); } if (m_texturesDirty) updateTextures(rub); - if (m_subtitleDirty) - updateSubtitle(rub); + if (m_subtitleDirty || m_subtitleLayout.videoSize != subtitleRect.size()) + updateSubtitle(rub, subtitleRect.size()); + + float mirrorFrame = m_currentFrame.mirrored() ? -1.f : 1.f; + float xscale = mirrorFrame * float(videoRect.width())/float(rect.width()); + float yscale = -1.f * float(videoRect.height())/float(rect.height()); - float xscale = 1.f - float(rect.width() - videoRect.width())/float(rect.width()); - float yscale = -1.f + float(rect.height() - videoRect.height())/float(rect.height()); + QMatrix4x4 transform; + transform.scale(xscale, yscale); - QMatrix4x4 transform = { - xscale, 0, 0, 0, - 0, yscale, 0, 0, - 0, 0, 1.f, 0, - 0, 0, 0, 1.f - }; + float maxNits = 100; + if (m_swapChain->format() == QRhiSwapChain::HDRExtendedSrgbLinear) { + auto info = m_swapChain->hdrInfo(); + if (info.limitsType == QRhiSwapChainHdrInfo::ColorComponentValue) + maxNits = 100 * info.limits.colorComponentValue.maxColorComponentValue; + else + maxNits = info.limits.luminanceInNits.maxLuminance; + } - QByteArray uniformData(64 + 64 + 4 + 4, Qt::Uninitialized); - QVideoTextureHelper::updateUniformData(&uniformData, m_currentFrame.surfaceFormat(), m_currentFrame, transform, 1.f); + QByteArray uniformData; + QVideoTextureHelper::updateUniformData(&uniformData, m_currentFrame.surfaceFormat(), m_currentFrame, transform, 1.f, maxNits); rub->updateDynamicBuffer(m_uniformBuf.get(), 0, uniformData.size(), uniformData.constData()); if (m_hasSubtitle) { - QMatrix4x4 t = { - xscale, 0, 0, 0, - 0, yscale, 0, 0, - 0, 0, 1.f, 0, - 0, 0, 0, 1.f - }; - QSizeF frameSize = m_currentFrame.size(); - t.translate(0, 2.*m_subtitleLayout.bounds.center().y()/frameSize.height() - 1.); - t.scale(m_subtitleLayout.bounds.width()/frameSize.width(), - m_subtitleLayout.bounds.height()/frameSize.height()); - - QByteArray uniformData(64 + 64 + 4 + 4, Qt::Uninitialized); + QMatrix4x4 st; + st.translate(0, -2.f * (float(m_subtitleLayout.bounds.center().y()) + float(subtitleRect.top()))/ float(rect.height()) + 1.f); + st.scale(float(m_subtitleLayout.bounds.width())/float(rect.width()), + -1.f * float(m_subtitleLayout.bounds.height())/float(rect.height())); + + QByteArray uniformData; QVideoFrameFormat fmt(m_subtitleLayout.bounds.size().toSize(), QVideoFrameFormat::Format_ARGB8888); - QVideoTextureHelper::updateUniformData(&uniformData, fmt, QVideoFrame(), t, 1.f); + QVideoTextureHelper::updateUniformData(&uniformData, fmt, QVideoFrame(), st, 1.f); rub->updateDynamicBuffer(m_subtitleUniformBuf.get(), 0, uniformData.size(), uniformData.constData()); } @@ -432,7 +423,8 @@ void QVideoWindowPrivate::render() cb->setViewport({ 0, 0, float(size.width()), float(size.height()) }); cb->setShaderResources(m_shaderResourceBindings.get()); - const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuf.get(), 0); + quint32 vertexOffset = quint32(sizeof(float)) * 16 * frameRotationIndex; + const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuf.get(), vertexOffset); cb->setVertexInput(0, 1, &vbufBinding); cb->draw(4); @@ -502,7 +494,7 @@ bool QVideoWindow::event(QEvent *e) case QEvent::Expose: d->isExposed = isExposed(); if (d->isExposed) - requestUpdate(); + d->render(); return true; default: @@ -532,3 +524,5 @@ void QVideoWindow::setVideoFrame(const QVideoFrame &frame) } QT_END_NAMESPACE + +#include "moc_qvideowindow_p.cpp" |