summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Fletcher <ben.fletcher@me.com>2023-02-20 21:25:03 -0800
committerBen Fletcher <ben.fletcher@me.com>2023-02-27 09:23:05 -0800
commit9ffa16baf0087faa9da692c74ae24c9f54b75395 (patch)
tree7443ddfd319c041302c61cc3a4166c4fe25891fc
parent742e79312fc98711f68749937d0db433d961f546 (diff)
rhi: Add support for half precision vertex atttributes
Runtime support is indicated via QRhi::Feature::HalfAttributes. OpenGL support is available in OpenGL 3.0+, OpenGL ES 3.0+, and in implementations that support the extension GL_ARB_half_float_vertex. Other RHI backends (Vulkan, Metal, D3D11, and D3D12) all support this feature. Note that D3D does not support the half3 type. D3D backends pass half3 as half4. tst_qrhi auto unit test included. Change-Id: Ide05d7f62f6102ad5cae1b3681fdda98d52bca31 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--src/gui/opengl/qopenglextensions_p.h3
-rw-r--r--src/gui/opengl/qopenglfunctions.cpp5
-rw-r--r--src/gui/rhi/qrhi.cpp29
-rw-r--r--src/gui/rhi/qrhi_p.h9
-rw-r--r--src/gui/rhi/qrhid3d11.cpp10
-rw-r--r--src/gui/rhi/qrhid3d12.cpp10
-rw-r--r--src/gui/rhi/qrhigles2.cpp24
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h4
-rw-r--r--src/gui/rhi/qrhimetal.mm10
-rw-r--r--src/gui/rhi/qrhivulkan.cpp10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat1
-rw-r--r--tests/auto/gui/rhi/qrhi/data/half.vert10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/half.vert.qsbbin0 -> 815 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp149
14 files changed, 269 insertions, 5 deletions
diff --git a/src/gui/opengl/qopenglextensions_p.h b/src/gui/opengl/qopenglextensions_p.h
index 2924754e3a..fdb9b51f06 100644
--- a/src/gui/opengl/qopenglextensions_p.h
+++ b/src/gui/opengl/qopenglextensions_p.h
@@ -58,7 +58,8 @@ public:
TextureSwizzle = 0x01000000,
StandardDerivatives = 0x02000000,
ASTCTextureCompression = 0x04000000,
- ETC2TextureCompression = 0x08000000
+ ETC2TextureCompression = 0x08000000,
+ HalfFloatVertex = 0x10000000
};
Q_DECLARE_FLAGS(OpenGLExtensions, OpenGLExtension)
diff --git a/src/gui/opengl/qopenglfunctions.cpp b/src/gui/opengl/qopenglfunctions.cpp
index d8c8e4704d..ee76987566 100644
--- a/src/gui/opengl/qopenglfunctions.cpp
+++ b/src/gui/opengl/qopenglfunctions.cpp
@@ -346,6 +346,8 @@ static int qt_gl_resolve_extensions()
extensions |= QOpenGLExtensions::TextureSwizzle;
if (extensionMatcher.match("GL_OES_standard_derivatives"))
extensions |= QOpenGLExtensions::StandardDerivatives;
+ if (extensionMatcher.match("GL_ARB_half_float_vertex"))
+ extensions |= QOpenGLExtensions::HalfFloatVertex;
if (ctx->isOpenGLES()) {
if (format.majorVersion() >= 2)
@@ -360,7 +362,8 @@ static int qt_gl_resolve_extensions()
| QOpenGLExtensions::FramebufferMultisample
| QOpenGLExtensions::Sized8Formats
| QOpenGLExtensions::StandardDerivatives
- | QOpenGLExtensions::ETC2TextureCompression;
+ | QOpenGLExtensions::ETC2TextureCompression
+ | QOpenGLExtensions::HalfFloatVertex;
#ifndef Q_OS_WASM
// WebGL 2.0 specification explicitly does not support texture swizzles
// https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.19
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 1c83f4e790..c591d90e62 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -740,6 +740,16 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
unsupported on backends that do not report support for
\l{OneDimensionalTextures}, and Metal.
+ \value HalfAttributes Indicates that specifying input attributes with half
+ precision (16bit) floating point types for a shader pipeline is supported.
+ When not supported, build() will succeed but just show a warning message
+ and the values of the target attributes will be broken. In practice this
+ feature will be unsupported in some OpenGL ES 2.0 and OpenGL 2.x
+ implementations. Note that while D3D does support half precision input
+ attributes, it does not support the half3 type. The D3D backends pass
+ half3 attributes as half4. To ensure cross platform compatibility, half3
+ inputs should be padded to 8 bytes.
+
*/
/*!
@@ -1287,6 +1297,16 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b)
\value SInt3 Three component signed integer vector
\value SInt2 Two component signed integer vector
\value SInt Signed integer
+ \value Half4 Four component half precision (16bit) float vector
+ \value Half3 Three component half precision (16bit) float vector
+ \value Half2 Two component half precision (16bit) float vector
+ \value Half half precision (16bit) float
+
+ \note Support for half precision floating point attributes is indicated at
+ run time by the QRhi::Feature::HalfAttributes feature flag. Note that D3D
+ supports half input attributes, but does not support the Half3 type. The
+ D3D backends pass through Half3 as Half4. To ensure cross platform
+ compatibility, Half3 inputs should be padded to 8 bytes.
*/
/*!
@@ -1419,6 +1439,15 @@ quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInpu
case QRhiVertexInputAttribute::SInt:
return sizeof(qint32);
+ case QRhiVertexInputAttribute::Half4:
+ return 4 * sizeof(qfloat16);
+ case QRhiVertexInputAttribute::Half3:
+ return 4 * sizeof(qfloat16); // half3 still takes 8 bytes
+ case QRhiVertexInputAttribute::Half2:
+ return 2 * sizeof(qfloat16);
+ case QRhiVertexInputAttribute::Half:
+ return sizeof(qfloat16);
+
default:
Q_UNREACHABLE_RETURN(1);
}
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
index ec033fac2d..ec9372e08c 100644
--- a/src/gui/rhi/qrhi_p.h
+++ b/src/gui/rhi/qrhi_p.h
@@ -247,7 +247,11 @@ public:
SInt4,
SInt3,
SInt2,
- SInt
+ SInt,
+ Half4,
+ Half3,
+ Half2,
+ Half
};
QRhiVertexInputAttribute() = default;
@@ -1819,7 +1823,8 @@ public:
TextureArrayRange,
NonFillPolygonMode,
OneDimensionalTextures,
- OneDimensionalTextureMipmaps
+ OneDimensionalTextureMipmaps,
+ HalfAttributes
};
enum BeginFrameFlag {
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index ab511d0cdf..86038b4ae5 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -528,6 +528,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::OneDimensionalTextureMipmaps:
return true;
+ case QRhi::HalfAttributes:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -4038,6 +4040,14 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
return DXGI_FORMAT_R32G32_SINT;
case QRhiVertexInputAttribute::SInt:
return DXGI_FORMAT_R32_SINT;
+ case QRhiVertexInputAttribute::Half4:
+ // Note: D3D does not support half3. Pass through half3 as half4.
+ case QRhiVertexInputAttribute::Half3:
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ case QRhiVertexInputAttribute::Half2:
+ return DXGI_FORMAT_R16G16_FLOAT;
+ case QRhiVertexInputAttribute::Half:
+ return DXGI_FORMAT_R16_FLOAT;
default:
Q_UNREACHABLE();
return DXGI_FORMAT_R32G32B32A32_FLOAT;
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index eca02a8b8e..352c7d5d10 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -583,6 +583,8 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::OneDimensionalTextureMipmaps:
return false;
+ case QRhi::HalfAttributes:
+ return true;
}
return false;
}
@@ -5053,6 +5055,14 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
return DXGI_FORMAT_R32G32_SINT;
case QRhiVertexInputAttribute::SInt:
return DXGI_FORMAT_R32_SINT;
+ case QRhiVertexInputAttribute::Half4:
+ // Note: D3D does not support half3. Pass through half3 as half4.
+ case QRhiVertexInputAttribute::Half3:
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ case QRhiVertexInputAttribute::Half2:
+ return DXGI_FORMAT_R16G16_FLOAT;
+ case QRhiVertexInputAttribute::Half:
+ return DXGI_FORMAT_R16_FLOAT;
}
Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT);
}
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 58eed34aa8..d36cc7067c 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -450,6 +450,10 @@ QT_BEGIN_NAMESPACE
# define GL_TEXTURE_1D_ARRAY 0x8C18
#endif
+#ifndef GL_HALF_FLOAT
+#define GL_HALF_FLOAT 0x140B
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -959,6 +963,8 @@ bool QRhiGles2::create(QRhi::Flags flags)
if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)))
f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+ caps.halfAttributes = f->hasOpenGLExtension(QOpenGLExtensions::HalfFloatVertex);
+
nativeHandlesStruct.context = ctx;
contextLost = false;
@@ -1315,6 +1321,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.texture1D;
case QRhi::OneDimensionalTextureMipmaps:
return caps.texture1D;
+ case QRhi::HalfAttributes:
+ return caps.halfAttributes;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -2939,6 +2947,22 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
type = GL_INT;
size = 1;
break;
+ case QRhiVertexInputAttribute::Half4:
+ type = GL_HALF_FLOAT;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::Half3:
+ type = GL_HALF_FLOAT;
+ size = 3;
+ break;
+ case QRhiVertexInputAttribute::Half2:
+ type = GL_HALF_FLOAT;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::Half:
+ type = GL_HALF_FLOAT;
+ size = 1;
+ break;
default:
break;
}
diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h
index 7caec5f7d5..c3d85c1d09 100644
--- a/src/gui/rhi/qrhigles2_p_p.h
+++ b/src/gui/rhi/qrhigles2_p_p.h
@@ -961,7 +961,8 @@ public:
tessellation(false),
geometryShader(false),
texture1D(false),
- hasDrawBuffersFunc(false)
+ hasDrawBuffersFunc(false),
+ halfAttributes(false)
{ }
int ctxMajor;
int ctxMinor;
@@ -1014,6 +1015,7 @@ public:
uint geometryShader : 1;
uint texture1D : 1;
uint hasDrawBuffersFunc : 1;
+ uint halfAttributes : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QSet<GLint> supportedCompressedFormats;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 2d4e826df7..3fe8c8d219 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -794,6 +794,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::OneDimensionalTextureMipmaps:
return false;
+ case QRhi::HalfAttributes:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -4297,6 +4299,14 @@ static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::F
return MTLVertexFormatInt2;
case QRhiVertexInputAttribute::SInt:
return MTLVertexFormatInt;
+ case QRhiVertexInputAttribute::Half4:
+ return MTLVertexFormatHalf4;
+ case QRhiVertexInputAttribute::Half3:
+ return MTLVertexFormatHalf3;
+ case QRhiVertexInputAttribute::Half2:
+ return MTLVertexFormatHalf2;
+ case QRhiVertexInputAttribute::Half:
+ return MTLVertexFormatHalf;
default:
Q_UNREACHABLE();
return MTLVertexFormatFloat4;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index 2c4f1bd18b..e387463be4 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -4265,6 +4265,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::OneDimensionalTextureMipmaps:
return true;
+ case QRhi::HalfAttributes:
+ return true;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -5279,6 +5281,14 @@ static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format form
return VK_FORMAT_R32G32_SINT;
case QRhiVertexInputAttribute::SInt:
return VK_FORMAT_R32_SINT;
+ case QRhiVertexInputAttribute::Half4:
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case QRhiVertexInputAttribute::Half3:
+ return VK_FORMAT_R16G16B16_SFLOAT;
+ case QRhiVertexInputAttribute::Half2:
+ return VK_FORMAT_R16G16_SFLOAT;
+ case QRhiVertexInputAttribute::Half:
+ return VK_FORMAT_R16_SFLOAT;
default:
Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT);
}
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 37050fe80f..8fdca5623c 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -21,3 +21,4 @@ qsb --glsl 320es,430 --msl 12 --tess-mode triangles storagebuffer_runtime.tesc -
qsb --glsl 320es,430 --msl 12 --tess-vertex-count 3 storagebuffer_runtime.tese -o storagebuffer_runtime.tese.qsb
qsb --glsl 320es,430 --msl 12 storagebuffer_runtime.frag -o storagebuffer_runtime.frag.qsb
qsb --glsl 320es,430 --hlsl 50 -c --msl 12 storagebuffer_runtime.comp -o storagebuffer_runtime.comp.qsb
+qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o half.vert.qsb half.vert
diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert b/tests/auto/gui/rhi/qrhi/data/half.vert
new file mode 100644
index 0000000000..b503201351
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/half.vert
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/half.vert.qsb b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb
new file mode 100644
index 0000000000..fb8680024a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index 41487f2b2b..08ddba7e02 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -152,6 +152,9 @@ private slots:
void storageBufferRuntimeSizeGraphics_data();
void storageBufferRuntimeSizeGraphics();
+ void halfPrecisionAttributes_data();
+ void halfPrecisionAttributes();
+
private:
void setWindowType(QWindow *window, QRhi::Implementation impl);
@@ -6190,5 +6193,151 @@ void tst_QRhi::storageBufferRuntimeSizeGraphics()
QCOMPARE(result.pixel(32, 32), qRgb(red, green, blue));
}
+void tst_QRhi::halfPrecisionAttributes_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::halfPrecisionAttributes()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ if (!rhi->isFeatureSupported(QRhi::HalfAttributes)) {
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QVERIFY(rhi->backend() != QRhi::D3D11);
+ QVERIFY(rhi->backend() != QRhi::D3D12);
+ QSKIP("Half precision vertex attributes are not supported with this graphics API, skipping test");
+ }
+
+ const QSize outputSize(1920, 1080);
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ //
+ // This test uses half3 vertices
+ //
+ // Note: D3D does not support half3 - rhi passes it through as half4. Because of this, D3D will
+ // report the following warning and error if we don't take precautions:
+ //
+ // D3D11 WARNING: ID3D11DeviceContext::Draw: Input vertex slot 0 has stride 6 which is less than
+ // the minimum stride logically expected from the current Input Layout (8 bytes). This is OK, as
+ // hardware is perfectly capable of reading overlapping data. However the developer probably did
+ // not intend to make use of this behavior. [ EXECUTION WARNING #355:
+ // DEVICE_DRAW_VERTEX_BUFFER_STRIDE_TOO_SMALL]
+ //
+ // D3D11 ERROR: ID3D11DeviceContext::Draw: Vertex Buffer Stride (6) at the input vertex slot 0
+ // is not aligned properly. The current Input Layout imposes an alignment of (4) because of the
+ // Formats used with this slot. [ EXECUTION ERROR #367: DEVICE_DRAW_VERTEX_STRIDE_UNALIGNED]
+ //
+ // The same warning and error are produced for D3D12. The rendered output is correct despite
+ // the warning and error.
+ //
+ // To avoid these errors, we pad the vertices to 8 byte stride.
+ //
+ static const qfloat16 vertices[] = {
+ -1.0, -1.0, 0.0, 0.0,
+ 1.0, -1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ };
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ QShader vs = loadShader(":/data/half.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/simple.frag.qsb");
+ QVERIFY(fs.isValid());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 4 * sizeof(qfloat16) } }); // 8 byte vertex stride for D3D
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Half3, 0 } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(pipeline->create());
+
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readResult, &result] {
+ result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+ // Offscreen frames are synchronous, so the readback is guaranteed to
+ // complete at this point. This would not be the case with swapchain-based
+ // frames.
+ QCOMPARE(result.size(), texture->pixelSize());
+
+ if (impl == QRhi::Null)
+ return;
+
+ // Now we have a red rectangle on blue background.
+ const int y = 100;
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ int redCount = 0;
+ int blueCount = 0;
+ const int maxFuzz = 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0)
+ ++redCount;
+ else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz))
+ ++blueCount;
+ else
+ QFAIL("Encountered a pixel that is neither red or blue");
+ }
+
+ QCOMPARE(redCount + blueCount, texture->pixelSize().width());
+ QVERIFY(redCount != 0);
+ QVERIFY(blueCount != 0);
+
+ // The triangle is "pointing up" in the resulting image with OpenGL
+ // (because Y is up both in normalized device coordinates and in images)
+ // and Vulkan (because Y is down in both and the vertex data was specified
+ // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC
+ // but down in images).
+ if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
+ QVERIFY(redCount < blueCount);
+ else
+ QVERIFY(redCount > blueCount);
+
+}
+
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)