Coverage Report

Created: 2026-02-26 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/painting/qbackingstoredefaultcompositor.cpp
Line
Count
Source
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
// Qt-Security score:significant reason:default
4
5
#include "qbackingstoredefaultcompositor_p.h"
6
#include <QtGui/private/qwindow_p.h>
7
#include <qpa/qplatformgraphicsbuffer.h>
8
#include <QtCore/qfile.h>
9
10
QT_BEGIN_NAMESPACE
11
12
using namespace Qt::StringLiterals;
13
14
QBackingStoreDefaultCompositor::~QBackingStoreDefaultCompositor()
15
0
{
16
0
    reset();
17
0
}
18
19
void QBackingStoreDefaultCompositor::reset()
20
0
{
21
0
    m_rhi = nullptr;
22
0
    m_psNoBlend.reset();
23
0
    m_psBlend.reset();
24
0
    m_psPremulBlend.reset();
25
0
    m_samplerNearest.reset();
26
0
    m_samplerLinear.reset();
27
0
    m_vbuf.reset();
28
0
    m_texture.reset();
29
0
    m_widgetQuadData.reset();
30
0
    for (PerQuadData &d : m_textureQuadData)
31
0
        d.reset();
32
0
}
33
34
QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QPlatformBackingStore *backingStore,
35
                                                       QRhi *rhi,
36
                                                       QRhiResourceUpdateBatch *resourceUpdates,
37
                                                       const QRegion &dirtyRegion,
38
                                                       QPlatformBackingStore::TextureFlags *flags) const
39
0
{
40
0
    return toTexture(backingStore->toImage(), rhi, resourceUpdates, dirtyRegion, flags);
41
0
}
42
43
QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QImage &sourceImage,
44
                                                       QRhi *rhi,
45
                                                       QRhiResourceUpdateBatch *resourceUpdates,
46
                                                       const QRegion &dirtyRegion,
47
                                                       QPlatformBackingStore::TextureFlags *flags) const
48
0
{
49
0
    Q_ASSERT(rhi);
50
0
    Q_ASSERT(resourceUpdates);
51
0
    Q_ASSERT(flags);
52
53
0
    if (!m_rhi) {
54
0
        m_rhi = rhi;
55
0
    } else if (m_rhi != rhi) {
56
0
        qWarning("QBackingStoreDefaultCompositor: the QRhi has changed unexpectedly, this should not happen");
57
0
        return nullptr;
58
0
    }
59
60
0
    QImage image = sourceImage;
61
62
0
    bool needsConversion = false;
63
0
    *flags = {};
64
65
0
    switch (image.format()) {
66
0
    case QImage::Format_ARGB32_Premultiplied:
67
0
        *flags |= QPlatformBackingStore::TexturePremultiplied;
68
0
        Q_FALLTHROUGH();
69
0
    case QImage::Format_RGB32:
70
0
    case QImage::Format_ARGB32:
71
0
        *flags |= QPlatformBackingStore::TextureSwizzle;
72
0
        break;
73
0
    case QImage::Format_RGBA8888_Premultiplied:
74
0
        *flags |= QPlatformBackingStore::TexturePremultiplied;
75
0
        Q_FALLTHROUGH();
76
0
    case QImage::Format_RGBX8888:
77
0
    case QImage::Format_RGBA8888:
78
0
        break;
79
0
    case QImage::Format_BGR30:
80
0
    case QImage::Format_A2BGR30_Premultiplied:
81
        // no fast path atm
82
0
        needsConversion = true;
83
0
        break;
84
0
    case QImage::Format_RGB30:
85
0
    case QImage::Format_A2RGB30_Premultiplied:
86
        // no fast path atm
87
0
        needsConversion = true;
88
0
        break;
89
0
    default:
90
0
        needsConversion = true;
91
0
        break;
92
0
    }
93
94
0
    if (image.size().isEmpty())
95
0
        return nullptr;
96
97
0
    const bool resized = !m_texture || m_texture->pixelSize() != image.size();
98
0
    if (dirtyRegion.isEmpty() && !resized)
99
0
        return m_texture.get();
100
101
0
    if (needsConversion)
102
0
        image = image.convertToFormat(QImage::Format_RGBA8888);
103
0
    else
104
0
        image.detach(); // if it was just wrapping data, that's no good, we need ownership, so detach
105
106
0
    if (resized) {
107
0
        if (!m_texture)
108
0
            m_texture.reset(rhi->newTexture(QRhiTexture::RGBA8, image.size()));
109
0
        else
110
0
            m_texture->setPixelSize(image.size());
111
0
        m_texture->create();
112
0
        resourceUpdates->uploadTexture(m_texture.get(), image);
113
0
    } else {
114
0
        QRect imageRect = image.rect();
115
0
        QRect rect = dirtyRegion.boundingRect() & imageRect;
116
0
        QRhiTextureSubresourceUploadDescription subresDesc(image);
117
0
        subresDesc.setSourceTopLeft(rect.topLeft());
118
0
        subresDesc.setSourceSize(rect.size());
119
0
        subresDesc.setDestinationTopLeft(rect.topLeft());
120
0
        QRhiTextureUploadDescription uploadDesc(QRhiTextureUploadEntry(0, 0, subresDesc));
121
0
        resourceUpdates->uploadTexture(m_texture.get(), uploadDesc);
122
0
    }
123
124
0
    return m_texture.get();
125
0
}
126
127
static inline QRect scaledRect(const QRect &rect, qreal factor)
128
0
{
129
0
    return QRect(rect.topLeft() * factor, rect.size() * factor);
130
0
}
131
132
static inline QPoint scaledOffset(const QPoint &pt, qreal factor)
133
0
{
134
0
    return pt * factor;
135
0
}
136
137
static QRegion scaledDirtyRegion(const QRegion &region, qreal factor, const QPoint &offset)
138
0
{
139
0
    if (offset.isNull() && factor <= 1)
140
0
        return region;
141
142
0
    QVarLengthArray<QRect, 4> rects;
143
0
    rects.reserve(region.rectCount());
144
    // Add 1x1 to the size since this is specifically for dirty regions for
145
    // toTexture(), so a slightly bigger size is not an issue in any case. This
146
    // fixes not uploading the last line/column in with non-integer high dpi
147
    // scale factors such as 1.25.
148
0
    for (const QRect &rect : region)
149
0
        rects.append(scaledRect(rect.translated(offset), factor).adjusted(0, 0, 1, 1));
150
151
0
    QRegion deviceRegion;
152
0
    deviceRegion.setRects(rects.constData(), rects.size());
153
0
    return deviceRegion;
154
0
}
155
156
static QMatrix4x4 targetTransform(const QRectF &target, const QRect &viewport, bool invertY)
157
0
{
158
0
    qreal x_scale = target.width() / viewport.width();
159
0
    qreal y_scale = target.height() / viewport.height();
160
161
0
    const QPointF relative_to_viewport = target.topLeft() - viewport.topLeft();
162
0
    qreal x_translate = x_scale - 1 + ((relative_to_viewport.x() / viewport.width()) * 2);
163
0
    qreal y_translate;
164
0
    if (invertY)
165
0
        y_translate = y_scale - 1 + ((relative_to_viewport.y() / viewport.height()) * 2);
166
0
    else
167
0
        y_translate = -y_scale + 1 - ((relative_to_viewport.y() / viewport.height()) * 2);
168
169
0
    QMatrix4x4 matrix;
170
0
    matrix(0,3) = x_translate;
171
0
    matrix(1,3) = y_translate;
172
173
0
    matrix(0,0) = x_scale;
174
0
    matrix(1,1) = (invertY ? -1.0 : 1.0) * y_scale;
175
176
0
    return matrix;
177
0
}
178
179
enum class SourceTransformOrigin {
180
    BottomLeft,
181
    TopLeft
182
};
183
184
static QMatrix3x3 sourceTransform(const QRectF &subTexture,
185
                                  const QSize &textureSize,
186
                                  SourceTransformOrigin origin)
