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
60Q_DECLARE_METATYPE(QRhi::Implementation)
61Q_DECLARE_METATYPE(QRhiInitParams *)
62
63class tst_QRhi : public QObject
64{
65 Q_OBJECT
66
67private 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
105private:
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
128void 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
158void tst_QRhi::cleanupTestCase()
159{
160#ifdef TST_VK
161 vulkanInstance.destroy();
162#endif
163
164 delete fallbackSurface;
165}
166
167void 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
188void tst_QRhi::create_data()
189{
190 rhiTestData();
191}
192
193static int aligned(int v, int a)
194{
195 return (v + a - 1) & ~(a - 1);
196}
197
198void 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
318void tst_QRhi::nativeHandles_data()
319{
320 rhiTestData();
321}
322
323void 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
488void tst_QRhi::nativeTexture_data()
489{
490 rhiTestData();
491}
492
493void 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
553void tst_QRhi::nativeBuffer_data()
554{
555 rhiTestData();
556}
557
558void 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
633static 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
650void tst_QRhi::resourceUpdateBatchBuffer_data()
651{
652 rhiTestData();
653}
654
655void 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
726inline 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
757void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data()
758{
759 rhiTestData();
760}
761
762void 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
963void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data()
964{
965 rhiTestData();
966}
967
968void 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
1062void tst_QRhi::resourceUpdateBatchRGBATextureMip_data()
1063{
1064 rhiTestData();
1065}
1066
1067void 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
1127static 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
1137void tst_QRhi::invalidPipeline_data()
1138{
1139 rhiTestData();
1140}
1141
1142void 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
1230void tst_QRhi::renderToTextureSimple_data()
1231{
1232 rhiTestData();
1233}
1234
1235void 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
1344void tst_QRhi::renderToTextureTexturedQuad_data()
1345{
1346 rhiTestData();
1347}
1348
1349void 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
1473void tst_QRhi::renderToTextureArrayOfTexturedQuad_data()
1474{
1475 rhiTestData();
1476}
1477
1478void 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
1614void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data()
1615{
1616 rhiTestData();
1617}
1618
1619void 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
1818void tst_QRhi::renderToWindowSimple_data()
1819{
1820 rhiTestData();
1821}
1822
1823void 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
1978void tst_QRhi::srbLayoutCompatibility_data()
1979{
1980 rhiTestData();
1981}
1982
1983void 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
2129void tst_QRhi::renderPassDescriptorCompatibility_data()
2130{
2131 rhiTestData();
2132}
2133
2134void 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>
2292QTEST_MAIN(tst_QRhi)
2293

source code of qtbase/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp