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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/gui/painting/qbackingstoredefaultcompositor.cpp