187
0
{
188
0
    qreal x_scale = subTexture.width() / textureSize.width();
189
0
    qreal y_scale = subTexture.height() / textureSize.height();
190
191
0
    const QPointF topLeft = subTexture.topLeft();
192
0
    qreal x_translate = topLeft.x() / textureSize.width();
193
0
    qreal y_translate = topLeft.y() / textureSize.height();
194
195
0
    if (origin == SourceTransformOrigin::TopLeft) {
196
0
        y_scale = -y_scale;
197
0
        y_translate = 1 - y_translate;
198
0
    }
199
200
0
    QMatrix3x3 matrix;
201
0
    matrix(0,2) = x_translate;
202
0
    matrix(1,2) = y_translate;
203
204
0
    matrix(0,0) = x_scale;
205
0
    matrix(1,1) = y_scale;
206
207
0
    return matrix;
208
0
}
209
210
static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
211
0
{
212
0
    return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
213
0
                 topLeftRect.width(), topLeftRect.height());
214
0
}
215
216
static bool prepareDrawForRenderToTextureWidget(const QPlatformTextureList *textures,
217
                                                int idx,
218
                                                QWindow *window,
219
                                                const QRect &deviceWindowRect,
220
                                                const QPoint &offset,
221
                                                bool invertTargetY,
222
                                                bool invertSource,
223
                                                QMatrix4x4 *target,
224
                                                QMatrix3x3 *source)
225
0
{
226
0
    const QRect clipRect = textures->clipRect(idx);
227
0
    if (clipRect.isEmpty())
228
0
        return false;
229
230
0
    QRect rectInWindow = textures->geometry(idx);
231
    // relative to the TLW, not necessarily our window (if the flush is for a native child widget), have to adjust
232
0
    rectInWindow.translate(-offset);
233
234
0
    const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
235
0
    const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
236
237
0
    *target = targetTransform(scaledRect(clippedRectInWindow, window->devicePixelRatio()),
238
0
                              deviceWindowRect,
239
0
                              invertTargetY);
240
241
0
    *source = sourceTransform(scaledRect(srcRect, window->devicePixelRatio()),
242
0
                              scaledRect(rectInWindow, window->devicePixelRatio()).size(),
243
0
                              invertSource ? SourceTransformOrigin::TopLeft : SourceTransformOrigin::BottomLeft);
244
245
0
    return true;
246
0
}
247
248
static QShader getShader(const QString &name)
249
0
{
250
0
    QFile f(name);
251
0
    if (f.open(QIODevice::ReadOnly))
252
0
        return QShader::fromSerialized(f.readAll());
253
254
0
    qWarning("QBackingStoreDefaultCompositor: Could not find built-in shader %s "
255
0
             "(is something wrong with QtGui library resources?)",
256
0
             qPrintable(name));
257
0
    return QShader();
258
0
}
259
260
static void updateMatrix3x3(QRhiResourceUpdateBatch *resourceUpdates, QRhiBuffer *ubuf, const QMatrix3x3 &m)
261
0
{
262
    // mat3 is still 4 floats per column in the uniform buffer (but there is no
263
    // 4th column), so 48 bytes altogether, not 36 or 64.
264
265
0
    float f[12];
266
0
    const float *src = static_cast<const float *>(m.constData());
267
0
    float *dst = f;
268
0
    memcpy(dst, src, 3 * sizeof(float));
269
0
    memcpy(dst + 4, src + 3, 3 * sizeof(float));
270
0
    memcpy(dst + 8, src + 6, 3 * sizeof(float));
271
272
0
    resourceUpdates->updateDynamicBuffer(ubuf, 64, 48, f);
273
0
}
274
275
enum class PipelineBlend {
276
    None,
277
    Alpha,
278
    PremulAlpha
279
};
280
281
static QRhiGraphicsPipeline *createGraphicsPipeline(QRhi *rhi,
282
                                                    QRhiShaderResourceBindings *srb,
283
                                                    QRhiRenderPassDescriptor *rpDesc,
284
                                                    PipelineBlend blend)
