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