| 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 | 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 | |
| 33 | QRhiTexture *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 | |
| 42 | QRhiTexture *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 | |
| 126 | static inline QRect scaledRect(const QRect &rect, qreal factor) |
| 127 | { |
| 128 | return QRect(rect.topLeft() * factor, rect.size() * factor); |
| 129 | } |
| 130 | |
| 131 | static inline QPoint scaledOffset(const QPoint &pt, qreal factor) |
| 132 | { |
| 133 | return pt * factor; |
| 134 | } |
| 135 | |
| 136 | static QRegion scaledRegion(const QRegion ®ion, 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 | |
| 151 | static 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 | |
| 174 | enum class SourceTransformOrigin { |
| 175 | BottomLeft, |
| 176 | TopLeft |
| 177 | }; |
| 178 | |
| 179 | static 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 | |
| 205 | static 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 | |
| 211 | static 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 | |
| 243 | static 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 | |
| 255 | static 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 | |
| 270 | enum class PipelineBlend { |
| 271 | None, |
| 272 | Alpha, |
| 273 | PremulAlpha |
| 274 | }; |
| 275 | |
| 276 | static 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 | |
| 332 | static const int UBUF_SIZE = 120; |
| 333 | |
| 334 | QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::createPerQuadData(QRhiTexture *texture, QRhiTexture *) |
| 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 | |
| 366 | void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *, |
| 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 | |
| 398 | void 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 | |
| 410 | void 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 | |
| 455 | QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatformBackingStore *backingStore, |
| 456 | QRhi *rhi, |
| 457 | QRhiSwapChain *swapchain, |
| 458 | QWindow *window, |
| 459 | qreal sourceDevicePixelRatio, |
| 460 | const QRegion ®ion, |
| 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 * = 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 | |
| 676 | QT_END_NAMESPACE |
| 677 | |