285
0
{
286
0
    QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline();
287
288
0
    switch (blend) {
289
0
    case PipelineBlend::Alpha:
290
0
    {
291
0
        QRhiGraphicsPipeline::TargetBlend blend;
292
0
        blend.enable = true;
293
0
        blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
294
0
        blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
295
0
        blend.srcAlpha = QRhiGraphicsPipeline::One;
296
0
        blend.dstAlpha = QRhiGraphicsPipeline::One;
297
0
        ps->setTargetBlends({ blend });
298
0
    }
299
0
        break;
300
0
    case PipelineBlend::PremulAlpha:
301
0
    {
302
0
        QRhiGraphicsPipeline::TargetBlend blend;
303
0
        blend.enable = true;
304
0
        blend.srcColor = QRhiGraphicsPipeline::One;
305
0
        blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
306
0
        blend.srcAlpha = QRhiGraphicsPipeline::One;
307
0
        blend.dstAlpha = QRhiGraphicsPipeline::One;
308
0
        ps->setTargetBlends({ blend });
309
0
    }
310
0
        break;
311
0
    default:
312
0
        break;
313
0
    }
314
315
0
    ps->setShaderStages({
316
0
        { QRhiShaderStage::Vertex, getShader(":/qt-project.org/gui/painting/shaders/backingstorecompose.vert.qsb"_L1) },
317
0
        { QRhiShaderStage::Fragment, getShader(":/qt-project.org/gui/painting/shaders/backingstorecompose.frag.qsb"_L1) }
318
0
    });
319
0
    QRhiVertexInputLayout inputLayout;
320
0
    inputLayout.setBindings({ { 5 * sizeof(float) } });
321
0
    inputLayout.setAttributes({
322
0
        { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
323
0
        { 0, 1, QRhiVertexInputAttribute::Float2, quint32(3 * sizeof(float)) }
324
0
    });
325
0
    ps->setVertexInputLayout(inputLayout);
326
0
    ps->setShaderResourceBindings(srb);
327
0
    ps->setRenderPassDescriptor(rpDesc);
328
329
0
    if (!ps->create()) {
330
0
        qWarning("QBackingStoreDefaultCompositor: Failed to build graphics pipeline");
331
0
        delete ps;
332
0
        return nullptr;
333
0
    }
334
0
    return ps;
335
0
}
336
337
static const int UBUF_SIZE = 120;
338
339
QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::createPerQuadData(QRhiTexture *texture, QRhiTexture *textureExtra)
340
0
{
341
0
    PerQuadData d;
342
343
0
    d.ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
344
0
    if (!d.ubuf->create())
345
0
        qWarning("QBackingStoreDefaultCompositor: Failed to create uniform buffer");
346
347
0
    d.srb = m_rhi->newShaderResourceBindings();
348
0
    d.srb->setBindings({
349
0
        QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
350
0
        QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_samplerNearest.get())
351
0
    });
352
0
    if (!d.srb->create())
353
0
        qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
354
0
    d.lastUsedTexture = texture;
355
356
0
    if (textureExtra) {
357
0
        d.srbExtra = m_rhi->newShaderResourceBindings();
358
0
        d.srbExtra->setBindings({
359
0
            QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
360
0
            QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_samplerNearest.get())
361
0
        });
362
0
        if (!d.srbExtra->create())
363
0
            qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
364
0
    }
365
366
0
    d.lastUsedTextureExtra = textureExtra;
367
368
0
    return d;
369
0
}
370
371
void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra,
372
                                                       UpdateQuadDataOptions options)
