summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2022-08-18 12:43:23 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2022-09-01 19:44:19 +0200
commitc681c7c23f79e2f0d6dbb2ce8961edd216cefd91 (patch)
tree861706d22bb395ff5e1f86c4948a0cc7e8cab9ac
parent855a9ca217ad3b9d8eb8f6544698a174323843fc (diff)
rhi: metal: Add support for tessellation
Change-Id: Ie8d226a6a959aa5e78284ea72505fd26aec1e671 Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r--src/gui/rhi/qrhi.cpp106
-rw-r--r--src/gui/rhi/qrhi_p_p.h3
-rw-r--r--src/gui/rhi/qrhimetal.mm1382
-rw-r--r--src/gui/rhi/qrhimetal_p_p.h48
-rw-r--r--src/gui/rhi/qshader.cpp118
-rw-r--r--src/gui/rhi/qshader_p.h16
-rw-r--r--src/gui/rhi/qshader_p_p.h16
-rw-r--r--src/gui/rhi/qshaderdescription.cpp364
-rw-r--r--src/gui/rhi/qshaderdescription_p.h76
-rw-r--r--src/gui/rhi/qshaderdescription_p_p.h17
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat4
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.frag10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsbbin0 -> 591 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tesc22
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsbbin0 -> 1402 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tese17
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsbbin0 -> 1401 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.vert12
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsbbin0 -> 936 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp163
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsbbin0 -> 729 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsbbin0 -> 1749 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsbbin0 -> 2390 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsbbin0 -> 1106 bytes
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/color.vert (renamed from tests/auto/gui/rhi/qshader/data/color.vert)0
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/texture.frag (renamed from tests/auto/gui/rhi/qshader/data/texture.frag)0
-rw-r--r--tests/auto/gui/rhi/qshader/data_src/texture_sep.frag (renamed from tests/auto/gui/rhi/qshader/data/texture_sep.frag)0
-rw-r--r--tests/auto/gui/rhi/qshader/tst_qshader.cpp83
-rw-r--r--tests/manual/rhi/tessellation/buildshaders.bat8
-rw-r--r--tests/manual/rhi/tessellation/tessellation.cpp26
-rw-r--r--tests/manual/rhi/tessellation/test.frag.qsbbin581 -> 729 bytes
-rw-r--r--tests/manual/rhi/tessellation/test.tesc6
-rw-r--r--tests/manual/rhi/tessellation/test.tesc.qsbbin1202 -> 1765 bytes
-rw-r--r--tests/manual/rhi/tessellation/test.tese13
-rw-r--r--tests/manual/rhi/tessellation/test.tese.qsbbin1522 -> 2436 bytes
-rw-r--r--tests/manual/rhi/tessellation/test.vert.qsbbin714 -> 1106 bytes
-rw-r--r--tests/manual/rhi/tessellation/test_domain.hlsl2
37 files changed, 2261 insertions, 251 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index e822568c4f..0ebcd7ffeb 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -677,16 +677,17 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
can be set via
\l{QRhiGraphicsPipeline::setPatchControlPointCount()}{setPatchControlPointCount()},
and shaders for tessellation control and evaluation can be specified in the
- QRhiShaderStage list. Tessellation is considered an experimental feature in
- QRhi and can only be expected to be supported with Vulkan, OpenGL (ES), and
- Direct 3D, assuming the implementation reports it as supported at run time.
- Tessellation shaders have portability issues between APIs (for example,
- translating GLSL/SPIR-V to HLSL is problematic due to the way hull shaders
- are structured, whereas Metal uses a somewhat different tessellation
- pipeline than others), and therefore no guarantees can be given for a
- universal solution for now. (for Direct 3D in particular, handwritten HLSL
- hull and domain shaders must be injected into each QShader since qsb cannot
- generate these from SPIR-V)
+ QRhiShaderStage list. Tessellation shaders have portability issues between
+ APIs (for example, translating GLSL/SPIR-V to HLSL is problematic due to
+ the way hull shaders are structured, whereas Metal uses a somewhat
+ different tessellation pipeline than others), and therefore unexpected
+ issues may still arise, even though basic functionality is implemented
+ across all the underlying APIs. For Direct 3D in particular, handwritten
+ HLSL hull and domain shaders must be injected into each QShader for the
+ tessellation control and evaluation stages, respectively, since qsb cannot
+ generate these from SPIR-V. Note that isoline tessellation should be
+ avoided as it will not be supported by all backends. The maximum patch
+ control point count portable between backends is 32.
\value GeometryShader Indicates that the geometry shader stage is
supported. When supported, a geometry shader can be specified in the
@@ -695,9 +696,9 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
Direct 3D, OpenGL (3.2+) and OpenGL ES (3.2+), assuming the implementation
reports it as supported at run time. Geometry shaders have portability
issues between APIs, and therefore no guarantees can be given for a
- universal solution for now. (for Direct 3D in particular, a handwritten
- HLSL geometry shader must be injected into each QShader since qsb cannot
- generate this from SPIR-V)
+ universal solution. They will never be supported with Metal. Whereas with
+ Direct 3D a handwritten HLSL geometry shader must be injected into each
+ QShader for the geometry stage since qsb cannot generate this from SPIR-V.
\value TextureArrayRange Indicates that for
\l{QRhi::newTextureArray()}{texture arrays} it is possible to specify a
@@ -1403,6 +1404,85 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputAttribute &a)
}
#endif
+QRhiVertexInputAttribute::Format QRhiImplementation::shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const
+{
+ switch (type) {
+ case QShaderDescription::Vec4:
+ return QRhiVertexInputAttribute::Float4;
+ case QShaderDescription::Vec3:
+ return QRhiVertexInputAttribute::Float3;
+ case QShaderDescription::Vec2:
+ return QRhiVertexInputAttribute::Float2;
+ case QShaderDescription::Float:
+ return QRhiVertexInputAttribute::Float;
+
+ case QShaderDescription::Int4:
+ return QRhiVertexInputAttribute::SInt4;
+ case QShaderDescription::Int3:
+ return QRhiVertexInputAttribute::SInt3;
+ case QShaderDescription::Int2:
+ return QRhiVertexInputAttribute::SInt2;
+ case QShaderDescription::Int:
+ return QRhiVertexInputAttribute::SInt;
+
+ case QShaderDescription::Uint4:
+ return QRhiVertexInputAttribute::UInt4;
+ case QShaderDescription::Uint3:
+ return QRhiVertexInputAttribute::UInt3;
+ case QShaderDescription::Uint2:
+ return QRhiVertexInputAttribute::UInt2;
+ case QShaderDescription::Uint:
+ return QRhiVertexInputAttribute::UInt;
+
+ default:
+ Q_UNREACHABLE();
+ return QRhiVertexInputAttribute::Float;
+ }
+}
+
+quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return 4 * sizeof(float);
+ case QRhiVertexInputAttribute::Float3:
+ return 4 * sizeof(float); // vec3 still takes 16 bytes
+ case QRhiVertexInputAttribute::Float2:
+ return 2 * sizeof(float);
+ case QRhiVertexInputAttribute::Float:
+ return sizeof(float);
+
+ case QRhiVertexInputAttribute::UNormByte4:
+ return 4 * sizeof(quint8);
+ case QRhiVertexInputAttribute::UNormByte2:
+ return 2 * sizeof(quint8);
+ case QRhiVertexInputAttribute::UNormByte:
+ return sizeof(quint8);
+
+ case QRhiVertexInputAttribute::UInt4:
+ return 4 * sizeof(quint32);
+ case QRhiVertexInputAttribute::UInt3:
+ return 4 * sizeof(quint32); // ivec3 still takes 16 bytes
+ case QRhiVertexInputAttribute::UInt2:
+ return 2 * sizeof(quint32);
+ case QRhiVertexInputAttribute::UInt:
+ return sizeof(quint32);
+
+ case QRhiVertexInputAttribute::SInt4:
+ return 4 * sizeof(qint32);
+ case QRhiVertexInputAttribute::SInt3:
+ return 4 * sizeof(qint32); // uvec3 still takes 16 bytes
+ case QRhiVertexInputAttribute::SInt2:
+ return 2 * sizeof(qint32);
+ case QRhiVertexInputAttribute::SInt:
+ return sizeof(qint32);
+
+ default:
+ Q_UNREACHABLE();
+ return 1;
+ }
+}
+
/*!
\class QRhiVertexInputLayout
\internal
diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h
index d7490f07c0..bf596abef1 100644
--- a/src/gui/rhi/qrhi_p_p.h
+++ b/src/gui/rhi/qrhi_p_p.h
@@ -217,6 +217,9 @@ public:
return accumulatedPipelineCreationTime;
}
+ QRhiVertexInputAttribute::Format shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const;
+ quint32 byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const;
+
QRhi *q;
static const int MAX_SHADER_CACHE_ENTRIES = 128;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index a969b4d332..7eb15a2079 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qrhimetal_p_p.h"
+#include <QtGui/private/qshader_p_p.h>
#include <QGuiApplication>
#include <QWindow>
#include <qmath.h>
@@ -104,8 +105,11 @@ struct QMetalShader
{
id<MTLLibrary> lib = nil;
id<MTLFunction> func = nil;
- std::array<uint, 3> localSize;
+ std::array<uint, 3> localSize = {};
+ uint outputVertexCount = 0;
+ QShaderDescription desc;
QShader::NativeResourceBindingMap nativeResourceBindingMap;
+ QShader::NativeShaderInfo nativeShaderInfo;
void destroy() {
nativeResourceBindingMap.clear();
@@ -164,6 +168,8 @@ struct QRhiMetalData
struct {
id<MTLRenderPipelineState> pipelineState;
id<MTLDepthStencilState> depthStencilState;
+ std::array<id<MTLComputePipelineState>, 3> tessVertexComputeState;
+ id<MTLComputePipelineState> tessTessControlComputeState;
} graphicsPipeline;
struct {
id<MTLComputePipelineState> pipelineState;
@@ -268,6 +274,7 @@ struct QMetalCommandBufferData
id<MTLCommandBuffer> cb;
id<MTLRenderCommandEncoder> currentRenderPassEncoder;
id<MTLComputeCommandEncoder> currentComputePassEncoder;
+ id<MTLComputeCommandEncoder> tessellationComputeEncoder;
MTLRenderPassDescriptor *currentPassRpDesc;
int currentFirstVertexBinding;
QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
@@ -308,6 +315,7 @@ struct QMetalRenderTargetData
struct QMetalGraphicsPipelineData
{
+ QMetalGraphicsPipeline *q = nullptr;
id<MTLRenderPipelineState> ps = nil;
id<MTLDepthStencilState> ds = nil;
MTLPrimitiveType primitiveType;
@@ -318,6 +326,48 @@ struct QMetalGraphicsPipelineData
float slopeScaledDepthBias;
QMetalShader vs;
QMetalShader fs;
+ struct Tessellation {
+ QMetalGraphicsPipelineData *q = nullptr;
+ bool enabled = false;
+ bool failed = false;
+ uint inControlPointCount;
+ uint outControlPointCount;
+ QMetalShader compVs[3];
+ std::array<id<MTLComputePipelineState>, 3> vertexComputeState = {};
+ id<MTLComputePipelineState> tessControlComputeState = nil;
+ QMetalShader compTesc;
+ QMetalShader vertTese;
+ quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
+ {
+ // max vertex output components = resourceLimit(MaxVertexOutputs) * 4 = 60
+ return vertexOrIndexCount * instanceCount * sizeof(float) * 60;
+ }
+ quint32 tescCompOutputBufferSize(quint32 patchCount) const
+ {
+ return outControlPointCount * patchCount * sizeof(float) * 60;
+ }
+ quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
+ {
+ // assume maxTessellationControlPerPatchOutputComponents is 128
+ return patchCount * sizeof(float) * 128;
+ }
+ quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
+ {
+ return ((vertexOrIndexCount + inControlPointCount - 1) / inControlPointCount) * instanceCount;
+ }
+ static int vsCompVariantToIndex(QShader::Variant vertexCompVariant);
+ id<MTLComputePipelineState> vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant);
+ id<MTLComputePipelineState> tescCompPipeline(QRhiMetal *rhiD);
+ id<MTLRenderPipelineState> teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline);
+ enum class WorkBufType {
+ DeviceLocal,
+ HostVisible
+ };
+ QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal);
+ QVector<QMetalBuffer *> deviceLocalWorkBuffers;
+ QVector<QMetalBuffer *> hostVisibleWorkBuffers;
+ } tess;
+ template<typename T> void setupVertexOrStageInputDescriptor(T *desc);
};
struct QMetalComputePipelineData
@@ -661,7 +711,7 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
case QRhi::TextureArrays:
return true;
case QRhi::Tessellation:
- return false;
+ return true;
case QRhi::GeometryShader:
return false;
case QRhi::TextureArrayRange:
@@ -824,6 +874,119 @@ static inline int mapBinding(int binding,
return -1;
}
+static inline void bindStageBuffers(QMetalCommandBuffer *cbD,
+ int stage,
+ const QRhiBatchedBindings<id<MTLBuffer>>::Batch &bufferBatch,
+ const QRhiBatchedBindings<NSUInteger>::Batch &offsetBatch)
+{
+ switch (stage) {
+ case QMetalShaderResourceBindingsData::VERTEX:
+ [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::FRAGMENT:
+ [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::COMPUTE:
+ [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+}
+
+static inline void bindStageTextures(QMetalCommandBuffer *cbD,
+ int stage,
+ const QRhiBatchedBindings<id<MTLTexture>>::Batch &textureBatch)
+{
+ switch (stage) {
+ case QMetalShaderResourceBindingsData::VERTEX:
+ [cbD->d->currentRenderPassEncoder setVertexTextures: textureBatch.resources.constData()
+ withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::FRAGMENT:
+ [cbD->d->currentRenderPassEncoder setFragmentTextures: textureBatch.resources.constData()
+ withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::COMPUTE:
+ [cbD->d->currentComputePassEncoder setTextures: textureBatch.resources.constData()
+ withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+}
+
+static inline void bindStageSamplers(QMetalCommandBuffer *cbD,
+ int encoderStage,
+ const QRhiBatchedBindings<id<MTLSamplerState>>::Batch &samplerBatch)
+{
+ switch (encoderStage) {
+ case QMetalShaderResourceBindingsData::VERTEX:
+ [cbD->d->currentRenderPassEncoder setVertexSamplerStates: samplerBatch.resources.constData()
+ withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::FRAGMENT:
+ [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: samplerBatch.resources.constData()
+ withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
+ break;
+ case QMetalShaderResourceBindingsData::COMPUTE:
+ [cbD->d->currentComputePassEncoder setSamplerStates: samplerBatch.resources.constData()
+ withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+}
+
+// Helper that is not used during the common vertex+fragment and compute
+// pipelines, but is necessary when tessellation is involved and so the
+// graphics pipeline is under the hood a combination of multiple compute and
+// render pipelines. We need to be able to set the buffers, textures, samplers
+// when a switching between render and compute encoders.
+static inline void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceStage, int encoderStage,
+ const QMetalShaderResourceBindingsData *customBindingState = nullptr)
+{
+ const QMetalShaderResourceBindingsData *bindingData = customBindingState ? customBindingState : &cbD->d->currentShaderResourceBindingState;
+
+ for (int i = 0, ie = bindingData->res[resourceStage].bufferBatches.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(bindingData->res[resourceStage].bufferBatches.batches[i]);
+ const auto &offsetBatch(bindingData->res[resourceStage].bufferOffsetBatches.batches[i]);
+ bindStageBuffers(cbD, encoderStage, bufferBatch, offsetBatch);
+ }
+
+ for (int i = 0, ie = bindingData->res[resourceStage].textureBatches.batches.count(); i != ie; ++i) {
+ const auto &batch(bindingData->res[resourceStage].textureBatches.batches[i]);
+ bindStageTextures(cbD, encoderStage, batch);
+ }
+
+ for (int i = 0, ie = bindingData->res[resourceStage].samplerBatches.batches.count(); i != ie; ++i) {
+ const auto &batch(bindingData->res[resourceStage].samplerBatches.batches[i]);
+ bindStageSamplers(cbD, encoderStage, batch);
+ }
+}
+
+// Resources marked for the tess.control and/or eval. stages are treated as if
+// they were for the vertex stage. For tess.eval. this is trivial because
+// that's translated to a Metal a vertex function, but tess.control (and the
+// GLSL vertex) shader becomes compute. Yet dumping them under the vertex
+// category still works, because rebindShaderResources(VERTEX, COMPUTE) can
+// then be used to set them active on the compute encoder.
+static inline bool isVertexishResource(QRhiShaderResourceBinding::StageFlags stages)
+{
+ return stages.testAnyFlags(QRhiShaderResourceBinding::VertexStage
+ | QRhiShaderResourceBinding::TessellationControlStage
+ | QRhiShaderResourceBinding::TessellationEvaluationStage);
+}
+
void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD,
QMetalCommandBuffer *cbD,
int dynamicOffsetCount,
@@ -848,7 +1011,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
break;
}
}
- if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ if (isVertexishResource(b->stage)) {
const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0)
bindingData.res[QMetalShaderResourceBindingsData::VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
@@ -873,7 +1036,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
for (int elem = 0; elem < data->count; ++elem) {
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.texSamplers[elem].tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.texSamplers[elem].sampler);
- if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ if (isVertexishResource(b->stage)) {
// Must handle all three cases (combined, separate, separate):
// first = texture binding, second = sampler binding
// first = texture binding
@@ -913,7 +1076,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
{
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
- if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ if (isVertexishResource(b->stage)) {
const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Texture);
if (nativeBinding >= 0)
bindingData.res[QMetalShaderResourceBindingsData::VERTEX].textures.append({ nativeBinding, t });
@@ -937,7 +1100,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
id<MTLBuffer> mtlbuf = bufD->d->buf[0];
quint32 offset = b->u.sbuf.offset;
- if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ if (isVertexishResource(b->stage)) {
const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Buffer);
if (nativeBinding >= 0)
bindingData.res[QMetalShaderResourceBindingsData::VERTEX].buffers.append({ nativeBinding, mtlbuf, offset });
@@ -994,26 +1157,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
{
continue;
}
- switch (stage) {
- case QMetalShaderResourceBindingsData::VERTEX:
- [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
- offsets: offsetBatch.resources.constData()
- withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::FRAGMENT:
- [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
- offsets: offsetBatch.resources.constData()
- withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::COMPUTE:
- [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
- offsets: offsetBatch.resources.constData()
- withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
- break;
- default:
- Q_UNREACHABLE();
- break;
- }
+ bindStageBuffers(cbD, stage, bufferBatch, offsetBatch);
}
if (offsetOnlyChange)
@@ -1044,23 +1188,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
{
continue;
}
- switch (stage) {
- case QMetalShaderResourceBindingsData::VERTEX:
- [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::FRAGMENT:
- [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::COMPUTE:
- [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- default:
- Q_UNREACHABLE();
- break;
- }
+ bindStageTextures(cbD, stage, batch);
}
for (int i = 0, ie = bindingData.res[stage].samplerBatches.batches.count(); i != ie; ++i) {
@@ -1071,65 +1199,68 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
{
continue;
}
- switch (stage) {
- case QMetalShaderResourceBindingsData::VERTEX:
- [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::FRAGMENT:
- [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- case QMetalShaderResourceBindingsData::COMPUTE:
- [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData()
- withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))];
- break;
- default:
- Q_UNREACHABLE();
- break;
- }
+ bindStageSamplers(cbD, stage, batch);
}
}
+
cbD->d->currentShaderResourceBindingState = bindingData;
}
+void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD)
+{
+ [cbD->d->currentRenderPassEncoder setRenderPipelineState: d->ps];
+
+ if (cbD->d->currentDepthStencilState != d->ds) {
+ [cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
+ cbD->d->currentDepthStencilState = d->ds;
+ }
+
+ if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
+ [cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
+ cbD->currentCullMode = int(d->cullMode);
+ }
+ if (cbD->currentTriangleFillMode == -1 || d->triangleFillMode != uint(cbD->currentTriangleFillMode)) {
+ [cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
+ cbD->currentTriangleFillMode = int(d->triangleFillMode);
+ }
+ if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
+ [cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
+ cbD->currentFrontFaceWinding = int(d->winding);
+ }
+ if (!qFuzzyCompare(d->depthBias, cbD->currentDepthBiasValues.first)
+ || !qFuzzyCompare(d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
+ {
+ [cbD->d->currentRenderPassEncoder setDepthBias: d->depthBias
+ slopeScale: d->slopeScaledDepthBias
+ clamp: 0.0f];
+ cbD->currentDepthBiasValues = { d->depthBias, d->slopeScaledDepthBias };
+ }
+}
+
void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
{
QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
- if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
- cbD->currentGraphicsPipeline = ps;
- cbD->currentComputePipeline = nullptr;
- cbD->currentPipelineGeneration = psD->generation;
+ if (cbD->currentGraphicsPipeline == psD && cbD->currentPipelineGeneration == psD->generation)
+ return;
- [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps];
+ cbD->currentGraphicsPipeline = psD;
+ cbD->currentComputePipeline = nullptr;
+ cbD->currentPipelineGeneration = psD->generation;
- if (cbD->d->currentDepthStencilState != psD->d->ds) {
- [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds];
- cbD->d->currentDepthStencilState = psD->d->ds;
- }
-
- if (cbD->currentCullMode == -1 || psD->d->cullMode != uint(cbD->currentCullMode)) {
- [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode];
- cbD->currentCullMode = int(psD->d->cullMode);
- }
- if (cbD->currentTriangleFillMode == -1 || psD->d->triangleFillMode != uint(cbD->currentTriangleFillMode)) {
- [cbD->d->currentRenderPassEncoder setTriangleFillMode: psD->d->triangleFillMode];
- cbD->currentTriangleFillMode = int(psD->d->triangleFillMode);
- }
- if (cbD->currentFrontFaceWinding == -1 || psD->d->winding != uint(cbD->currentFrontFaceWinding)) {
- [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding];
- cbD->currentFrontFaceWinding = int(psD->d->winding);
+ if (!psD->d->tess.enabled && !psD->d->tess.failed) {
+ psD->makeActiveForCurrentRenderPassEncoder(cbD);
+ } else {
+ // mark work buffers that can now be safely reused as reusable
+ for (QMetalBuffer *workBuf : psD->d->tess.deviceLocalWorkBuffers) {
+ if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
+ workBuf->lastActiveFrameSlot = -1;
}
- if (!qFuzzyCompare(psD->d->depthBias, cbD->currentDepthBiasValues.first)
- || !qFuzzyCompare(psD->d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
- {
- [cbD->d->currentRenderPassEncoder setDepthBias: psD->d->depthBias
- slopeScale: psD->d->slopeScaledDepthBias
- clamp: 0.0f];
- cbD->currentDepthBiasValues = { psD->d->depthBias, psD->d->slopeScaledDepthBias };
+ for (QMetalBuffer *workBuf : psD->d->tess.hostVisibleWorkBuffers) {
+ if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
+ workBuf->lastActiveFrameSlot = -1;
}
}
@@ -1142,8 +1273,8 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
{
QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass);
- QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline);
- QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
+ QMetalGraphicsPipeline *gfxPsD = cbD->currentGraphicsPipeline;
+ QMetalComputePipeline *compPsD = cbD->currentComputePipeline;
if (!srb) {
if (gfxPsD)
@@ -1253,20 +1384,20 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
resNeedsRebind = true;
- const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+ const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srbD) : (cbD->currentComputeSrb != srbD);
const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
// dynamic uniform buffer offsets always trigger a rebind
if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr };
if (gfxPsD) {
- cbD->currentGraphicsSrb = srb;
+ cbD->currentGraphicsSrb = srbD;
cbD->currentComputeSrb = nullptr;
resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap;
resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap;
} else {
cbD->currentGraphicsSrb = nullptr;
- cbD->currentComputeSrb = srb;
+ cbD->currentComputeSrb = srbD;
resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap;
}
cbD->currentSrbGeneration = srbD->generation;
@@ -1298,13 +1429,13 @@ void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
offsets.finish();
// same binding space for vertex and constant buffers - work it around
- QRhiShaderResourceBindings *srb = cbD->currentGraphicsSrb;
+ QMetalShaderResourceBindings *srbD = cbD->currentGraphicsSrb;
// There's nothing guaranteeing setShaderResources() was called before
// setVertexInput()... but whatever srb will get bound will have to be
// layout-compatible anyways so maxBinding is the same.
- if (!srb)
- srb = cbD->currentGraphicsPipeline->shaderResourceBindings();
- const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
+ if (!srbD)
+ srbD = QRHI_RES(QMetalShaderResourceBindings, cbD->currentGraphicsPipeline->shaderResourceBindings());
+ const int firstVertexBinding = srbD->maxBinding + 1;
if (firstVertexBinding != cbD->d->currentFirstVertexBinding
|| buffers != cbD->d->currentVertexInputsBuffers
@@ -1328,7 +1459,7 @@ void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
executeBufferHostWritesForCurrentFrame(ibufD);
ibufD->lastActiveFrameSlot = currentFrameSlot;
- cbD->currentIndexBuffer = indexBuf;
+ cbD->currentIndexBuffer = ibufD;
cbD->currentIndexOffset = indexOffset;
cbD->currentIndexFormat = indexFormat;
} else {
@@ -1357,7 +1488,7 @@ void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
[cbD->d->currentRenderPassEncoder setViewport: vp];
- if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ if (!cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
MTLScissorRect s;
s.x = NSUInteger(x);
s.y = NSUInteger(y);
@@ -1371,7 +1502,7 @@ void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
{
QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
- Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ Q_ASSERT(cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
const QSize outputSize = cbD->currentTarget->pixelSize();
// x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
@@ -1405,20 +1536,240 @@ void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
[cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
}
+static id<MTLComputeCommandEncoder> tessellationComputeEncoder(QMetalCommandBuffer *cbD)
+{
+ if (cbD->d->currentRenderPassEncoder) {
+ [cbD->d->currentRenderPassEncoder endEncoding];
+ cbD->d->currentRenderPassEncoder = nil;
+ }
+
+ if (!cbD->d->tessellationComputeEncoder)
+ cbD->d->tessellationComputeEncoder = [cbD->d->cb computeCommandEncoder];
+
+ return cbD->d->tessellationComputeEncoder;
+}
+
+static void endTessellationComputeEncoding(QMetalCommandBuffer *cbD)
+{
+ if (cbD->d->tessellationComputeEncoder) {
+ [cbD->d->tessellationComputeEncoder endEncoding];
+ cbD->d->tessellationComputeEncoder = nil;
+ }
+
+ cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
+ cbD->resetPerPassCachedState();
+}
+
+void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
+{
+ QMetalCommandBuffer *cbD = args.cbD;
+ QMetalGraphicsPipeline *graphicsPipeline = cbD->currentGraphicsPipeline;
+ if (graphicsPipeline->d->tess.failed)
+ return;
+
+ const bool indexed = args.type != TessDrawArgs::NonIndexed;
+ const quint32 instanceCount = indexed ? args.drawIndexed.instanceCount : args.draw.instanceCount;
+ const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount;
+
+ QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess);
+ const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount);
+ QMetalBuffer *vertOutBuf = nullptr;
+ QMetalBuffer *tescOutBuf = nullptr;
+ QMetalBuffer *tescPatchOutBuf = nullptr;
+ QMetalBuffer *tescFactorBuf = nullptr;
+ QMetalBuffer *tescParamsBuf = nullptr;
+ id<MTLComputeCommandEncoder> vertTescComputeEncoder = tessellationComputeEncoder(cbD);
+
+ // Step 1: vertex shader (as compute)
+ {
+ id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
+ QShader::Variant shaderVariant = QShader::NonIndexedVertexAsComputeShader;
+ if (args.type == TessDrawArgs::U16Indexed)
+ shaderVariant = QShader::UInt16IndexedVertexAsComputeShader;
+ else if (args.type == TessDrawArgs::U32Indexed)
+ shaderVariant = QShader::UInt32IndexedVertexAsComputeShader;
+ const int varIndex = QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(shaderVariant);
+ id<MTLComputePipelineState> computePipelineState = tess.vsCompPipeline(this, shaderVariant);
+ [computeEncoder setComputePipelineState: computePipelineState];
+
+ // Make uniform buffers, textures, and samplers (meant for the
+ // vertex stage from the client's point of view) visible in the
+ // compute shaders (both vertex and tess.control).
+ cbD->d->currentComputePassEncoder = computeEncoder;
+ rebindShaderResources(cbD, QMetalShaderResourceBindingsData::VERTEX, QMetalShaderResourceBindingsData::COMPUTE);
+ cbD->d->currentComputePassEncoder = nil;
+
+ const QMap<int, int> &ebb(tess.compVs[varIndex].nativeShaderInfo.extraBufferBindings);
+ const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
+ const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
+
+ if (outputBufferBinding >= 0) {
+ const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount);
+ vertOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ if (!vertOutBuf)
+ return;
+ [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
+ }
+
+ if (indexBufferBinding >= 0)
+ [computeEncoder setBuffer: (id<MTLBuffer>) args.drawIndexed.indexBuffer offset: 0 atIndex: indexBufferBinding];
+
+ for (int i = 0, ie = cbD->d->currentVertexInputsBuffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(cbD->d->currentVertexInputsBuffers.batches[i]);
+ const auto &offsetBatch(cbD->d->currentVertexInputOffsets.batches[i]);
+ [computeEncoder setBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(uint(cbD->d->currentFirstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
+ }
+
+ if (indexed) {
+ [computeEncoder setStageInRegion: MTLRegionMake2D(args.drawIndexed.vertexOffset, args.drawIndexed.firstInstance,
+ args.drawIndexed.indexCount, args.drawIndexed.instanceCount)];
+ } else {
+ [computeEncoder setStageInRegion: MTLRegionMake2D(args.draw.firstVertex, args.draw.firstInstance,
+ args.draw.vertexCount, args.draw.instanceCount)];
+ }
+
+ [computeEncoder dispatchThreads: MTLSizeMake(vertexOrIndexCount, instanceCount, 1)
+ threadsPerThreadgroup: MTLSizeMake(computePipelineState.threadExecutionWidth, 1, 1)];
+ }
+
+ // Step 2: tessellation control shader (as compute)
+ {
+ id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
+ id<MTLComputePipelineState> computePipelineState = tess.tescCompPipeline(this);
+ [computeEncoder setComputePipelineState: computePipelineState];
+
+ // Shader resources are set already in step 1. (because srb stage
+ // flags for tesc and tese visibility are treated as if they were
+ // specified as vertex visibility -> QMSRBD::VERTEX includes those too)
+
+ const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
+ const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
+ const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
+ const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
+ const int paramsBufferBinding = ebb.value(QShaderPrivate::MslTessTescParamsBufferBinding, -1);
+ const int inputBufferBinding = ebb.value(QShaderPrivate::MslTessTescInputBufferBinding, -1);
+
+ if (outputBufferBinding >= 0) {
+ const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount);
+ tescOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ if (!tescOutBuf)
+ return;
+ [computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
+ }
+
+ if (patchOutputBufferBinding >= 0) {
+ const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount);
+ tescPatchOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ if (!tescPatchOutBuf)
+ return;
+ [computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
+ }
+
+ if (tessFactorBufferBinding >= 0) {
+ tescFactorBuf = tess.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
+ [computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
+ }
+
+ if (paramsBufferBinding >= 0) {
+ struct {
+ quint32 inControlPointCount;
+ quint32 patchCount;
+ } params;
+ tescParamsBuf = tess.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::Tessellation::WorkBufType::HostVisible);
+ if (!tescParamsBuf)
+ return;
+ params.inControlPointCount = tess.inControlPointCount;
+ params.patchCount = patchCount;
+ id<MTLBuffer> paramsBuf = tescParamsBuf->d->buf[0];
+ char *p = reinterpret_cast<char *>([paramsBuf contents]);
+ memcpy(p, &params, sizeof(params));
+ [computeEncoder setBuffer: paramsBuf offset: 0 atIndex: paramsBufferBinding];
+ }
+
+ if (vertOutBuf && inputBufferBinding >= 0)
+ [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: inputBufferBinding];
+
+ int sgSize = int(computePipelineState.threadExecutionWidth);
+ int wgSize = std::lcm(tess.outControlPointCount, sgSize);
+ while (wgSize > caps.maxThreadGroupSize) {
+ sgSize /= 2;
+ wgSize = std::lcm(tess.outControlPointCount, sgSize);
+ }
+ [computeEncoder dispatchThreads: MTLSizeMake(patchCount * tess.outControlPointCount, 1, 1)
+ threadsPerThreadgroup: MTLSizeMake(wgSize, 1, 1)];
+ }
+
+ // Much of the state in the QMetalCommandBuffer is going to be reset
+ // when we get a new render encoder. Save what we need. (cheaper than
+ // starting to walk over the srb again)
+ const QMetalShaderResourceBindingsData resourceBindings = cbD->d->currentShaderResourceBindingState;
+
+ endTessellationComputeEncoding(cbD);
+
+ // Step 3: tessellation evaluation (as vertex) + fragment shader
+ {
+ // No need to call tess.teseFragRenderPipeline because it was done
+ // once and we know the result is stored in the standard place
+ // (graphicsPipeline->d->ps).
+
+ graphicsPipeline->makeActiveForCurrentRenderPassEncoder(cbD);
+ id<MTLRenderCommandEncoder> renderEncoder = cbD->d->currentRenderPassEncoder;
+
+ rebindShaderResources(cbD, QMetalShaderResourceBindingsData::VERTEX, QMetalShaderResourceBindingsData::VERTEX, &resourceBindings);
+ rebindShaderResources(cbD, QMetalShaderResourceBindingsData::FRAGMENT, QMetalShaderResourceBindingsData::FRAGMENT, &resourceBindings);
+
+ const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
+ const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
+ const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
+ const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
+
+ if (outputBufferBinding >= 0 && tescOutBuf)
+ [renderEncoder setVertexBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
+
+ if (patchOutputBufferBinding >= 0 && tescPatchOutBuf)
+ [renderEncoder setVertexBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
+
+ if (tessFactorBufferBinding >= 0 && tescFactorBuf) {
+ [renderEncoder setTessellationFactorBuffer: tescFactorBuf->d->buf[0] offset: 0 instanceStride: 0];
+ [renderEncoder setVertexBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
+ }
+
+ [cbD->d->currentRenderPassEncoder drawPatches: tess.outControlPointCount
+ patchStart: 0
+ patchCount: patchCount
+ patchIndexBuffer: nil
+ patchIndexBufferOffset: 0
+ instanceCount: 1
+ baseInstance: 0];
+ }
+}
+
void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
{
QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ if (cbD->currentGraphicsPipeline->d->tess.enabled) {
+ TessDrawArgs a;
+ a.cbD = cbD;
+ a.type = TessDrawArgs::NonIndexed;
+ a.draw.vertexCount = vertexCount;
+ a.draw.instanceCount = instanceCount;
+ a.draw.firstVertex = firstVertex;
+ a.draw.firstInstance = firstInstance;
+ tessellatedDraw(a);
+ return;
+ }
+
if (caps.baseVertexAndInstance) {
- [cbD->d->currentRenderPassEncoder drawPrimitives:
- QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
- vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
+ [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
+ vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
} else {
- [cbD->d->currentRenderPassEncoder drawPrimitives:
- QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
- vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount];
+ [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
+ vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount];
}
}
@@ -1434,23 +1785,37 @@ void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
Q_ASSERT(indexOffset == aligned(indexOffset, 4u));
- QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
- id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
+ QMetalBuffer *ibufD = cbD->currentIndexBuffer;
+ id<MTLBuffer> mtlibuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
+
+ if (cbD->currentGraphicsPipeline->d->tess.enabled) {
+ TessDrawArgs a;
+ a.cbD = cbD;
+ a.type = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? TessDrawArgs::U16Indexed : TessDrawArgs::U32Indexed;
+ a.drawIndexed.indexCount = indexCount;
+ a.drawIndexed.instanceCount = instanceCount;
+ a.drawIndexed.firstIndex = firstIndex;
+ a.drawIndexed.vertexOffset = vertexOffset;
+ a.drawIndexed.firstInstance = firstInstance;
+ a.drawIndexed.indexBuffer = mtlibuf;
+ tessellatedDraw(a);
+ return;
+ }
if (caps.baseVertexAndInstance) {
- [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
indexCount: indexCount
indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
- indexBuffer: mtlbuf
+ indexBuffer: mtlibuf
indexBufferOffset: indexOffset
instanceCount: instanceCount
baseVertex: vertexOffset
baseInstance: firstInstance];
} else {
- [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
indexCount: indexCount
indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
- indexBuffer: mtlbuf
+ indexBuffer: mtlibuf
indexBufferOffset: indexOffset
instanceCount: instanceCount];
}
@@ -2221,9 +2586,9 @@ void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *p
Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps);
- if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ if (cbD->currentComputePipeline != psD || cbD->currentPipelineGeneration != psD->generation) {
cbD->currentGraphicsPipeline = nullptr;
- cbD->currentComputePipeline = ps;
+ cbD->currentComputePipeline = psD;
cbD->currentPipelineGeneration = psD->generation;
[cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
@@ -2289,8 +2654,12 @@ void QRhiMetal::executeDeferredReleases(bool forced)
[e.stagingBuffer.buffer release];
break;
case QRhiMetalData::DeferredReleaseEntry::GraphicsPipeline:
- [e.graphicsPipeline.depthStencilState release];
[e.graphicsPipeline.pipelineState release];
+ [e.graphicsPipeline.depthStencilState release];
+ [e.graphicsPipeline.tessVertexComputeState[0] release];
+ [e.graphicsPipeline.tessVertexComputeState[1] release];
+ [e.graphicsPipeline.tessVertexComputeState[2] release];
+ [e.graphicsPipeline.tessTessControlComputeState release];
break;
case QRhiMetalData::DeferredReleaseEntry::ComputePipeline:
[e.computePipeline.pipelineState release];
@@ -2393,6 +2762,9 @@ bool QMetalBuffer::create()
// Static maps to on macOS) is not safe when another frame reading from the
// same buffer is still in flight.
d->slotted = !m_usage.testFlag(QRhiBuffer::StorageBuffer); // except for SSBOs written in the shader
+ // and a special case for internal work buffers
+ if (int(m_usage) == WorkBufPoolUsage)
+ d->slotted = false;
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
if (i == 0 || d->slotted) {
@@ -3453,6 +3825,8 @@ QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
: QRhiGraphicsPipeline(rhi),
d(new QMetalGraphicsPipelineData)
{
+ d->q = this;
+ d->tess.q = d;
}
QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
@@ -3466,16 +3840,36 @@ void QMetalGraphicsPipeline::destroy()
d->vs.destroy();
d->fs.destroy();
- if (!d->ps)
+ d->tess.compVs[0].destroy();
+ d->tess.compVs[1].destroy();
+ d->tess.compVs[2].destroy();
+
+ d->tess.compTesc.destroy();
+ d->tess.vertTese.destroy();
+
+ qDeleteAll(d->tess.deviceLocalWorkBuffers);
+ d->tess.deviceLocalWorkBuffers.clear();
+ qDeleteAll(d->tess.hostVisibleWorkBuffers);
+ d->tess.hostVisibleWorkBuffers.clear();
+
+ if (!d->ps && !d->ds
+ && !d->tess.vertexComputeState[0] && !d->tess.vertexComputeState[1] && !d->tess.vertexComputeState[2]
+ && !d->tess.tessControlComputeState)
+ {
return;
+ }
QRhiMetalData::DeferredReleaseEntry e;
e.type = QRhiMetalData::DeferredReleaseEntry::GraphicsPipeline;
e.lastActiveFrameSlot = lastActiveFrameSlot;
- e.graphicsPipeline.depthStencilState = d->ds;
e.graphicsPipeline.pipelineState = d->ps;
- d->ds = nil;
+ e.graphicsPipeline.depthStencilState = d->ds;
+ e.graphicsPipeline.tessVertexComputeState = d->tess.vertexComputeState;
+ e.graphicsPipeline.tessTessControlComputeState = d->tess.tessControlComputeState;
d->ps = nil;
+ d->ds = nil;
+ d->tess.vertexComputeState = {};
+ d->tess.tessControlComputeState = nil;
QRHI_RES_RHI(QRhiMetal);
if (rhiD) {
@@ -3700,6 +4094,34 @@ static inline MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::
}
}
+static inline MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
+{
+ switch (w) {
+ case QShaderDescription::CwTessellationWindingOrder:
+ return MTLWindingClockwise;
+ case QShaderDescription::CcwTessellationWindingOrder:
+ return MTLWindingCounterClockwise;
+ default:
+ // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
+ return MTLWindingCounterClockwise;
+ }
+}
+
+static inline MTLTessellationPartitionMode toMetalTessellationPartitionMode(QShaderDescription::TessellationPartitioning p)
+{
+ switch (p) {
+ case QShaderDescription::EqualTessellationPartitioning:
+ return MTLTessellationPartitionModePow2;
+ case QShaderDescription::FractionalEvenTessellationPartitioning:
+ return MTLTessellationPartitionModeFractionalEven;
+ case QShaderDescription::FractionalOddTessellationPartitioning:
+ return MTLTessellationPartitionModeFractionalOdd;
+ default:
+ // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
+ return MTLTessellationPartitionModePow2;
+ }
+}
+
id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
{
@@ -3768,48 +4190,135 @@ id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const
return f;
}
-bool QMetalGraphicsPipeline::create()
+void QMetalGraphicsPipeline::setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD)
{
- if (d->ps)
- destroy();
+ MTLRenderPipelineDescriptor *rpDesc = reinterpret_cast<MTLRenderPipelineDescriptor *>(metalRpDesc);
+
+ if (rpD->colorAttachmentCount) {
+ // defaults when no targetBlends are provided
+ rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
+ rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
+ rpDesc.colorAttachments[0].blendingEnabled = false;
+
+ Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
+ || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
+
+ for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
+ rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
+ rpDesc.colorAttachments[i].blendingEnabled = b.enable;
+ rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
+ rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
+ rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
+ rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
+ rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
+ rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
+ rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
+ }
+ }
+
+ if (rpD->hasDepthStencil) {
+ // Must only be set when a depth-stencil buffer will actually be bound,
+ // validation blows up otherwise.
+ MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
+ rpDesc.depthAttachmentPixelFormat = fmt;
+#if defined(Q_OS_MACOS)
+ if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
+#else
+ if (fmt != MTLPixelFormatDepth32Float)
+#endif
+ rpDesc.stencilAttachmentPixelFormat = fmt;
+ }
QRHI_RES_RHI(QRhiMetal);
- rhiD->pipelineCreationStart();
- if (!rhiD->sanityCheckGraphicsPipeline(this))
- return false;
+ rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
+}
+
+void QMetalGraphicsPipeline::setupMetalDepthStencilDescriptor(void *metalDsDesc)
+{
+ MTLDepthStencilDescriptor *dsDesc = reinterpret_cast<MTLDepthStencilDescriptor *>(metalDsDesc);
+
+ dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
+ dsDesc.depthWriteEnabled = m_depthWrite;
+ if (m_stencilTest) {
+ dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
+ dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
+ dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
+ dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
+ dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
+ dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
+ dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
+ dsDesc.backFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
+ }
+}
+
+void QMetalGraphicsPipeline::mapStates()
+{
+ d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
+ d->cullMode = toMetalCullMode(m_cullMode);
+ d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
+ d->depthBias = float(m_depthBias);
+ d->slopeScaledDepthBias = m_slopeScaledDepthBias;
+}
+
+template<typename T>
+void QMetalGraphicsPipelineData::setupVertexOrStageInputDescriptor(T *desc)
+{
// same binding space for vertex and constant buffers - work it around
- const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
- MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
- for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
+ QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
+ for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
it != itEnd; ++it)
{
const uint loc = uint(it->location());
- inputLayout.attributes[loc].format = toMetalAttributeFormat(it->format());
- inputLayout.attributes[loc].offset = NSUInteger(it->offset());
- inputLayout.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
+ // either MTLVertexFormat or MTLAttributeFormat, the values are the same
+ desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
+ desc.attributes[loc].offset = NSUInteger(it->offset());
+ desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
}
int bindingIndex = 0;
- for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings();
+ for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
it != itEnd; ++it, ++bindingIndex)
{
const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
- inputLayout.layouts[layoutIdx].stepFunction =
- it->classification() == QRhiVertexInputBinding::PerInstance
- ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
- inputLayout.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
- inputLayout.layouts[layoutIdx].stride = it->stride();
+ using StepT = decltype(desc.layouts[layoutIdx].stepFunction);
+ if (std::is_same_v<StepT, MTLStepFunction>) {
+ desc.layouts[layoutIdx].stepFunction = StepT(
+ it->classification() == QRhiVertexInputBinding::PerInstance
+ ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX);
+ } else {
+ desc.layouts[layoutIdx].stepFunction = StepT(
+ it->classification() == QRhiVertexInputBinding::PerInstance
+ ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex);
+ }
+ desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
+ desc.layouts[layoutIdx].stride = it->stride();
}
+}
- MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+bool QMetalGraphicsPipeline::createVertexFragmentPipeline()
+{
+ QRHI_RES_RHI(QRhiMetal);
- rpDesc.vertexDescriptor = inputLayout;
+ MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
+ d->setupVertexOrStageInputDescriptor(vertexDesc);
+
+ MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+ rpDesc.vertexDescriptor = vertexDesc;
- // mutability cannot be determined (slotted buffers could be set as
+ // Mutability cannot be determined (slotted buffers could be set as
// MTLMutabilityImmutable, but then we potentially need a different
// descriptor for each buffer combination as this depends on the actual
- // buffers not just the resource binding layout) so leave it at the default
+ // buffers not just the resource binding layout), so leave
+ // rpDesc.vertex/fragmentBuffers at the defaults.
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
auto cacheIt = rhiD->d->shaderCache.constFind(shaderStage);
@@ -3881,85 +4390,557 @@ bool QMetalGraphicsPipeline::create()
}
QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
+ setupAttachmentsInMetalRenderPassDescriptor(rpDesc, rpD);
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
+ [rpDesc release];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ return false;
+ }
- if (rpD->colorAttachmentCount) {
- // defaults when no targetBlends are provided
- rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
- rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
- rpDesc.colorAttachments[0].blendingEnabled = false;
+ MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
+ setupMetalDepthStencilDescriptor(dsDesc);
+ d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
+ [dsDesc release];
- Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
- || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
+ d->primitiveType = toMetalPrimitiveType(m_topology);
+ mapStates();
- for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
- const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
- rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
- rpDesc.colorAttachments[i].blendingEnabled = b.enable;
- rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
- rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
- rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
- rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
- rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
- rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
- rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
+ return true;
+}
+
+int QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(QShader::Variant vertexCompVariant)
+{
+ switch (vertexCompVariant) {
+ case QShader::NonIndexedVertexAsComputeShader:
+ return 0;
+ case QShader::UInt32IndexedVertexAsComputeShader:
+ return 1;
+ case QShader::UInt16IndexedVertexAsComputeShader:
+ return 2;
+ default:
+ break;
+ }
+ return -1;
+}
+
+id<MTLComputePipelineState> QMetalGraphicsPipelineData::Tessellation::vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant)
+{
+ const int varIndex = vsCompVariantToIndex(vertexCompVariant);
+ if (varIndex >= 0 && vertexComputeState[varIndex])
+ return vertexComputeState[varIndex];
+
+ id<MTLFunction> func = nil;
+ if (varIndex >= 0)
+ func = compVs[varIndex].func;
+
+ if (!func) {
+ qWarning("No compute function found for vertex shader translated for tessellation, this should not happen");
+ return nil;
+ }
+
+ const QMap<int, int> &ebb(compVs[varIndex].nativeShaderInfo.extraBufferBindings);
+ const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
+
+ MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
+ cpDesc.computeFunction = func;
+ cpDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
+ cpDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
+ if (indexBufferBinding >= 0) {
+ if (vertexCompVariant == QShader::UInt32IndexedVertexAsComputeShader) {
+ cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt32;
+ cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
+ } else if (vertexCompVariant == QShader::UInt16IndexedVertexAsComputeShader) {
+ cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt16;
+ cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
}
}
+ q->setupVertexOrStageInputDescriptor(cpDesc.stageInputDescriptor);
- if (rpD->hasDepthStencil) {
- // Must only be set when a depth-stencil buffer will actually be bound,
- // validation blows up otherwise.
- MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
- rpDesc.depthAttachmentPixelFormat = fmt;
-#if defined(Q_OS_MACOS)
- if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
-#else
- if (fmt != MTLPixelFormatDepth32Float)
-#endif
- rpDesc.stencilAttachmentPixelFormat = fmt;
+ NSError *err = nil;
+ id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
+ options: MTLPipelineOptionNone
+ reflection: nil
+ error: &err];
+ if (!ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
+ } else {
+ vertexComputeState[varIndex] = ps;
}
+ // not retained, the only owner is vertexComputeState and so the QRhiGraphicsPipeline
+ return ps;
+}
- rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
+id<MTLComputePipelineState> QMetalGraphicsPipelineData::Tessellation::tescCompPipeline(QRhiMetal *rhiD)
+{
+ if (tessControlComputeState)
+ return tessControlComputeState;
+
+ MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
+ cpDesc.computeFunction = compTesc.func;
NSError *err = nil;
- d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
- if (!d->ps) {
+ id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
+ options: MTLPipelineOptionNone
+ reflection: nil
+ error: &err];
+ if (!ps) {
const QString msg = QString::fromNSString(err.localizedDescription);
- qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
- [rpDesc release];
- return false;
+ qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
+ } else {
+ tessControlComputeState = ps;
+ }
+ // not retained, the only owner is tessControlComputeState and so the QRhiGraphicsPipeline
+ return ps;
+}
+
+static inline bool hasBuiltin(const QVector<QShaderDescription::BuiltinVariable> &builtinList, QShaderDescription::BuiltinType builtin)
+{
+ return std::find_if(builtinList.cbegin(), builtinList.cend(),
+ [builtin](const QShaderDescription::BuiltinVariable &b) { return b.type == builtin; }) != builtinList.cend();
+}
+
+id<MTLRenderPipelineState> QMetalGraphicsPipelineData::Tessellation::teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline)
+{
+ if (pipeline->d->ps)
+ return pipeline->d->ps;
+
+ MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+ MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
+
+ // Going to use the same buffer indices for the extra buffers as the tess.control compute shader did.
+ const QMap<int, int> &ebb(compTesc.nativeShaderInfo.extraBufferBindings);
+ const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
+ const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
+ const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
+
+ QVarLengthArray<int, 16> teseInputLocations;
+ for (const QShaderDescription::InOutVariable &v : vertTese.desc.inputVariables())
+ teseInputLocations.append(v.location);
+
+ quint32 offsetInTescOutput = 0;
+ quint32 offsetInTescPatchOutput = 0;
+ int lastLocation = -1;
+
+ for (const QShaderDescription::InOutVariable &tescOutVar : compTesc.desc.outputVariables()) {
+ const int location = tescOutVar.location;
+ lastLocation = location;
+ const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(tescOutVar.type);
+ if (teseInputLocations.contains(location)) {
+ if (tescOutVar.perPatch) {
+ if (tescPatchOutputBufferBinding >= 0) {
+ vertexDesc.attributes[location].bufferIndex = tescPatchOutputBufferBinding;
+ vertexDesc.attributes[location].format = toMetalAttributeFormat(format);
+ vertexDesc.attributes[location].offset = offsetInTescPatchOutput;
+ }
+ } else {
+ if (tescOutputBufferBinding >= 0) {
+ vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding;
+ vertexDesc.attributes[location].format = toMetalAttributeFormat(format);
+ vertexDesc.attributes[location].offset = offsetInTescOutput;
+ }
+ }
+ }
+ if (tescOutVar.perPatch)
+ offsetInTescPatchOutput += rhiD->byteSizePerVertexForVertexInputFormat(format);
+ else
+ offsetInTescOutput += rhiD->byteSizePerVertexForVertexInputFormat(format);
+ }
+
+ const QVector<QShaderDescription::BuiltinVariable> tescOutBuiltins = compTesc.desc.outputBuiltinVariables();
+ const QVector<QShaderDescription::BuiltinVariable> teseInBuiltins = vertTese.desc.inputBuiltinVariables();
+
+ // Take a tess.control shader with an output variable layout(location = 0) out vec3 outColor[].
+ // Assume it also writes to glPosition, e.g. gl_out[gl_InvocationID].gl_Position = ...
+ // The tess.eval. shader translated to a Metal vertex function will then contain:
+ //
+ // struct main0_in {
+ // float3 inColor [[attribute(0)]];
+ // float4 gl_Position [[attribute(1)]]; }
+ //
+ // The vertex description has to be set up accordingly. The color is
+ // simple because that will be in the input/output variable list with
+ // location 0. The position is a builtin however. So for now just
+ // assume that builtins such as that come after the other variables,
+ // with increasing location values.
+
+ if (hasBuiltin(tescOutBuiltins, QShaderDescription::PositionBuiltin)
+ && hasBuiltin(teseInBuiltins, QShaderDescription::PositionBuiltin)
+ && tescOutputBufferBinding >= 0)
+ {
+ const int location = ++lastLocation;
+ vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding;
+ vertexDesc.attributes[location].format = toMetalAttributeFormat(QRhiVertexInputAttribute::Float4);
+ vertexDesc.attributes[location].offset = offsetInTescOutput;
+ offsetInTescOutput += 4 * sizeof(float);
+ }
+
+ // Per-patch outputs from the tess.control stage. are mostly handled above.
+ // Consider:
+ // layout(location = 1) patch in vec3 stuff;
+ // layout(location = 2) patch in float more_stuff;
+ //
+ // This maps to:
+ //
+ // struct main0_patchIn {
+ // float3 stuff [[attribute(1)]];
+ // float more_stuff [[attribute(2)]];
+ // patch_control_point<main0_in> gl_in; };
+ //
+ // These are already in place (location 1 and 2, referencing the per-patch
+ // output buffer of tesc) at this point. But now if the tess.eval.shader
+ // reads gl_TessLevelInner and gl_TessLevelOuter, which are also per-patch,
+ // that adds, if the mode is triangles:
+ // (assuming gl_Position got location 3, sorted based on the builtin type
+ // (Position < Outer < Inner))
+ //
+ // float4 gl_TessLevel [[attribute(4)]];
+ //
+ // or if the mode is quads:
+ //
+ // float4 gl_TessLevelOuter [[attribute(4)]];
+ // float2 gl_TessLevelInner [[attribute(5)]];
+ //
+ // Like gl_Position, these built-ins needs to be handled specially.
+ // Note that the data is in a dedicated buffer, not in the patch buffer.
+
+ const bool hasTessLevelOuter = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelOuterBuiltin)
+ && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelOuterBuiltin);
+ const bool hasTessLevelInner = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelInnerBuiltin)
+ && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelInnerBuiltin);
+ if (vertTese.desc.tessellationMode() != QShaderDescription::TrianglesTessellationMode
+ && vertTese.desc.tessellationMode() != QShaderDescription::QuadTessellationMode)
+ {
+ qWarning("Tessellation evaluation stage mode is neither 'triangles' nor 'quads', this should not happen");
+ }
+ const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode;
+ if ((hasTessLevelOuter || hasTessLevelInner) && tessFactorBufferBinding >= 0) {
+ int loc0 = -1;
+ int loc1 = -1;
+ if (trianglesMode) {
+ loc0 = ++lastLocation; // float4 gl_TessLevel
+ } else {
+ loc0 = ++lastLocation; // float4 gl_TessLevelOuter
+ loc1 = ++lastLocation; // float2 gl_TessLevelInner
+ }
+ if (loc0 >= 0) {
+ vertexDesc.attributes[loc0].bufferIndex = tessFactorBufferBinding;
+ vertexDesc.attributes[loc0].format = MTLVertexFormatHalf4;
+ vertexDesc.attributes[loc0].offset = 0;
+ }
+ if (loc1 >= 0) {
+ vertexDesc.attributes[loc1].bufferIndex = tessFactorBufferBinding;
+ vertexDesc.attributes[loc1].format = MTLVertexFormatHalf2;
+ vertexDesc.attributes[loc1].offset = 8;
+ }
+ vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
+ vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? 8 : 12;
+ }
+
+ if (offsetInTescOutput > 0) {
+ vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint;
+ vertexDesc.layouts[tescOutputBufferBinding].stride = offsetInTescOutput;
+ }
+
+ if (offsetInTescPatchOutput > 0) {
+ vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
+ vertexDesc.layouts[tescPatchOutputBufferBinding].stride = offsetInTescPatchOutput;
}
+
+ rpDesc.vertexDescriptor = vertexDesc;
+ rpDesc.vertexFunction = vertTese.func;
+ rpDesc.fragmentFunction = pipeline->d->fs.func;
+
+ // The portable, cross-API approach is to use CCW, the results are then
+ // identical (assuming the applied clipSpaceCorrMatrix) for all the 3D
+ // APIs. The tess.eval. GLSL shader is thus expected to specify ccw. If it
+ // doesn't, things may not work as expected.
+ rpDesc.tessellationOutputWindingOrder = toMetalTessellationWindingOrder(vertTese.desc.tessellationWindingOrder());
+
+ rpDesc.tessellationPartitionMode = toMetalTessellationPartitionMode(vertTese.desc.tessellationPartitioning());
+
+ QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, pipeline->renderPassDescriptor());
+ pipeline->setupAttachmentsInMetalRenderPassDescriptor(rpDesc, rpD);
+
+ NSError *err = nil;
+ id<MTLRenderPipelineState> ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
[rpDesc release];
+ if (!ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state for tessellation: %s", qPrintable(msg));
+ } else {
+ // ps is stored in the QMetalGraphicsPipelineData so the end result in this
+ // regard is no different from what createVertexFragmentPipeline does
+ pipeline->d->ps = ps;
+ }
+ return ps;
+}
- MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
- dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
- dsDesc.depthWriteEnabled = m_depthWrite;
- if (m_stencilTest) {
- dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
- dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
- dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
- dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
- dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
- dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
- dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
+QMetalBuffer *QMetalGraphicsPipelineData::Tessellation::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type)
+{
+ QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers;
- dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
- dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
- dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
- dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
- dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
- dsDesc.backFaceStencil.readMask = m_stencilReadMask;
- dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
+ // Check if something is reusable as-is.
+ for (QMetalBuffer *workBuf : *workBuffers) {
+ if (workBuf && workBuf->lastActiveFrameSlot == -1 && workBuf->size() >= size) {
+ workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
+ return workBuf;
+ }
}
+ // Once the pool is above a certain threshold, see if there is something
+ // unused (but too small) and recreate that our size.
+ if (workBuffers->count() > QMTL_FRAMES_IN_FLIGHT * 8) {
+ for (QMetalBuffer *workBuf : *workBuffers) {
+ if (workBuf && workBuf->lastActiveFrameSlot == -1) {
+ workBuf->setSize(size);
+ if (workBuf->create()) {
+ workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
+ return workBuf;
+ }
+ }
+ }
+ }
+
+ // Add a new buffer to the pool.
+ QMetalBuffer *buf;
+ if (type == WorkBufType::DeviceLocal) {
+ // for GPU->GPU data (non-slotted, not necessarily host writable)
+ buf = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
+ } else {
+ // for CPU->GPU (non-slotted, host writable/coherent)
+ buf = new QMetalBuffer(rhiD, QRhiBuffer::Dynamic, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
+ }
+ if (buf->create()) {
+ buf->lastActiveFrameSlot = rhiD->currentFrameSlot;
+ workBuffers->append(buf);
+ return buf;
+ }
+
+ qWarning("Failed to acquire work buffer of size %u", size);
+ return nullptr;
+}
+
+bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag)
+{
+ QRHI_RES_RHI(QRhiMetal);
+ QString error;
+ QByteArray entryPoint;
+ QShaderKey activeKey;
+
+ const QShaderDescription tescDesc = tesc.description();
+ const QShaderDescription teseDesc = tese.description();
+ d->tess.inControlPointCount = uint(m_patchControlPointCount);
+ d->tess.outControlPointCount = tescDesc.tessellationOutputVertexCount();
+ if (!d->tess.outControlPointCount)
+ d->tess.outControlPointCount = teseDesc.tessellationOutputVertexCount();
+
+ if (!d->tess.outControlPointCount) {
+ qWarning("Failed to determine output vertex count from the tessellation control or evaluation shader, cannot tessellate");
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+
+ // Now the vertex shader is a compute shader.
+ // It should have three dedicated *VertexAsComputeShader variants.
+ // What the requested variant was (Standard or Batchable) plays no role here.
+ // (the Qt Quick scenegraph does not use tessellation with its materials)
+ // Create all three versions.
+
+ bool variantsPresent[3] = {};
+ const QVector<QShaderKey> tessVertKeys = tessVert.availableShaders();
+ for (const QShaderKey &k : tessVertKeys) {
+ switch (k.sourceVariant()) {
+ case QShader::NonIndexedVertexAsComputeShader:
+ variantsPresent[0] = true;
+ break;
+ case QShader::UInt32IndexedVertexAsComputeShader:
+ variantsPresent[1] = true;
+ break;
+ case QShader::UInt16IndexedVertexAsComputeShader:
+ variantsPresent[2] = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!(variantsPresent[0] && variantsPresent[1] && variantsPresent[2])) {
+ qWarning("Vertex shader is not prepared for Metal tessellation. Cannot tessellate. "
+ "Perhaps the relevant variants (UInt32IndexedVertexAsComputeShader et al) were not generated? "
+ "Try passing --msltess to qsb.");
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+
+ int varIndex = 0; // Will map NonIndexed as 0, UInt32 as 1, UInt16 as 2. Do not change this ordering.
+ for (QShader::Variant variant : {
+ QShader::NonIndexedVertexAsComputeShader,
+ QShader::UInt32IndexedVertexAsComputeShader,
+ QShader::UInt16IndexedVertexAsComputeShader })
+ {
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(tessVert, variant, &error, &entryPoint, &activeKey);
+ if (!lib) {
+ qWarning("MSL shader compilation failed for vertex-as-compute shader %d: %s", int(variant), qPrintable(error));
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ QMetalShader &compVs(d->tess.compVs[varIndex]);
+ compVs.lib = lib;
+ compVs.func = func;
+ compVs.desc = tessVert.description();
+ compVs.nativeResourceBindingMap = tessVert.nativeResourceBindingMap(activeKey);
+ compVs.nativeShaderInfo = tessVert.nativeShaderInfo(activeKey);
+
+ // pre-create all three MTLComputePipelineStates
+ if (!d->tess.vsCompPipeline(rhiD, variant)) {
+ qWarning("Failed to pre-generate compute pipeline for vertex compute shader (tessellation variant %d)", int(variant));
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+
+ ++varIndex;
+ }
+
+ // Pipeline #2 is a compute that runs the tessellation control (compute) shader
+ id<MTLLibrary> tessControlLib = rhiD->d->createMetalLib(tesc, QShader::StandardShader, &error, &entryPoint, &activeKey);
+ if (!tessControlLib) {
+ qWarning("MSL shader compilation failed for tessellation control compute shader: %s", qPrintable(error));
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ id<MTLFunction> tessControlFunc = rhiD->d->createMSLShaderFunction(tessControlLib, entryPoint);
+ if (!tessControlFunc) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [tessControlLib release];
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ d->tess.compTesc.lib = tessControlLib;
+ d->tess.compTesc.func = tessControlFunc;
+ d->tess.compTesc.desc = tesc.description();
+ d->tess.compTesc.nativeResourceBindingMap = tesc.nativeResourceBindingMap(activeKey);
+ d->tess.compTesc.nativeShaderInfo = tesc.nativeShaderInfo(activeKey);
+ if (!d->tess.tescCompPipeline(rhiD)) {
+ qWarning("Failed to pre-generate compute pipeline for tessellation control shader");
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+
+ // Pipeline #3 is a render pipeline with the tessellation evaluation (vertex) + the fragment shader
+ id<MTLLibrary> tessEvalLib = rhiD->d->createMetalLib(tese, QShader::StandardShader, &error, &entryPoint, &activeKey);
+ if (!tessEvalLib) {
+ qWarning("MSL shader compilation failed for tessellation evaluation vertex shader: %s", qPrintable(error));
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ id<MTLFunction> tessEvalFunc = rhiD->d->createMSLShaderFunction(tessEvalLib, entryPoint);
+ if (!tessEvalFunc) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [tessEvalLib release];
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ d->tess.vertTese.lib = tessEvalLib;
+ d->tess.vertTese.func = tessEvalFunc;
+ d->tess.vertTese.desc = tese.description();
+ d->tess.vertTese.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey);
+ d->tess.vertTese.nativeShaderInfo = tese.nativeShaderInfo(activeKey);
+
+ id<MTLLibrary> fragLib = rhiD->d->createMetalLib(tessFrag, QShader::StandardShader, &error, &entryPoint, &activeKey);
+ if (!fragLib) {
+ qWarning("MSL shader compilation failed for fragment shader: %s", qPrintable(error));
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ id<MTLFunction> fragFunc = rhiD->d->createMSLShaderFunction(fragLib, entryPoint);
+ if (!fragFunc) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [fragLib release];
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+ d->fs.lib = fragLib;
+ d->fs.func = fragFunc;
+ d->fs.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey);
+
+ if (!d->tess.teseFragRenderPipeline(rhiD, this)) {
+ qWarning("Failed to pre-generate render pipeline for tessellation evaluation + fragment shader");
+ d->tess.enabled = false;
+ d->tess.failed = true;
+ return false;
+ }
+
+ MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
+ setupMetalDepthStencilDescriptor(dsDesc);
d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
[dsDesc release];
- d->primitiveType = toMetalPrimitiveType(m_topology);
- d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
- d->cullMode = toMetalCullMode(m_cullMode);
- d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
- d->depthBias = float(m_depthBias);
- d->slopeScaledDepthBias = m_slopeScaledDepthBias;
+ // no primitiveType
+ mapStates();
+
+ return true;
+}
+
+bool QMetalGraphicsPipeline::create()
+{
+ destroy(); // no early test, always invoke and leave it to destroy to decide what to clean up
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->pipelineCreationStart();
+ if (!rhiD->sanityCheckGraphicsPipeline(this))
+ return false;
+
+ // See if tessellation is involved. Things will be very different, if so.
+ QShader tessVert;
+ QShader tesc;
+ QShader tese;
+ QShader tessFrag;
+ for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ switch (shaderStage.type()) {
+ case QRhiShaderStage::Vertex:
+ tessVert = shaderStage.shader();
+ break;
+ case QRhiShaderStage::TessellationControl:
+ tesc = shaderStage.shader();
+ break;
+ case QRhiShaderStage::TessellationEvaluation:
+ tese = shaderStage.shader();
+ break;
+ case QRhiShaderStage::Fragment:
+ tessFrag = shaderStage.shader();
+ break;
+ default:
+ break;
+ }
+ }
+ d->tess.enabled = tesc.isValid() && tese.isValid() && m_topology == Patches && m_patchControlPointCount > 0;
+ d->tess.failed = false;
+
+ bool ok = d->tess.enabled ? createTessellationPipelines(tessVert, tesc, tese, tessFrag) : createVertexFragmentPipeline();
+ if (!ok)
+ return false;
rhiD->pipelineCreationEnd();
lastActiveFrameSlot = -1;
@@ -4050,7 +5031,7 @@ bool QMetalComputePipeline::create()
d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->cs.func error: &err];
if (!d->ps) {
const QString msg = QString::fromNSString(err.localizedDescription);
- qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
return false;
}
@@ -4090,6 +5071,7 @@ void QMetalCommandBuffer::resetState()
{
d->currentRenderPassEncoder = nil;
d->currentComputePassEncoder = nil;
+ d->tessellationComputeEncoder = nil;
d->currentPassRpDesc = nil;
resetPerPassState();
}
diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h
index bba6134c0e..fe43e46f41 100644
--- a/src/gui/rhi/qrhimetal_p_p.h
+++ b/src/gui/rhi/qrhimetal_p_p.h
@@ -41,6 +41,9 @@ struct QMetalBuffer : public QRhiBuffer
int lastActiveFrameSlot = -1;
friend class QRhiMetal;
friend struct QMetalShaderResourceBindings;
+
+ static constexpr int WorkBufPoolUsage = 1 << 8;
+ static_assert(WorkBufPoolUsage > QRhiBuffer::StorageBuffer);
};
struct QMetalRenderBufferData;
@@ -204,6 +207,7 @@ struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings
};
struct QMetalGraphicsPipelineData;
+struct QMetalCommandBuffer;
struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
{
@@ -212,6 +216,13 @@ struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
void destroy() override;
bool create() override;
+ void makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD);
+ void setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD);
+ void setupMetalDepthStencilDescriptor(void *metalDsDesc);
+ void mapStates();
+ bool createVertexFragmentPipeline();
+ bool createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag);
+
QMetalGraphicsPipelineData *d;
uint generation = 0;
int lastActiveFrameSlot = -1;
@@ -256,14 +267,14 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer
QRhiRenderTarget *currentTarget;
// per-pass (render or compute command encoder) volatile (cached) state
- QRhiGraphicsPipeline *currentGraphicsPipeline;
- QRhiComputePipeline *currentComputePipeline;
+ QMetalGraphicsPipeline *currentGraphicsPipeline;
+ QMetalComputePipeline *currentComputePipeline;
uint currentPipelineGeneration;
- QRhiShaderResourceBindings *currentGraphicsSrb;
- QRhiShaderResourceBindings *currentComputeSrb;
+ QMetalShaderResourceBindings *currentGraphicsSrb;
+ QMetalShaderResourceBindings *currentComputeSrb;
uint currentSrbGeneration;
int currentResSlot;
- QRhiBuffer *currentIndexBuffer;
+ QMetalBuffer *currentIndexBuffer;
quint32 currentIndexOffset;
QRhiCommandBuffer::IndexFormat currentIndexFormat;
int currentCullMode;
@@ -442,6 +453,33 @@ public:
bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]);
int effectiveSampleCount(int sampleCount) const;
+ struct TessDrawArgs {
+ QMetalCommandBuffer *cbD;
+ enum {
+ NonIndexed,
+ U16Indexed,
+ U32Indexed
+ } type;
+ struct NonIndexedArgs {
+ quint32 vertexCount;
+ quint32 instanceCount;
+ quint32 firstVertex;
+ quint32 firstInstance;
+ };
+ struct IndexedArgs {
+ quint32 indexCount;
+ quint32 instanceCount;
+ quint32 firstIndex;
+ qint32 vertexOffset;
+ quint32 firstInstance;
+ void *indexBuffer;
+ };
+ union {
+ NonIndexedArgs draw;
+ IndexedArgs drawIndexed;
+ };
+ };
+ void tessellatedDraw(const TessDrawArgs &args);
bool importedDevice = false;
bool importedCmdQueue = false;
diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp
index 8a0e841132..6802be36ef 100644
--- a/src/gui/rhi/qshader.cpp
+++ b/src/gui/rhi/qshader.cpp
@@ -167,7 +167,31 @@ QT_BEGIN_NAMESPACE
Describes what kind of shader code an entry contains.
\value StandardShader The normal, unmodified version of the shader code.
+
\value BatchableVertexShader Vertex shader rewritten to be suitable for Qt Quick scenegraph batching.
+
+ \value UInt16IndexedVertexAsComputeShader A vertex shader meant to be used
+ in a Metal pipeline with tessellation in combination with indexed draw
+ calls sourcing index data from a uint16 index buffer. To support the Metal
+ tessellation pipeline, the vertex shader is translated to a compute shader
+ that may be dependent on the index buffer usage in the draw calls (e.g. if
+ the shader is using gl_VertexIndex), hence the need for three dedicated
+ variants.
+
+ \value UInt32IndexedVertexAsComputeShader A vertex shader meant to be used
+ in a Metal pipeline with tessellation in combination with indexed draw
+ calls sourcing index data from a uint32 index buffer. To support the Metal
+ tessellation pipeline, the vertex shader is translated to a compute shader
+ that may be dependent on the index buffer usage in the draw calls (e.g. if
+ the shader is using gl_VertexIndex), hence the need for three dedicated
+ variants.
+
+ \value NonIndexedVertexAsComputeShader A vertex shader meant to be used in
+ a Metal pipeline with tessellation in combination with non-indexed draw
+ calls. To support the Metal tessellation pipeline, the vertex shader is
+ translated to a compute shader that may be dependent on the index buffer
+ usage in the draw calls (e.g. if the shader is using gl_VertexIndex), hence
+ the need for three dedicated variants.
*/
/*!
@@ -367,6 +391,19 @@ QByteArray QShader::serialized() const
ds << listIt->samplerBinding;
}
}
+ ds << int(d->nativeShaderInfoMap.count());
+ for (auto it = d->nativeShaderInfoMap.cbegin(), itEnd = d->nativeShaderInfoMap.cend(); it != itEnd; ++it) {
+ const QShaderKey &k(it.key());
+ writeShaderKey(&ds, k);
+ ds << it->flags;
+ ds << int(it->extraBufferBindings.count());
+ for (auto mapIt = it->extraBufferBindings.cbegin(), mapItEnd = it->extraBufferBindings.cend();
+ mapIt != mapItEnd; ++mapIt)
+ {
+ ds << mapIt.key();
+ ds << mapIt.value();
+ }
+ }
return qCompress(buf.buffer());
}
@@ -407,6 +444,7 @@ QShader QShader::fromSerialized(const QByteArray &data)
ds >> intVal;
d->qsbVersion = intVal;
if (d->qsbVersion != QShaderPrivate::QSB_VERSION
+ && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITH_CBOR
@@ -484,6 +522,26 @@ QShader QShader::fromSerialized(const QByteArray &data)
}
}
+ if (d->qsbVersion > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
+ ds >> count;
+ for (int i = 0; i < count; ++i) {
+ QShaderKey k;
+ readShaderKey(&ds, &k);
+ int flags;
+ ds >> flags;
+ QMap<int, int> extraBufferBindings;
+ int mapSize;
+ ds >> mapSize;
+ for (int b = 0; b < mapSize; ++b) {
+ int k, v;
+ ds >> k;
+ ds >> v;
+ extraBufferBindings.insert(k, v);
+ }
+ d->nativeShaderInfoMap.insert(k, { flags, extraBufferBindings });
+ }
+ }
+
return bs;
}
@@ -711,7 +769,7 @@ QDebug operator<<(QDebug dbg, const QShaderVersion &v)
/*!
\typedef QShader::NativeResourceBindingMap
- Synonym for QHash<int, QPair<int, int>>.
+ Synonym for QMap<int, QPair<int, int>>.
The resource binding model QRhi assumes is based on SPIR-V. This means that
uniform buffers, storage buffers, combined image samplers, and storage
@@ -839,4 +897,62 @@ void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &
d->combinedImageMap.erase(it);
}
+/*!
+ \struct QShader::NativeShaderInfo
+
+ Describes information about the native shader code, if applicable. This
+ becomes relevant with certain shader languages for certain shader stages,
+ in case the translation from SPIR-V involves the introduction of
+ additional, "magic" inputs, outputs, or resources in the generated shader.
+ Such additions may be dependent on the original source code (i.e. the usage
+ of various GLSL language constructs or built-ins), and therefore it needs
+ to be indicated in a dynamic manner if certain features got added to the
+ generated shader code.
+
+ As an example, consider a tessellation control shader with a per-patch (not
+ per-vertex) output variable. This is translated to a Metal compute shader
+ outputting (among others) into an spvPatchOut buffer. But this buffer would
+ not be present at all if per-patch output variables were not used. The fact
+ that the shader code relies on such a buffer present can be indicated by
+ the data in this struct.
+ */
+
+/*!
+ \return the native shader info struct for \a key, or an empty object if
+ there is no data available for \a key, for example because such a mapping
+ is not applicable for the shading language or the shader stage.
+ */
+QShader::NativeShaderInfo QShader::nativeShaderInfo(const QShaderKey &key) const
+{
+ auto it = d->nativeShaderInfoMap.constFind(key);
+ if (it == d->nativeShaderInfoMap.cend())
+ return {};
+
+ return it.value();
+}
+
+/*!
+ Stores the given native shader \a info associated with \a key.
+
+ \sa nativeShaderInfo()
+ */
+void QShader::setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info)
+{
+ detach();
+ d->nativeShaderInfoMap[key] = info;
+}
+
+/*!
+ Removes the native shader information for \a key.
+ */
+void QShader::removeNativeShaderInfo(const QShaderKey &key)
+{
+ auto it = d->nativeShaderInfoMap.find(key);
+ if (it == d->nativeShaderInfoMap.end())
+ return;
+
+ detach();
+ d->nativeShaderInfoMap.erase(it);
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h
index c6ef338bfa..335712b3e1 100644
--- a/src/gui/rhi/qshader_p.h
+++ b/src/gui/rhi/qshader_p.h
@@ -17,6 +17,7 @@
#include <QtGui/qtguiglobal.h>
#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
#include <private/qshaderdescription_p.h>
QT_BEGIN_NAMESPACE
@@ -102,7 +103,10 @@ public:
enum Variant {
StandardShader = 0,
- BatchableVertexShader
+ BatchableVertexShader,
+ UInt16IndexedVertexAsComputeShader,
+ UInt32IndexedVertexAsComputeShader,
+ NonIndexedVertexAsComputeShader
};
QShader();
@@ -127,7 +131,7 @@ public:
QByteArray serialized() const;
static QShader fromSerialized(const QByteArray &data);
- using NativeResourceBindingMap = QHash<int, QPair<int, int> >; // binding -> native_binding[, native_binding]
+ using NativeResourceBindingMap = QMap<int, QPair<int, int> >; // binding -> native_binding[, native_binding]
NativeResourceBindingMap nativeResourceBindingMap(const QShaderKey &key) const;
void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map);
void removeResourceBindingMap(const QShaderKey &key);
@@ -143,6 +147,14 @@ public:
const SeparateToCombinedImageSamplerMappingList &list);
void removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key);
+ struct NativeShaderInfo {
+ int flags = 0;
+ QMap<int, int> extraBufferBindings;
+ };
+ NativeShaderInfo nativeShaderInfo(const QShaderKey &key) const;
+ void setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info);
+ void removeNativeShaderInfo(const QShaderKey &key);
+
private:
QShaderPrivate *d;
friend struct QShaderPrivate;
diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h
index e9d1e31aaf..88406b1ea2 100644
--- a/src/gui/rhi/qshader_p_p.h
+++ b/src/gui/rhi/qshader_p_p.h
@@ -24,13 +24,23 @@ QT_BEGIN_NAMESPACE
struct Q_GUI_EXPORT QShaderPrivate
{
- static const int QSB_VERSION = 6;
+ static const int QSB_VERSION = 7;
+ static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6;
static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5;
static const int QSB_VERSION_WITHOUT_VAR_ARRAYDIMS = 4;
static const int QSB_VERSION_WITH_CBOR = 3;
static const int QSB_VERSION_WITH_BINARY_JSON = 2;
static const int QSB_VERSION_WITHOUT_BINDINGS = 1;
+ enum MslNativeShaderInfoExtraBufferBindings {
+ MslTessVertIndicesBufferBinding = 0,
+ MslTessVertTescOutputBufferBinding,
+ MslTessTescTessLevelBufferBinding,
+ MslTessTescPatchOutputBufferBinding,
+ MslTessTescParamsBufferBinding,
+ MslTessTescInputBufferBinding
+ };
+
QShaderPrivate()
: ref(1)
{
@@ -43,7 +53,8 @@ struct Q_GUI_EXPORT QShaderPrivate
desc(other.desc),
shaders(other.shaders),
bindings(other.bindings),
- combinedImageMap(other.combinedImageMap)
+ combinedImageMap(other.combinedImageMap),
+ nativeShaderInfoMap(other.nativeShaderInfoMap)
{
}
@@ -58,6 +69,7 @@ struct Q_GUI_EXPORT QShaderPrivate
QMap<QShaderKey, QShaderCode> shaders;
QMap<QShaderKey, QShader::NativeResourceBindingMap> bindings;
QMap<QShaderKey, QShader::SeparateToCombinedImageSamplerMappingList> combinedImageMap;
+ QMap<QShaderKey, QShader::NativeShaderInfo> nativeShaderInfoMap;
};
QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp
index d55caed210..2018a3bc13 100644
--- a/src/gui/rhi/qshaderdescription.cpp
+++ b/src/gui/rhi/qshaderdescription.cpp
@@ -302,7 +302,8 @@ bool QShaderDescription::isValid() const
return !d->inVars.isEmpty() || !d->outVars.isEmpty()
|| !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty()
|| !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty()
- || !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty();
+ || !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty()
+ || !d->inBuiltins.isEmpty() || !d->outBuiltins.isEmpty();
}
/*!
@@ -514,7 +515,26 @@ QList<QShaderDescription::InOutVariable> QShaderDescription::storageImages() con
}
/*!
- Returns the local size of a compute shader.
+ \return the list of active builtins used as input. For example, a
+ tessellation evaluation shader reading the value of gl_TessCoord and
+ gl_Position will have TessCoordBuiltin and PositionBuiltin listed here.
+ */
+QVector<QShaderDescription::BuiltinVariable> QShaderDescription::inputBuiltinVariables() const
+{
+ return d->inBuiltins;
+}
+
+/*!
+ \return the list of active built-in variables used as input. For example, a
+ vertex shader will very often have PositionBuiltin as an output built-in.
+ */
+QVector<QShaderDescription::BuiltinVariable> QShaderDescription::outputBuiltinVariables() const
+{
+ return d->outBuiltins;
+}
+
+/*!
+ \return the local size of a compute shader.
For example, for a compute shader with the following declaration the
function returns { 256, 16, 1}.
@@ -528,6 +548,101 @@ std::array<uint, 3> QShaderDescription::computeShaderLocalSize() const
return d->localSize;
}
+/*!
+ \return the number of output vertices.
+
+ For example, for a tessellation control shader with the following
+ declaration the function returns 3.
+
+ \badcode
+ layout(vertices = 3) out;
+ \endcode
+ */
+uint QShaderDescription::tessellationOutputVertexCount() const
+{
+ return d->tessOutVertCount;
+}
+
+/*!
+ \enum QShaderDescription::TessellationMode
+
+ \value UnknownTessellationMode
+ \value TrianglesTessellationMode
+ \value QuadTessellationMode
+ \value IsolinesTessellationMode
+ */
+
+/*!
+ \return the tessellation execution mode for a tessellation control or
+ evaluation shader.
+
+ When not set, the returned value is UnknownTessellationMode.
+
+ For example, for a tessellation evaluation shader with the following
+ declaration the function returns TrianglesTessellationMode.
+
+ \badcode
+ layout(triangles) in;
+ \endcode
+ */
+QShaderDescription::TessellationMode QShaderDescription::tessellationMode() const
+{
+ return d->tessMode;
+}
+
+/*!
+ \enum QShaderDescription::TessellationWindingOrder
+
+ \value UnknownTessellationWindingOrder
+ \value CwTessellationWindingOrder
+ \value CcwTessellationWindingOrder
+ */
+
+/*!
+ \return the tessellation winding order for a tessellation control or
+ evaluation shader.
+
+ When not set, the returned value is UnknownTessellationWindingOrder.
+
+ For example, for a tessellation evaluation shader with the following
+ declaration the function returns CcwTessellationWindingOrder.
+
+ \badcode
+ layout(triangles, fractional_odd_spacing, ccw) in;
+ \endcode
+ */
+QShaderDescription::TessellationWindingOrder QShaderDescription::tessellationWindingOrder() const
+{
+ return d->tessWind;
+}
+
+/*!
+ \enum QShaderDescription::TessellationPartitioning
+
+ \value UnknownTessellationPartitioning
+ \value EqualTessellationPartitioning
+ \value FractionalEvenTessellationPartitioning
+ \value FractionalOddTessellationPartitioning
+ */
+
+/*!
+ \return the tessellation partitioning mode for a tessellation control or
+ evaluation shader.
+
+ When not set, the returned value is UnknownTessellationPartitioning.
+
+ For example, for a tessellation evaluation shader with the following
+ declaration the function returns FractionalOddTessellationPartitioning.
+
+ \badcode
+ layout(triangles, fractional_odd_spacing, ccw) in;
+ \endcode
+ */
+QShaderDescription::TessellationPartitioning QShaderDescription::tessellationPartitioning() const
+{
+ return d->tessPart;
+}
+
static const struct TypeTab {
const char k[20];
QShaderDescription::VariableType v;
@@ -607,7 +722,7 @@ static const struct TypeTab {
{ "imageBuffer", QShaderDescription::ImageBuffer }
};
-static QLatin1StringView typeStr(const QShaderDescription::VariableType &t)
+static QLatin1StringView typeStr(QShaderDescription::VariableType t)
{
for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
if (typeTab[i].v == t)
@@ -662,7 +777,7 @@ static const struct ImageFormatTab {
{ "r8ui", QShaderDescription::ImageFormatR8ui }
};
-static QLatin1StringView imageFormatStr(const QShaderDescription::ImageFormat &f)
+static QLatin1StringView imageFormatStr(QShaderDescription::ImageFormat f)
{
for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
if (imageFormatTab[i].v == f)
@@ -671,6 +786,106 @@ static QLatin1StringView imageFormatStr(const QShaderDescription::ImageFormat &f
return {};
}
+static const struct BuiltinTypeTab {
+ const char k[21];
+ QShaderDescription::BuiltinType v;
+} builtinTypeTab[] = {
+ { "Position", QShaderDescription::PositionBuiltin },
+ { "PointSize", QShaderDescription::PointSizeBuiltin },
+ { "ClipDistance", QShaderDescription::ClipDistanceBuiltin },
+ { "CullDistance", QShaderDescription::CullDistanceBuiltin },
+ { "VertexId", QShaderDescription::VertexIdBuiltin },
+ { "InstanceId", QShaderDescription::InstanceIdBuiltin },
+ { "PrimitiveId", QShaderDescription::PrimitiveIdBuiltin },
+ { "InvocationId", QShaderDescription::InvocationIdBuiltin },
+ { "Layer", QShaderDescription::LayerBuiltin },
+ { "ViewportIndex", QShaderDescription::ViewportIndexBuiltin },
+ { "TessLevelOuter", QShaderDescription::TessLevelOuterBuiltin },
+ { "TessLevelInner", QShaderDescription::TessLevelInnerBuiltin },
+ { "TessCoord", QShaderDescription::TessCoordBuiltin },
+ { "PatchVertices", QShaderDescription::PatchVerticesBuiltin },
+ { "FragCoord", QShaderDescription::FragCoordBuiltin },
+ { "PointCoord", QShaderDescription::PointCoordBuiltin },
+ { "FrontFacing", QShaderDescription::FrontFacingBuiltin },
+ { "SampleId", QShaderDescription::SampleIdBuiltin },
+ { "SamplePosition", QShaderDescription::SamplePositionBuiltin },
+ { "SampleMask", QShaderDescription::SampleMaskBuiltin },
+ { "FragDepth", QShaderDescription::FragDepthBuiltin },
+ { "NumWorkGroups", QShaderDescription::NumWorkGroupsBuiltin },
+ { "WorkgroupSize", QShaderDescription::WorkgroupSizeBuiltin },
+ { "WorkgroupId", QShaderDescription::WorkgroupIdBuiltin },
+ { "LocalInvocationId", QShaderDescription::LocalInvocationIdBuiltin },
+ { "GlobalInvocationId", QShaderDescription::GlobalInvocationIdBuiltin },
+ { "LocalInvocationIndex", QShaderDescription::LocalInvocationIndexBuiltin },
+ { "VertexIndex", QShaderDescription::VertexIndexBuiltin },
+ { "InstanceIndex", QShaderDescription::InstanceIndexBuiltin }
+};
+
+static QLatin1StringView builtinTypeStr(QShaderDescription::BuiltinType t)
+{
+ for (size_t i = 0; i < sizeof(builtinTypeTab) / sizeof(BuiltinTypeTab); ++i) {
+ if (builtinTypeTab[i].v == t)
+ return QLatin1StringView(builtinTypeTab[i].k);
+ }
+ return {};
+}
+
+static const struct TessellationModeTab {
+ const char k[10];
+ QShaderDescription::TessellationMode v;
+} tessellationModeTab[] {
+ { "unknown", QShaderDescription::UnknownTessellationMode },
+ { "triangles", QShaderDescription::TrianglesTessellationMode },
+ { "quad", QShaderDescription::QuadTessellationMode },
+ { "isoline", QShaderDescription::IsolineTessellationMode }
+};
+
+static QLatin1StringView tessModeStr(QShaderDescription::TessellationMode mode)
+{
+ for (size_t i = 0; i < sizeof(tessellationModeTab) / sizeof(TessellationModeTab); ++i) {
+ if (tessellationModeTab[i].v == mode)
+ return QLatin1StringView(tessellationModeTab[i].k);
+ }
+ return {};
+}
+
+static const struct TessellationWindingOrderTab {
+ const char k[8];
+ QShaderDescription::TessellationWindingOrder v;
+} tessellationWindingOrderTab[] {
+ { "unknown", QShaderDescription::UnknownTessellationWindingOrder },
+ { "cw", QShaderDescription::CwTessellationWindingOrder },
+ { "ccw", QShaderDescription::CcwTessellationWindingOrder }
+};
+
+static QLatin1StringView tessWindStr(QShaderDescription::TessellationWindingOrder w)
+{
+ for (size_t i = 0; i < sizeof(tessellationWindingOrderTab) / sizeof(TessellationWindingOrderTab); ++i) {
+ if (tessellationWindingOrderTab[i].v == w)
+ return QLatin1StringView(tessellationWindingOrderTab[i].k);
+ }
+ return {};
+}
+
+static const struct TessellationPartitioningTab {
+ const char k[24];
+ QShaderDescription::TessellationPartitioning v;
+} tessellationPartitioningTab[] {
+ { "unknown", QShaderDescription::UnknownTessellationPartitioning },
+ { "equal_spacing", QShaderDescription::EqualTessellationPartitioning },
+ { "fractional_even_spacing", QShaderDescription::FractionalEvenTessellationPartitioning },
+ { "fractional_odd_spacing", QShaderDescription::FractionalOddTessellationPartitioning }
+};
+
+static QLatin1StringView tessPartStr(QShaderDescription::TessellationPartitioning p)
+{
+ for (size_t i = 0; i < sizeof(tessellationPartitioningTab) / sizeof(TessellationPartitioningTab); ++i) {
+ if (tessellationPartitioningTab[i].v == p)
+ return QLatin1StringView(tessellationPartitioningTab[i].k);
+ }
+ return {};
+}
+
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
{
@@ -688,6 +903,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
<< " storageImages " << d->storageImages
<< " separateImages " << d->separateImages
<< " separateSamplers " << d->separateSamplers
+ << " inBuiltins " << d->inBuiltins
+ << " outBuiltins " << d->outBuiltins
<< ')';
} else {
dbg.nospace() << "QShaderDescription(null)";
@@ -700,6 +917,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "InOutVariable(" << typeStr(var.type) << ' ' << var.name;
+ if (var.perPatch)
+ dbg.nospace() << " per-patch";
if (var.location >= 0)
dbg.nospace() << " location=" << var.location;
if (var.binding >= 0)
@@ -768,6 +987,13 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
dbg.nospace() << ' ' << blk.members << ')';
return dbg;
}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::BuiltinVariable &builtin)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type) << ")";
+ return dbg;
+}
#endif
#define JSON_KEY(key) static constexpr QLatin1StringView key ## Key() noexcept { return QLatin1StringView( #key ); }
@@ -776,6 +1002,7 @@ JSON_KEY(type)
JSON_KEY(location)
JSON_KEY(binding)
JSON_KEY(set)
+JSON_KEY(perPatch)
JSON_KEY(imageFormat)
JSON_KEY(imageFlags)
JSON_KEY(offset)
@@ -797,7 +1024,13 @@ JSON_KEY(pushConstantBlocks)
JSON_KEY(storageBlocks)
JSON_KEY(combinedImageSamplers)
JSON_KEY(storageImages)
-JSON_KEY(localSize)
+JSON_KEY(inBuiltins)
+JSON_KEY(outBuiltins)
+JSON_KEY(computeLocalSize)
+JSON_KEY(tessellationOutputVertexCount)
+JSON_KEY(tessellationMode)
+JSON_KEY(tessellationWindingOrder)
+JSON_KEY(tessellationPartitioning)
JSON_KEY(separateImages)
JSON_KEY(separateSamplers)
#undef JSON_KEY
@@ -810,6 +1043,8 @@ static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v
(*obj)[bindingKey()] = v.binding;
if (v.descriptorSet >= 0)
(*obj)[setKey()] = v.descriptorSet;
+ if (v.perPatch)
+ (*obj)[perPatchKey()] = v.perPatch;
if (v.imageFormat != QShaderDescription::ImageFormatUnknown)
(*obj)[imageFormatKey()] = imageFormatStr(v.imageFormat);
if (v.imageFlags)
@@ -832,6 +1067,7 @@ static void serializeDecorations(QDataStream *stream, const QShaderDescription::
(*stream) << int(v.arrayDims.count());
for (int dim : v.arrayDims)
(*stream) << dim;
+ (*stream) << quint8(v.perPatch);
}
static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
@@ -985,10 +1221,42 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc()
if (!jstorageImages.isEmpty())
root[storageImagesKey()] = jstorageImages;
- QJsonArray jlocalSize;
- for (int i = 0; i < 3; ++i)
- jlocalSize.append(QJsonValue(int(localSize[i])));
- root[localSizeKey()] = jlocalSize;
+ QJsonArray jinBuiltins;
+ for (const QShaderDescription::BuiltinVariable &v : qAsConst(inBuiltins)) {
+ QJsonObject builtin;
+ builtin[typeKey()] = builtinTypeStr(v.type);
+ jinBuiltins.append(builtin);
+ }
+ if (!jinBuiltins.isEmpty())
+ root[inBuiltinsKey()] = jinBuiltins;
+
+ QJsonArray joutBuiltins;
+ for (const QShaderDescription::BuiltinVariable &v : qAsConst(outBuiltins)) {
+ QJsonObject builtin;
+ builtin[typeKey()] = builtinTypeStr(v.type);
+ joutBuiltins.append(builtin);
+ }
+ if (!joutBuiltins.isEmpty())
+ root[outBuiltinsKey()] = joutBuiltins;
+
+ if (localSize[0] || localSize[1] || localSize[2]) {
+ QJsonArray jlocalSize;
+ for (size_t i = 0; i < 3; ++i)
+ jlocalSize.append(QJsonValue(int(localSize[i])));
+ root[computeLocalSizeKey()] = jlocalSize;
+ }
+
+ if (tessOutVertCount)
+ root[tessellationOutputVertexCountKey()] = int(tessOutVertCount);
+
+ if (tessMode != QShaderDescription::UnknownTessellationMode)
+ root[tessellationModeKey()] = tessModeStr(tessMode);
+
+ if (tessWind != QShaderDescription::UnknownTessellationWindingOrder)
+ root[tessellationWindingOrderKey()] = tessWindStr(tessWind);
+
+ if (tessPart != QShaderDescription::UnknownTessellationPartitioning)
+ root[tessellationPartitioningKey()] = tessPartStr(tessPart);
QJsonArray jseparateImages;
for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) {
@@ -1073,7 +1341,7 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream)
}
for (size_t i = 0; i < 3; ++i)
- (*stream) << localSize[i];
+ (*stream) << quint32(localSize[i]);
(*stream) << int(separateImages.count());
for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) {
@@ -1088,6 +1356,19 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream)
(*stream) << int(v.type);
serializeDecorations(stream, v);
}
+
+ (*stream) << quint32(tessOutVertCount);
+ (*stream) << quint32(tessMode);
+ (*stream) << quint32(tessWind);
+ (*stream) << quint32(tessPart);
+
+ (*stream) << int(inBuiltins.count());
+ for (const QShaderDescription::BuiltinVariable &v : qAsConst(inBuiltins))
+ (*stream) << int(v.type);
+
+ (*stream) << int(outBuiltins.count());
+ for (const QShaderDescription::BuiltinVariable &v : qAsConst(outBuiltins))
+ (*stream) << int(v.type);
}
static void deserializeDecorations(QDataStream *stream, int version, QShaderDescription::InOutVariable *v)
@@ -1107,6 +1388,12 @@ static void deserializeDecorations(QDataStream *stream, int version, QShaderDesc
for (int i = 0; i < f; ++i)
(*stream) >> v->arrayDims[i];
}
+
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
+ quint8 b;
+ (*stream) >> b;
+ v->perPatch = b;
+ }
}
static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version)
@@ -1237,8 +1524,11 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
deserializeDecorations(stream, version, &storageImages[i]);
}
- for (size_t i = 0; i < 3; ++i)
- (*stream) >> localSize[i];
+ for (size_t i = 0; i < 3; ++i) {
+ quint32 v;
+ (*stream) >> v;
+ localSize[i] = v;
+ }
if (version > QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS) {
(*stream) >> count;
@@ -1265,6 +1555,34 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
deserializeDecorations(stream, version, &separateSamplers[i]);
}
}
+
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
+ quint32 v;
+ (*stream) >> v;
+ tessOutVertCount = v;
+ (*stream) >> v;
+ tessMode = QShaderDescription::TessellationMode(v);
+ (*stream) >> v;
+ tessWind = QShaderDescription::TessellationWindingOrder(v);
+ (*stream) >> v;
+ tessPart = QShaderDescription::TessellationPartitioning(v);
+
+ (*stream) >> count;
+ inBuiltins.resize(count);
+ for (int i = 0; i < count; ++i) {
+ int t;
+ (*stream) >> t;
+ inBuiltins[i].type = QShaderDescription::BuiltinType(t);
+ }
+
+ (*stream) >> count;
+ outBuiltins.resize(count);
+ for (int i = 0; i < count; ++i) {
+ int t;
+ (*stream) >> t;
+ outBuiltins[i].type = QShaderDescription::BuiltinType(t);
+ }
+ }
}
/*!
@@ -1287,7 +1605,13 @@ bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) no
&& lhs.d->separateImages == rhs.d->separateImages
&& lhs.d->separateSamplers == rhs.d->separateSamplers
&& lhs.d->storageImages == rhs.d->storageImages
- && lhs.d->localSize == rhs.d->localSize;
+ && lhs.d->inBuiltins == rhs.d->inBuiltins
+ && lhs.d->outBuiltins == rhs.d->outBuiltins
+ && lhs.d->localSize == rhs.d->localSize
+ && lhs.d->tessOutVertCount == rhs.d->tessOutVertCount
+ && lhs.d->tessMode == rhs.d->tessMode
+ && lhs.d->tessWind == rhs.d->tessWind
+ && lhs.d->tessPart == rhs.d->tessPart;
}
/*!
@@ -1305,7 +1629,8 @@ bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescr
&& lhs.descriptorSet == rhs.descriptorSet
&& lhs.imageFormat == rhs.imageFormat
&& lhs.imageFlags == rhs.imageFlags
- && lhs.arrayDims == rhs.arrayDims;
+ && lhs.arrayDims == rhs.arrayDims
+ && lhs.perPatch == rhs.perPatch;
}
/*!
@@ -1372,4 +1697,15 @@ bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescri
&& lhs.members == rhs.members;
}
+/*!
+ Returns \c true if the two BuiltinVariable objects \a lhs and \a rhs are
+ equal.
+
+ \relates QShaderDescription::BuiltinVariable
+ */
+bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
+{
+ return lhs.type == rhs.type;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h
index a64496ead7..df4e8fd873 100644
--- a/src/gui/rhi/qshaderdescription_p.h
+++ b/src/gui/rhi/qshaderdescription_p.h
@@ -181,6 +181,7 @@ public:
ImageFormat imageFormat = ImageFormatUnknown;
ImageFlags imageFlags;
QList<int> arrayDims;
+ bool perPatch = false;
};
struct BlockVariable {
@@ -229,8 +230,76 @@ public:
QList<InOutVariable> separateSamplers() const;
QList<InOutVariable> storageImages() const;
+ enum BuiltinType {
+ // must match SpvBuiltIn
+ PositionBuiltin = 0,
+ PointSizeBuiltin = 1,
+ ClipDistanceBuiltin = 3,
+ CullDistanceBuiltin = 4,
+ VertexIdBuiltin = 5,
+ InstanceIdBuiltin = 6,
+ PrimitiveIdBuiltin = 7,
+ InvocationIdBuiltin = 8,
+ LayerBuiltin = 9,
+ ViewportIndexBuiltin = 10,
+ TessLevelOuterBuiltin = 11,
+ TessLevelInnerBuiltin = 12,
+ TessCoordBuiltin = 13,
+ PatchVerticesBuiltin = 14,
+ FragCoordBuiltin = 15,
+ PointCoordBuiltin = 16,
+ FrontFacingBuiltin = 17,
+ SampleIdBuiltin = 18,
+ SamplePositionBuiltin = 19,
+ SampleMaskBuiltin = 20,
+ FragDepthBuiltin = 22,
+ NumWorkGroupsBuiltin = 24,
+ WorkgroupSizeBuiltin = 25,
+ WorkgroupIdBuiltin = 26,
+ LocalInvocationIdBuiltin = 27,
+ GlobalInvocationIdBuiltin = 28,
+ LocalInvocationIndexBuiltin = 29,
+ VertexIndexBuiltin = 42,
+ InstanceIndexBuiltin = 43
+ };
+
+ struct BuiltinVariable {
+ BuiltinType type;
+ };
+
+ QList<BuiltinVariable> inputBuiltinVariables() const;
+ QList<BuiltinVariable> outputBuiltinVariables() const;
+
std::array<uint, 3> computeShaderLocalSize() const;
+ uint tessellationOutputVertexCount() const;
+
+ enum TessellationMode {
+ UnknownTessellationMode,
+ TrianglesTessellationMode,
+ QuadTessellationMode,
+ IsolineTessellationMode
+ };
+
+ TessellationMode tessellationMode() const;
+
+ enum TessellationWindingOrder {
+ UnknownTessellationWindingOrder,
+ CwTessellationWindingOrder,
+ CcwTessellationWindingOrder
+ };
+
+ TessellationWindingOrder tessellationWindingOrder() const;
+
+ enum TessellationPartitioning {
+ UnknownTessellationPartitioning,
+ EqualTessellationPartitioning,
+ FractionalEvenTessellationPartitioning,
+ FractionalOddTessellationPartitioning
+ };
+
+ TessellationPartitioning tessellationPartitioning() const;
+
private:
QShaderDescriptionPrivate *d;
friend struct QShaderDescriptionPrivate;
@@ -249,6 +318,7 @@ Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BuiltinVariable &);
#endif
Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept;
@@ -257,6 +327,7 @@ Q_GUI_EXPORT bool operator==(const QShaderDescription::BlockVariable &lhs, const
Q_GUI_EXPORT bool operator==(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept;
Q_GUI_EXPORT bool operator==(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept;
Q_GUI_EXPORT bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept;
+Q_GUI_EXPORT bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept;
inline bool operator!=(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept
{
@@ -288,6 +359,11 @@ inline bool operator!=(const QShaderDescription::StorageBlock &lhs, const QShade
return !(lhs == rhs);
}
+inline bool operator!=(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
+{
+ return !(lhs == rhs);
+}
+
QT_END_NAMESPACE
#endif
diff --git a/src/gui/rhi/qshaderdescription_p_p.h b/src/gui/rhi/qshaderdescription_p_p.h
index 6a981213b1..1ccb7a14ed 100644
--- a/src/gui/rhi/qshaderdescription_p_p.h
+++ b/src/gui/rhi/qshaderdescription_p_p.h
@@ -27,7 +27,6 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
QShaderDescriptionPrivate()
: ref(1)
{
- localSize[0] = localSize[1] = localSize[2] = 0;
}
QShaderDescriptionPrivate(const QShaderDescriptionPrivate &other)
@@ -41,7 +40,13 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
separateImages(other.separateImages),
separateSamplers(other.separateSamplers),
storageImages(other.storageImages),
- localSize(other.localSize)
+ inBuiltins(other.inBuiltins),
+ outBuiltins(other.outBuiltins),
+ localSize(other.localSize),
+ tessOutVertCount(other.tessOutVertCount),
+ tessMode(other.tessMode),
+ tessWind(other.tessWind),
+ tessPart(other.tessPart)
{
}
@@ -62,7 +67,13 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
QList<QShaderDescription::InOutVariable> separateImages;
QList<QShaderDescription::InOutVariable> separateSamplers;
QList<QShaderDescription::InOutVariable> storageImages;
- std::array<uint, 3> localSize;
+ QList<QShaderDescription::BuiltinVariable> inBuiltins;
+ QList<QShaderDescription::BuiltinVariable> outBuiltins;
+ std::array<uint, 3> localSize = {};
+ uint tessOutVertCount = 0;
+ QShaderDescription::TessellationMode tessMode = QShaderDescription::UnknownTessellationMode;
+ QShaderDescription::TessellationWindingOrder tessWind = QShaderDescription::UnknownTessellationWindingOrder;
+ QShaderDescription::TessellationPartitioning tessPart = QShaderDescription::UnknownTessellationPartitioning;
};
QT_END_NAMESPACE
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 68d9bb8ae7..8518db8afc 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -11,3 +11,7 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.frag.qsb textured_multiubuf.frag
+qsb --glsl 320es,410 --msl 12 --msltess simpletess.vert -o simpletess.vert.qsb
+qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletess.tesc.qsb
+qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 simpletess.tese -o simpletess.tese.qsb
+qsb --glsl 320es,410 --msl 12 simpletess.frag -o simpletess.frag.qsb
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.frag b/tests/auto/gui/rhi/qrhi/data/simpletess.frag
new file mode 100644
index 0000000000..375587662f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb
new file mode 100644
index 0000000000..0f42103ac5
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tesc b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc
new file mode 100644
index 0000000000..e192fc77c7
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc
@@ -0,0 +1,22 @@
+#version 440
+
+layout(vertices = 3) out;
+
+layout(location = 0) in vec3 inColor[];
+layout(location = 0) out vec3 outColor[];
+layout(location = 1) patch out float a_per_patch_output_variable;
+
+void main()
+{
+ if (gl_InvocationID == 0) {
+ gl_TessLevelOuter[0] = 4.0;
+ gl_TessLevelOuter[1] = 4.0;
+ gl_TessLevelOuter[2] = 4.0;
+
+ gl_TessLevelInner[0] = 4.0;
+ }
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+ outColor[gl_InvocationID] = inColor[gl_InvocationID];
+ a_per_patch_output_variable = 1.0;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb
new file mode 100644
index 0000000000..8c98d92c46
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tese b/tests/auto/gui/rhi/qrhi/data/simpletess.tese
new file mode 100644
index 0000000000..17b348635a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tese
@@ -0,0 +1,17 @@
+#version 440
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout(location = 0) in vec3 inColor[];
+layout(location = 0) out vec3 outColor;
+layout(location = 1) patch in float a_per_patch_output_variable;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+void main()
+{
+ gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
+ outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2] * a_per_patch_output_variable;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb
new file mode 100644
index 0000000000..8aa7632717
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.vert b/tests/auto/gui/rhi/qrhi/data/simpletess.vert
new file mode 100644
index 0000000000..3838d2f3bb
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.vert
@@ -0,0 +1,12 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+ v_color = color;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb
new file mode 100644
index 0000000000..ee90983e0b
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletess.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 d1252f8abf..45aa8799f9 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -136,6 +136,9 @@ private slots:
void renderToRgb10Texture_data();
void renderToRgb10Texture();
+ void tessellation_data();
+ void tessellation();
+
private:
void setWindowType(QWindow *window, QRhi::Implementation impl);
@@ -4899,5 +4902,165 @@ void tst_QRhi::renderToRgb10Texture()
QVERIFY(redCount > blueCount); // 1742 > 178
}
+void tst_QRhi::tessellation_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::tessellation()
+{
+ 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::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 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());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ // Use the 3D API specific correction matrix that flips Y, so we can use
+ // the OpenGL-targeted vertex data and the tessellation winding order of
+ // counter-clockwise to get uniform results.
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/simpletess.vert.qsb") },
+ { QRhiShaderStage::TessellationControl, loadShader(":/data/simpletess.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/simpletess.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/simpletess.frag.qsb") }
+ });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct
+
+ // won't get the wireframe with OpenGL ES
+ if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode))
+ pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 6 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) }
+ });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ 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);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many other tests
+ result = std::move(result).mirrored();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ int redCount = 0, greenCount = 0, blueCount = 0;
+ for (int y = 0; y < result.height(); ++y) {
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ const int red = qRed(c);
+ const int green = qGreen(c);
+ const int blue = qBlue(c);
+ // just count the color components that are above a certain threshold
+ if (red > 240)
+ ++redCount;
+ if (green > 240)
+ ++greenCount;
+ if (blue > 240)
+ ++blueCount;
+ }
+ }
+
+ // Line drawing can be different between the 3D APIs. What we will check if
+ // the number of strong-enough r/g/b components above a certain threshold.
+ // That is good enough to ensure that something got rendered, i.e. that
+ // tessellation is not completely broken.
+ //
+ // For the record the actual values are something like:
+ // OpenGL (NVIDIA, Windows) 59 82 82
+ // Metal (Intel, macOS 12.5) 59 79 79
+ // Vulkan (NVIDIA, Windows) 71 85 85
+
+ QVERIFY(redCount > 50);
+ QVERIFY(blueCount > 50);
+ QVERIFY(greenCount > 50);
+}
+
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb
new file mode 100644
index 0000000000..4d49ede3ff
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb
new file mode 100644
index 0000000000..ea68da7eb4
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb
new file mode 100644
index 0000000000..41005f76bc
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb
new file mode 100644
index 0000000000..39734b6d5d
--- /dev/null
+++ b/tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qshader/data/color.vert b/tests/auto/gui/rhi/qshader/data_src/color.vert
index c92f71b9e1..c92f71b9e1 100644
--- a/tests/auto/gui/rhi/qshader/data/color.vert
+++ b/tests/auto/gui/rhi/qshader/data_src/color.vert
diff --git a/tests/auto/gui/rhi/qshader/data/texture.frag b/tests/auto/gui/rhi/qshader/data_src/texture.frag
index bd22f817e0..bd22f817e0 100644
--- a/tests/auto/gui/rhi/qshader/data/texture.frag
+++ b/tests/auto/gui/rhi/qshader/data_src/texture.frag
diff --git a/tests/auto/gui/rhi/qshader/data/texture_sep.frag b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag
index 368e851bb4..368e851bb4 100644
--- a/tests/auto/gui/rhi/qshader/data/texture_sep.frag
+++ b/tests/auto/gui/rhi/qshader/data_src/texture_sep.frag
diff --git a/tests/auto/gui/rhi/qshader/tst_qshader.cpp b/tests/auto/gui/rhi/qshader/tst_qshader.cpp
index 40aa9d9a87..3065386ea9 100644
--- a/tests/auto/gui/rhi/qshader/tst_qshader.cpp
+++ b/tests/auto/gui/rhi/qshader/tst_qshader.cpp
@@ -25,6 +25,7 @@ private slots:
void loadV4();
void manualShaderPackCreation();
void loadV6WithSeparateImagesAndSamplers();
+ void loadV7();
};
static QShader getShader(const QString &name)
@@ -590,5 +591,87 @@ void tst_QShader::loadV6WithSeparateImagesAndSamplers()
}
}
+void tst_QShader::loadV7()
+{
+ QShader vert = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.vert.qsb"));
+ QVERIFY(vert.isValid());
+ QCOMPARE(QShaderPrivate::get(&vert)->qsbVersion, 7);
+ QCOMPARE(vert.availableShaders().count(), 8);
+
+ QCOMPARE(vert.description().inputVariables().count(), 2);
+ QCOMPARE(vert.description().outputBuiltinVariables().count(), 1);
+ QCOMPARE(vert.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(vert.description().outputVariables().count(), 1);
+ QCOMPARE(vert.description().outputVariables()[0].name, QByteArrayLiteral("v_color"));
+
+ QVERIFY(vert.availableShaders().contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader)).shader().isEmpty());
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt16IndexedVertexAsComputeShader)).shader().isEmpty());
+ QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader)).shader().isEmpty());
+
+ QShader tesc = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tesc.qsb"));
+ QVERIFY(tesc.isValid());
+ QCOMPARE(QShaderPrivate::get(&tesc)->qsbVersion, 7);
+ QCOMPARE(tesc.availableShaders().count(), 5);
+ QCOMPARE(tesc.description().tessellationOutputVertexCount(), 3);
+
+ QCOMPARE(tesc.description().inputBuiltinVariables().count(), 2);
+ QCOMPARE(tesc.description().outputBuiltinVariables().count(), 3);
+ // builtins must be sorted based on the type
+ QCOMPARE(tesc.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tesc.description().inputBuiltinVariables()[1].type, QShaderDescription::InvocationIdBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
+ QCOMPARE(tesc.description().outputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
+
+ QCOMPARE(tesc.description().outputVariables().count(), 3);
+ for (const QShaderDescription::InOutVariable &v : tesc.description().outputVariables()) {
+ switch (v.location) {
+ case 0:
+ QCOMPARE(v.name, QByteArrayLiteral("outColor"));
+ QCOMPARE(v.type, QShaderDescription::Vec3);
+ QCOMPARE(v.perPatch, false);
+ break;
+ case 1:
+ QCOMPARE(v.name, QByteArrayLiteral("stuff"));
+ QCOMPARE(v.type, QShaderDescription::Vec3);
+ QCOMPARE(v.perPatch, true);
+ break;
+ case 2:
+ QCOMPARE(v.name, QByteArrayLiteral("more_stuff"));
+ QCOMPARE(v.type, QShaderDescription::Float);
+ QCOMPARE(v.perPatch, true);
+ break;
+ default:
+ QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location)));
+ break;
+ }
+ }
+
+ QVERIFY(!tesc.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().isEmpty());
+ QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::SpirvShader, QShaderVersion(100))).extraBufferBindings.count(), 0);
+ QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12))).extraBufferBindings.count(), 5);
+
+ QShader tese = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tese.qsb"));
+ QVERIFY(tese.isValid());
+ QCOMPARE(QShaderPrivate::get(&tese)->qsbVersion, 7);
+ QCOMPARE(tese.availableShaders().count(), 5);
+ QCOMPARE(tese.description().tessellationMode(), QShaderDescription::TrianglesTessellationMode);
+ QCOMPARE(tese.description().tessellationWindingOrder(), QShaderDescription::CcwTessellationWindingOrder);
+ QCOMPARE(tese.description().tessellationPartitioning(), QShaderDescription::FractionalOddTessellationPartitioning);
+
+ QCOMPARE(tese.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
+ QCOMPARE(tese.description().inputBuiltinVariables()[3].type, QShaderDescription::TessCoordBuiltin);
+
+ QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).count(), 1);
+ QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).value(0), qMakePair(0, -1));
+
+ QShader frag = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.frag.qsb"));
+ QVERIFY(frag.isValid());
+ QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7);
+}
+
#include <tst_qshader.moc>
QTEST_MAIN(tst_QShader)
diff --git a/tests/manual/rhi/tessellation/buildshaders.bat b/tests/manual/rhi/tessellation/buildshaders.bat
index c9afe1b178..bc992dc28c 100644
--- a/tests/manual/rhi/tessellation/buildshaders.bat
+++ b/tests/manual/rhi/tessellation/buildshaders.bat
@@ -1,6 +1,6 @@
-qsb --glsl 320es,410 --hlsl 50 test.vert -o test.vert.qsb
-qsb --glsl 320es,410 test.tesc -o test.tesc.qsb
+qsb --glsl 320es,410 --hlsl 50 --msl 12 --msltess test.vert -o test.vert.qsb
+qsb --glsl 320es,410 --msl 12 --tess-mode triangles test.tesc -o test.tesc.qsb
qsb -r hlsl,50,test_hull.hlsl test.tesc.qsb
-qsb --glsl 320es,410 test.tese -o test.tese.qsb
+qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 test.tese -o test.tese.qsb
qsb -r hlsl,50,test_domain.hlsl test.tese.qsb
-qsb --glsl 320es,410 --hlsl 50 test.frag -o test.frag.qsb
+qsb --glsl 320es,410 --hlsl 50 --msl 12 test.frag -o test.frag.qsb
diff --git a/tests/manual/rhi/tessellation/tessellation.cpp b/tests/manual/rhi/tessellation/tessellation.cpp
index 56e9503afc..c746e6f83b 100644
--- a/tests/manual/rhi/tessellation/tessellation.cpp
+++ b/tests/manual/rhi/tessellation/tessellation.cpp
@@ -9,9 +9,13 @@ static const float tri[] = {
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
};
+static const bool INDEXED = false;
+static const quint32 indices[] = { 0, 1, 2 };
+
struct {
QVector<QRhiResource *> releasePool;
QRhiBuffer *vbuf = nullptr;
+ QRhiBuffer *ibuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
@@ -29,6 +33,12 @@ void Window::customInit()
d.vbuf->create();
d.releasePool << d.vbuf;
+ if (INDEXED) {
+ d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indices));
+ d.ibuf->create();
+ d.releasePool << d.ibuf;
+ }
+
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4 + 4);
d.ubuf->create();
d.releasePool << d.ubuf;
@@ -71,8 +81,12 @@ void Window::customInit()
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.initialUpdates->uploadStaticBuffer(d.vbuf, tri);
+
const float amplitude = 0.5f;
d.initialUpdates->updateDynamicBuffer(d.ubuf, 68, 4, &amplitude);
+
+ if (INDEXED)
+ d.initialUpdates->uploadStaticBuffer(d.ibuf, indices);
}
void Window::customRelease()
@@ -96,14 +110,20 @@ void Window::customRender()
u->updateDynamicBuffer(d.ubuf, 0, 64, d.winProj.constData());
}
u->updateDynamicBuffer(d.ubuf, 64, 4, &d.time);
- d.time += 0.1f;
+ d.time += 0.01f;
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.ps);
cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
cb->setShaderResources();
QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0);
- cb->setVertexInput(0, 1, &vbufBinding);
- cb->draw(3);
+ if (INDEXED) {
+ cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt32);
+ cb->drawIndexed(3);
+ } else {
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(3);
+ }
+
cb->endPass();
}
diff --git a/tests/manual/rhi/tessellation/test.frag.qsb b/tests/manual/rhi/tessellation/test.frag.qsb
index 4ec03e5700..4d49ede3ff 100644
--- a/tests/manual/rhi/tessellation/test.frag.qsb
+++ b/tests/manual/rhi/tessellation/test.frag.qsb
Binary files differ
diff --git a/tests/manual/rhi/tessellation/test.tesc b/tests/manual/rhi/tessellation/test.tesc
index 9cbf9c12c7..54937967fa 100644
--- a/tests/manual/rhi/tessellation/test.tesc
+++ b/tests/manual/rhi/tessellation/test.tesc
@@ -6,6 +6,10 @@ layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor[];
+// these serve no purpose, just exist to test per-patch outputs
+layout(location = 1) patch out vec3 stuff;
+layout(location = 2) patch out float more_stuff;
+
void main()
{
if (gl_InvocationID == 0) {
@@ -18,4 +22,6 @@ void main()
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
outColor[gl_InvocationID] = inColor[gl_InvocationID];
+ stuff = vec3(1.0);
+ more_stuff = 1.0;
}
diff --git a/tests/manual/rhi/tessellation/test.tesc.qsb b/tests/manual/rhi/tessellation/test.tesc.qsb
index 57451d7c08..064e26040a 100644
--- a/tests/manual/rhi/tessellation/test.tesc.qsb
+++ b/tests/manual/rhi/tessellation/test.tesc.qsb
Binary files differ
diff --git a/tests/manual/rhi/tessellation/test.tese b/tests/manual/rhi/tessellation/test.tese
index c82344af8e..c50230f852 100644
--- a/tests/manual/rhi/tessellation/test.tese
+++ b/tests/manual/rhi/tessellation/test.tese
@@ -6,6 +6,10 @@ layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor;
+// these serve no purpose, just exist to test per-patch outputs
+layout(location = 1) patch in vec3 stuff;
+layout(location = 2) patch in float more_stuff;
+
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float time;
@@ -14,7 +18,10 @@ layout(std140, binding = 0) uniform buf {
void main()
{
- gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
- gl_Position.x += sin(time + gl_Position.y) * amplitude;
- outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2];
+ vec4 pos = (gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position);
+ gl_Position = mvp * pos;
+ gl_Position.x += sin(time + pos.y) * amplitude;
+ outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2]
+ // these are all 1.0, just here to exercise the shader generation and the runtime pipeline setup
+ * stuff.x * more_stuff * (gl_TessLevelOuter[0] / 4.0) * (gl_TessLevelInner[0] / 4.0);
}
diff --git a/tests/manual/rhi/tessellation/test.tese.qsb b/tests/manual/rhi/tessellation/test.tese.qsb
index 4ca3c35e92..a6caab67c3 100644
--- a/tests/manual/rhi/tessellation/test.tese.qsb
+++ b/tests/manual/rhi/tessellation/test.tese.qsb
Binary files differ
diff --git a/tests/manual/rhi/tessellation/test.vert.qsb b/tests/manual/rhi/tessellation/test.vert.qsb
index ac261d2b41..39734b6d5d 100644
--- a/tests/manual/rhi/tessellation/test.vert.qsb
+++ b/tests/manual/rhi/tessellation/test.vert.qsb
Binary files differ
diff --git a/tests/manual/rhi/tessellation/test_domain.hlsl b/tests/manual/rhi/tessellation/test_domain.hlsl
index a3de658c0e..a9697d32cf 100644
--- a/tests/manual/rhi/tessellation/test_domain.hlsl
+++ b/tests/manual/rhi/tessellation/test_domain.hlsl
@@ -30,7 +30,7 @@ PixelInput main(Input input, float3 uvwCoord : SV_DomainLocation, const OutputPa
float3 vertexPosition = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
output.position = mul(float4(vertexPosition, 1.0f), mvp);
- output.position.x += sin(time + output.position.y) * amplitude;
+ output.position.x += sin(time + vertexPosition.y) * amplitude;
output.color = uvwCoord.x * patch[0].color + uvwCoord.y * patch[1].color + uvwCoord.z * patch[2].color;