| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2019 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the test suite of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QtTest/QtTest> | 
| 30 | #include <QThread> | 
| 31 | #include <QFile> | 
| 32 | #include <QOffscreenSurface> | 
| 33 | #include <QPainter> | 
| 34 |  | 
| 35 | #include <QtGui/private/qrhi_p.h> | 
| 36 | #include <QtGui/private/qrhinull_p.h> | 
| 37 |  | 
| 38 | #if QT_CONFIG(opengl) | 
| 39 | # include <QOpenGLContext> | 
| 40 | # include <QtGui/private/qrhigles2_p.h> | 
| 41 | # define TST_GL | 
| 42 | #endif | 
| 43 |  | 
| 44 | #if QT_CONFIG(vulkan) | 
| 45 | # include <QVulkanInstance> | 
| 46 | # include <QtGui/private/qrhivulkan_p.h> | 
| 47 | # define TST_VK | 
| 48 | #endif | 
| 49 |  | 
| 50 | #ifdef Q_OS_WIN | 
| 51 | #include <QtGui/private/qrhid3d11_p.h> | 
| 52 | # define TST_D3D11 | 
| 53 | #endif | 
| 54 |  | 
| 55 | #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) | 
| 56 | # include <QtGui/private/qrhimetal_p.h> | 
| 57 | # define TST_MTL | 
| 58 | #endif | 
| 59 |  | 
| 60 | Q_DECLARE_METATYPE(QRhi::Implementation) | 
| 61 | Q_DECLARE_METATYPE(QRhiInitParams *) | 
| 62 |  | 
| 63 | class tst_QRhi : public QObject | 
| 64 | { | 
| 65 |     Q_OBJECT | 
| 66 |  | 
| 67 | private slots: | 
| 68 |     void initTestCase(); | 
| 69 |     void cleanupTestCase(); | 
| 70 |  | 
| 71 |     void rhiTestData(); | 
| 72 |     void create_data(); | 
| 73 |     void create(); | 
| 74 |     void nativeHandles_data(); | 
| 75 |     void nativeHandles(); | 
| 76 |     void nativeTexture_data(); | 
| 77 |     void nativeTexture(); | 
| 78 |     void nativeBuffer_data(); | 
| 79 |     void nativeBuffer(); | 
| 80 |     void resourceUpdateBatchBuffer_data(); | 
| 81 |     void resourceUpdateBatchBuffer(); | 
| 82 |     void resourceUpdateBatchRGBATextureUpload_data(); | 
| 83 |     void resourceUpdateBatchRGBATextureUpload(); | 
| 84 |     void resourceUpdateBatchRGBATextureCopy_data(); | 
| 85 |     void resourceUpdateBatchRGBATextureCopy(); | 
| 86 |     void resourceUpdateBatchRGBATextureMip_data(); | 
| 87 |     void resourceUpdateBatchRGBATextureMip(); | 
| 88 |     void invalidPipeline_data(); | 
| 89 |     void invalidPipeline(); | 
| 90 |     void renderToTextureSimple_data(); | 
| 91 |     void renderToTextureSimple(); | 
| 92 |     void renderToTextureTexturedQuad_data(); | 
| 93 |     void renderToTextureTexturedQuad(); | 
| 94 |     void renderToTextureArrayOfTexturedQuad_data(); | 
| 95 |     void renderToTextureArrayOfTexturedQuad(); | 
| 96 |     void renderToTextureTexturedQuadAndUniformBuffer_data(); | 
| 97 |     void renderToTextureTexturedQuadAndUniformBuffer(); | 
| 98 |     void renderToWindowSimple_data(); | 
| 99 |     void renderToWindowSimple(); | 
| 100 |     void srbLayoutCompatibility_data(); | 
| 101 |     void srbLayoutCompatibility(); | 
| 102 |     void renderPassDescriptorCompatibility_data(); | 
| 103 |     void renderPassDescriptorCompatibility(); | 
| 104 |  | 
| 105 | private: | 
| 106 |     struct { | 
| 107 |         QRhiNullInitParams null; | 
| 108 | #ifdef TST_GL | 
| 109 |         QRhiGles2InitParams gl; | 
| 110 | #endif | 
| 111 | #ifdef TST_VK | 
| 112 |         QRhiVulkanInitParams vk; | 
| 113 | #endif | 
| 114 | #ifdef TST_D3D11 | 
| 115 |         QRhiD3D11InitParams d3d; | 
| 116 | #endif | 
| 117 | #ifdef TST_MTL | 
| 118 |         QRhiMetalInitParams mtl; | 
| 119 | #endif | 
| 120 |     } initParams; | 
| 121 |  | 
| 122 | #ifdef TST_VK | 
| 123 |     QVulkanInstance vulkanInstance; | 
| 124 | #endif | 
| 125 |     QOffscreenSurface *fallbackSurface = nullptr; | 
| 126 | }; | 
| 127 |  | 
| 128 | void tst_QRhi::initTestCase() | 
| 129 | { | 
| 130 | #ifdef TST_GL | 
| 131 |     fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); | 
| 132 |     initParams.gl.fallbackSurface = fallbackSurface; | 
| 133 | #endif | 
| 134 |  | 
| 135 | #ifdef TST_VK | 
| 136 | #ifndef Q_OS_ANDROID | 
| 137 |     vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_LUNARG_standard_validation" ) }); | 
| 138 | #else | 
| 139 |     vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_GOOGLE_threading" ), | 
| 140 |                                QByteArrayLiteral("VK_LAYER_LUNARG_parameter_validation" ), | 
| 141 |                                QByteArrayLiteral("VK_LAYER_LUNARG_object_tracker" ), | 
| 142 |                                QByteArrayLiteral("VK_LAYER_LUNARG_core_validation" ), | 
| 143 |                                QByteArrayLiteral("VK_LAYER_LUNARG_image" ), | 
| 144 |                                QByteArrayLiteral("VK_LAYER_LUNARG_swapchain" ), | 
| 145 |                                QByteArrayLiteral("VK_LAYER_GOOGLE_unique_objects" ) }); | 
| 146 | #endif | 
| 147 |     vulkanInstance.setExtensions(QByteArrayList() | 
| 148 |                                  << "VK_KHR_get_physical_device_properties2" ); | 
| 149 |     vulkanInstance.create(); | 
| 150 |     initParams.vk.inst = &vulkanInstance; | 
| 151 | #endif | 
| 152 |  | 
| 153 | #ifdef TST_D3D11 | 
| 154 |     initParams.d3d.enableDebugLayer = true; | 
| 155 | #endif | 
| 156 | } | 
| 157 |  | 
| 158 | void tst_QRhi::cleanupTestCase() | 
| 159 | { | 
| 160 | #ifdef TST_VK | 
| 161 |     vulkanInstance.destroy(); | 
| 162 | #endif | 
| 163 |  | 
| 164 |     delete fallbackSurface; | 
| 165 | } | 
| 166 |  | 
| 167 | void tst_QRhi::rhiTestData() | 
| 168 | { | 
| 169 |     QTest::addColumn<QRhi::Implementation>(name: "impl" ); | 
| 170 |     QTest::addColumn<QRhiInitParams *>(name: "initParams" ); | 
| 171 |  | 
| 172 |     QTest::newRow(dataTag: "Null" ) << QRhi::Null << static_cast<QRhiInitParams *>(&initParams.null); | 
| 173 | #ifdef TST_GL | 
| 174 |     QTest::newRow(dataTag: "OpenGL" ) << QRhi::OpenGLES2 << static_cast<QRhiInitParams *>(&initParams.gl); | 
| 175 | #endif | 
| 176 | #ifdef TST_VK | 
| 177 |     if (vulkanInstance.isValid()) | 
| 178 |         QTest::newRow(dataTag: "Vulkan" ) << QRhi::Vulkan << static_cast<QRhiInitParams *>(&initParams.vk); | 
| 179 | #endif | 
| 180 | #ifdef TST_D3D11 | 
| 181 |     QTest::newRow("Direct3D 11" ) << QRhi::D3D11 << static_cast<QRhiInitParams *>(&initParams.d3d); | 
| 182 | #endif | 
| 183 | #ifdef TST_MTL | 
| 184 |     QTest::newRow("Metal" ) << QRhi::Metal << static_cast<QRhiInitParams *>(&initParams.mtl); | 
| 185 | #endif | 
| 186 | } | 
| 187 |  | 
| 188 | void tst_QRhi::create_data() | 
| 189 | { | 
| 190 |     rhiTestData(); | 
| 191 | } | 
| 192 |  | 
| 193 | static int aligned(int v, int a) | 
| 194 | { | 
| 195 |     return (v + a - 1) & ~(a - 1); | 
| 196 | } | 
| 197 |  | 
| 198 | void tst_QRhi::create() | 
| 199 | { | 
| 200 |     // Merely attempting to create a QRhi should survive, with an error when | 
| 201 |     // not supported. (of course, there is always a chance we encounter a crash | 
| 202 |     // due to some random graphics stack...) | 
| 203 |  | 
| 204 |     QFETCH(QRhi::Implementation, impl); | 
| 205 |     QFETCH(QRhiInitParams *, initParams); | 
| 206 |  | 
| 207 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 208 |  | 
| 209 |     if (rhi) { | 
| 210 |         QCOMPARE(rhi->backend(), impl); | 
| 211 |         QCOMPARE(rhi->thread(), QThread::currentThread()); | 
| 212 |  | 
| 213 |         // do a basic smoke test for the apis that do not directly render anything | 
| 214 |  | 
| 215 |         int cleanupOk = 0; | 
| 216 |         QRhi *rhiPtr = rhi.data(); | 
| 217 |         auto cleanupFunc = [rhiPtr, &cleanupOk](QRhi *dyingRhi) { | 
| 218 |             if (rhiPtr == dyingRhi) | 
| 219 |                 cleanupOk += 1; | 
| 220 |         }; | 
| 221 |         rhi->addCleanupCallback(callback: cleanupFunc); | 
| 222 |         rhi->runCleanup(); | 
| 223 |         QCOMPARE(cleanupOk, 1); | 
| 224 |         cleanupOk = 0; | 
| 225 |         rhi->addCleanupCallback(callback: cleanupFunc); | 
| 226 |  | 
| 227 |         QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch(); | 
| 228 |         QVERIFY(resUpd); | 
| 229 |         resUpd->release(); | 
| 230 |  | 
| 231 |         QVERIFY(!rhi->supportedSampleCounts().isEmpty()); | 
| 232 |         QVERIFY(rhi->supportedSampleCounts().contains(1)); | 
| 233 |  | 
| 234 |         QVERIFY(rhi->ubufAlignment() > 0); | 
| 235 |         QCOMPARE(rhi->ubufAligned(123), aligned(123, rhi->ubufAlignment())); | 
| 236 |  | 
| 237 |         QCOMPARE(rhi->mipLevelsForSize(QSize(512, 300)), 10); | 
| 238 |         QCOMPARE(rhi->sizeForMipLevel(0, QSize(512, 300)), QSize(512, 300)); | 
| 239 |         QCOMPARE(rhi->sizeForMipLevel(1, QSize(512, 300)), QSize(256, 150)); | 
| 240 |         QCOMPARE(rhi->sizeForMipLevel(2, QSize(512, 300)), QSize(128, 75)); | 
| 241 |         QCOMPARE(rhi->sizeForMipLevel(9, QSize(512, 300)), QSize(1, 1)); | 
| 242 |  | 
| 243 |         const bool fbUp = rhi->isYUpInFramebuffer(); | 
| 244 |         const bool ndcUp = rhi->isYUpInNDC(); | 
| 245 |         const bool d0to1 = rhi->isClipDepthZeroToOne(); | 
| 246 |         const QMatrix4x4 corrMat = rhi->clipSpaceCorrMatrix(); | 
| 247 |         if (impl == QRhi::OpenGLES2) { | 
| 248 |             QVERIFY(fbUp); | 
| 249 |             QVERIFY(ndcUp); | 
| 250 |             QVERIFY(!d0to1); | 
| 251 |             QVERIFY(corrMat.isIdentity()); | 
| 252 |         } else if (impl == QRhi::Vulkan) { | 
| 253 |             QVERIFY(!fbUp); | 
| 254 |             QVERIFY(!ndcUp); | 
| 255 |             QVERIFY(d0to1); | 
| 256 |             QVERIFY(!corrMat.isIdentity()); | 
| 257 |         } else if (impl == QRhi::D3D11) { | 
| 258 |             QVERIFY(!fbUp); | 
| 259 |             QVERIFY(ndcUp); | 
| 260 |             QVERIFY(d0to1); | 
| 261 |             QVERIFY(!corrMat.isIdentity()); | 
| 262 |         } else if (impl == QRhi::Metal) { | 
| 263 |             QVERIFY(!fbUp); | 
| 264 |             QVERIFY(ndcUp); | 
| 265 |             QVERIFY(d0to1); | 
| 266 |             QVERIFY(!corrMat.isIdentity()); | 
| 267 |         } | 
| 268 |  | 
| 269 |         const int texMin = rhi->resourceLimit(limit: QRhi::TextureSizeMin); | 
| 270 |         const int texMax = rhi->resourceLimit(limit: QRhi::TextureSizeMax); | 
| 271 |         const int maxAtt = rhi->resourceLimit(limit: QRhi::MaxColorAttachments); | 
| 272 |         const int framesInFlight = rhi->resourceLimit(limit: QRhi::FramesInFlight); | 
| 273 |         QVERIFY(texMin >= 1); | 
| 274 |         QVERIFY(texMax >= texMin); | 
| 275 |         QVERIFY(maxAtt >= 1); | 
| 276 |         QVERIFY(framesInFlight >= 1); | 
| 277 |  | 
| 278 |         QVERIFY(rhi->nativeHandles()); | 
| 279 |         QVERIFY(rhi->profiler()); | 
| 280 |  | 
| 281 |         const QRhi::Feature features[] = { | 
| 282 |             QRhi::MultisampleTexture, | 
| 283 |             QRhi::MultisampleRenderBuffer, | 
| 284 |             QRhi::DebugMarkers, | 
| 285 |             QRhi::Timestamps, | 
| 286 |             QRhi::Instancing, | 
| 287 |             QRhi::CustomInstanceStepRate, | 
| 288 |             QRhi::PrimitiveRestart, | 
| 289 |             QRhi::NonDynamicUniformBuffers, | 
| 290 |             QRhi::NonFourAlignedEffectiveIndexBufferOffset, | 
| 291 |             QRhi::NPOTTextureRepeat, | 
| 292 |             QRhi::RedOrAlpha8IsRed, | 
| 293 |             QRhi::ElementIndexUint, | 
| 294 |             QRhi::Compute, | 
| 295 |             QRhi::WideLines, | 
| 296 |             QRhi::VertexShaderPointSize, | 
| 297 |             QRhi::BaseVertex, | 
| 298 |             QRhi::BaseInstance, | 
| 299 |             QRhi::TriangleFanTopology, | 
| 300 |             QRhi::ReadBackNonUniformBuffer, | 
| 301 |             QRhi::ReadBackNonBaseMipLevel, | 
| 302 |             QRhi::TexelFetch | 
| 303 |         }; | 
| 304 |         for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i) | 
| 305 |             rhi->isFeatureSupported(feature: features[i]); | 
| 306 |  | 
| 307 |         QVERIFY(rhi->isTextureFormatSupported(QRhiTexture::RGBA8)); | 
| 308 |  | 
| 309 |         rhi->releaseCachedResources(); | 
| 310 |  | 
| 311 |         QVERIFY(!rhi->isDeviceLost()); | 
| 312 |  | 
| 313 |         rhi.reset(); | 
| 314 |         QCOMPARE(cleanupOk, 1); | 
| 315 |     } | 
| 316 | } | 
| 317 |  | 
| 318 | void tst_QRhi::nativeHandles_data() | 
| 319 | { | 
| 320 |     rhiTestData(); | 
| 321 | } | 
| 322 |  | 
| 323 | void tst_QRhi::nativeHandles() | 
| 324 | { | 
| 325 |     QFETCH(QRhi::Implementation, impl); | 
| 326 |     QFETCH(QRhiInitParams *, initParams); | 
| 327 |  | 
| 328 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 329 |     if (!rhi) | 
| 330 |         QSKIP("QRhi could not be created, skipping testing native handles" ); | 
| 331 |  | 
| 332 |     // QRhi::nativeHandles() | 
| 333 |     { | 
| 334 |         const QRhiNativeHandles *rhiHandles = rhi->nativeHandles(); | 
| 335 |         Q_ASSERT(rhiHandles); | 
| 336 |  | 
| 337 |         switch (impl) { | 
| 338 |         case QRhi::Null: | 
| 339 |             break; | 
| 340 | #ifdef TST_VK | 
| 341 |         case QRhi::Vulkan: | 
| 342 |         { | 
| 343 |             const QRhiVulkanNativeHandles *vkHandles = static_cast<const QRhiVulkanNativeHandles *>(rhiHandles); | 
| 344 |             QVERIFY(vkHandles->physDev); | 
| 345 |             QVERIFY(vkHandles->dev); | 
| 346 |             QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0); | 
| 347 |             QVERIFY(vkHandles->gfxQueue); | 
| 348 |             QVERIFY(vkHandles->cmdPool); | 
| 349 |             QVERIFY(vkHandles->vmemAllocator); | 
| 350 |         } | 
| 351 |             break; | 
| 352 | #endif | 
| 353 | #ifdef TST_GL | 
| 354 |         case QRhi::OpenGLES2: | 
| 355 |         { | 
| 356 |             const QRhiGles2NativeHandles *glHandles = static_cast<const QRhiGles2NativeHandles *>(rhiHandles); | 
| 357 |             QVERIFY(glHandles->context); | 
| 358 |             QVERIFY(glHandles->context->isValid()); | 
| 359 |             glHandles->context->doneCurrent(); | 
| 360 |             QVERIFY(!QOpenGLContext::currentContext()); | 
| 361 |             rhi->makeThreadLocalNativeContextCurrent(); | 
| 362 |             QVERIFY(QOpenGLContext::currentContext() == glHandles->context); | 
| 363 |         } | 
| 364 |             break; | 
| 365 | #endif | 
| 366 | #ifdef TST_D3D11 | 
| 367 |         case QRhi::D3D11: | 
| 368 |         { | 
| 369 |             const QRhiD3D11NativeHandles *d3dHandles = static_cast<const QRhiD3D11NativeHandles *>(rhiHandles); | 
| 370 |             QVERIFY(d3dHandles->dev); | 
| 371 |             QVERIFY(d3dHandles->context); | 
| 372 |         } | 
| 373 |             break; | 
| 374 | #endif | 
| 375 | #ifdef TST_MTL | 
| 376 |         case QRhi::Metal: | 
| 377 |         { | 
| 378 |             const QRhiMetalNativeHandles *mtlHandles = static_cast<const QRhiMetalNativeHandles *>(rhiHandles); | 
| 379 |             QVERIFY(mtlHandles->dev); | 
| 380 |             QVERIFY(mtlHandles->cmdQueue); | 
| 381 |         } | 
| 382 |             break; | 
| 383 | #endif | 
| 384 |         default: | 
| 385 |             Q_ASSERT(false); | 
| 386 |         } | 
| 387 |     } | 
| 388 |  | 
| 389 |     // QRhiCommandBuffer::nativeHandles() | 
| 390 |     { | 
| 391 |         QRhiCommandBuffer *cb = nullptr; | 
| 392 |         QRhi::FrameOpResult result = rhi->beginOffscreenFrame(cb: &cb); | 
| 393 |         QVERIFY(result == QRhi::FrameOpSuccess); | 
| 394 |         QVERIFY(cb); | 
| 395 |  | 
| 396 |         const QRhiNativeHandles *cbHandles = cb->nativeHandles(); | 
| 397 |         // no null check here, backends where not applicable will return null | 
| 398 |  | 
| 399 |         switch (impl) { | 
| 400 |         case QRhi::Null: | 
| 401 |             break; | 
| 402 | #ifdef TST_VK | 
| 403 |         case QRhi::Vulkan: | 
| 404 |         { | 
| 405 |             const QRhiVulkanCommandBufferNativeHandles *vkHandles = static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbHandles); | 
| 406 |             QVERIFY(vkHandles); | 
| 407 |             QVERIFY(vkHandles->commandBuffer); | 
| 408 |         } | 
| 409 |             break; | 
| 410 | #endif | 
| 411 | #ifdef TST_GL | 
| 412 |         case QRhi::OpenGLES2: | 
| 413 |             break; | 
| 414 | #endif | 
| 415 | #ifdef TST_D3D11 | 
| 416 |         case QRhi::D3D11: | 
| 417 |             break; | 
| 418 | #endif | 
| 419 | #ifdef TST_MTL | 
| 420 |         case QRhi::Metal: | 
| 421 |         { | 
| 422 |             const QRhiMetalCommandBufferNativeHandles *mtlHandles = static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbHandles); | 
| 423 |             QVERIFY(mtlHandles); | 
| 424 |             QVERIFY(mtlHandles->commandBuffer); | 
| 425 |             QVERIFY(!mtlHandles->encoder); | 
| 426 |  | 
| 427 |             QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); | 
| 428 |             QVERIFY(tex->build()); | 
| 429 |             QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() })); | 
| 430 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 431 |             QVERIFY(rpDesc); | 
| 432 |             rt->setRenderPassDescriptor(rpDesc.data()); | 
| 433 |             QVERIFY(rt->build()); | 
| 434 |             cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); | 
| 435 |             QVERIFY(static_cast<const QRhiMetalCommandBufferNativeHandles *>(cb->nativeHandles())->encoder); | 
| 436 |             cb->endPass(); | 
| 437 |         } | 
| 438 |             break; | 
| 439 | #endif | 
| 440 |         default: | 
| 441 |             Q_ASSERT(false); | 
| 442 |         } | 
| 443 |  | 
| 444 |         rhi->endOffscreenFrame(); | 
| 445 |     } | 
| 446 |  | 
| 447 |     // QRhiRenderPassDescriptor::nativeHandles() | 
| 448 |     { | 
| 449 |         QScopedPointer<QRhiTexture> tex(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(512, 512), sampleCount: 1, flags: QRhiTexture::RenderTarget)); | 
| 450 |         QVERIFY(tex->build()); | 
| 451 |         QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { tex.data() })); | 
| 452 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 453 |         QVERIFY(rpDesc); | 
| 454 |         rt->setRenderPassDescriptor(rpDesc.data()); | 
| 455 |         QVERIFY(rt->build()); | 
| 456 |  | 
| 457 |         const QRhiNativeHandles *rpHandles = rpDesc->nativeHandles(); | 
| 458 |         switch (impl) { | 
| 459 |         case QRhi::Null: | 
| 460 |             break; | 
| 461 | #ifdef TST_VK | 
| 462 |         case QRhi::Vulkan: | 
| 463 |         { | 
| 464 |             const QRhiVulkanRenderPassNativeHandles *vkHandles = static_cast<const QRhiVulkanRenderPassNativeHandles *>(rpHandles); | 
| 465 |             QVERIFY(vkHandles); | 
| 466 |             QVERIFY(vkHandles->renderPass); | 
| 467 |         } | 
| 468 |             break; | 
| 469 | #endif | 
| 470 | #ifdef TST_GL | 
| 471 |         case QRhi::OpenGLES2: | 
| 472 |             break; | 
| 473 | #endif | 
| 474 | #ifdef TST_D3D11 | 
| 475 |         case QRhi::D3D11: | 
| 476 |             break; | 
| 477 | #endif | 
| 478 | #ifdef TST_MTL | 
| 479 |         case QRhi::Metal: | 
| 480 |             break; | 
| 481 | #endif | 
| 482 |         default: | 
| 483 |             Q_ASSERT(false); | 
| 484 |         } | 
| 485 |     } | 
| 486 | } | 
| 487 |  | 
| 488 | void tst_QRhi::nativeTexture_data() | 
| 489 | { | 
| 490 |     rhiTestData(); | 
| 491 | } | 
| 492 |  | 
| 493 | void tst_QRhi::nativeTexture() | 
| 494 | { | 
| 495 |     QFETCH(QRhi::Implementation, impl); | 
| 496 |     QFETCH(QRhiInitParams *, initParams); | 
| 497 |  | 
| 498 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 499 |     if (!rhi) | 
| 500 |         QSKIP("QRhi could not be created, skipping testing native texture" ); | 
| 501 |  | 
| 502 |     QScopedPointer<QRhiTexture> tex(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(512, 256))); | 
| 503 |     QVERIFY(tex->build()); | 
| 504 |  | 
| 505 |     const QRhiTexture::NativeTexture nativeTex = tex->nativeTexture(); | 
| 506 |  | 
| 507 |     switch (impl) { | 
| 508 |     case QRhi::Null: | 
| 509 |         break; | 
| 510 | #ifdef TST_VK | 
| 511 |     case QRhi::Vulkan: | 
| 512 |     { | 
| 513 |         auto *image = static_cast<const VkImage *>(nativeTex.object); | 
| 514 |         QVERIFY(image); | 
| 515 |         QVERIFY(*image); | 
| 516 |         QVERIFY(nativeTex.layout >= 1); // VK_IMAGE_LAYOUT_GENERAL | 
| 517 |         QVERIFY(nativeTex.layout <= 8); // VK_IMAGE_LAYOUT_PREINITIALIZED | 
| 518 |     } | 
| 519 |         break; | 
| 520 | #endif | 
| 521 | #ifdef TST_GL | 
| 522 |     case QRhi::OpenGLES2: | 
| 523 |     { | 
| 524 |         auto *textureId = static_cast<const uint *>(nativeTex.object); | 
| 525 |         QVERIFY(textureId); | 
| 526 |         QVERIFY(*textureId); | 
| 527 |     } | 
| 528 |         break; | 
| 529 | #endif | 
| 530 | #ifdef TST_D3D11 | 
| 531 |     case QRhi::D3D11: | 
| 532 |     { | 
| 533 |         auto *texture = static_cast<void * const *>(nativeTex.object); | 
| 534 |         QVERIFY(texture); | 
| 535 |         QVERIFY(*texture); | 
| 536 |     } | 
| 537 |         break; | 
| 538 | #endif | 
| 539 | #ifdef TST_MTL | 
| 540 |     case QRhi::Metal: | 
| 541 |     { | 
| 542 |         void * const * texture = (void * const *)nativeTex.object; | 
| 543 |         QVERIFY(texture); | 
| 544 |         QVERIFY(*texture); | 
| 545 |     } | 
| 546 |         break; | 
| 547 | #endif | 
| 548 |     default: | 
| 549 |         Q_ASSERT(false); | 
| 550 |     } | 
| 551 | } | 
| 552 |  | 
| 553 | void tst_QRhi::nativeBuffer_data() | 
| 554 | { | 
| 555 |     rhiTestData(); | 
| 556 | } | 
| 557 |  | 
| 558 | void tst_QRhi::nativeBuffer() | 
| 559 | { | 
| 560 |     QFETCH(QRhi::Implementation, impl); | 
| 561 |     QFETCH(QRhiInitParams *, initParams); | 
| 562 |  | 
| 563 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 564 |     if (!rhi) | 
| 565 |         QSKIP("QRhi could not be created, skipping testing native buffer query" ); | 
| 566 |  | 
| 567 |     const QRhiBuffer::Type types[3] = { QRhiBuffer::Immutable, QRhiBuffer::Static, QRhiBuffer::Dynamic }; | 
| 568 |     const QRhiBuffer::UsageFlags usages[3] = { QRhiBuffer::VertexBuffer, QRhiBuffer::IndexBuffer, QRhiBuffer::UniformBuffer }; | 
| 569 |     for (int typeUsageIdx = 0; typeUsageIdx < 3; ++typeUsageIdx) { | 
| 570 |         QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(type: types[typeUsageIdx], usage: usages[typeUsageIdx], size: 256)); | 
| 571 |         QVERIFY(buf->build()); | 
| 572 |  | 
| 573 |         const QRhiBuffer::NativeBuffer nativeBuf = buf->nativeBuffer(); | 
| 574 |         QVERIFY(nativeBuf.slotCount <= rhi->resourceLimit(QRhi::FramesInFlight)); | 
| 575 |  | 
| 576 |         switch (impl) { | 
| 577 |         case QRhi::Null: | 
| 578 |             break; | 
| 579 |     #ifdef TST_VK | 
| 580 |         case QRhi::Vulkan: | 
| 581 |         { | 
| 582 |             QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers | 
| 583 |             for (int i = 0; i < nativeBuf.slotCount; ++i) { | 
| 584 |                 auto *buffer = static_cast<const VkBuffer *>(nativeBuf.objects[i]); | 
| 585 |                 QVERIFY(buffer); | 
| 586 |                 QVERIFY(*buffer); | 
| 587 |             } | 
| 588 |         } | 
| 589 |             break; | 
| 590 |     #endif | 
| 591 |     #ifdef TST_GL | 
| 592 |         case QRhi::OpenGLES2: | 
| 593 |         { | 
| 594 |             QVERIFY(nativeBuf.slotCount >= 0); // UniformBuffers are not backed by native buffers, so 0 is perfectly valid | 
| 595 |             for (int i = 0; i < nativeBuf.slotCount; ++i) { | 
| 596 |                 auto *bufferId = static_cast<const uint *>(nativeBuf.objects[i]); | 
| 597 |                 QVERIFY(bufferId); | 
| 598 |                 QVERIFY(*bufferId); | 
| 599 |             } | 
| 600 |         } | 
| 601 |             break; | 
| 602 |     #endif | 
| 603 |     #ifdef TST_D3D11 | 
| 604 |         case QRhi::D3D11: | 
| 605 |         { | 
| 606 |             QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers | 
| 607 |             for (int i = 0; i < nativeBuf.slotCount; ++i) { | 
| 608 |                 auto *buffer = static_cast<void * const *>(nativeBuf.objects[i]); | 
| 609 |                 QVERIFY(buffer); | 
| 610 |                 QVERIFY(*buffer); | 
| 611 |             } | 
| 612 |         } | 
| 613 |             break; | 
| 614 |     #endif | 
| 615 |     #ifdef TST_MTL | 
| 616 |         case QRhi::Metal: | 
| 617 |         { | 
| 618 |             QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers | 
| 619 |             for (int i = 0; i < nativeBuf.slotCount; ++i) { | 
| 620 |                 void * const * buffer = (void * const *) nativeBuf.objects[i]; | 
| 621 |                 QVERIFY(buffer); | 
| 622 |                 QVERIFY(*buffer); | 
| 623 |             } | 
| 624 |         } | 
| 625 |             break; | 
| 626 |     #endif | 
| 627 |         default: | 
| 628 |             Q_ASSERT(false); | 
| 629 |         } | 
| 630 |     } | 
| 631 | } | 
| 632 |  | 
| 633 | static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch) | 
| 634 | { | 
| 635 |     QRhiCommandBuffer *cb = nullptr; | 
| 636 |     QRhi::FrameOpResult result = rhi->beginOffscreenFrame(cb: &cb); | 
| 637 |     if (result != QRhi::FrameOpSuccess) { | 
| 638 |         qWarning(msg: "beginOffscreenFrame returned %d" , result); | 
| 639 |         return false; | 
| 640 |     } | 
| 641 |     if (!cb) { | 
| 642 |         qWarning(msg: "No command buffer from beginOffscreenFrame" ); | 
| 643 |         return false; | 
| 644 |     } | 
| 645 |     cb->resourceUpdate(resourceUpdates: batch); | 
| 646 |     rhi->endOffscreenFrame(); | 
| 647 |     return true; | 
| 648 | } | 
| 649 |  | 
| 650 | void tst_QRhi::resourceUpdateBatchBuffer_data() | 
| 651 | { | 
| 652 |     rhiTestData(); | 
| 653 | } | 
| 654 |  | 
| 655 | void tst_QRhi::resourceUpdateBatchBuffer() | 
| 656 | { | 
| 657 |     QFETCH(QRhi::Implementation, impl); | 
| 658 |     QFETCH(QRhiInitParams *, initParams); | 
| 659 |  | 
| 660 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 661 |     if (!rhi) | 
| 662 |         QSKIP("QRhi could not be created, skipping testing buffer resource updates" ); | 
| 663 |  | 
| 664 |     const int bufferSize = 23; | 
| 665 |     const QByteArray a(bufferSize, 'A'); | 
| 666 |     const QByteArray b(bufferSize, 'B'); | 
| 667 |  | 
| 668 |     // dynamic buffer, updates, readback | 
| 669 |     { | 
| 670 |         QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: bufferSize)); | 
| 671 |         QVERIFY(dynamicBuffer->build()); | 
| 672 |  | 
| 673 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 674 |         QVERIFY(batch); | 
| 675 |  | 
| 676 |         batch->updateDynamicBuffer(buf: dynamicBuffer.data(), offset: 10, size: bufferSize - 10, data: a.constData()); | 
| 677 |         batch->updateDynamicBuffer(buf: dynamicBuffer.data(), offset: 0, size: 12, data: b.constData()); | 
| 678 |  | 
| 679 |         QRhiBufferReadbackResult readResult; | 
| 680 |         bool readCompleted = false; | 
| 681 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 682 |         batch->readBackBuffer(buf: dynamicBuffer.data(), offset: 5, size: 10, result: &readResult); | 
| 683 |  | 
| 684 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 685 |  | 
| 686 |         // Offscreen frames are synchronous, so the readback must have | 
| 687 |         // completed at this point. With swapchain frames this would not be the | 
| 688 |         // case. | 
| 689 |         QVERIFY(readCompleted); | 
| 690 |         QVERIFY(readResult.data.size() == 10); | 
| 691 |         QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB" )); | 
| 692 |         QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA" )); | 
| 693 |     } | 
| 694 |  | 
| 695 |     // static buffer, updates, readback | 
| 696 |     { | 
| 697 |         QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(type: QRhiBuffer::Static, usage: QRhiBuffer::VertexBuffer, size: bufferSize)); | 
| 698 |         QVERIFY(dynamicBuffer->build()); | 
| 699 |  | 
| 700 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 701 |         QVERIFY(batch); | 
| 702 |  | 
| 703 |         batch->uploadStaticBuffer(buf: dynamicBuffer.data(), offset: 10, size: bufferSize - 10, data: a.constData()); | 
| 704 |         batch->uploadStaticBuffer(buf: dynamicBuffer.data(), offset: 0, size: 12, data: b.constData()); | 
| 705 |  | 
| 706 |         QRhiBufferReadbackResult readResult; | 
| 707 |         bool readCompleted = false; | 
| 708 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 709 |  | 
| 710 |         if (rhi->isFeatureSupported(feature: QRhi::ReadBackNonUniformBuffer)) | 
| 711 |             batch->readBackBuffer(buf: dynamicBuffer.data(), offset: 5, size: 10, result: &readResult); | 
| 712 |  | 
| 713 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 714 |  | 
| 715 |         if (rhi->isFeatureSupported(feature: QRhi::ReadBackNonUniformBuffer)) { | 
| 716 |             QVERIFY(readCompleted); | 
| 717 |             QVERIFY(readResult.data.size() == 10); | 
| 718 |             QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB" )); | 
| 719 |             QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA" )); | 
| 720 |         } else { | 
| 721 |             qDebug(msg: "Skipping verifying buffer contents because readback is not supported" ); | 
| 722 |         } | 
| 723 |     } | 
| 724 | } | 
| 725 |  | 
| 726 | inline bool imageRGBAEquals(const QImage &a, const QImage &b) | 
| 727 | { | 
| 728 |     const int maxFuzz = 1; | 
| 729 |  | 
| 730 |     if (a.size() != b.size()) | 
| 731 |         return false; | 
| 732 |  | 
| 733 |     const QImage image0 = a.convertToFormat(f: QImage::Format_RGBA8888_Premultiplied); | 
| 734 |     const QImage image1 = b.convertToFormat(f: QImage::Format_RGBA8888_Premultiplied); | 
| 735 |  | 
| 736 |     const int width = image0.width(); | 
| 737 |     const int height = image0.height(); | 
| 738 |     for (int y = 0; y < height; ++y) { | 
| 739 |         const quint32 *p0 = reinterpret_cast<const quint32 *>(image0.constScanLine(y)); | 
| 740 |         const quint32 *p1 = reinterpret_cast<const quint32 *>(image1.constScanLine(y)); | 
| 741 |         int x = width - 1; | 
| 742 |         while (x-- >= 0) { | 
| 743 |             const QRgb c0(*p0++); | 
| 744 |             const QRgb c1(*p1++); | 
| 745 |             const int red = qAbs(t: qRed(rgb: c0) - qRed(rgb: c1)); | 
| 746 |             const int green = qAbs(t: qGreen(rgb: c0) - qGreen(rgb: c1)); | 
| 747 |             const int blue = qAbs(t: qBlue(rgb: c0) - qBlue(rgb: c1)); | 
| 748 |             const int alpha = qAbs(t: qAlpha(rgb: c0) - qAlpha(rgb: c1)); | 
| 749 |             if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz) | 
| 750 |                 return false; | 
| 751 |         } | 
| 752 |     } | 
| 753 |  | 
| 754 |     return true; | 
| 755 | } | 
| 756 |  | 
| 757 | void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data() | 
| 758 | { | 
| 759 |     rhiTestData(); | 
| 760 | } | 
| 761 |  | 
| 762 | void tst_QRhi::resourceUpdateBatchRGBATextureUpload() | 
| 763 | { | 
| 764 |     QFETCH(QRhi::Implementation, impl); | 
| 765 |     QFETCH(QRhiInitParams *, initParams); | 
| 766 |  | 
| 767 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 768 |     if (!rhi) | 
| 769 |         QSKIP("QRhi could not be created, skipping testing texture resource updates" ); | 
| 770 |  | 
| 771 |     QImage image(234, 123, QImage::Format_RGBA8888_Premultiplied); | 
| 772 |     image.fill(color: Qt::red); | 
| 773 |     QPainter painter; | 
| 774 |     const QPoint greenRectPos(35, 50); | 
| 775 |     const QSize greenRectSize(100, 50); | 
| 776 |     painter.begin(&image); | 
| 777 |     painter.fillRect(r: QRect(greenRectPos, greenRectSize), c: Qt::green); | 
| 778 |     painter.end(); | 
| 779 |  | 
| 780 |     // simple image upload; uploading and reading back RGBA8 is supported by the Null backend even | 
| 781 |     { | 
| 782 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: image.size(), | 
| 783 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 784 |         QVERIFY(texture->build()); | 
| 785 |  | 
| 786 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 787 |         batch->uploadTexture(tex: texture.data(), image); | 
| 788 |  | 
| 789 |         QRhiReadbackResult readResult; | 
| 790 |         bool readCompleted = false; | 
| 791 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 792 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 793 |  | 
| 794 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 795 |         // like with buffers, the readback is now complete due to endOffscreenFrame() | 
| 796 |         QVERIFY(readCompleted); | 
| 797 |         QCOMPARE(readResult.format, QRhiTexture::RGBA8); | 
| 798 |         QCOMPARE(readResult.pixelSize, image.size()); | 
| 799 |  | 
| 800 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 801 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 802 |                             image.format()); | 
| 803 |  | 
| 804 |         QVERIFY(imageRGBAEquals(image, wrapperImage)); | 
| 805 |     } | 
| 806 |  | 
| 807 |     // the same with raw data | 
| 808 |     { | 
| 809 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: image.size(), | 
| 810 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 811 |         QVERIFY(texture->build()); | 
| 812 |  | 
| 813 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 814 |  | 
| 815 |         QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) }); | 
| 816 |         QRhiTextureUploadDescription uploadDesc(upload); | 
| 817 |         batch->uploadTexture(tex: texture.data(), desc: uploadDesc); | 
| 818 |  | 
| 819 |         QRhiReadbackResult readResult; | 
| 820 |         bool readCompleted = false; | 
| 821 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 822 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 823 |  | 
| 824 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 825 |         QVERIFY(readCompleted); | 
| 826 |         QCOMPARE(readResult.format, QRhiTexture::RGBA8); | 
| 827 |         QCOMPARE(readResult.pixelSize, image.size()); | 
| 828 |  | 
| 829 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 830 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 831 |                             image.format()); | 
| 832 |  | 
| 833 |         QVERIFY(imageRGBAEquals(image, wrapperImage)); | 
| 834 |     } | 
| 835 |  | 
| 836 |     // partial image upload at a non-zero destination position | 
| 837 |     { | 
| 838 |         const QSize copySize(30, 40); | 
| 839 |         const int gap = 10; | 
| 840 |         const QSize fullSize(copySize.width() + gap, copySize.height() + gap); | 
| 841 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: fullSize, | 
| 842 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 843 |         QVERIFY(texture->build()); | 
| 844 |  | 
| 845 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 846 |  | 
| 847 |         QImage clearImage(fullSize, image.format()); | 
| 848 |         clearImage.fill(color: Qt::black); | 
| 849 |         batch->uploadTexture(tex: texture.data(), image: clearImage); | 
| 850 |  | 
| 851 |         // copy green pixels of copySize to (gap, gap), leaving a black bar of | 
| 852 |         // gap pixels on the left and top | 
| 853 |         QRhiTextureSubresourceUploadDescription desc; | 
| 854 |         desc.setImage(image); | 
| 855 |         desc.setSourceSize(copySize); | 
| 856 |         desc.setDestinationTopLeft(QPoint(gap, gap)); | 
| 857 |         desc.setSourceTopLeft(greenRectPos); | 
| 858 |  | 
| 859 |         batch->uploadTexture(tex: texture.data(), desc: QRhiTextureUploadDescription({ 0, 0, desc })); | 
| 860 |  | 
| 861 |         QRhiReadbackResult readResult; | 
| 862 |         bool readCompleted = false; | 
| 863 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 864 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 865 |  | 
| 866 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 867 |         QVERIFY(readCompleted); | 
| 868 |         QCOMPARE(readResult.format, QRhiTexture::RGBA8); | 
| 869 |         QCOMPARE(readResult.pixelSize, clearImage.size()); | 
| 870 |  | 
| 871 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 872 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 873 |                             image.format()); | 
| 874 |  | 
| 875 |         QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); | 
| 876 |  | 
| 877 |         QImage expectedImage = clearImage; | 
| 878 |         QPainter painter(&expectedImage); | 
| 879 |         painter.fillRect(r: QRect(QPoint(gap, gap), QSize(copySize)), c: Qt::green); | 
| 880 |         painter.end(); | 
| 881 |  | 
| 882 |         QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); | 
| 883 |     } | 
| 884 |  | 
| 885 |     // the same (partial upload) with raw data as source | 
| 886 |     { | 
| 887 |         const QSize copySize(30, 40); | 
| 888 |         const int gap = 10; | 
| 889 |         const QSize fullSize(copySize.width() + gap, copySize.height() + gap); | 
| 890 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: fullSize, | 
| 891 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 892 |         QVERIFY(texture->build()); | 
| 893 |  | 
| 894 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 895 |  | 
| 896 |         QImage clearImage(fullSize, image.format()); | 
| 897 |         clearImage.fill(color: Qt::black); | 
| 898 |         batch->uploadTexture(tex: texture.data(), image: clearImage); | 
| 899 |  | 
| 900 |         // SourceTopLeft is not supported for non-QImage-based uploads. | 
| 901 |         const QImage im = image.copy(rect: QRect(greenRectPos, copySize)); | 
| 902 |         QRhiTextureSubresourceUploadDescription desc; | 
| 903 |         desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), | 
| 904 |                                              size: int(im.sizeInBytes()))); | 
| 905 |         desc.setSourceSize(copySize); | 
| 906 |         desc.setDestinationTopLeft(QPoint(gap, gap)); | 
| 907 |  | 
| 908 |         batch->uploadTexture(tex: texture.data(), desc: QRhiTextureUploadDescription({ 0, 0, desc })); | 
| 909 |  | 
| 910 |         QRhiReadbackResult readResult; | 
| 911 |         bool readCompleted = false; | 
| 912 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 913 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 914 |  | 
| 915 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 916 |         QVERIFY(readCompleted); | 
| 917 |         QCOMPARE(readResult.format, QRhiTexture::RGBA8); | 
| 918 |         QCOMPARE(readResult.pixelSize, clearImage.size()); | 
| 919 |  | 
| 920 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 921 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 922 |                             image.format()); | 
| 923 |  | 
| 924 |         QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); | 
| 925 |  | 
| 926 |         QImage expectedImage = clearImage; | 
| 927 |         QPainter painter(&expectedImage); | 
| 928 |         painter.fillRect(r: QRect(QPoint(gap, gap), QSize(copySize)), c: Qt::green); | 
| 929 |         painter.end(); | 
| 930 |  | 
| 931 |         QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); | 
| 932 |     } | 
| 933 |  | 
| 934 |     // now a QImage from an actual file | 
| 935 |     { | 
| 936 |         QImage inputImage; | 
| 937 |         inputImage.load(fileName: QLatin1String(":/data/qt256.png" )); | 
| 938 |         QVERIFY(!inputImage.isNull()); | 
| 939 |         inputImage = std::move(inputImage).convertToFormat(f: image.format()); | 
| 940 |  | 
| 941 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size(), | 
| 942 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 943 |         QVERIFY(texture->build()); | 
| 944 |  | 
| 945 |         QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 946 |         batch->uploadTexture(tex: texture.data(), image: inputImage); | 
| 947 |  | 
| 948 |         QRhiReadbackResult readResult; | 
| 949 |         bool readCompleted = false; | 
| 950 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 951 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 952 |  | 
| 953 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 954 |         QVERIFY(readCompleted); | 
| 955 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 956 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 957 |                             inputImage.format()); | 
| 958 |  | 
| 959 |         QVERIFY(imageRGBAEquals(inputImage, wrapperImage)); | 
| 960 |     } | 
| 961 | } | 
| 962 |  | 
| 963 | void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data() | 
| 964 | { | 
| 965 |     rhiTestData(); | 
| 966 | } | 
| 967 |  | 
| 968 | void tst_QRhi::resourceUpdateBatchRGBATextureCopy() | 
| 969 | { | 
| 970 |     QFETCH(QRhi::Implementation, impl); | 
| 971 |     QFETCH(QRhiInitParams *, initParams); | 
| 972 |  | 
| 973 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 974 |     if (!rhi) | 
| 975 |         QSKIP("QRhi could not be created, skipping testing texture resource updates" ); | 
| 976 |  | 
| 977 |     QImage red(256, 256, QImage::Format_RGBA8888_Premultiplied); | 
| 978 |     red.fill(color: Qt::red); | 
| 979 |  | 
| 980 |     QImage green(35, 73, red.format()); | 
| 981 |     green.fill(color: Qt::green); | 
| 982 |  | 
| 983 |     QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 984 |  | 
| 985 |     QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: red.size(), | 
| 986 |                                                            sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 987 |     QVERIFY(redTexture->build()); | 
| 988 |     batch->uploadTexture(tex: redTexture.data(), image: red); | 
| 989 |  | 
| 990 |     QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: green.size(), | 
| 991 |                                                              sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 992 |     QVERIFY(greenTexture->build()); | 
| 993 |     batch->uploadTexture(tex: greenTexture.data(), image: green); | 
| 994 |  | 
| 995 |     // 1. simple copy red -> texture; 2. subimage copy green -> texture; 3. partial subimage copy green -> texture | 
| 996 |     { | 
| 997 |         QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: red.size(), | 
| 998 |                                                             sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource)); | 
| 999 |         QVERIFY(texture->build()); | 
| 1000 |  | 
| 1001 |         // 1. | 
| 1002 |         batch->copyTexture(dst: texture.data(), src: redTexture.data()); | 
| 1003 |  | 
| 1004 |         QRhiReadbackResult readResult; | 
| 1005 |         bool readCompleted = false; | 
| 1006 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 1007 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 1008 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 1009 |         QVERIFY(readCompleted); | 
| 1010 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1011 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1012 |                             red.format()); | 
| 1013 |         QVERIFY(imageRGBAEquals(red, wrapperImage)); | 
| 1014 |  | 
| 1015 |         batch = rhi->nextResourceUpdateBatch(); | 
| 1016 |         readCompleted = false; | 
| 1017 |  | 
| 1018 |         // 2. | 
| 1019 |         QRhiTextureCopyDescription copyDesc; | 
| 1020 |         copyDesc.setDestinationTopLeft(QPoint(15, 23)); | 
| 1021 |         batch->copyTexture(dst: texture.data(), src: greenTexture.data(), desc: copyDesc); | 
| 1022 |  | 
| 1023 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 1024 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 1025 |         QVERIFY(readCompleted); | 
| 1026 |         wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1027 |                               readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1028 |                               red.format()); | 
| 1029 |  | 
| 1030 |         QImage expectedImage = red; | 
| 1031 |         QPainter painter(&expectedImage); | 
| 1032 |         painter.drawImage(p: copyDesc.destinationTopLeft(), image: green); | 
| 1033 |         painter.end(); | 
| 1034 |  | 
| 1035 |         QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); | 
| 1036 |  | 
| 1037 |         batch = rhi->nextResourceUpdateBatch(); | 
| 1038 |         readCompleted = false; | 
| 1039 |  | 
| 1040 |         // 3. | 
| 1041 |         copyDesc.setDestinationTopLeft(QPoint(125, 89)); | 
| 1042 |         copyDesc.setSourceTopLeft(QPoint(5, 5)); | 
| 1043 |         copyDesc.setPixelSize(QSize(26, 45)); | 
| 1044 |         batch->copyTexture(dst: texture.data(), src: greenTexture.data(), desc: copyDesc); | 
| 1045 |  | 
| 1046 |         batch->readBackTexture(rb: texture.data(), result: &readResult); | 
| 1047 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 1048 |         QVERIFY(readCompleted); | 
| 1049 |         wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1050 |                               readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1051 |                               red.format()); | 
| 1052 |  | 
| 1053 |         painter.begin(&expectedImage); | 
| 1054 |         painter.drawImage(p: copyDesc.destinationTopLeft(), image: green, | 
| 1055 |                           sr: QRect(copyDesc.sourceTopLeft(), copyDesc.pixelSize())); | 
| 1056 |         painter.end(); | 
| 1057 |  | 
| 1058 |         QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); | 
| 1059 |     } | 
| 1060 | } | 
| 1061 |  | 
| 1062 | void tst_QRhi::resourceUpdateBatchRGBATextureMip_data() | 
| 1063 | { | 
| 1064 |     rhiTestData(); | 
| 1065 | } | 
| 1066 |  | 
| 1067 | void tst_QRhi::resourceUpdateBatchRGBATextureMip() | 
| 1068 | { | 
| 1069 |     QFETCH(QRhi::Implementation, impl); | 
| 1070 |     QFETCH(QRhiInitParams *, initParams); | 
| 1071 |  | 
| 1072 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1073 |     if (!rhi) | 
| 1074 |         QSKIP("QRhi could not be created, skipping testing texture resource updates" ); | 
| 1075 |  | 
| 1076 |  | 
| 1077 |     QImage red(512, 512, QImage::Format_RGBA8888_Premultiplied); | 
| 1078 |     red.fill(color: Qt::red); | 
| 1079 |  | 
| 1080 |     const QRhiTexture::Flags textureFlags = | 
| 1081 |             QRhiTexture::UsedAsTransferSource | 
| 1082 |             | QRhiTexture::MipMapped | 
| 1083 |             | QRhiTexture::UsedWithGenerateMips; | 
| 1084 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: red.size(), sampleCount: 1, flags: textureFlags)); | 
| 1085 |     QVERIFY(texture->build()); | 
| 1086 |  | 
| 1087 |     QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); | 
| 1088 |     batch->uploadTexture(tex: texture.data(), image: red); | 
| 1089 |     batch->generateMips(tex: texture.data()); | 
| 1090 |     QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 1091 |  | 
| 1092 |     const int levelCount = rhi->mipLevelsForSize(size: red.size()); | 
| 1093 |     QCOMPARE(levelCount, 10); | 
| 1094 |     for (int level = 0; level < levelCount; ++level) { | 
| 1095 |         batch = rhi->nextResourceUpdateBatch(); | 
| 1096 |  | 
| 1097 |         QRhiReadbackDescription readDesc(texture.data()); | 
| 1098 |         readDesc.setLevel(level); | 
| 1099 |         QRhiReadbackResult readResult; | 
| 1100 |         bool readCompleted = false; | 
| 1101 |         readResult.completed = [&readCompleted] { readCompleted = true; }; | 
| 1102 |         batch->readBackTexture(rb: readDesc, result: &readResult); | 
| 1103 |  | 
| 1104 |         QVERIFY(submitResourceUpdates(rhi.data(), batch)); | 
| 1105 |         QVERIFY(readCompleted); | 
| 1106 |  | 
| 1107 |         const QSize expectedSize = rhi->sizeForMipLevel(mipLevel: level, baseLevelSize: texture->pixelSize()); | 
| 1108 |         QCOMPARE(readResult.pixelSize, expectedSize); | 
| 1109 |  | 
| 1110 |         QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1111 |                             readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1112 |                             red.format()); | 
| 1113 |         QImage expectedImage; | 
| 1114 |         if (level == 0 || rhi->isFeatureSupported(feature: QRhi::ReadBackNonBaseMipLevel)) { | 
| 1115 |             // Compare to a scaled version; we can do this safely only because we | 
| 1116 |             // only have plain red pixels in the source image. | 
| 1117 |             expectedImage = red.scaled(s: expectedSize); | 
| 1118 |         } else { | 
| 1119 |             qDebug(msg: "Expecting all-zero image for level %d because reading back a level other than 0 is not supported" , level); | 
| 1120 |             expectedImage = QImage(readResult.pixelSize, red.format()); | 
| 1121 |             expectedImage.fill(pixel: 0); | 
| 1122 |         } | 
| 1123 |         QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); | 
| 1124 |     } | 
| 1125 | } | 
| 1126 |  | 
| 1127 | static QShader loadShader(const char *name) | 
| 1128 | { | 
| 1129 |     QFile f(QString::fromUtf8(str: name)); | 
| 1130 |     if (f.open(flags: QIODevice::ReadOnly)) { | 
| 1131 |         const QByteArray contents = f.readAll(); | 
| 1132 |         return QShader::fromSerialized(data: contents); | 
| 1133 |     } | 
| 1134 |     return QShader(); | 
| 1135 | } | 
| 1136 |  | 
| 1137 | void tst_QRhi::invalidPipeline_data() | 
| 1138 | { | 
| 1139 |     rhiTestData(); | 
| 1140 | } | 
| 1141 |  | 
| 1142 | void tst_QRhi::invalidPipeline() | 
| 1143 | { | 
| 1144 |     QFETCH(QRhi::Implementation, impl); | 
| 1145 |     QFETCH(QRhiInitParams *, initParams); | 
| 1146 |  | 
| 1147 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1148 |     if (!rhi) | 
| 1149 |         QSKIP("QRhi could not be created, skipping testing empty shader" ); | 
| 1150 |  | 
| 1151 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(256, 256), sampleCount: 1, flags: QRhiTexture::RenderTarget)); | 
| 1152 |     QVERIFY(texture->build()); | 
| 1153 |     QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { texture.data() })); | 
| 1154 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 1155 |     rt->setRenderPassDescriptor(rpDesc.data()); | 
| 1156 |     QVERIFY(rt->build()); | 
| 1157 |  | 
| 1158 |     QRhiCommandBuffer *cb = nullptr; | 
| 1159 |     QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); | 
| 1160 |     QVERIFY(cb); | 
| 1161 |  | 
| 1162 |     QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); | 
| 1163 |     QVERIFY(srb->build()); | 
| 1164 |  | 
| 1165 |     QRhiVertexInputLayout inputLayout; | 
| 1166 |     inputLayout.setBindings({ { 2 * sizeof(float) } }); | 
| 1167 |     inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); | 
| 1168 |  | 
| 1169 |     // no stages | 
| 1170 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1171 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1172 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1173 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1174 |     QVERIFY(!pipeline->build()); | 
| 1175 |  | 
| 1176 |     QShader vs; | 
| 1177 |     QShader fs; | 
| 1178 |  | 
| 1179 |     // no shaders in the stages | 
| 1180 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1181 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1182 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1183 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1184 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1185 |     QVERIFY(!pipeline->build()); | 
| 1186 |  | 
| 1187 |     vs = loadShader(name: ":/data/simple.vert.qsb" ); | 
| 1188 |     QVERIFY(vs.isValid()); | 
| 1189 |     fs = loadShader(name: ":/data/simple.frag.qsb" ); | 
| 1190 |     QVERIFY(fs.isValid()); | 
| 1191 |  | 
| 1192 |     // no vertex stage | 
| 1193 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1194 |     pipeline->setShaderStages({ { QRhiShaderStage::Fragment, fs } }); | 
| 1195 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1196 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1197 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1198 |     QVERIFY(!pipeline->build()); | 
| 1199 |  | 
| 1200 |     // no vertex inputs | 
| 1201 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1202 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1203 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1204 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1205 |     QVERIFY(!pipeline->build()); | 
| 1206 |  | 
| 1207 |     // no renderpass descriptor | 
| 1208 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1209 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1210 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1211 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1212 |     QVERIFY(!pipeline->build()); | 
| 1213 |  | 
| 1214 |     // no shader resource bindings | 
| 1215 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1216 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1217 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1218 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1219 |     QVERIFY(!pipeline->build()); | 
| 1220 |  | 
| 1221 |     // correct | 
| 1222 |     pipeline.reset(other: rhi->newGraphicsPipeline()); | 
| 1223 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1224 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1225 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1226 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1227 |     QVERIFY(pipeline->build()); | 
| 1228 | } | 
| 1229 |  | 
| 1230 | void tst_QRhi::renderToTextureSimple_data() | 
| 1231 | { | 
| 1232 |     rhiTestData(); | 
| 1233 | } | 
| 1234 |  | 
| 1235 | void tst_QRhi::renderToTextureSimple() | 
| 1236 | { | 
| 1237 |     QFETCH(QRhi::Implementation, impl); | 
| 1238 |     QFETCH(QRhiInitParams *, initParams); | 
| 1239 |  | 
| 1240 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1241 |     if (!rhi) | 
| 1242 |         QSKIP("QRhi could not be created, skipping testing rendering" ); | 
| 1243 |  | 
| 1244 |     const QSize outputSize(1920, 1080); | 
| 1245 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: outputSize, sampleCount: 1, | 
| 1246 |                                                         flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); | 
| 1247 |     QVERIFY(texture->build()); | 
| 1248 |  | 
| 1249 |     QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { texture.data() })); | 
| 1250 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 1251 |     rt->setRenderPassDescriptor(rpDesc.data()); | 
| 1252 |     QVERIFY(rt->build()); | 
| 1253 |  | 
| 1254 |     QRhiCommandBuffer *cb = nullptr; | 
| 1255 |     QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); | 
| 1256 |     QVERIFY(cb); | 
| 1257 |  | 
| 1258 |     QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); | 
| 1259 |  | 
| 1260 |     static const float vertices[] = { | 
| 1261 |         -1.0f, -1.0f, | 
| 1262 |         1.0f, -1.0f, | 
| 1263 |         0.0f, 1.0f | 
| 1264 |     }; | 
| 1265 |     QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(vertices))); | 
| 1266 |     QVERIFY(vbuf->build()); | 
| 1267 |     updates->uploadStaticBuffer(buf: vbuf.data(), data: vertices); | 
| 1268 |  | 
| 1269 |     QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); | 
| 1270 |     QVERIFY(srb->build()); | 
| 1271 |  | 
| 1272 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1273 |     QShader vs = loadShader(name: ":/data/simple.vert.qsb" ); | 
| 1274 |     QVERIFY(vs.isValid()); | 
| 1275 |     QShader fs = loadShader(name: ":/data/simple.frag.qsb" ); | 
| 1276 |     QVERIFY(fs.isValid()); | 
| 1277 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1278 |     QRhiVertexInputLayout inputLayout; | 
| 1279 |     inputLayout.setBindings({ { 2 * sizeof(float) } }); | 
| 1280 |     inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); | 
| 1281 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1282 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1283 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1284 |  | 
| 1285 |     QVERIFY(pipeline->build()); | 
| 1286 |  | 
| 1287 |     cb->beginPass(rt: rt.data(), colorClearValue: Qt::blue, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: updates); | 
| 1288 |     cb->setGraphicsPipeline(pipeline.data()); | 
| 1289 |     cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); | 
| 1290 |     QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); | 
| 1291 |     cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1292 |     cb->draw(vertexCount: 3); | 
| 1293 |  | 
| 1294 |     QRhiReadbackResult readResult; | 
| 1295 |     QImage result; | 
| 1296 |     readResult.completed = [&readResult, &result] { | 
| 1297 |         result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1298 |                         readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1299 |                         QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result | 
| 1300 |     }; | 
| 1301 |     QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1302 |     readbackBatch->readBackTexture(rb: { texture.data() }, result: &readResult); | 
| 1303 |     cb->endPass(resourceUpdates: readbackBatch); | 
| 1304 |  | 
| 1305 |     rhi->endOffscreenFrame(); | 
| 1306 |     // Offscreen frames are synchronous, so the readback is guaranteed to | 
| 1307 |     // complete at this point. This would not be the case with swapchain-based | 
| 1308 |     // frames. | 
| 1309 |     QCOMPARE(result.size(), texture->pixelSize()); | 
| 1310 |  | 
| 1311 |     if (impl == QRhi::Null) | 
| 1312 |         return; | 
| 1313 |  | 
| 1314 |     // Now we have a red rectangle on blue background. | 
| 1315 |     const int y = 100; | 
| 1316 |     const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); | 
| 1317 |     int x = result.width() - 1; | 
| 1318 |     int redCount = 0; | 
| 1319 |     int blueCount = 0; | 
| 1320 |     const int maxFuzz = 1; | 
| 1321 |     while (x-- >= 0) { | 
| 1322 |         const QRgb c(*p++); | 
| 1323 |         if (qRed(rgb: c) >= (255 - maxFuzz) && qGreen(rgb: c) == 0 && qBlue(rgb: c) == 0) | 
| 1324 |             ++redCount; | 
| 1325 |         else if (qRed(rgb: c) == 0 && qGreen(rgb: c) == 0 && qBlue(rgb: c) >= (255 - maxFuzz)) | 
| 1326 |             ++blueCount; | 
| 1327 |         else | 
| 1328 |             QFAIL("Encountered a pixel that is neither red or blue" ); | 
| 1329 |     } | 
| 1330 |  | 
| 1331 |     QCOMPARE(redCount + blueCount, texture->pixelSize().width()); | 
| 1332 |  | 
| 1333 |     // The triangle is "pointing up" in the resulting image with OpenGL | 
| 1334 |     // (because Y is up both in normalized device coordinates and in images) | 
| 1335 |     // and Vulkan (because Y is down in both and the vertex data was specified | 
| 1336 |     // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC | 
| 1337 |     // but down in images). | 
| 1338 |     if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) | 
| 1339 |         QVERIFY(redCount < blueCount); | 
| 1340 |     else | 
| 1341 |         QVERIFY(redCount > blueCount); | 
| 1342 | } | 
| 1343 |  | 
| 1344 | void tst_QRhi::renderToTextureTexturedQuad_data() | 
| 1345 | { | 
| 1346 |     rhiTestData(); | 
| 1347 | } | 
| 1348 |  | 
| 1349 | void tst_QRhi::renderToTextureTexturedQuad() | 
| 1350 | { | 
| 1351 |     QFETCH(QRhi::Implementation, impl); | 
| 1352 |     QFETCH(QRhiInitParams *, initParams); | 
| 1353 |  | 
| 1354 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1355 |     if (!rhi) | 
| 1356 |         QSKIP("QRhi could not be created, skipping testing rendering" ); | 
| 1357 |  | 
| 1358 |     QImage inputImage; | 
| 1359 |     inputImage.load(fileName: QLatin1String(":/data/qt256.png" )); | 
| 1360 |     QVERIFY(!inputImage.isNull()); | 
| 1361 |  | 
| 1362 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size(), sampleCount: 1, | 
| 1363 |                                                         flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); | 
| 1364 |     QVERIFY(texture->build()); | 
| 1365 |  | 
| 1366 |     QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { texture.data() })); | 
| 1367 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 1368 |     rt->setRenderPassDescriptor(rpDesc.data()); | 
| 1369 |     QVERIFY(rt->build()); | 
| 1370 |  | 
| 1371 |     QRhiCommandBuffer *cb = nullptr; | 
| 1372 |     QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); | 
| 1373 |     QVERIFY(cb); | 
| 1374 |  | 
| 1375 |     QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); | 
| 1376 |  | 
| 1377 |     static const float verticesUvs[] = { | 
| 1378 |         -1.0f, -1.0f,   0.0f, 0.0f, | 
| 1379 |         1.0f, -1.0f,    1.0f, 0.0f, | 
| 1380 |         -1.0f, 1.0f,    0.0f, 1.0f, | 
| 1381 |         1.0f, 1.0f,     1.0f, 1.0f | 
| 1382 |     }; | 
| 1383 |     QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(verticesUvs))); | 
| 1384 |     QVERIFY(vbuf->build()); | 
| 1385 |     updates->uploadStaticBuffer(buf: vbuf.data(), data: verticesUvs); | 
| 1386 |  | 
| 1387 |     QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size())); | 
| 1388 |     QVERIFY(inputTexture->build()); | 
| 1389 |     updates->uploadTexture(tex: inputTexture.data(), image: inputImage); | 
| 1390 |  | 
| 1391 |     QScopedPointer<QRhiSampler> sampler(rhi->newSampler(magFilter: QRhiSampler::Nearest, minFilter: QRhiSampler::Nearest, mipmapMode: QRhiSampler::None, | 
| 1392 |                                                         addressU: QRhiSampler::ClampToEdge, addressV: QRhiSampler::ClampToEdge)); | 
| 1393 |     QVERIFY(sampler->build()); | 
| 1394 |  | 
| 1395 |     QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); | 
| 1396 |     srb->setBindings({ | 
| 1397 |                          QRhiShaderResourceBinding::sampledTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: inputTexture.data(), sampler: sampler.data()) | 
| 1398 |                      }); | 
| 1399 |     QVERIFY(srb->build()); | 
| 1400 |  | 
| 1401 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1402 |     pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); | 
| 1403 |     QShader vs = loadShader(name: ":/data/simpletextured.vert.qsb" ); | 
| 1404 |     QVERIFY(vs.isValid()); | 
| 1405 |     QShader fs = loadShader(name: ":/data/simpletextured.frag.qsb" ); | 
| 1406 |     QVERIFY(fs.isValid()); | 
| 1407 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1408 |     QRhiVertexInputLayout inputLayout; | 
| 1409 |     inputLayout.setBindings({ { 4 * sizeof(float) } }); | 
| 1410 |     inputLayout.setAttributes({ | 
| 1411 |                                   { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, | 
| 1412 |                                   { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } | 
| 1413 |                               }); | 
| 1414 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1415 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1416 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1417 |  | 
| 1418 |     QVERIFY(pipeline->build()); | 
| 1419 |  | 
| 1420 |     cb->beginPass(rt: rt.data(), colorClearValue: Qt::black, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: updates); | 
| 1421 |     cb->setGraphicsPipeline(pipeline.data()); | 
| 1422 |     cb->setShaderResources(); | 
| 1423 |     cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); | 
| 1424 |     QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); | 
| 1425 |     cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1426 |     cb->draw(vertexCount: 4); | 
| 1427 |  | 
| 1428 |     QRhiReadbackResult readResult; | 
| 1429 |     QImage result; | 
| 1430 |     readResult.completed = [&readResult, &result] { | 
| 1431 |         result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1432 |                         readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1433 |                         QImage::Format_RGBA8888_Premultiplied); | 
| 1434 |     }; | 
| 1435 |     QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1436 |     readbackBatch->readBackTexture(rb: { texture.data() }, result: &readResult); | 
| 1437 |     cb->endPass(resourceUpdates: readbackBatch); | 
| 1438 |  | 
| 1439 |     rhi->endOffscreenFrame(); | 
| 1440 |  | 
| 1441 |     QVERIFY(!result.isNull()); | 
| 1442 |  | 
| 1443 |     if (impl == QRhi::Null) | 
| 1444 |         return; | 
| 1445 |  | 
| 1446 |     // Flip with D3D and Metal because these have Y down in images. Vulkan does | 
| 1447 |     // not need this because there Y is down both in images and in NDC, which | 
| 1448 |     // just happens to give correct results with our OpenGL-targeted vertex and | 
| 1449 |     // UV data. | 
| 1450 |     if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) | 
| 1451 |         result = std::move(result).mirrored(); | 
| 1452 |  | 
| 1453 |     // check a few points that are expected to match regardless of the implementation | 
| 1454 |     QRgb white = qRgba(r: 255, g: 255, b: 255, a: 255); | 
| 1455 |     QCOMPARE(result.pixel(79, 77), white); | 
| 1456 |     QCOMPARE(result.pixel(124, 81), white); | 
| 1457 |     QCOMPARE(result.pixel(128, 149), white); | 
| 1458 |     QCOMPARE(result.pixel(120, 189), white); | 
| 1459 |     QCOMPARE(result.pixel(116, 185), white); | 
| 1460 |  | 
| 1461 |     QRgb empty = qRgba(r: 0, g: 0, b: 0, a: 0); | 
| 1462 |     QCOMPARE(result.pixel(11, 45), empty); | 
| 1463 |     QCOMPARE(result.pixel(246, 202), empty); | 
| 1464 |     QCOMPARE(result.pixel(130, 18), empty); | 
| 1465 |     QCOMPARE(result.pixel(4, 227), empty); | 
| 1466 |  | 
| 1467 |     QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52))); | 
| 1468 |     QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52))); | 
| 1469 |     QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191))); | 
| 1470 |     QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); | 
| 1471 | } | 
| 1472 |  | 
| 1473 | void tst_QRhi::renderToTextureArrayOfTexturedQuad_data() | 
| 1474 | { | 
| 1475 |     rhiTestData(); | 
| 1476 | } | 
| 1477 |  | 
| 1478 | void tst_QRhi::renderToTextureArrayOfTexturedQuad() | 
| 1479 | { | 
| 1480 |     QFETCH(QRhi::Implementation, impl); | 
| 1481 |     QFETCH(QRhiInitParams *, initParams); | 
| 1482 |  | 
| 1483 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1484 |     if (!rhi) | 
| 1485 |         QSKIP("QRhi could not be created, skipping testing rendering" ); | 
| 1486 |  | 
| 1487 |     QImage inputImage; | 
| 1488 |     inputImage.load(fileName: QLatin1String(":/data/qt256.png" )); | 
| 1489 |     QVERIFY(!inputImage.isNull()); | 
| 1490 |  | 
| 1491 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size(), sampleCount: 1, | 
| 1492 |                                                         flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); | 
| 1493 |     QVERIFY(texture->build()); | 
| 1494 |  | 
| 1495 |     QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { texture.data() })); | 
| 1496 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 1497 |     rt->setRenderPassDescriptor(rpDesc.data()); | 
| 1498 |     QVERIFY(rt->build()); | 
| 1499 |  | 
| 1500 |     QRhiCommandBuffer *cb = nullptr; | 
| 1501 |     QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); | 
| 1502 |     QVERIFY(cb); | 
| 1503 |  | 
| 1504 |     QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); | 
| 1505 |  | 
| 1506 |     static const float verticesUvs[] = { | 
| 1507 |         -1.0f, -1.0f,   0.0f, 0.0f, | 
| 1508 |         1.0f, -1.0f,    1.0f, 0.0f, | 
| 1509 |         -1.0f, 1.0f,    0.0f, 1.0f, | 
| 1510 |         1.0f, 1.0f,     1.0f, 1.0f | 
| 1511 |     }; | 
| 1512 |     QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(verticesUvs))); | 
| 1513 |     QVERIFY(vbuf->build()); | 
| 1514 |     updates->uploadStaticBuffer(buf: vbuf.data(), data: verticesUvs); | 
| 1515 |  | 
| 1516 |     // In this test we pass 3 textures (and samplers) to the fragment shader in | 
| 1517 |     // form of an array of combined image samplers. | 
| 1518 |  | 
| 1519 |     QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size())); | 
| 1520 |     QVERIFY(inputTexture->build()); | 
| 1521 |     updates->uploadTexture(tex: inputTexture.data(), image: inputImage); | 
| 1522 |  | 
| 1523 |     QImage redImage(inputImage.size(), QImage::Format_RGBA8888); | 
| 1524 |     redImage.fill(color: Qt::red); | 
| 1525 |  | 
| 1526 |     QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size())); | 
| 1527 |     QVERIFY(redTexture->build()); | 
| 1528 |     updates->uploadTexture(tex: redTexture.data(), image: redImage); | 
| 1529 |  | 
| 1530 |     QImage greenImage(inputImage.size(), QImage::Format_RGBA8888); | 
| 1531 |     greenImage.fill(color: Qt::green); | 
| 1532 |  | 
| 1533 |     QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size())); | 
| 1534 |     QVERIFY(greenTexture->build()); | 
| 1535 |     updates->uploadTexture(tex: greenTexture.data(), image: greenImage); | 
| 1536 |  | 
| 1537 |     QScopedPointer<QRhiSampler> sampler(rhi->newSampler(magFilter: QRhiSampler::Nearest, minFilter: QRhiSampler::Nearest, mipmapMode: QRhiSampler::None, | 
| 1538 |                                                         addressU: QRhiSampler::ClampToEdge, addressV: QRhiSampler::ClampToEdge)); | 
| 1539 |     QVERIFY(sampler->build()); | 
| 1540 |  | 
| 1541 |     QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); | 
| 1542 |     QRhiShaderResourceBinding::TextureAndSampler texSamplers[3] = { | 
| 1543 |         { .tex: inputTexture.data(), .sampler: sampler.data() }, | 
| 1544 |         { .tex: redTexture.data(), .sampler: sampler.data() }, | 
| 1545 |         { .tex: greenTexture.data(), .sampler: sampler.data() } | 
| 1546 |     }; | 
| 1547 |     srb->setBindings({ | 
| 1548 |                          QRhiShaderResourceBinding::sampledTextures(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, count: 3, texSamplers) | 
| 1549 |                      }); | 
| 1550 |     QVERIFY(srb->build()); | 
| 1551 |  | 
| 1552 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1553 |     pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); | 
| 1554 |     QShader vs = loadShader(name: ":/data/simpletextured.vert.qsb" ); | 
| 1555 |     QVERIFY(vs.isValid()); | 
| 1556 |     QShader fs = loadShader(name: ":/data/simpletextured_array.frag.qsb" ); | 
| 1557 |     QVERIFY(fs.isValid()); | 
| 1558 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1559 |     QRhiVertexInputLayout inputLayout; | 
| 1560 |     inputLayout.setBindings({ { 4 * sizeof(float) } }); | 
| 1561 |     inputLayout.setAttributes({ | 
| 1562 |                                   { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, | 
| 1563 |                                   { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } | 
| 1564 |                               }); | 
| 1565 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1566 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1567 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1568 |  | 
| 1569 |     QVERIFY(pipeline->build()); | 
| 1570 |  | 
| 1571 |     cb->beginPass(rt: rt.data(), colorClearValue: Qt::black, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: updates); | 
| 1572 |     cb->setGraphicsPipeline(pipeline.data()); | 
| 1573 |     cb->setShaderResources(); | 
| 1574 |     cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); | 
| 1575 |     QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); | 
| 1576 |     cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1577 |     cb->draw(vertexCount: 4); | 
| 1578 |  | 
| 1579 |     QRhiReadbackResult readResult; | 
| 1580 |     QImage result; | 
| 1581 |     readResult.completed = [&readResult, &result] { | 
| 1582 |         result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1583 |                         readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1584 |                         QImage::Format_RGBA8888_Premultiplied); | 
| 1585 |     }; | 
| 1586 |     QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1587 |     readbackBatch->readBackTexture(rb: { texture.data() }, result: &readResult); | 
| 1588 |     cb->endPass(resourceUpdates: readbackBatch); | 
| 1589 |  | 
| 1590 |     rhi->endOffscreenFrame(); | 
| 1591 |  | 
| 1592 |     QVERIFY(!result.isNull()); | 
| 1593 |  | 
| 1594 |     if (impl == QRhi::Null) | 
| 1595 |         return; | 
| 1596 |  | 
| 1597 |     // Flip with D3D and Metal because these have Y down in images. Vulkan does | 
| 1598 |     // not need this because there Y is down both in images and in NDC, which | 
| 1599 |     // just happens to give correct results with our OpenGL-targeted vertex and | 
| 1600 |     // UV data. | 
| 1601 |     if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) | 
| 1602 |         result = std::move(result).mirrored(); | 
| 1603 |  | 
| 1604 |     // we added the input image + red + green together, so red and green must be all 1 | 
| 1605 |     for (int y = 0; y < result.height(); ++y) { | 
| 1606 |         for (int x = 0; x < result.width(); ++x) { | 
| 1607 |             const QRgb pixel = result.pixel(x, y); | 
| 1608 |             QCOMPARE(qRed(pixel), 255); | 
| 1609 |             QCOMPARE(qGreen(pixel), 255); | 
| 1610 |         } | 
| 1611 |     } | 
| 1612 | } | 
| 1613 |  | 
| 1614 | void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data() | 
| 1615 | { | 
| 1616 |     rhiTestData(); | 
| 1617 | } | 
| 1618 |  | 
| 1619 | void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() | 
| 1620 | { | 
| 1621 |     QFETCH(QRhi::Implementation, impl); | 
| 1622 |     QFETCH(QRhiInitParams *, initParams); | 
| 1623 |  | 
| 1624 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1625 |     if (!rhi) | 
| 1626 |         QSKIP("QRhi could not be created, skipping testing rendering" ); | 
| 1627 |  | 
| 1628 |     QImage inputImage; | 
| 1629 |     inputImage.load(fileName: QLatin1String(":/data/qt256.png" )); | 
| 1630 |     QVERIFY(!inputImage.isNull()); | 
| 1631 |  | 
| 1632 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size(), sampleCount: 1, | 
| 1633 |                                                         flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); | 
| 1634 |     QVERIFY(texture->build()); | 
| 1635 |  | 
| 1636 |     QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { texture.data() })); | 
| 1637 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 1638 |     rt->setRenderPassDescriptor(rpDesc.data()); | 
| 1639 |     QVERIFY(rt->build()); | 
| 1640 |  | 
| 1641 |     QRhiCommandBuffer *cb = nullptr; | 
| 1642 |     QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); | 
| 1643 |     QVERIFY(cb); | 
| 1644 |  | 
| 1645 |     QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); | 
| 1646 |  | 
| 1647 |     static const float verticesUvs[] = { | 
| 1648 |         -1.0f, -1.0f,   0.0f, 0.0f, | 
| 1649 |         1.0f, -1.0f,    1.0f, 0.0f, | 
| 1650 |         -1.0f, 1.0f,    0.0f, 1.0f, | 
| 1651 |         1.0f, 1.0f,     1.0f, 1.0f | 
| 1652 |     }; | 
| 1653 |     QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(verticesUvs))); | 
| 1654 |     QVERIFY(vbuf->build()); | 
| 1655 |     updates->uploadStaticBuffer(buf: vbuf.data(), data: verticesUvs); | 
| 1656 |  | 
| 1657 |     // There will be two renderpasses. One renders with no transformation and | 
| 1658 |     // an opacity of 0.5, the second has a rotation. Bake the uniform data for | 
| 1659 |     // both into a single buffer. | 
| 1660 |  | 
| 1661 |     const int UNIFORM_BLOCK_SIZE = 64 + 4; // matrix + opacity | 
| 1662 |     const int secondUbufOffset = rhi->ubufAligned(v: UNIFORM_BLOCK_SIZE); | 
| 1663 |     const int UBUF_SIZE = secondUbufOffset + UNIFORM_BLOCK_SIZE; | 
| 1664 |  | 
| 1665 |     QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: UBUF_SIZE)); | 
| 1666 |     QVERIFY(ubuf->build()); | 
| 1667 |  | 
| 1668 |     QMatrix4x4 matrix; | 
| 1669 |     updates->updateDynamicBuffer(buf: ubuf.data(), offset: 0, size: 64, data: matrix.constData()); | 
| 1670 |     float opacity = 0.5f; | 
| 1671 |     updates->updateDynamicBuffer(buf: ubuf.data(), offset: 64, size: 4, data: &opacity); | 
| 1672 |  | 
| 1673 |     // rotation by 45 degrees around the Z axis | 
| 1674 |     matrix.rotate(angle: 45, x: 0, y: 0, z: 1); | 
| 1675 |     updates->updateDynamicBuffer(buf: ubuf.data(), offset: secondUbufOffset, size: 64, data: matrix.constData()); | 
| 1676 |     updates->updateDynamicBuffer(buf: ubuf.data(), offset: secondUbufOffset + 64, size: 4, data: &opacity); | 
| 1677 |  | 
| 1678 |     QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: inputImage.size())); | 
| 1679 |     QVERIFY(inputTexture->build()); | 
| 1680 |     updates->uploadTexture(tex: inputTexture.data(), image: inputImage); | 
| 1681 |  | 
| 1682 |     QScopedPointer<QRhiSampler> sampler(rhi->newSampler(magFilter: QRhiSampler::Nearest, minFilter: QRhiSampler::Nearest, mipmapMode: QRhiSampler::None, | 
| 1683 |                                                         addressU: QRhiSampler::ClampToEdge, addressV: QRhiSampler::ClampToEdge)); | 
| 1684 |     QVERIFY(sampler->build()); | 
| 1685 |  | 
| 1686 |     const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; | 
| 1687 |     QScopedPointer<QRhiShaderResourceBindings> srb0(rhi->newShaderResourceBindings()); | 
| 1688 |     srb0->setBindings({ | 
| 1689 |                          QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: commonVisibility, buf: ubuf.data(), offset: 0, size: UNIFORM_BLOCK_SIZE), | 
| 1690 |                          QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: inputTexture.data(), sampler: sampler.data()) | 
| 1691 |                      }); | 
| 1692 |     QVERIFY(srb0->build()); | 
| 1693 |  | 
| 1694 |     QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 1695 |     srb1->setBindings({ | 
| 1696 |                          QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: commonVisibility, buf: ubuf.data(), offset: secondUbufOffset, size: UNIFORM_BLOCK_SIZE), | 
| 1697 |                          QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: inputTexture.data(), sampler: sampler.data()) | 
| 1698 |                      }); | 
| 1699 |     QVERIFY(srb1->build()); | 
| 1700 |     QVERIFY(srb1->isLayoutCompatible(srb0.data())); // hence no need for a second pipeline | 
| 1701 |  | 
| 1702 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1703 |     pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); | 
| 1704 |     QShader vs = loadShader(name: ":/data/textured.vert.qsb" ); | 
| 1705 |     QVERIFY(vs.isValid()); | 
| 1706 |     QShaderDescription shaderDesc = vs.description(); | 
| 1707 |     QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); | 
| 1708 |     QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); | 
| 1709 |  | 
| 1710 |     QShader fs = loadShader(name: ":/data/textured.frag.qsb" ); | 
| 1711 |     QVERIFY(fs.isValid()); | 
| 1712 |     shaderDesc = fs.description(); | 
| 1713 |     QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); | 
| 1714 |     QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); | 
| 1715 |  | 
| 1716 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1717 |     QRhiVertexInputLayout inputLayout; | 
| 1718 |     inputLayout.setBindings({ { 4 * sizeof(float) } }); | 
| 1719 |     inputLayout.setAttributes({ | 
| 1720 |                                   { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, | 
| 1721 |                                   { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } | 
| 1722 |                               }); | 
| 1723 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1724 |     pipeline->setShaderResourceBindings(srb0.data()); | 
| 1725 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1726 |  | 
| 1727 |     QVERIFY(pipeline->build()); | 
| 1728 |  | 
| 1729 |     cb->beginPass(rt: rt.data(), colorClearValue: Qt::black, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: updates); | 
| 1730 |     cb->setGraphicsPipeline(pipeline.data()); | 
| 1731 |     cb->setShaderResources(); | 
| 1732 |     cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); | 
| 1733 |     QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); | 
| 1734 |     cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1735 |     cb->draw(vertexCount: 4); | 
| 1736 |  | 
| 1737 |     QRhiReadbackResult readResult0; | 
| 1738 |     QImage result0; | 
| 1739 |     readResult0.completed = [&readResult0, &result0] { | 
| 1740 |         result0 = QImage(reinterpret_cast<const uchar *>(readResult0.data.constData()), | 
| 1741 |                         readResult0.pixelSize.width(), readResult0.pixelSize.height(), | 
| 1742 |                         QImage::Format_RGBA8888_Premultiplied); | 
| 1743 |     }; | 
| 1744 |     QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1745 |     readbackBatch->readBackTexture(rb: { texture.data() }, result: &readResult0); | 
| 1746 |     cb->endPass(resourceUpdates: readbackBatch); | 
| 1747 |  | 
| 1748 |     // second pass (rotated) | 
| 1749 |     cb->beginPass(rt: rt.data(), colorClearValue: Qt::black, depthStencilClearValue: { 1.0f, 0 }); | 
| 1750 |     cb->setGraphicsPipeline(pipeline.data()); | 
| 1751 |     cb->setShaderResources(srb: srb1.data()); // sources data from a different offset in ubuf | 
| 1752 |     cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); | 
| 1753 |     cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1754 |     cb->draw(vertexCount: 4); | 
| 1755 |  | 
| 1756 |     QRhiReadbackResult readResult1; | 
| 1757 |     QImage result1; | 
| 1758 |     readResult1.completed = [&readResult1, &result1] { | 
| 1759 |         result1 = QImage(reinterpret_cast<const uchar *>(readResult1.data.constData()), | 
| 1760 |                         readResult1.pixelSize.width(), readResult1.pixelSize.height(), | 
| 1761 |                         QImage::Format_RGBA8888_Premultiplied); | 
| 1762 |     }; | 
| 1763 |     readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1764 |     readbackBatch->readBackTexture(rb: { texture.data() }, result: &readResult1); | 
| 1765 |     cb->endPass(resourceUpdates: readbackBatch); | 
| 1766 |  | 
| 1767 |     rhi->endOffscreenFrame(); | 
| 1768 |  | 
| 1769 |     QVERIFY(!result0.isNull()); | 
| 1770 |     QVERIFY(!result1.isNull()); | 
| 1771 |  | 
| 1772 |     if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) { | 
| 1773 |         result0 = std::move(result0).mirrored(); | 
| 1774 |         result1 = std::move(result1).mirrored(); | 
| 1775 |     } | 
| 1776 |  | 
| 1777 |     if (impl == QRhi::Null) | 
| 1778 |         return; | 
| 1779 |  | 
| 1780 |     // opacity 0.5 (premultiplied) | 
| 1781 |     static const auto checkSemiWhite = [](const QRgb &c) { | 
| 1782 |         QRgb semiWhite127 = qPremultiply(x: qRgba(r: 255, g: 255, b: 255, a: 127)); | 
| 1783 |         QRgb semiWhite128 = qPremultiply(x: qRgba(r: 255, g: 255, b: 255, a: 128)); | 
| 1784 |         return c == semiWhite127 || c == semiWhite128; | 
| 1785 |     }; | 
| 1786 |     QVERIFY(checkSemiWhite(result0.pixel(79, 77))); | 
| 1787 |     QVERIFY(checkSemiWhite(result0.pixel(124, 81))); | 
| 1788 |     QVERIFY(checkSemiWhite(result0.pixel(128, 149))); | 
| 1789 |     QVERIFY(checkSemiWhite(result0.pixel(120, 189))); | 
| 1790 |     QVERIFY(checkSemiWhite(result0.pixel(116, 185))); | 
| 1791 |     QVERIFY(checkSemiWhite(result0.pixel(191, 172))); | 
| 1792 |  | 
| 1793 |     QRgb empty = qRgba(r: 0, g: 0, b: 0, a: 0); | 
| 1794 |     QCOMPARE(result0.pixel(11, 45), empty); | 
| 1795 |     QCOMPARE(result0.pixel(246, 202), empty); | 
| 1796 |     QCOMPARE(result0.pixel(130, 18), empty); | 
| 1797 |     QCOMPARE(result0.pixel(4, 227), empty); | 
| 1798 |  | 
| 1799 |     // also rotated 45 degrees around Z | 
| 1800 |     QRgb black = qRgba(r: 0, g: 0, b: 0, a: 255); | 
| 1801 |     QCOMPARE(result1.pixel(20, 23), black); | 
| 1802 |     QCOMPARE(result1.pixel(47, 5), black); | 
| 1803 |     QCOMPARE(result1.pixel(238, 22), black); | 
| 1804 |     QCOMPARE(result1.pixel(250, 203), black); | 
| 1805 |     QCOMPARE(result1.pixel(224, 237), black); | 
| 1806 |     QCOMPARE(result1.pixel(12, 221), black); | 
| 1807 |  | 
| 1808 |     QVERIFY(checkSemiWhite(result1.pixel(142, 67))); | 
| 1809 |     QVERIFY(checkSemiWhite(result1.pixel(81, 79))); | 
| 1810 |     QVERIFY(checkSemiWhite(result1.pixel(79, 168))); | 
| 1811 |     QVERIFY(checkSemiWhite(result1.pixel(146, 204))); | 
| 1812 |     QVERIFY(checkSemiWhite(result1.pixel(186, 156))); | 
| 1813 |  | 
| 1814 |     QCOMPARE(result1.pixel(204, 45), empty); | 
| 1815 |     QCOMPARE(result1.pixel(28, 178), empty); | 
| 1816 | } | 
| 1817 |  | 
| 1818 | void tst_QRhi::renderToWindowSimple_data() | 
| 1819 | { | 
| 1820 |     rhiTestData(); | 
| 1821 | } | 
| 1822 |  | 
| 1823 | void tst_QRhi::renderToWindowSimple() | 
| 1824 | { | 
| 1825 |     QFETCH(QRhi::Implementation, impl); | 
| 1826 |     QFETCH(QRhiInitParams *, initParams); | 
| 1827 |  | 
| 1828 | #ifdef Q_OS_WINRT | 
| 1829 |     if (impl == QRhi::D3D11) | 
| 1830 |         QSKIP("Skipping window-based QRhi rendering on WinRT as the platform and the D3D11 backend are not prepared for this yet" ); | 
| 1831 | #endif | 
| 1832 |  | 
| 1833 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1834 |     if (!rhi) | 
| 1835 |         QSKIP("QRhi could not be created, skipping testing rendering" ); | 
| 1836 |  | 
| 1837 |     QScopedPointer<QWindow> window(new QWindow); | 
| 1838 |     switch (impl) { | 
| 1839 |     case QRhi::OpenGLES2: | 
| 1840 | #if QT_CONFIG(opengl) | 
| 1841 |         window->setFormat(QRhiGles2InitParams::adjustedFormat()); | 
| 1842 | #endif | 
| 1843 |         Q_FALLTHROUGH(); | 
| 1844 |     case QRhi::D3D11: | 
| 1845 |         window->setSurfaceType(QSurface::OpenGLSurface); | 
| 1846 |         break; | 
| 1847 |     case QRhi::Metal: | 
| 1848 |         window->setSurfaceType(QSurface::MetalSurface); | 
| 1849 |         break; | 
| 1850 |     case QRhi::Vulkan: | 
| 1851 |         window->setSurfaceType(QSurface::VulkanSurface); | 
| 1852 | #if QT_CONFIG(vulkan) | 
| 1853 |         window->setVulkanInstance(&vulkanInstance); | 
| 1854 | #endif | 
| 1855 |         break; | 
| 1856 |     default: | 
| 1857 |         break; | 
| 1858 |     } | 
| 1859 |  | 
| 1860 |     window->setGeometry(posx: 0, posy: 0, w: 640, h: 480); | 
| 1861 |     window->show(); | 
| 1862 |     QVERIFY(QTest::qWaitForWindowExposed(window.data())); | 
| 1863 |  | 
| 1864 |     QScopedPointer<QRhiSwapChain> swapChain(rhi->newSwapChain()); | 
| 1865 |     swapChain->setWindow(window.data()); | 
| 1866 |     swapChain->setFlags(QRhiSwapChain::UsedAsTransferSource); | 
| 1867 |     QScopedPointer<QRhiRenderPassDescriptor> rpDesc(swapChain->newCompatibleRenderPassDescriptor()); | 
| 1868 |     swapChain->setRenderPassDescriptor(rpDesc.data()); | 
| 1869 |     QVERIFY(swapChain->buildOrResize()); | 
| 1870 |  | 
| 1871 |     QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); | 
| 1872 |  | 
| 1873 |     static const float vertices[] = { | 
| 1874 |         -1.0f, -1.0f, | 
| 1875 |         1.0f, -1.0f, | 
| 1876 |         0.0f, 1.0f | 
| 1877 |     }; | 
| 1878 |     QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(vertices))); | 
| 1879 |     QVERIFY(vbuf->build()); | 
| 1880 |     updates->uploadStaticBuffer(buf: vbuf.data(), data: vertices); | 
| 1881 |  | 
| 1882 |     QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); | 
| 1883 |     QVERIFY(srb->build()); | 
| 1884 |  | 
| 1885 |     QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); | 
| 1886 |     QShader vs = loadShader(name: ":/data/simple.vert.qsb" ); | 
| 1887 |     QVERIFY(vs.isValid()); | 
| 1888 |     QShader fs = loadShader(name: ":/data/simple.frag.qsb" ); | 
| 1889 |     QVERIFY(fs.isValid()); | 
| 1890 |     pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); | 
| 1891 |     QRhiVertexInputLayout inputLayout; | 
| 1892 |     inputLayout.setBindings({ { 2 * sizeof(float) } }); | 
| 1893 |     inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); | 
| 1894 |     pipeline->setVertexInputLayout(inputLayout); | 
| 1895 |     pipeline->setShaderResourceBindings(srb.data()); | 
| 1896 |     pipeline->setRenderPassDescriptor(rpDesc.data()); | 
| 1897 |  | 
| 1898 |     QVERIFY(pipeline->build()); | 
| 1899 |  | 
| 1900 |     const int asyncReadbackFrames = rhi->resourceLimit(limit: QRhi::MaxAsyncReadbackFrames); | 
| 1901 |     // one frame issues the readback, then we do MaxAsyncReadbackFrames more to ensure the readback completes | 
| 1902 |     const int FRAME_COUNT = asyncReadbackFrames + 1; | 
| 1903 |     bool readCompleted = false; | 
| 1904 |     QRhiReadbackResult readResult; | 
| 1905 |     QImage result; | 
| 1906 |     int readbackWidth = 0; | 
| 1907 |  | 
| 1908 |     for (int frameNo = 0; frameNo < FRAME_COUNT; ++frameNo) { | 
| 1909 |         QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess); | 
| 1910 |         QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); | 
| 1911 |         QRhiRenderTarget *rt = swapChain->currentFrameRenderTarget(); | 
| 1912 |         const QSize outputSize = swapChain->currentPixelSize(); | 
| 1913 |         QCOMPARE(rt->pixelSize(), outputSize); | 
| 1914 |         QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height())); | 
| 1915 |  | 
| 1916 |         cb->beginPass(rt, colorClearValue: Qt::blue, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: updates); | 
| 1917 |         updates = nullptr; | 
| 1918 |         cb->setGraphicsPipeline(pipeline.data()); | 
| 1919 |         cb->setViewport(viewport); | 
| 1920 |         QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); | 
| 1921 |         cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbindings); | 
| 1922 |         cb->draw(vertexCount: 3); | 
| 1923 |  | 
| 1924 |         if (frameNo == 0) { | 
| 1925 |             readResult.completed = [&readCompleted, &readResult, &result, &rhi] { | 
| 1926 |                 readCompleted = true; | 
| 1927 |                 QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), | 
| 1928 |                                     readResult.pixelSize.width(), readResult.pixelSize.height(), | 
| 1929 |                                     QImage::Format_ARGB32_Premultiplied); | 
| 1930 |                 if (readResult.format == QRhiTexture::RGBA8) | 
| 1931 |                     wrapperImage = wrapperImage.rgbSwapped(); | 
| 1932 |                 if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) | 
| 1933 |                     result = wrapperImage.mirrored(); | 
| 1934 |                 else | 
| 1935 |                     result = wrapperImage.copy(); | 
| 1936 |             }; | 
| 1937 |             QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); | 
| 1938 |             readbackBatch->readBackTexture(rb: {}, result: &readResult); // read back the current backbuffer | 
| 1939 |             readbackWidth = outputSize.width(); | 
| 1940 |             cb->endPass(resourceUpdates: readbackBatch); | 
| 1941 |         } else { | 
| 1942 |             cb->endPass(); | 
| 1943 |         } | 
| 1944 |  | 
| 1945 |         rhi->endFrame(swapChain: swapChain.data()); | 
| 1946 |     } | 
| 1947 |  | 
| 1948 |     // The readback is asynchronous here. However it is guaranteed that it | 
| 1949 |     // finished at latest after rendering QRhi::MaxAsyncReadbackFrames frames | 
| 1950 |     // after the one that enqueues the readback. | 
| 1951 |     QVERIFY(readCompleted); | 
| 1952 |     QVERIFY(readbackWidth > 0); | 
| 1953 |  | 
| 1954 |     if (impl == QRhi::Null) | 
| 1955 |         return; | 
| 1956 |  | 
| 1957 |     // Now we have a red rectangle on blue background. | 
| 1958 |     const int y = 50; | 
| 1959 |     const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); | 
| 1960 |     int x = result.width() - 1; | 
| 1961 |     int redCount = 0; | 
| 1962 |     int blueCount = 0; | 
| 1963 |     const int maxFuzz = 1; | 
| 1964 |     while (x-- >= 0) { | 
| 1965 |         const QRgb c(*p++); | 
| 1966 |         if (qRed(rgb: c) >= (255 - maxFuzz) && qGreen(rgb: c) == 0 && qBlue(rgb: c) == 0) | 
| 1967 |             ++redCount; | 
| 1968 |         else if (qRed(rgb: c) == 0 && qGreen(rgb: c) == 0 && qBlue(rgb: c) >= (255 - maxFuzz)) | 
| 1969 |             ++blueCount; | 
| 1970 |         else | 
| 1971 |             QFAIL("Encountered a pixel that is neither red or blue" ); | 
| 1972 |     } | 
| 1973 |  | 
| 1974 |     QCOMPARE(redCount + blueCount, readbackWidth); | 
| 1975 |     QVERIFY(redCount < blueCount); | 
| 1976 | } | 
| 1977 |  | 
| 1978 | void tst_QRhi::srbLayoutCompatibility_data() | 
| 1979 | { | 
| 1980 |     rhiTestData(); | 
| 1981 | } | 
| 1982 |  | 
| 1983 | void tst_QRhi::srbLayoutCompatibility() | 
| 1984 | { | 
| 1985 |     QFETCH(QRhi::Implementation, impl); | 
| 1986 |     QFETCH(QRhiInitParams *, initParams); | 
| 1987 |  | 
| 1988 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 1989 |     if (!rhi) | 
| 1990 |         QSKIP("QRhi could not be created, skipping testing texture resource updates" ); | 
| 1991 |  | 
| 1992 |     QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(512, 512))); | 
| 1993 |     QVERIFY(texture->build()); | 
| 1994 |     QScopedPointer<QRhiSampler> sampler(rhi->newSampler(magFilter: QRhiSampler::Nearest, minFilter: QRhiSampler::Nearest, mipmapMode: QRhiSampler::None, | 
| 1995 |                                                         addressU: QRhiSampler::ClampToEdge, addressV: QRhiSampler::ClampToEdge)); | 
| 1996 |     QVERIFY(sampler->build()); | 
| 1997 |     QScopedPointer<QRhiSampler> otherSampler(rhi->newSampler(magFilter: QRhiSampler::Nearest, minFilter: QRhiSampler::Nearest, mipmapMode: QRhiSampler::None, | 
| 1998 |                                                              addressU: QRhiSampler::ClampToEdge, addressV: QRhiSampler::ClampToEdge)); | 
| 1999 |     QVERIFY(otherSampler->build()); | 
| 2000 |     QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: 1024)); | 
| 2001 |     QVERIFY(buf->build()); | 
| 2002 |     QScopedPointer<QRhiBuffer> otherBuf(rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: 256)); | 
| 2003 |     QVERIFY(otherBuf->build()); | 
| 2004 |  | 
| 2005 |     // empty (compatible) | 
| 2006 |     { | 
| 2007 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2008 |         QVERIFY(srb1->build()); | 
| 2009 |  | 
| 2010 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2011 |         QVERIFY(srb2->build()); | 
| 2012 |  | 
| 2013 |         QVERIFY(srb1->isLayoutCompatible(srb2.data())); | 
| 2014 |         QVERIFY(srb2->isLayoutCompatible(srb1.data())); | 
| 2015 |     } | 
| 2016 |  | 
| 2017 |     // different count (not compatible) | 
| 2018 |     { | 
| 2019 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2020 |         QVERIFY(srb1->build()); | 
| 2021 |  | 
| 2022 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2023 |         srb2->setBindings({ | 
| 2024 |                               QRhiShaderResourceBinding::sampledTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2025 |                          }); | 
| 2026 |         QVERIFY(srb2->build()); | 
| 2027 |  | 
| 2028 |         QVERIFY(!srb1->isLayoutCompatible(srb2.data())); | 
| 2029 |         QVERIFY(!srb2->isLayoutCompatible(srb1.data())); | 
| 2030 |     } | 
| 2031 |  | 
| 2032 |     // full match (compatible) | 
| 2033 |     { | 
| 2034 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2035 |         srb1->setBindings({ | 
| 2036 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2037 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2038 |                          }); | 
| 2039 |         QVERIFY(srb1->build()); | 
| 2040 |  | 
| 2041 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2042 |         srb2->setBindings({ | 
| 2043 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2044 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2045 |                          }); | 
| 2046 |         QVERIFY(srb2->build()); | 
| 2047 |  | 
| 2048 |         QVERIFY(srb1->isLayoutCompatible(srb2.data())); | 
| 2049 |         QVERIFY(srb2->isLayoutCompatible(srb1.data())); | 
| 2050 |     } | 
| 2051 |  | 
| 2052 |     // different visibility (not compatible) | 
| 2053 |     { | 
| 2054 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2055 |         srb1->setBindings({ | 
| 2056 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, buf: buf.data()), | 
| 2057 |                          }); | 
| 2058 |         QVERIFY(srb1->build()); | 
| 2059 |  | 
| 2060 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2061 |         srb2->setBindings({ | 
| 2062 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2063 |                          }); | 
| 2064 |         QVERIFY(srb2->build()); | 
| 2065 |  | 
| 2066 |         QVERIFY(!srb1->isLayoutCompatible(srb2.data())); | 
| 2067 |         QVERIFY(!srb2->isLayoutCompatible(srb1.data())); | 
| 2068 |     } | 
| 2069 |  | 
| 2070 |     // different binding points (not compatible) | 
| 2071 |     { | 
| 2072 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2073 |         srb1->setBindings({ | 
| 2074 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2075 |                          }); | 
| 2076 |         QVERIFY(srb1->build()); | 
| 2077 |  | 
| 2078 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2079 |         srb2->setBindings({ | 
| 2080 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 1, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2081 |                          }); | 
| 2082 |         QVERIFY(srb2->build()); | 
| 2083 |  | 
| 2084 |         QVERIFY(!srb1->isLayoutCompatible(srb2.data())); | 
| 2085 |         QVERIFY(!srb2->isLayoutCompatible(srb1.data())); | 
| 2086 |     } | 
| 2087 |  | 
| 2088 |     // different buffer region offset and size (compatible) | 
| 2089 |     { | 
| 2090 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2091 |         srb1->setBindings({ | 
| 2092 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data(), offset: rhi->ubufAligned(v: 1), size: 128), | 
| 2093 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2094 |                          }); | 
| 2095 |         QVERIFY(srb1->build()); | 
| 2096 |  | 
| 2097 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2098 |         srb2->setBindings({ | 
| 2099 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2100 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2101 |                          }); | 
| 2102 |         QVERIFY(srb2->build()); | 
| 2103 |  | 
| 2104 |         QVERIFY(srb1->isLayoutCompatible(srb2.data())); | 
| 2105 |         QVERIFY(srb2->isLayoutCompatible(srb1.data())); | 
| 2106 |     } | 
| 2107 |  | 
| 2108 |     // different resources (compatible) | 
| 2109 |     { | 
| 2110 |         QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); | 
| 2111 |         srb1->setBindings({ | 
| 2112 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: otherBuf.data()), | 
| 2113 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: otherSampler.data()) | 
| 2114 |                          }); | 
| 2115 |         QVERIFY(srb1->build()); | 
| 2116 |  | 
| 2117 |         QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); | 
| 2118 |         srb2->setBindings({ | 
| 2119 |                               QRhiShaderResourceBinding::uniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: buf.data()), | 
| 2120 |                               QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: texture.data(), sampler: sampler.data()) | 
| 2121 |                          }); | 
| 2122 |         QVERIFY(srb2->build()); | 
| 2123 |  | 
| 2124 |         QVERIFY(srb1->isLayoutCompatible(srb2.data())); | 
| 2125 |         QVERIFY(srb2->isLayoutCompatible(srb1.data())); | 
| 2126 |     } | 
| 2127 | } | 
| 2128 |  | 
| 2129 | void tst_QRhi::renderPassDescriptorCompatibility_data() | 
| 2130 | { | 
| 2131 |     rhiTestData(); | 
| 2132 | } | 
| 2133 |  | 
| 2134 | void tst_QRhi::renderPassDescriptorCompatibility() | 
| 2135 | { | 
| 2136 |     QFETCH(QRhi::Implementation, impl); | 
| 2137 |     QFETCH(QRhiInitParams *, initParams); | 
| 2138 |  | 
| 2139 |     QScopedPointer<QRhi> rhi(QRhi::create(impl, params: initParams, flags: QRhi::Flags(), importDevice: nullptr)); | 
| 2140 |     if (!rhi) | 
| 2141 |         QSKIP("QRhi could not be created, skipping testing texture resource updates" ); | 
| 2142 |  | 
| 2143 |     // Note that checking compatibility is only relevant with backends where | 
| 2144 |     // there is a concept of renderpass descriptions (Vulkan, and partially | 
| 2145 |     // Metal). It is perfectly fine for isCompatible() to always return true | 
| 2146 |     // when that is not the case (D3D11, OpenGL). Hence the 'if (Vulkan or | 
| 2147 |     // Metal)' for all the negative tests. Also note "partial" for Metal: | 
| 2148 |     // resolve textures for examples have no effect on compatibility with Metal. | 
| 2149 |  | 
| 2150 |     // tex and tex2 have the same format | 
| 2151 |     QScopedPointer<QRhiTexture> tex(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(512, 512), sampleCount: 1, flags: QRhiTexture::RenderTarget)); | 
| 2152 |     QVERIFY(tex->build()); | 
| 2153 |     QScopedPointer<QRhiTexture> tex2(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(512, 512), sampleCount: 1, flags: QRhiTexture::RenderTarget)); | 
| 2154 |     QVERIFY(tex2->build()); | 
| 2155 |  | 
| 2156 |     QScopedPointer<QRhiRenderBuffer> ds(rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: QSize(512, 512))); | 
| 2157 |     QVERIFY(ds->build()); | 
| 2158 |  | 
| 2159 |     // two texture rendertargets with tex and tex2 as color0 (compatible) | 
| 2160 |     { | 
| 2161 |         QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { tex.data() })); | 
| 2162 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2163 |         rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2164 |         QVERIFY(rt->build()); | 
| 2165 |  | 
| 2166 |         QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc: { tex2.data() })); | 
| 2167 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2168 |         rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2169 |         QVERIFY(rt2->build()); | 
| 2170 |  | 
| 2171 |         QVERIFY(rpDesc->isCompatible(rpDesc2.data())); | 
| 2172 |         QVERIFY(rpDesc2->isCompatible(rpDesc.data())); | 
| 2173 |     } | 
| 2174 |  | 
| 2175 |     // two texture rendertargets with tex and tex2 as color0, and a depth-stencil attachment as well (compatible) | 
| 2176 |     { | 
| 2177 |         QRhiTextureRenderTargetDescription desc({ tex.data() }, ds.data()); | 
| 2178 |         QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc)); | 
| 2179 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2180 |         rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2181 |         QVERIFY(rt->build()); | 
| 2182 |  | 
| 2183 |         QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc)); | 
| 2184 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2185 |         rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2186 |         QVERIFY(rt2->build()); | 
| 2187 |  | 
| 2188 |         QVERIFY(rpDesc->isCompatible(rpDesc2.data())); | 
| 2189 |         QVERIFY(rpDesc2->isCompatible(rpDesc.data())); | 
| 2190 |     } | 
| 2191 |  | 
| 2192 |     // now one of them does not have the ds attachment (not compatible) | 
| 2193 |     { | 
| 2194 |         QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { { tex.data() }, ds.data() })); | 
| 2195 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2196 |         rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2197 |         QVERIFY(rt->build()); | 
| 2198 |  | 
| 2199 |         QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc: { tex.data() })); | 
| 2200 |         QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2201 |         rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2202 |         QVERIFY(rt2->build()); | 
| 2203 |  | 
| 2204 |         if (impl == QRhi::Vulkan || impl == QRhi::Metal) { | 
| 2205 |             QVERIFY(!rpDesc->isCompatible(rpDesc2.data())); | 
| 2206 |             QVERIFY(!rpDesc2->isCompatible(rpDesc.data())); | 
| 2207 |         } | 
| 2208 |     } | 
| 2209 |  | 
| 2210 |     if (rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer)) { | 
| 2211 |         // resolve attachments (compatible) | 
| 2212 |         { | 
| 2213 |             QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer(rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: QSize(512, 512), sampleCount: 4)); | 
| 2214 |             QVERIFY(msaaRenderBuffer->build()); | 
| 2215 |             QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer2(rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: QSize(512, 512), sampleCount: 4)); | 
| 2216 |             QVERIFY(msaaRenderBuffer2->build()); | 
| 2217 |  | 
| 2218 |             QRhiColorAttachment colorAtt(msaaRenderBuffer.data()); // color0, multisample | 
| 2219 |             colorAtt.setResolveTexture(tex.data()); // resolved into a non-msaa texture | 
| 2220 |             QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { colorAtt })); | 
| 2221 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2222 |             rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2223 |             QVERIFY(rt->build()); | 
| 2224 |  | 
| 2225 |             QRhiColorAttachment colorAtt2(msaaRenderBuffer2.data()); // color0, multisample | 
| 2226 |             colorAtt2.setResolveTexture(tex2.data()); // resolved into a non-msaa texture | 
| 2227 |             QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc: { colorAtt2 })); | 
| 2228 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2229 |             rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2230 |             QVERIFY(rt2->build()); | 
| 2231 |  | 
| 2232 |             QVERIFY(rpDesc->isCompatible(rpDesc2.data())); | 
| 2233 |             QVERIFY(rpDesc2->isCompatible(rpDesc.data())); | 
| 2234 |         } | 
| 2235 |  | 
| 2236 |         // missing resolve for one of them (not compatible) | 
| 2237 |         { | 
| 2238 |             QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer(rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: QSize(512, 512), sampleCount: 4)); | 
| 2239 |             QVERIFY(msaaRenderBuffer->build()); | 
| 2240 |             QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer2(rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: QSize(512, 512), sampleCount: 4)); | 
| 2241 |             QVERIFY(msaaRenderBuffer2->build()); | 
| 2242 |  | 
| 2243 |             QRhiColorAttachment colorAtt(msaaRenderBuffer.data()); // color0, multisample | 
| 2244 |             colorAtt.setResolveTexture(tex.data()); // resolved into a non-msaa texture | 
| 2245 |             QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { colorAtt })); | 
| 2246 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2247 |             rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2248 |             QVERIFY(rt->build()); | 
| 2249 |  | 
| 2250 |             QRhiColorAttachment colorAtt2(msaaRenderBuffer2.data()); // color0, multisample | 
| 2251 |             QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc: { colorAtt2 })); | 
| 2252 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2253 |             rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2254 |             QVERIFY(rt2->build()); | 
| 2255 |  | 
| 2256 |             if (impl == QRhi::Vulkan) { // no Metal here | 
| 2257 |                 QVERIFY(!rpDesc->isCompatible(rpDesc2.data())); | 
| 2258 |                 QVERIFY(!rpDesc2->isCompatible(rpDesc.data())); | 
| 2259 |             } | 
| 2260 |         } | 
| 2261 |     } else { | 
| 2262 |         qDebug(msg: "Skipping multisample renderbuffer dependent tests" ); | 
| 2263 |     } | 
| 2264 |  | 
| 2265 |     if (rhi->isTextureFormatSupported(format: QRhiTexture::RGBA32F)) { | 
| 2266 |         QScopedPointer<QRhiTexture> tex3(rhi->newTexture(format: QRhiTexture::RGBA32F, pixelSize: QSize(512, 512), sampleCount: 1, flags: QRhiTexture::RenderTarget)); | 
| 2267 |         QVERIFY(tex3->build()); | 
| 2268 |  | 
| 2269 |         // different texture formats (not compatible) | 
| 2270 |         { | 
| 2271 |             QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: { tex.data() })); | 
| 2272 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); | 
| 2273 |             rt->setRenderPassDescriptor(rpDesc.data()); | 
| 2274 |             QVERIFY(rt->build()); | 
| 2275 |  | 
| 2276 |             QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc: { tex3.data() })); | 
| 2277 |             QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor()); | 
| 2278 |             rt2->setRenderPassDescriptor(rpDesc2.data()); | 
| 2279 |             QVERIFY(rt2->build()); | 
| 2280 |  | 
| 2281 |             if (impl == QRhi::Vulkan || impl == QRhi::Metal) { | 
| 2282 |                 QVERIFY(!rpDesc->isCompatible(rpDesc2.data())); | 
| 2283 |                 QVERIFY(!rpDesc2->isCompatible(rpDesc.data())); | 
| 2284 |             } | 
| 2285 |         } | 
| 2286 |     } else { | 
| 2287 |         qDebug(msg: "Skipping texture format dependent tests" ); | 
| 2288 |     } | 
| 2289 | } | 
| 2290 |  | 
| 2291 | #include <tst_qrhi.moc> | 
| 2292 | QTEST_MAIN(tst_QRhi) | 
| 2293 |  |