373
0
{
374
    // This whole check-if-texture-ptr-is-different is needed because there is
375
    // nothing saying a QPlatformTextureList cannot return a different
376
    // QRhiTexture* from the same index in a subsequent flush.
377
378
0
    const QRhiSampler::Filter filter = options.testFlag(NeedsLinearFiltering) ? QRhiSampler::Linear : QRhiSampler::Nearest;
379
0
    if ((d->lastUsedTexture == texture && d->lastUsedFilter == filter) || !d->srb)
380
0
        return;
381
382
0
    QRhiSampler *sampler = filter == QRhiSampler::Linear ? m_samplerLinear.get() : m_samplerNearest.get();
383
0
    d->srb->setBindings({
384
0
        QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
385
0
        QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler)
386
0
    });
387
388
0
    d->srb->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
389
0
    d->lastUsedTexture = texture;
390
0
    d->lastUsedFilter = filter;
391
392
0
    if (textureExtra) {
393
0
        d->srbExtra->setBindings({
394
0
            QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
395
0
            QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, sampler)
396
0
        });
397
398
0
        d->srbExtra->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
399
0
        d->lastUsedTextureExtra = textureExtra;
400
0
    }
401
0
}
402
403
void QBackingStoreDefaultCompositor::updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
404
                                                    const QMatrix4x4 &target, const QMatrix3x3 &source,
405
                                                    UpdateUniformOptions options)
406
0
{
407
0
    resourceUpdates->updateDynamicBuffer(d->ubuf, 0, 64, target.constData());
408
0
    updateMatrix3x3(resourceUpdates, d->ubuf, source);
409
0
    float opacity = 1.0f;
410
0
    resourceUpdates->updateDynamicBuffer(d->ubuf, 112, 4, &opacity);
411
0
    qint32 textureSwizzle = options;
412
0
    resourceUpdates->updateDynamicBuffer(d->ubuf, 116, 4, &textureSwizzle);
413
0
}
414
415
void QBackingStoreDefaultCompositor::ensureResources(QRhiResourceUpdateBatch *resourceUpdates, QRhiRenderPassDescriptor *rpDesc)
416
0
{
417
0
    static const float vertexData[] = {
418
0
        -1, -1, 0,   0, 0,
419
0
        -1,  1, 0,   0, 1,
420
0
         1, -1, 0,   1, 0,
421
0
        -1,  1, 0,   0, 1,
422
0
         1, -1, 0,   1, 0,
423
0
         1,  1, 0,   1, 1
424
0
    };
425
426
0
    if (!m_vbuf) {
427
0
        m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
428
0
        if (m_vbuf->create())
429
0
            resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
430
0
        else
431
0
            qWarning("QBackingStoreDefaultCompositor: Failed to create vertex buffer");
432
0
    }
433
434
0
    if (!m_samplerNearest) {
435
0
        m_samplerNearest.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
436
0
                                                 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
437
0
        if (!m_samplerNearest->create())
438
0
            qWarning("QBackingStoreDefaultCompositor: Failed to create sampler (Nearest filtering)");
439
0
    }
440
441
0
    if (!m_samplerLinear) {
442
0
        m_samplerLinear.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
443
0
                                                QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
444
0
        if (!m_samplerLinear->create())
445
0
            qWarning("QBackingStoreDefaultCompositor: Failed to create sampler (Linear filtering)");
446
0
    }
447
448
0
    if (!m_widgetQuadData.isValid())
449
0
        m_widgetQuadData = createPerQuadData(m_texture.get());
450
451
0
    QRhiShaderResourceBindings *srb = m_widgetQuadData.srb; // just for the layout
452
0
    if (!m_psNoBlend)
453
0
        m_psNoBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::None));
454
0
    if (!m_psBlend)
455
0
        m_psBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::Alpha));
456
0
    if (!m_psPremulBlend)
457
0
        m_psPremulBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::PremulAlpha));
458
0
}
459
460
QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatformBackingStore *backingStore,
461
                                                                         QRhi *rhi,
462
                                                                         QRhiSwapChain *swapchain,
463
                                                                         QWindow *window,
