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