diff options
author | Ben Fletcher <ben.fletcher@me.com> | 2023-02-20 21:25:03 -0800 |
---|---|---|
committer | Ben Fletcher <ben.fletcher@me.com> | 2023-02-27 09:23:05 -0800 |
commit | 9ffa16baf0087faa9da692c74ae24c9f54b75395 (patch) | |
tree | 7443ddfd319c041302c61cc3a4166c4fe25891fc | |
parent | 742e79312fc98711f68749937d0db433d961f546 (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.h | 3 | ||||
-rw-r--r-- | src/gui/opengl/qopenglfunctions.cpp | 5 | ||||
-rw-r--r-- | src/gui/rhi/qrhi.cpp | 29 | ||||
-rw-r--r-- | src/gui/rhi/qrhi_p.h | 9 | ||||
-rw-r--r-- | src/gui/rhi/qrhid3d11.cpp | 10 | ||||
-rw-r--r-- | src/gui/rhi/qrhid3d12.cpp | 10 | ||||
-rw-r--r-- | src/gui/rhi/qrhigles2.cpp | 24 | ||||
-rw-r--r-- | src/gui/rhi/qrhigles2_p_p.h | 4 | ||||
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 10 | ||||
-rw-r--r-- | src/gui/rhi/qrhivulkan.cpp | 10 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 1 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/half.vert | 10 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/half.vert.qsb | bin | 0 -> 815 bytes | |||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 149 |
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 Binary files differnew file mode 100644 index 0000000000..fb8680024a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/half.vert.qsb 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) |