464
                                                                         qreal sourceDevicePixelRatio,
465
                                                                         const QRegion &region,
466
                                                                         const QPoint &offset,
467
                                                                         QPlatformTextureList *textures,
468
                                                                         bool translucentBackground,
469
                                                                         qreal sourceTransformFactor)
470
0
{
471
0
    if (!rhi || !swapchain)
472
0
        return QPlatformBackingStore::FlushFailed;
473
474
    // Note, the sourceTransformFactor is different from the sourceDevicePixelRatio,
475
    // as the former may reflect the fact that the region and offset is pre-transformed,
476
    // in which case we don't need to do a full transform here based on the source DPR.
477
    // In the default case where no explicit source transform has been passed, we fall
478
    // back to the source device pixel ratio.
479
0
    if (!sourceTransformFactor)
480
0
        sourceTransformFactor = sourceDevicePixelRatio;
481
482
0
    Q_ASSERT(textures); // may be empty if there are no render-to-texture widgets at all, but null it cannot be
483
484
0
    if (!m_rhi) {
485
0
        m_rhi = rhi;
486
0
    } else if (m_rhi != rhi) {
487
0
        qWarning("QBackingStoreDefaultCompositor: the QRhi has changed unexpectedly, this should not happen");
488
0
        return QPlatformBackingStore::FlushFailed;
489
0
    }
490
491
0
    if (!qt_window_private(window)->receivedExpose)
492
0
        return QPlatformBackingStore::FlushSuccess;
493
494
0
    qCDebug(lcQpaBackingStore) << "Composing and flushing" << region << "of" << window
495
0
                               << "at offset" << offset << "with" << textures->count() << "texture(s) in" << textures
496
0
                               << "via swapchain" << swapchain;
497
498
0
    QWindowPrivate::get(window)->lastComposeTime.start();
499
500
0
    if (swapchain->currentPixelSize() != swapchain->surfacePixelSize())
501
0
        swapchain->createOrResize();
502
503
    // Start recording a new frame.
504
0
    QRhi::FrameOpResult frameResult = rhi->beginFrame(swapchain);
505
0
    if (frameResult == QRhi::FrameOpSwapChainOutOfDate) {
506
0
        if (!swapchain->createOrResize())
507
0
            return QPlatformBackingStore::FlushFailed;
508
0
        frameResult = rhi->beginFrame(swapchain);
509
0
    }
510
0
    if (frameResult == QRhi::FrameOpDeviceLost)
511
0
        return QPlatformBackingStore::FlushFailedDueToLostDevice;
512
0
    if (frameResult != QRhi::FrameOpSuccess)
513
0
        return QPlatformBackingStore::FlushFailed;
514
515
    // Prepare resource updates.
516
0
    QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
517
0
    QPlatformBackingStore::TextureFlags flags;
518
519
0
    const QRegion dirtyRegion = scaledDirtyRegion(region, sourceTransformFactor, offset);
520
0
    bool gotTextureFromGraphicsBuffer = false;
521
0
    if (QPlatformGraphicsBuffer *graphicsBuffer = backingStore->graphicsBuffer()) {
522
0
        if (graphicsBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess)) {
523
0
            const QImage::Format format = QImage::toImageFormat(graphicsBuffer->format());
524
0
            const QSize size = graphicsBuffer->size();
525
0
            QImage wrapperImage(graphicsBuffer->data(), size.width(), size.height(), graphicsBuffer->bytesPerLine(), format);
526
0
            toTexture(wrapperImage, rhi, resourceUpdates, dirtyRegion, &flags);
527
0
            gotTextureFromGraphicsBuffer = true;
528
0
            graphicsBuffer->unlock();
529
0
            if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft)
530
0
                flags |= QPlatformBackingStore::TextureFlip;
531
0
        }
532
0
    }
533
0
    if (!gotTextureFromGraphicsBuffer)
534
0
        toTexture(backingStore, rhi, resourceUpdates, dirtyRegion, &flags);
