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