Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qvideowindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QPlatformSurfaceEvent>
6#include <qfile.h>
7#include <qpainter.h>
8#include <private/qguiapplication_p.h>
9#include <private/qmemoryvideobuffer_p.h>
10#include <private/qhwvideobuffer_p.h>
11#include <private/qmultimediautils_p.h>
12#include <private/qvideoframe_p.h>
13#include <qpa/qplatformintegration.h>
14
16
18{
19#if defined(Q_OS_DARWIN)
20 return QSurface::MetalSurface;
21#elif defined (Q_OS_WIN)
22 return QSurface::Direct3DSurface;
23#endif
24
25 auto *integration = QGuiApplicationPrivate::platformIntegration();
26
27 if (!integration->hasCapability(QPlatformIntegration::OpenGL))
28 return QSurface::RasterSurface;
29
30 if (QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets))
31 return QSurface::RasterSurface;
32
33 return QSurface::OpenGLSurface;
34}
35
37 : q(q),
38 m_sink(new QVideoSink)
39{
40 Q_ASSERT(q);
41
42 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)) {
43 auto surfaceType = ::platformSurfaceType();
44 q->setSurfaceType(surfaceType);
45 switch (surfaceType) {
46 case QSurface::RasterSurface:
47 case QSurface::OpenVGSurface:
48 default:
49 // can't use those surfaces, need to render in SW
50 m_graphicsApi = QRhi::Null;
51 break;
52 case QSurface::OpenGLSurface:
53 m_graphicsApi = QRhi::OpenGLES2;
54 break;
55 case QSurface::VulkanSurface:
56 m_graphicsApi = QRhi::Vulkan;
57 break;
58 case QSurface::MetalSurface:
59 m_graphicsApi = QRhi::Metal;
60 break;
61 case QSurface::Direct3DSurface:
62 m_graphicsApi = QRhi::D3D11;
63 break;
64 }
65 }
66
67 QObject::connect(m_sink.get(), &QVideoSink::videoFrameChanged, q, &QVideoWindow::setVideoFrame);
68}
69
70QVideoWindowPrivate::~QVideoWindowPrivate()
71{
72 QObject::disconnect(m_sink.get(), &QVideoSink::videoFrameChanged,
73 q, &QVideoWindow::setVideoFrame);
74}
75
76static const float g_vw_quad[] = {
77 // 4 clockwise rotation of texture vertexes (the second pair)
78 // Rotation 0
79 -1.f, -1.f, 0.f, 0.f,
80 -1.f, 1.f, 0.f, 1.f,
81 1.f, -1.f, 1.f, 0.f,
82 1.f, 1.f, 1.f, 1.f,
83 // Rotation 90
84 -1.f, -1.f, 0.f, 1.f,
85 -1.f, 1.f, 1.f, 1.f,
86 1.f, -1.f, 0.f, 0.f,
87 1.f, 1.f, 1.f, 0.f,
88
89 // Rotation 180
90 -1.f, -1.f, 1.f, 1.f,
91 -1.f, 1.f, 1.f, 0.f,
92 1.f, -1.f, 0.f, 1.f,
93 1.f, 1.f, 0.f, 0.f,
94 // Rotation 270
95 -1.f, -1.f, 1.f, 0.f,
96 -1.f, 1.f, 0.f, 0.f,
97 1.f, -1.f, 1.f, 1.f,
98 1.f, 1.f, 0.f, 1.f
99};
100
101static QShader vwGetShader(const QString &name)
102{
103 QFile f(name);
104 if (f.open(QIODevice::ReadOnly))
105 return QShader::fromSerialized(f.readAll());
106
107 return QShader();
108}
109
111{
112 if (m_graphicsApi == QRhi::Null)
113 return;
114
115 QRhi::Flags rhiFlags = {};//QRhi::EnableDebugMarkers | QRhi::EnableProfiling;
116
117#if QT_CONFIG(opengl)
118 if (m_graphicsApi == QRhi::OpenGLES2) {
119 m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface(q->format()));
120 QRhiGles2InitParams params;
121 params.fallbackSurface = m_fallbackSurface.get();
122 params.window = q;
123 params.format = q->format();
124 m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params, rhiFlags));
125 }
126#endif
127
128#if QT_CONFIG(vulkan)
129 if (m_graphicsApi == QRhi::Vulkan) {
130 QRhiVulkanInitParams params;
131 params.inst = q->vulkanInstance();
132 params.window = q;
133 m_rhi.reset(QRhi::create(QRhi::Vulkan, &params, rhiFlags));
134 }
135#endif
136
137#ifdef Q_OS_WIN
138 if (m_graphicsApi == QRhi::D3D11) {
139 QRhiD3D11InitParams params;
140 params.enableDebugLayer = true;
141 m_rhi.reset(QRhi::create(QRhi::D3D11, &params, rhiFlags));
142 }
143#endif
144
145#if QT_CONFIG(metal)
146 if (m_graphicsApi == QRhi::Metal) {
147 QRhiMetalInitParams params;
148 m_rhi.reset(QRhi::create(QRhi::Metal, &params, rhiFlags));
149 }
150#endif
151 if (!m_rhi)
152 return;
153
154 m_swapChain.reset(m_rhi->newSwapChain());
155 m_swapChain->setWindow(q);
156 m_renderPass.reset(m_swapChain->newCompatibleRenderPassDescriptor());
157 m_swapChain->setRenderPassDescriptor(m_renderPass.get());
158
159 m_vertexBuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_vw_quad)));
160 m_vertexBuf->create();
161 m_vertexBufReady = false;
162
163 m_uniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(QVideoTextureHelper::UniformData)));
164 m_uniformBuf->create();
165
166 m_textureSampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
167 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
168 m_textureSampler->create();
169
170 m_shaderResourceBindings.reset(m_rhi->newShaderResourceBindings());
171 m_subtitleResourceBindings.reset(m_rhi->newShaderResourceBindings());
172
173 m_subtitleUniformBuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, sizeof(QVideoTextureHelper::UniformData)));
174 m_subtitleUniformBuf->create();
175}
176
177void QVideoWindowPrivate::setupGraphicsPipeline(QRhiGraphicsPipeline *pipeline, QRhiShaderResourceBindings *bindings, const QVideoFrameFormat &fmt)
178{
179
180 pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
181 QShader vs = vwGetShader(QVideoTextureHelper::vertexShaderFileName(fmt));
182 Q_ASSERT(vs.isValid());
183 QShader fs = vwGetShader(QVideoTextureHelper::fragmentShaderFileName(
184 fmt, m_rhi.get(), m_swapChain->format()));
185 Q_ASSERT(fs.isValid());
186 pipeline->setShaderStages({
187 { QRhiShaderStage::Vertex, vs },
188 { QRhiShaderStage::Fragment, fs }
189 });
190 QRhiVertexInputLayout inputLayout;
191 inputLayout.setBindings({
192 { 4 * sizeof(float) }
193 });
194 inputLayout.setAttributes({
195 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
196 { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
197 });
198 pipeline->setVertexInputLayout(inputLayout);
199 pipeline->setShaderResourceBindings(bindings);
200 pipeline->setRenderPassDescriptor(m_renderPass.get());
201 pipeline->create();
202}
203
204void QVideoWindowPrivate::updateTextures(QRhiResourceUpdateBatch *rub)
205{
206 // We render a 1x1 black pixel when we don't have a video
207 if (!m_texturePool.currentFrame().isValid())
208 m_texturePool.setCurrentFrame(QVideoFramePrivate::createFrame(
209 std::make_unique<QMemoryVideoBuffer>(QByteArray{ 4, 0 }, 4),
210 QVideoFrameFormat(QSize(1, 1), QVideoFrameFormat::Format_RGBA8888)));
211
212 if (!m_texturePool.texturesDirty())
213 return;
214
215 QVideoFrameTextures *textures = m_texturePool.updateTextures(*m_rhi, *rub);
216 if (!textures)
217 return;
218
219 QRhiShaderResourceBinding bindings[4];
220 auto *b = bindings;
221 *(b++) = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
222 m_uniformBuf.get());
223
224 auto fmt = m_texturePool.currentFrame().surfaceFormat();
225 auto textureDesc = QVideoTextureHelper::textureDescription(fmt.pixelFormat());
226
227 for (int i = 0; i < textureDesc->nplanes; ++i)
228 (*b++) = QRhiShaderResourceBinding::sampledTexture(
229 i + 1, QRhiShaderResourceBinding::FragmentStage, textures->texture(i),
230 m_textureSampler.get());
231 m_shaderResourceBindings->setBindings(bindings, b);
232 m_shaderResourceBindings->create();
233
234 if (fmt != format) {
235 format = fmt;
236 if (!m_graphicsPipeline)
237 m_graphicsPipeline.reset(m_rhi->newGraphicsPipeline());
238
239 setupGraphicsPipeline(m_graphicsPipeline.get(), m_shaderResourceBindings.get(), format);
240 }
241}
242
243void QVideoWindowPrivate::updateSubtitle(QRhiResourceUpdateBatch *rub, const QSize &frameSize)
244{
245 m_subtitleDirty = false;
246 m_hasSubtitle = !m_texturePool.currentFrame().subtitleText().isEmpty();
247 if (!m_hasSubtitle)
248 return;
249
250 m_subtitleLayout.update(frameSize, m_texturePool.currentFrame().subtitleText());
251 QSize size = m_subtitleLayout.bounds.size().toSize();
252
253 QImage img = m_subtitleLayout.toImage();
254
255 m_subtitleTexture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, size));
256 m_subtitleTexture->create();
257 rub->uploadTexture(m_subtitleTexture.get(), img);
258
259 QRhiShaderResourceBinding bindings[2];
260
261 bindings[0] = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
262 m_subtitleUniformBuf.get());
263
264 bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage,
265 m_subtitleTexture.get(), m_textureSampler.get());
266 m_subtitleResourceBindings->setBindings(bindings, bindings + 2);
267 m_subtitleResourceBindings->create();
268
269 if (!m_subtitlePipeline) {
270 m_subtitlePipeline.reset(m_rhi->newGraphicsPipeline());
271
272 QRhiGraphicsPipeline::TargetBlend blend;
273 blend.enable = true;
274 m_subtitlePipeline->setTargetBlends({ blend });
275 setupGraphicsPipeline(m_subtitlePipeline.get(), m_subtitleResourceBindings.get(), QVideoFrameFormat(QSize(1, 1), QVideoFrameFormat::Format_RGBA8888));
276 }
277}
278
280{
281 if (initialized)
282 return;
283 initialized = true;
284
285 initRhi();
286
287 if (!m_rhi)
288 backingStore = new QBackingStore(q);
289 else
290 m_sink->setRhi(m_rhi.get());
291}
292
294{
295 m_hasSwapChain = m_swapChain->createOrResize();
296}
297
299{
300 if (m_hasSwapChain) {
301 m_hasSwapChain = false;
302 m_swapChain->destroy();
303 }
304}
305
307{
308 if (!initialized)
309 init();
310
311 if (!q->isExposed() || !isExposed)
312 return;
313
314 QRect rect(0, 0, q->width(), q->height());
315
316 if (backingStore) {
317 if (backingStore->size() != q->size())
318 backingStore->resize(q->size());
319
320 backingStore->beginPaint(rect);
321
322 QPaintDevice *device = backingStore->paintDevice();
323 if (!device)
324 return;
325 QPainter painter(device);
326
327 QVideoFrame frame = m_texturePool.currentFrame();
328 frame.paint(&painter, rect, { Qt::black, aspectRatioMode });
329 painter.end();
330
331 backingStore->endPaint();
332 backingStore->flush(rect);
333 return;
334 }
335
336 const VideoTransformation frameTransformation =
337 qNormalizedFrameTransformation(m_texturePool.currentFrame().surfaceFormat());
338 const QSize frameSize = qRotatedFramePresentationSize(m_texturePool.currentFrame());
339 const QSize scaled = frameSize.scaled(rect.size(), aspectRatioMode);
340 QRect videoRect = QRect(QPoint(0, 0), scaled);
341 videoRect.moveCenter(rect.center());
342 QRect subtitleRect = videoRect.intersected(rect);
343
344 if (!m_hasSwapChain || (m_swapChain->currentPixelSize() != m_swapChain->surfacePixelSize()))
346
347 const auto requiredSwapChainFormat =
348 qGetRequiredSwapChainFormat(m_texturePool.currentFrame().surfaceFormat());
349 if (qShouldUpdateSwapChainFormat(m_swapChain.get(), requiredSwapChainFormat)) {
351 m_swapChain->setFormat(requiredSwapChainFormat);
353 }
354
355 if (!m_hasSwapChain)
356 return;
357
358 QRhi::FrameOpResult r = m_rhi->beginFrame(m_swapChain.get());
359
360 if (r == QRhi::FrameOpSwapChainOutOfDate) {
362 if (!m_hasSwapChain)
363 return;
364 r = m_rhi->beginFrame(m_swapChain.get());
365 }
366 if (r != QRhi::FrameOpSuccess) {
367 qWarning("beginFrame failed with %d, retry", r);
368 q->requestUpdate();
369 return;
370 }
371
372 QRhiResourceUpdateBatch *rub = m_rhi->nextResourceUpdateBatch();
373
374 if (!m_vertexBufReady) {
375 m_vertexBufReady = true;
376 rub->uploadStaticBuffer(m_vertexBuf.get(), g_vw_quad);
377 }
378
380
381 if (m_subtitleDirty || m_subtitleLayout.videoSize != subtitleRect.size())
382 updateSubtitle(rub, subtitleRect.size());
383
384 const float mirrorFrame = frameTransformation.mirroredHorizontallyAfterRotation ? -1.f : 1.f;
385 const float xscale = mirrorFrame * float(videoRect.width()) / float(rect.width());
386 const float yscale = -1.f * float(videoRect.height()) / float(rect.height());
387
388 QMatrix4x4 transform;
389 transform.scale(xscale, yscale);
390
391 float maxNits = 100;
392 if (m_swapChain->format() == QRhiSwapChain::HDRExtendedSrgbLinear) {
393 auto info = m_swapChain->hdrInfo();
394 if (info.limitsType == QRhiSwapChainHdrInfo::ColorComponentValue)
395 maxNits = 100 * info.limits.colorComponentValue.maxColorComponentValue;
396 else
397 maxNits = info.limits.luminanceInNits.maxLuminance;
398 }
399
400 QByteArray uniformData;
401 QVideoTextureHelper::updateUniformData(&uniformData, m_rhi.get(),
402 m_texturePool.currentFrame().surfaceFormat(),
403 m_texturePool.currentFrame(), transform, 1.f, maxNits);
404 rub->updateDynamicBuffer(m_uniformBuf.get(), 0, uniformData.size(), uniformData.constData());
405
406 if (m_hasSubtitle) {
407 QMatrix4x4 st;
408 st.translate(0, -2.f * (float(m_subtitleLayout.bounds.center().y()) + float(subtitleRect.top()))/ float(rect.height()) + 1.f);
409 st.scale(float(m_subtitleLayout.bounds.width())/float(rect.width()),
410 -1.f * float(m_subtitleLayout.bounds.height())/float(rect.height()));
411
412 QByteArray uniformData;
413 QVideoFrameFormat fmt(m_subtitleLayout.bounds.size().toSize(), QVideoFrameFormat::Format_ARGB8888);
414 QVideoTextureHelper::updateUniformData(&uniformData, m_rhi.get(), fmt, QVideoFrame(), st,
415 1.f);
416 rub->updateDynamicBuffer(m_subtitleUniformBuf.get(), 0, uniformData.size(), uniformData.constData());
417 }
418
419 QRhiCommandBuffer *cb = m_swapChain->currentFrameCommandBuffer();
420 cb->beginPass(m_swapChain->currentFrameRenderTarget(), Qt::black, { 1.0f, 0 }, rub);
421 cb->setGraphicsPipeline(m_graphicsPipeline.get());
422 auto size = m_swapChain->currentPixelSize();
423 cb->setViewport({ 0, 0, float(size.width()), float(size.height()) });
424 cb->setShaderResources(m_shaderResourceBindings.get());
425
426 const quint32 vertexOffset = quint32(sizeof(float)) * 16 * frameTransformation.rotationIndex();
427 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuf.get(), vertexOffset);
428 cb->setVertexInput(0, 1, &vbufBinding);
429 cb->draw(4);
430
431 if (m_hasSubtitle) {
432 cb->setGraphicsPipeline(m_subtitlePipeline.get());
433 cb->setShaderResources(m_subtitleResourceBindings.get());
434 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuf.get(), 0);
435 cb->setVertexInput(0, 1, &vbufBinding);
436 cb->draw(4);
437 }
438
439 cb->endPass();
440
441 m_rhi->endFrame(m_swapChain.get());
442
443 m_texturePool.onFrameEndInvoked();
444}
445
446/*!
447 \class QVideoWindow
448 \internal
449*/
450QVideoWindow::QVideoWindow(QScreen *screen)
451 : QWindow(screen)
452 , d(new QVideoWindowPrivate(this))
453{
454}
455
456QVideoWindow::QVideoWindow(QWindow *parent)
457 : QWindow(parent)
458 , d(new QVideoWindowPrivate(this))
459{
460}
461
462QVideoWindow::~QVideoWindow() = default;
463
464QVideoSink *QVideoWindow::videoSink() const
465{
466 return d->m_sink.get();
467}
468
469Qt::AspectRatioMode QVideoWindow::aspectRatioMode() const
470{
471 return d->aspectRatioMode;
472}
473
474void QVideoWindow::setAspectRatioMode(Qt::AspectRatioMode mode)
475{
476 if (d->aspectRatioMode == mode)
477 return;
478 d->aspectRatioMode = mode;
479 emit aspectRatioModeChanged(mode);
480}
481
482bool QVideoWindow::event(QEvent *e)
483{
484 switch (e->type()) {
485 case QEvent::UpdateRequest:
486 d->render();
487 return true;
488
489 case QEvent::PlatformSurface:
490 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
491 if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
492 d->releaseSwapChain();
493 d->isExposed = false;
494 }
495 break;
496 case QEvent::Expose:
497 d->isExposed = isExposed();
498 if (d->isExposed)
499 d->render();
500 return true;
501
502 default:
503 break;
504 }
505
506 return QWindow::event(e);
507}
508
509void QVideoWindow::resizeEvent(QResizeEvent *resizeEvent)
510{
511 if (!d->backingStore)
512 return;
513 if (!d->initialized)
514 d->init();
515 d->backingStore->resize(resizeEvent->size());
516}
517
518void QVideoWindow::setVideoFrame(const QVideoFrame &frame)
519{
520 if (d->m_texturePool.currentFrame().subtitleText() != frame.subtitleText())
521 d->m_subtitleDirty = true;
522 d->m_texturePool.setCurrentFrame(frame);
523 if (d->isExposed)
524 requestUpdate();
525}
526
527QT_END_NAMESPACE
528
529#include "moc_qvideowindow_p.cpp"
\inmodule QtCore
Definition qfile.h:96
friend class QPainter
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:440
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
void updateSubtitle(QRhiResourceUpdateBatch *rub, const QSize &frameSize)
QVideoWindowPrivate(QVideoWindow *q)
void updateTextures(QRhiResourceUpdateBatch *rub)
static QT_BEGIN_NAMESPACE QSurface::SurfaceType platformSurfaceType()
static QShader vwGetShader(const QString &name)
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1551