535
536
0
    ensureResources(resourceUpdates, swapchain->renderPassDescriptor());
537
538
0
    UpdateUniformOptions uniformOptions;
539
0
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
540
0
    if (flags & QPlatformBackingStore::TextureSwizzle)
541
0
        uniformOptions |= NeedsRedBlueSwap;
542
#else
543
    if (flags & QPlatformBackingStore::TextureSwizzle)
544
        uniformOptions |= NeedsAlphaRotate;
545
#endif
546
0
    const bool premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
547
0
    SourceTransformOrigin origin = SourceTransformOrigin::TopLeft;
548
0
    if (flags & QPlatformBackingStore::TextureFlip)
549
0
        origin = SourceTransformOrigin::BottomLeft;
550
551
0
    const qreal dpr = window->devicePixelRatio();
552
0
    const QRect deviceWindowRect = scaledRect(QRect(QPoint(), window->size()), dpr);
553
0
    const QRect sourceWindowRect = scaledRect(QRect(QPoint(), window->size()), sourceDevicePixelRatio);
554
    // If sourceWindowRect is larger than deviceWindowRect, we are doing high
555
    // DPI downscaling. In that case Linear filtering is a must, whereas for the
556
    // 1:1 case Nearest must be used for Qt 5 visual compatibility.
557
0
    const bool needsLinearSampler = sourceWindowRect.width() > deviceWindowRect.width()
558
0
                                    && sourceWindowRect.height() > deviceWindowRect.height();
559
560
0
    const bool invertTargetY = !rhi->isYUpInNDC();
561
0
    const bool invertSource = !rhi->isYUpInFramebuffer();
562
563
0
    if (m_texture) {
564
        // The backingstore is for the entire tlw. In case of native children, offset tells the position
565
        // relative to the tlw. The window rect is scaled by the source device pixel ratio to get
566
        // the source rect.
567
0
        const QPoint sourceWindowOffset = scaledOffset(offset, sourceTransformFactor);
568
0
        const QRect srcRect = toBottomLeftRect(sourceWindowRect.translated(sourceWindowOffset), m_texture->pixelSize().height());
569
0
        const QMatrix3x3 source = sourceTransform(srcRect, m_texture->pixelSize(), origin);
570
0
        QMatrix4x4 target; // identity
571
0
        if (invertTargetY)
572
0
            target.data()[5] = -1.0f;
573
0
        updateUniforms(&m_widgetQuadData, resourceUpdates, target, source, uniformOptions);
574
0
        if (needsLinearSampler)
575
0
            updatePerQuadData(&m_widgetQuadData, m_texture.get(), nullptr, NeedsLinearFiltering);
576
0
    }
577
578
0
    const int textureWidgetCount = textures->count();
579
0
    const int oldTextureQuadDataCount = m_textureQuadData.size();
580
0
    if (oldTextureQuadDataCount != textureWidgetCount) {
581
0
        for (int i = textureWidgetCount; i < oldTextureQuadDataCount; ++i)
582
0
            m_textureQuadData[i].reset();
583
0
        m_textureQuadData.resize(textureWidgetCount);
584
0
    }
585
586
0
    for (int i = 0; i < textureWidgetCount; ++i) {
587
0
        const bool invertSourceForTextureWidget = textures->flags(i).testFlag(QPlatformTextureList::MirrorVertically)
588
0
                                                      ? !invertSource : invertSource;
589
0
        QMatrix4x4 target;
590
0
        QMatrix3x3 source;
591
0
        if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
592
0
                                                 offset, invertTargetY, invertSourceForTextureWidget,
593
0
                                                 &target, &source))
594
0
        {
595
0
            m_textureQuadData[i].reset();
596
0
            continue;
597
0
        }
598
0
        QRhiTexture *t = textures->texture(i);
599
0
        QRhiTexture *tExtra = textures->textureExtra(i);
600
0
        if (t) {
601
0
            if (!m_textureQuadData[i].isValid())
602
0
                m_textureQuadData[i] = createPerQuadData(t, tExtra);
603
0
            else
604
0
                updatePerQuadData(&m_textureQuadData[i], t, tExtra);
605
0
            updateUniforms(&m_textureQuadData[i], resourceUpdates, target, source);
606
0
            if (needsLinearSampler)
607
0
                updatePerQuadData(&m_textureQuadData[i], t, tExtra, NeedsLinearFiltering);
608
0
        } else {
609
0
            m_textureQuadData[i].reset();
610
0
        }
611
0
    }
612
613
    // Record the render pass (with committing the resource updates).
614
0
    QRhiCommandBuffer *cb = swapchain->currentFrameCommandBuffer();
615
0
    const QSize outputSizeInPixels = swapchain->currentPixelSize();
616
0
    QColor clearColor = translucentBackground ? Qt::transparent : Qt::black;
617
618
0
    cb->resourceUpdate(resourceUpdates);
619
620
0
    auto render = [&](std::optional<QRhiSwapChain::StereoTargetBuffer> buffer = std::nullopt) {
621
0
        QRhiRenderTarget* target = nullptr;
622
0
        if (buffer.has_value())
623
0
            target = swapchain->currentFrameRenderTarget(buffer.value());
624
0
        else
625
0
            target = swapchain->currentFrameRenderTarget();
626
627
0
        cb->beginPass(target, clearColor, { 1.0f, 0 });
628
629
0
        cb->setGraphicsPipeline(m_psNoBlend.get());
630
0
        cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
631
0
        QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
632
0
        cb->setVertexInput(0, 1, &vbufBinding);
633
634
        // Textures for renderToTexture widgets.
635
0
        for (int i = 0; i < textureWidgetCount; ++i) {
636
0
            if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
637
0
                if (m_textureQuadData[i].isValid()) {
638
639
0
                    QRhiShaderResourceBindings* srb = m_textureQuadData[i].srb;
640
0
                    if (buffer == QRhiSwapChain::RightBuffer && m_textureQuadData[i].srbExtra)
641
0
                        srb = m_textureQuadData[i].srbExtra;
642
643
0
                    cb->setShaderResources(srb);
644
0
                    cb->draw(6);
645
0
                }
646
0
            }
647
0
        }
648
649
0
        cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend.get() : m_psBlend.get());
650
651
        // Backingstore texture with the normal widgets.
652
0
        if (m_texture) {
653
0
            cb->setShaderResources(m_widgetQuadData.srb);
654
0
            cb->draw(6);
655
0
        }
656
657
        // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
658
0
        for (int i = 0; i < textureWidgetCount; ++i) {
659
0
            const QPlatformTextureList::Flags flags = textures->flags(i);
660
0
            if (flags.testFlag(QPlatformTextureList::StacksOnTop)) {
661
0
                if (m_textureQuadData[i].isValid()) {
662
0
                    if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending))
663
0
                        cb->setGraphicsPipeline(m_psPremulBlend.get());
664
0
                    else
665
0
                        cb->setGraphicsPipeline(m_psBlend.get());
666
667
0
                    QRhiShaderResourceBindings* srb = m_textureQuadData[i].srb;
668
0
                    if (buffer == QRhiSwapChain::RightBuffer && m_textureQuadData[i].srbExtra)
669
0
                        srb = m_textureQuadData[i].srbExtra;
670
671
0
                    cb->setShaderResources(srb);
672
0
                    cb->draw(6);
673
0
                }
674
0
            }
675
0
        }
676
677
0
        cb->endPass();
678
0
    };
679
680
0
    if (swapchain->window()->format().stereo()) {
681
0
        render(QRhiSwapChain::LeftBuffer);
682
0
        render(QRhiSwapChain::RightBuffer);
683
0
    } else
684
0
        render();
685
686
0
    rhi->endFrame(swapchain);
687
688
0
    return QPlatformBackingStore::FlushSuccess;
689
0
}
690
691
QT_END_NAMESPACE