From c681c7c23f79e2f0d6dbb2ce8961edd216cefd91 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 18 Aug 2022 12:43:23 +0200 Subject: [PATCH] rhi: metal: Add support for tessellation Change-Id: Ie8d226a6a959aa5e78284ea72505fd26aec1e671 Reviewed-by: Andy Nichols --- src/gui/rhi/qrhi.cpp | 106 +- src/gui/rhi/qrhi_p_p.h | 3 + src/gui/rhi/qrhimetal.mm | 1396 ++++++++++++++--- src/gui/rhi/qrhimetal_p_p.h | 48 +- src/gui/rhi/qshader.cpp | 118 +- src/gui/rhi/qshader_p.h | 16 +- src/gui/rhi/qshader_p_p.h | 16 +- src/gui/rhi/qshaderdescription.cpp | 364 ++++- src/gui/rhi/qshaderdescription_p.h | 76 + src/gui/rhi/qshaderdescription_p_p.h | 17 +- tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 4 + tests/auto/gui/rhi/qrhi/data/simpletess.frag | 10 + .../gui/rhi/qrhi/data/simpletess.frag.qsb | Bin 0 -> 591 bytes tests/auto/gui/rhi/qrhi/data/simpletess.tesc | 22 + .../gui/rhi/qrhi/data/simpletess.tesc.qsb | Bin 0 -> 1402 bytes tests/auto/gui/rhi/qrhi/data/simpletess.tese | 17 + .../gui/rhi/qrhi/data/simpletess.tese.qsb | Bin 0 -> 1401 bytes tests/auto/gui/rhi/qrhi/data/simpletess.vert | 12 + .../gui/rhi/qrhi/data/simpletess.vert.qsb | Bin 0 -> 936 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 163 ++ .../metal_enabled_tessellation_v7.frag.qsb | Bin 0 -> 729 bytes .../metal_enabled_tessellation_v7.tesc.qsb | Bin 0 -> 1749 bytes .../metal_enabled_tessellation_v7.tese.qsb | Bin 0 -> 2390 bytes .../metal_enabled_tessellation_v7.vert.qsb | Bin 0 -> 1106 bytes .../rhi/qshader/{data => data_src}/color.vert | 0 .../qshader/{data => data_src}/texture.frag | 0 .../{data => data_src}/texture_sep.frag | 0 tests/auto/gui/rhi/qshader/tst_qshader.cpp | 83 + .../manual/rhi/tessellation/buildshaders.bat | 8 +- .../manual/rhi/tessellation/tessellation.cpp | 26 +- tests/manual/rhi/tessellation/test.frag.qsb | Bin 581 -> 729 bytes tests/manual/rhi/tessellation/test.tesc | 6 + tests/manual/rhi/tessellation/test.tesc.qsb | Bin 1202 -> 1765 bytes tests/manual/rhi/tessellation/test.tese | 13 +- tests/manual/rhi/tessellation/test.tese.qsb | Bin 1522 -> 2436 bytes tests/manual/rhi/tessellation/test.vert.qsb | Bin 714 -> 1106 bytes .../manual/rhi/tessellation/test_domain.hlsl | 2 +- 37 files changed, 2268 insertions(+), 258 deletions(-) create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.tesc create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.tesc.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.tese create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.tese.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletess.vert.qsb create mode 100644 tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.frag.qsb create mode 100644 tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tesc.qsb create mode 100644 tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.tese.qsb create mode 100644 tests/auto/gui/rhi/qshader/data/metal_enabled_tessellation_v7.vert.qsb rename tests/auto/gui/rhi/qshader/{data => data_src}/color.vert (100%) rename tests/auto/gui/rhi/qshader/{data => data_src}/texture.frag (100%) rename tests/auto/gui/rhi/qshader/{data => data_src}/texture_sep.frag (100%) 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 #include #include #include @@ -104,8 +105,11 @@ struct QMetalShader { id lib = nil; id func = nil; - std::array localSize; + std::array localSize = {}; + uint outputVertexCount = 0; + QShaderDescription desc; QShader::NativeResourceBindingMap nativeResourceBindingMap; + QShader::NativeShaderInfo nativeShaderInfo; void destroy() { nativeResourceBindingMap.clear(); @@ -164,6 +168,8 @@ struct QRhiMetalData struct { id pipelineState; id depthStencilState; + std::array, 3> tessVertexComputeState; + id tessTessControlComputeState; } graphicsPipeline; struct { id pipelineState; @@ -268,6 +274,7 @@ struct QMetalCommandBufferData id cb; id currentRenderPassEncoder; id currentComputePassEncoder; + id tessellationComputeEncoder; MTLRenderPassDescriptor *currentPassRpDesc; int currentFirstVertexBinding; QRhiBatchedBindings > currentVertexInputsBuffers; @@ -308,6 +315,7 @@ struct QMetalRenderTargetData struct QMetalGraphicsPipelineData { + QMetalGraphicsPipeline *q = nullptr; id ps = nil; id 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, 3> vertexComputeState = {}; + id 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 vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant); + id tescCompPipeline(QRhiMetal *rhiD); + id teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline); + enum class WorkBufType { + DeviceLocal, + HostVisible + }; + QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal); + QVector deviceLocalWorkBuffers; + QVector hostVisibleWorkBuffers; + } tess; + template 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>::Batch &bufferBatch, + const QRhiBatchedBindings::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>::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>::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 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 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 (!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 (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 (!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 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 vertTescComputeEncoder = tessellationComputeEncoder(cbD); + + // Step 1: vertex shader (as compute) + { + id 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 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 &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) 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 computeEncoder = vertTescComputeEncoder; + id 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 &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 paramsBuf = tescParamsBuf->d->buf[0]; + char *p = reinterpret_cast([paramsBuf contents]); + memcpy(p, ¶ms, 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 renderEncoder = cbD->d->currentRenderPassEncoder; + + rebindShaderResources(cbD, QMetalShaderResourceBindingsData::VERTEX, QMetalShaderResourceBindingsData::VERTEX, &resourceBindings); + rebindShaderResources(cbD, QMetalShaderResourceBindingsData::FRAGMENT, QMetalShaderResourceBindingsData::FRAGMENT, &resourceBindings); + + const QMap &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 mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0]; + QMetalBuffer *ibufD = cbD->currentIndexBuffer; + id 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 QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { @@ -3768,48 +4190,135 @@ id QRhiMetalData::createMSLShaderFunction(id lib, const return f; } -bool QMetalGraphicsPipeline::create() +void QMetalGraphicsPipeline::setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD) { - if (d->ps) - destroy(); + MTLRenderPipelineDescriptor *rpDesc = reinterpret_cast(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(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 +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) { + 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(); } +} + +bool QMetalGraphicsPipeline::createVertexFragmentPipeline() +{ + QRHI_RES_RHI(QRhiMetal); + + MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor]; + d->setupVertexOrStageInputDescriptor(vertexDesc); MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; + rpDesc.vertexDescriptor = vertexDesc; - rpDesc.vertexDescriptor = inputLayout; - - // 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); - - 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; - } - - rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount)); - + 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)); - [rpDesc release]; return false; } - [rpDesc release]; 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; - - 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; - } - + 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; + mapStates(); + + 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 QMetalGraphicsPipelineData::Tessellation::vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant) +{ + const int varIndex = vsCompVariantToIndex(vertexCompVariant); + if (varIndex >= 0 && vertexComputeState[varIndex]) + return vertexComputeState[varIndex]; + + id 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 &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); + + NSError *err = nil; + id 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; +} + +id QMetalGraphicsPipelineData::Tessellation::tescCompPipeline(QRhiMetal *rhiD) +{ + if (tessControlComputeState) + return tessControlComputeState; + + MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new]; + cpDesc.computeFunction = compTesc.func; + + NSError *err = nil; + id 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 { + tessControlComputeState = ps; + } + // not retained, the only owner is tessControlComputeState and so the QRhiGraphicsPipeline + return ps; +} + +static inline bool hasBuiltin(const QVector &builtinList, QShaderDescription::BuiltinType builtin) +{ + return std::find_if(builtinList.cbegin(), builtinList.cend(), + [builtin](const QShaderDescription::BuiltinVariable &b) { return b.type == builtin; }) != builtinList.cend(); +} + +id 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 &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 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 tescOutBuiltins = compTesc.desc.outputBuiltinVariables(); + const QVector 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 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 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; +} + +QMetalBuffer *QMetalGraphicsPipelineData::Tessellation::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type) +{ + QVector *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers; + + // 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 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 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 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 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 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 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 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 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 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]; + + // 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 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>. + Synonym for QMap>. 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 #include +#include #include 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 >; // binding -> native_binding[, native_binding] + using NativeResourceBindingMap = QMap >; // 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 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 shaders; QMap bindings; QMap combinedImageMap; + QMap 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::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::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::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 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 arrayDims; + bool perPatch = false; }; struct BlockVariable { @@ -229,8 +230,76 @@ public: QList separateSamplers() const; QList 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 inputBuiltinVariables() const; + QList outputBuiltinVariables() const; + std::array 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 separateImages; QList separateSamplers; QList storageImages; - std::array localSize; + QList inBuiltins; + QList outBuiltins; + std::array 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 0000000000000000000000000000000000000000..0f42103ac5b67612404a3ac00bc0bc4c8979a366 GIT binary patch literal 591 zcmV-V0#EnU-pl~N0RT7{6^z@kh6#ia zLxhnaQp12BJYa87$qCFbF@QN-mdc#JmHq2*c%GJ&e5qAC2c$^AA)GRL3{|VK1r0l2 zUBA8d(roJWp7>zft!?|bgGM+^z6`V>yoqsy(Z)Q&$*L<{5q_WVp#4#4e~Ncjaeja` z#7^tGxN6`mG45eL@ih1@iO)mW7)^Me_f5tqUvPen^INQ0aDIz*67~@H4ci-yk1w7= z-D)UIBaUx~)Nvj#_bsk}gJ{EgvB98(wHd3Fuw%xE|23*7{U==C=5x|9#9Nzu_8l3X zSr%~EXWUd?GBu5_owZ6*9UFP^*q165t3-v`0t-D~tmG_;gRK7xtPO%93f{+g=0W{5 z-1EDA@j-~a?mq9nRQR;*u6OSLe{voE7K$J1RY#jJ%BM;`U#cvKCiwqE`rT0M@Ki6S zXY}wELm~21o4GW>Ql+aPRMJ+Vury2ZFtgJ933gJbqjykH!HW`A_GUGGz;bmcjIy4*)E@AAa9V3aNK!iKR4=KZM;2G dDLGMDo*1erOs-pA{?zxJ-dX;qv!5cl36&qHC4>L~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8c98d92c46983b8d821ddf2045e44cfbf0633d7f GIT binary patch literal 1402 zcmV-=1%>(m021qXob6a`Pa8)NUTj0C(*OyCCcK0tB-AEg3AprR}$zwA{_iGtbP<%0lKf8i3>7o#OV#AuBWxFNh-J8Ij8Fhohzg(5iL4I(IuOM3Rn>iTC% zG|A#nUWPa$ausA7L9Gq9z2Y5m4EMp1?LMu~X(b?6uHgg6@z~rw4w)pEub_QMG;!@X zpp!W``t~T}dX>!DdY0`&pk~{!c%bi0UNuZR2tGz@hE|GJhSnynZCd++3!TvT2_$(y z(jXEY)N)+=55qPF+kMz(LbjRE20w^?@glTo5z_TR*B8>opo@by(6N0Xf@dLuXCZv{xx*@um}M~Q(sW(gAq-($iI2!CSz$xXE8p7nXigV4PKK8bi^ z#KL)w6XP&=m~#Zr0zM;p2K=5*(a%T7@gZW4Vy}-7a}@oK!+%)w^7Yd$Jfo~e|S)PIJ3zBDnPlWiMBOde3!Twy=+n6oc-vA%vlSjNaz_Yv^%4Y}9>yY=rwGKS@eZONa-urX$c7tjZ z6W50lV}2t|KivzMiO+;T%fFB|c*j}C^ZYA$48ivg!n5sx5L(AP{Jckg^0y5?yZhkg;uP7-M_c!X#p7S-7fTu;hw(3oNOV+J79KjesNF# zq)FKHKV1oY2T17K6|L`CEI-#&j%(IznOn_D+^QL#G)i^ll(e=) z+Iqt)H4Vo!&MZY)HEU)?^6paU7mw7Hz7gp)eZi4pky)1AB+UjHYBFu?$-6Pdh z*2jjYoMQHrGFZ!G^F1&tx4^9S#2ngoSFEcfP_)5fNAjl`QXJP@6D1w;?_J7?Nut5H#M zyQ)0HD$#GhmU*_5yw3uzlw<<^a|lG=;9>v~S3?CJhyITf^6 z561P(<*$`%vaJqRog8{oAKQs~Pgoa)X}4#ATJ}!jJLTBQlC5dYrW&SA&ICNLYG;Pk zHm=2@cj+jjQmTW5f|f2EvvPX6C@ZRIa*2YPEJ(NBq%D!xS>ya%IjPmnGy}^u+w~0F zlN?H;4(*ERsIqr#I7ZbCIc%l7n1f1b$72W~m)BvJZ%&R*s9^SmrFhPjW$CSaZIr#5 z<9;x#b4}BF2SPj>-Jwkz*@j@N1Pk8q?h5#C4h`qLUyEX7T{GXyrRa*2<)zGF{3FXS zFM}tF5~|3Jk(Q(RcyIa*MzV)?u}X(m50=I5&haU<#AjthSLtLyTyDGUIthCfpxy7- zF{?CvO{LxLo@M?Y9n0Y{DYj>Eno*UDhyn8t(@UDN6rHH){J-K_`472P@4{u;u33p- z>!$iYz8W{N1!wKeRBh*A@UmoX{VnO&?+1S0?f9UyuA6NABKY&v|Fbvv*T^LB)4+`X I4S2o#8%?y;_W%F@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8aa7632717be6fa7e2493d44b43bb804a0010b27 GIT binary patch literal 1401 zcmV-<1%~~}7ER)z>6MuZS)%yHog zPizTaG-w8dWN3>2FhqBxXi8o@5Voj_mT<_N-?oTEO}wVHFWR(jsb^IjlO`Z~TO1I% z1Bz4vb*!K{35v7C)7dyo{B3clR#j0Hk7%_yVAG^{TD1BKsA)0TPy4BnLiI9u^B4op zV+ikhcgx7DOnP>NO|s&=_zmb3ZO^EvhSlcn-QRcfVN}mAEf-in9;dH#6J(VJdDC`1 z?erSWJ2Y2lZqVFQ8D*5+6=R(-*5_#M&{J1KzcrQumNKw3Fy`5&r^a@OaSHo2ua;*G zUX6{2xZ=z)1&(dvxP&}}A>TISYw$k?zL|vBXA)wcNr+uz%*%C~G5l=w$Qf173F!8q zWA01DkXA8fI*o9JR>LB`DAQ!mxenvZzNY7S=y=?NJPN*VNY283RQZYPt~s)5?~u5F zJXlwPJO+$?jKfC?KE@G0kE0T3W1;9htr;`_$@>vTd7VW;CPCDeWuINR`@*hCNL7^jsk#yLiop8#7m zxVIAO{uH?8egGhjhKE#T>Gf?3b23awmgh~Zinz&NJ45;bbf==&t{LEAas3CE-nvaMJK=Hj~OVS+FD_^4O^@@?y(x_(3Vz&h=t5 z*Hc#IN|IgCk}W%`y=LpfhHppGuC|<@s=z84x7tzl*rqHG8V*Ht?7MZzHOg7Y;iysF z_d^$a6nCByl5;Yb%aO|U%7seaw3*`_ubeR4^8KKmKe6OJ3CrTCmTxS>bm3Ifx0Y2> z!RZoFWVxI!M4^%#gw_`a^l;s=SQxN)YY}ov=_sD}*=%2f{UbUf{iM@iOU1&M?)uVQ zpVeLbi+`cpU6~KFu2*Zc>yBJ)IuW%G{jZ?m9;|1(c<46k_xX{3y-wW{Qe#QaZaUm# zj#RsnnoJ$*(uvV%FyTlF9H?p7Z7W$Ww?il3Zt)6MrBX83dQQDuZehtr0}foTmmfu> zZFWT0=b#-qxlizS4Hv9LAFAfhdmNp18axe)^`cds!@5+B7yB-k zW82R4pO=f4tW@a21(9>ot!%u?^#m#PyIc+<`^X{IN`*cjzDJ^`PVO$>Bhu6IkWPTB zGq1~Mww+OWVu>A8Dpu#~qNCusCf~j9T#EcDw-({$&whJ*cbw@K_ij1+ZO-@H=z&mm zZ=Eg7k67#5J`m$a?A6`Z#;1P+IE%oE+JVQ(=x@R~byE1ZOy7s0^EU=z{5>SePL2No HBamDYp@-VC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ee90983e0b182eb97b9b79c4b9a5075d2194b7bd GIT binary patch literal 936 zcmV;Z16TY201&Boob6XzYZE~fo=tjP+uB-fwHF;JNYa)yZKD)wtAY><1uK1ALf9sg zc5u5J_7Yl4!QbHz@ZCQm_~uXXMf9BAGu>P^`k)jl8JL}!GvE2nbwidhHq7FYfjP+< z%w;~4EP&}U2V_~i{7Hj#44qX0X2bLds`Y|uFeg}(Rk~Dqvj5G{XjS3WlB>+OY-VW~ zAvzmj4@f!%89=2QY$#ewdk1@lAJmNElLCu;&t4MWWTVl30OpA$F->e7<}H{DFc)FI zW`n>L549?pMl!8MmLVBhDZx|8N?-+f?-K`}7dp<1bVj~u&_cb3z>y`68Q2ZM%3;m| z9+NUe{@;P;2;JdWq4Oy5Ex=hP{Agq+_E&M}k?sH+Bj53Ei_YUDLmLxhLn9j#Q6A%g zK1~u|miVR!J4t*~lwYNf*xQlzhv$gDN4mqLKTl_+KToyEw(u!rcg5;nr3 z!yP@5`|_mr7$3&*j;@EkbdI=Vod~~SRRxc7|vZknMl zJdE8b|kzAaWfBD7S5pwY;K-dI#b(aX;SW zlsT-AY5IY6B;ZsotD+=$oZI~TBDM%8qrG5pkU8lXX;njfQaJ!o@$k+QLFhRMQN0PT;9!dw;z{8U59$NOU)1-HA_Z1+6V^3JM zigF2bDo38IC5nEdd4O!YP9qEsa0YZ+DEKtq`3VOXz2!>@1V zlcwo}KceP|mCRH>jWfR7?TB&8SJLA$PAQeL&U2P?mP?O*XSZn2l_4CZ{h3N!X1ANRXex-!RV4@Q)KERNbS>jdvU83KW^fW$8>vt!YLm# Kb^i-DXb^+QRmmm* literal 0 HcmV?d00001 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 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 texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + u->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer 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 srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()), + }); + QVERIFY(srb->create()); + + QScopedPointer 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(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(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 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 0000000000000000000000000000000000000000..4d49ede3ffb1233f0c607bbecdf6ff77b5162dc9 GIT binary patch literal 729 zcmV;~0w(|~qz;)??(JM+!CeDj@|FvgY{V^y#c*j+Yd z7IRp@Jg^Xu3K;uMgB1pbY_K6X+HAzST3GdOVgEXo=sVDue5;hVmXQ%>D{Pa{byiL} z)>$q69vqz=8Bshi_MVw6J#J-oj(Z?Pm6(rh=_9@hwg%P!Kg3b01DGNHkZRz1OHS*{T$>fQ!Pe0egc9bb}089ihmDiE!9F{Ov4(4l{9RfFwB1f?2-Ql#W!dldF(^g zRoeRn8D6AWSe0~!Md=Nt#_@e?>V}aFe6hP@3OC}D&~;=AT*}eqL=3&aj@vh2=>y0; z`%{p_O)qfl8238DYzaWb)OGg6FtkVAzzf3mVn^wt->#Tij`HHuKn?^lZMOIo7cgcy zAOw>($@z)cHq2I=UvpkK-Pio4vM;mK?QNU?A6@L_j`pyAL^0HJl;(4U^bMUs6o-iu zi?fr%(+jH`22o@k`jaHi=*;01-1EDk?T;zGPe1TvgmSm$;)a$`!7 zYJd*btwN|Qb5abI7B>qkId7atfB+-ck~q&wWT)!ro5oC=j0N#hz=DD*I7k=DNk@#i zgX9fCztD}7&@Uvo)*D=<$DTF#a7+4*mkeC-YV5|gXTcw&CqM4<9FOF9@DzWH%YDw1 zNctn;+haGH*p4gGsJwAl#>CRM^{dxQ)o7Znet)jzDpLB3FG{bML~e-p-_Ij4>)7?R zs%qBEa^VZ<^|EI4`&pxMf3%Lcx>ouHMS0adk-kxQHJaFg(ilZvO7qe)DmGY#=n5S?C literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ea68da7eb48788645a251a229ebcb2173c79bbc4 GIT binary patch literal 1749 zcmV;`1}gag02c~)ob6a`Pa8)NUa%q5X(2C!=A8u+Y7=6N4JpAuUQ8gCXpIt7MY1e( z@f~pL`OdmKyNxQO{*Hd`r~bjdNL4$tJA2-<50pmDw_LQoo0(^xnVp^6y}%f|z!)2% zG0tq}vI_H<1H%VR3I_X!F4nsc%^3kM0l34$_jVMv3=6=e!;aveR1GD-1%8sX1gpgk zV@_xrKFjHSuT-BrO!qO(b`i$sFh)o=d+^a;Q^Ru?Fk}W?c1e2~Xi~y)R3x*z_j)fM zgl2wawSe|UNO(`fXc@Mvf=|MjhLMGlhp`4@14aeL4#TRah0je&bQh#CN_I@h zDZw#8wn?&GB-^xRo7N`z5~g`in>f>SL!=whbi<@e5wFsreIbEoA%SNhfk){uKb-qr z;y`&LCZ|MA)LbfL8zS3|X4@ef@?M2syw3n8MY?N%8D+AyWs9ey7X8EKR-qM6xk-U zoUZrfc$4O+;@>72pVs(hDIW68k$qOwTlR_anbUmk z6DRu2!~GuVFo&X{_Y2UKh1T!i~Vq)W4hl;a|CqMX;(xjYcI^tfCPK`44%TAQ*i0FeflNaj#01a+-o76j>G1>FKfmZDksKdVh9X!U#_b_FP^n z7Pu8;9OIqW3A3OL?WPs*Od+&{GeX|-eCv=LGF{I$InFzq%lwq#aM?#Zd+Z!m z-7c|IxA`XKCae;J7hn#oAlS3ImQ(A5mR~P4AmB>AP(BB<{2rL%xtOZ!4#Yam5DI2l z?@PXs2gSY@*i;daZ1-G{*y}IKPV(O=vOkdmm z!lG;VP1JTP%Y{!p_o?STLeCA;4y+EggCO)f&5&2!b|<985i3~YO^nXh`N4}fm0Io9 zHkCnGS9n`ZcAdZePMg&!tT%B&p$AAcxBc4d>Or;kZ8<4urM6dl6=SP-ZV(!7xNU@n z&dDwF*b1Q8WhS_UJ4Q3~e2o`s8Q7*J5moW5<1mU&FS}W2b~{_5u%^ssRWm;xlrX6oFrCmyYPaoJVa|wU)CtI0JCxF%BuF@anx47^Z+bQv5Yj`%3JUjqN_C z{TyO`wSFSn!h3I$zOfH**lygLwy^WF_}=ne%i+->QOM!~mk_w?cyA3Sc1qOi;fZe< z=Al|F2zq$z+h(J&$W5zjV~tc%7I@I^!kobCEo5x@+2UFbfvROiF9}rDHErK<~y; zE6^Ms=LVQnsf6P`37X5-y302Qdk6G7;htkTj+lzvdxDpSb0lb@qetU;G#J`;DO(Lz z1ej|3!3xL&3mSM{)}o$R*ZhzCVRFU6a-Wyz)JZJEJXcQ;MXSh}k(QGAaBkw@q-3jZ zy#-swIV_8VD~EgML;QG2SXJ0&-{v>@hi6&-PhUfNo7Cf3G{hM( z*kJO2Ie{+Au0$URAIi1-KXMg6gv*YO?l{{Z)yMc)JBv+iwr5kt_CfWsW@1 zbu5j=d?$g2-f!GkCXwe;kD*l5@^sXtoCBI}vEE3Tb4f?0yQzPIC|Bh%Yt)8g% z2{CVaoDknpjtIl9|48oFQ2X zr3>mgQkPLmW|Z)~t;bu1IH?zwZWNh+TBlg?&d_KYhTEgT8S_ z)!+GD9_M&={L#<`NH;YZN%vFNSL%NWn5v}3B5r=Zv5#ToFafVd35WX1do#CIIyTi}_T ztq8NO>hXPyO%8ax2^#yq1)lN$SMX&Ecpg6h&E&9RjX7uTfd6&WmGyh@r4IdOxA7p) z{ybsuJZ9x7r%a^;BFLu9t4xaJl3(z(!*?R-BoR53Jn!5c`4EN%fpfiT& z zA|8>BZs0e+p<#NRG zgL1J_urYHivE#+@Mi@r*!oDNtC6J|;A`cu3y6{ryq2nwMI#<}2^Q$r!lk4mx7m3u_ zfCGp0FfT0_w3rG|e7T&-bFnfy6vHnK@eu%D2DC5)I%FqQwprhtm3a6!W%x zxczu@XLI|(vdyol65N&>+gsa@`ZfCd9PN-sBeC=OHyhjAkM0&NeM`1Ia=B-c71wBT zHoQp9(c3centn{V&g~XW*GJ)tYTJEA>b16gwF(fHmW$_MqY-;au2ysgmxTeqk~4Ch z7(`L$Xt&DsP|dwrTC`@JO12lco|F33=V#s|p}Y4&b(RkEu2Qgt`A$n-7{rOBP9hOi zn3Z06@^W!aF9s%R%U-BN$R{h`w7MqVzO@<{+cfv)v?g8+(!|gA00$|cG#YT20v39J zbb692J*idM?zVEeF1dc6^0HJYHk>>c%NZ%HmkC}autE&-)hb~v>9&#@R*BJZYwoz9 zhq!$a*Ypmcd(TU{QIJ;P$ez>uoqV!4r%(LzN+)ugZCBP>w9Co5?>2)lPW+nm=_T^& za`xKSZ9k@fo7Xx~n1sne$BSpPuOqihTPDk7ulPZ&#SOgD_7d7a?6>RJ z`B8YfMq43^%fM}Wyv=%2Nl}!ENBMOVZ-t#z>W?4KA{YnR8KcZ zCNOHC-^Z-ZeKQKXoe4TZ;eL>|c(uhL^lR8X8g*Q?H>=k;vO*89F~w>)xXq@=qjI?$ z(=JJ;$)M;|Dt)`HeOfD*&1%Y^Ap?HUuOEjc5_1#U_;%^olY52!9&phaX``cexqd_H znAIm;>NjJZ#`<6jBfLqEVSUJ@6sbI@QiYQ!Y*jmHp(7x=* zN(I|(uPXX2*B78PzvVK`kETajE0uh2W>ITg9o7n$&bYryf*A6mwk8@`aZsf0yk^J zWI>NXTOVdaL5~qppJ@hceXbeO5Bk|=E%l*3@$r%G4K?NPAAkerzVr{*!umu0D4e?f I3z6I<*5zc>wg3PC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..39734b6d5dd020174cb19ad52e32972803239fd0 GIT binary patch literal 1106 zcmV-Y1g-l3026R{ob6cMZrnr^9`EL-F>OdFq|hd1sz_+Jt;}v3P{on}YEc@gQbM*fAUMZ!X2)xL!v&~PQAQe%=gfD$^J{M$ z#@Hg0qXu(@4Oz%yCRhS9WC6(Pc=#6$Rv0Gh0nCRP5!9FkHDE5Y5pzl^Gui*mFlfC( zYhNGLey`Uy7ZGB@@rNW`g$$t5TWld)_nvm2T5;01wm)gH%=h|)_-(e7-Os^1bp@u0 zU4!`!%ypO>FrTn_;F3eFN@kGE$jNFXLo0i5RkA&>g1onh1NZBN{RZuk?>cCq-W}km z6UQ3dEx^iQ-T*w-e1ZIb53X0}497j%FA?85>`lTiWp<>$io?uw=h#*99nU(nzeX~& zu}n4$vay`yQ4aKJh4|{kw@TO*;#;NsDt*Mhn`!^>1>!#--9^&BMSG=xi)vHP@of;_ zrw~((^1%r62T0~&-67wull^Z%x{$>rWh^IKBbkwt-5?qAAA)!2-}|J$M&}sESAfSD zZc^SbH@o1;O~NZbZj;}5ruyJdu=r56{2cWoY>Y3Nehz-y9C=YJ!hk>6YH=?%P2W8a z)1={tJvTx54sSKN2>8hBJ>bJI7AWmh!$2Z&Bc~VoVN?ZcYc|4|qfqoYI@oBMuS^c( z#CHzK6sQ1ryP-_q=WVOi>|`)90}!Gbn`R-{pO_xMPJ-QTxBgd=Vi#@cKx;q733$=t z13z?=?YxnWnLoWH7!<7a%p{NSv{4@Ui>u(<& z96Wp6>f`}3D1F+7z^d5~(xgPMl2Xk+6*geHFLFs3t71S$B+dmDkyO()8C+15vd^;K zrA%>1?1|?HyjP}&+HUUD(ltVmvYAJa{mdyHkQ7vSBvr#~BSCqIHJyQ}<|$!y4kjIg zn8-`gC@2^j=UWzEs#kT(8oz9cpy#K3kAHsZC9dzlkE}0_cTFvi#cBUzyjU-HO*4%} zaKZ!k)Qg91&*PbvZXPt#LetefW7}!$MfkV|t>)3u%)YU0m3;(_i~J&((ebwJCP^fY z)5L2O45+rxL)uM6kM;A;wEOa|q0Hec#kN)LkB(I9Xgtmx{%R~OhNXH^M~lF!deMYa zH3d+)sG+PS7IQu`Tc{?yjA}o>)@shG-fJn?cU}~DKA*hWXV>aJPelN1;NhMh9=ra2 zFruG}{l|bzUPhkVca%$@w6=C$y3?7iz@KY z1MWB-44~n+9yWE;6yYCH^GGGT(ogH0-z__0o$-6saam`SN-^LKH#l!pN58vMCT%t| zMcsP;ZA)L!!u87*zHH$)yM^a2Tllht-@F!{FMrLbcFzB~Sd!H$Wc3P}`j%JsRlKvf Y)r#Nf@_!)J_WlW{d@$7c-)Qpiwd%??lmGw# literal 0 HcmV?d00001 diff --git a/tests/auto/gui/rhi/qshader/data/color.vert b/tests/auto/gui/rhi/qshader/data_src/color.vert similarity index 100% rename from tests/auto/gui/rhi/qshader/data/color.vert rename to 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 similarity index 100% rename from tests/auto/gui/rhi/qshader/data/texture.frag rename to 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 similarity index 100% rename from tests/auto/gui/rhi/qshader/data/texture_sep.frag rename to 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 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 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, &litude); + + 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 4ec03e570007751b0da257bfc0054ae366b9913d..4d49ede3ffb1233f0c607bbecdf6ff77b5162dc9 100644 GIT binary patch literal 729 zcmV;~0w(|~qz;)??(JM+!CeDj@|FvgY{V^y#c*j+Yd z7IRp@Jg^Xu3K;uMgB1pbY_K6X+HAzST3GdOVgEXo=sVDue5;hVmXQ%>D{Pa{byiL} z)>$q69vqz=8Bshi_MVw6J#J-oj(Z?Pm6(rh=_9@hwg%P!Kg3b01DGNHkZRz1OHS*{T$>fQ!Pe0egc9bb}089ihmDiE!9F{Ov4(4l{9RfFwB1f?2-Ql#W!dldF(^g zRoeRn8D6AWSe0~!Md=Nt#_@e?>V}aFe6hP@3OC}D&~;=AT*}eqL=3&aj@vh2=>y0; z`%{p_O)qfl8238DYzaWb)OGg6FtkVAzzf3mVn^wt->#Tij`HHuKn?^lZMOIo7cgcy zAOw>($@z)cHq2I=UvpkK-Pio4vM;mK?QNU?A6@L_j`pyAL^0HJl;(4U^bMUs6o-iu zi?fr%(+jH`22o@k`jaHi=*;01-1EDk?T;zGPe1TvgmSm$;)a$`!7 zYJd*btwN|Qb5abI7B>qkId7atfB+-ck~q&wWT)!ro5oC=j0N#hz=DD*I7k=DNk@#i zgX9fCztD}7&@Uvo)*D=<$DTF#a7+4*mkeC-YV5|gXTcw&CqM4<9FOF9@DzWH%YDw1 zNctn;+haGH*p4gGsJwAl#>CRM^{dxQ)o7Znet)jzDpLB3FG{bML~e-p-_Ij4>)7?R zs%qBEa^VZ<^|EI4`&pxMf3%Lcx>ouHMS0adk-kxQHJaFg(ilZvO7qe)DmGY#=n5S?C literal 581 zcmV-L0=oSG00r`RoXt~Ri_=gLoqlY)yIRWx zE4mv>vnLUend|iBEVjK-6h!f0Gtm!;cPul@DOe(l{HY|T-JbX;B(8-9rjTft$&cir zVfF^%T8NeIzTAAN@OjZ)|IqyZ?9#hD(*9WA6NUAhV&+SR^b6fVqTxGMPuJt2v0G@04^jwxGyBg}3#$q<53-(n28wwW5QGO^$I$|s}a`Qrn7rQEr T!xe$+mG5!&B^T>A(jhgBHUTEh 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 57451d7c08e5d803ce5d78ac5974f8e627ed283c..064e26040a609fde82e1c6b91eb3edc77f194efd 100644 GIT binary patch literal 1765 zcmV^4e~`aAl$pZW*;B313o?%a9LK2RDp-*VCVZfBl(W_D(7_X1<=9Aj*V z#sy|EhgF!%Y#1I$QZU#*bg|xrXwC?55x^Z5e6XR=Y0L+c4m*T@QZ$qR7x+om609aW zh#8@7_%x#ry;6PiVY-hgy7Mr;gfT*MqX!@TH8nhU4qa+svP05KK$8-Nqav7{-PgN0 zKQMADt9jI4AZpZ2zzpwc7%km$Lg$k(reS1Y7N0^Nbx7zH~f z<|)B3LApuOohRM2qMKG0`4X0SS6MhyWJ4qyQe?v*xt+g5bjq(j~-@Qe2uz@$ia6)>Yrwzh2Xbo7FtyhQUclHDh~ zMzKr+3$9@b7$=AaIWN*PBO9DwqMF}=-^&!^CGvTN@^G1aUZH+Sk^O|=#p~y%h@T?e zgc8%Wz8G)N8bklLXpa7GlkF|?e~0*PlYg8~lk6*)&k#PX@XeAxw0x81qB8uPQM=BHcsc$N8FqUnhJRPl^1k6CURqN<5qNyh`(D#I;IzoLBn# zVoUbLlSQaH;$Dy}93|?P?I_j}(FbWJFO>ZO65Q;{)H`1=+4H}FG~6dphJF7#SX?06 zzW|TAamIuUdGPre*iDHX%05QBO|ZlHRYiZD^w&s#y-$CW^fyRHmZ{ z!gwwy>&nu)!ub--pTP`!VVU}4i8#yD`vuY=4tr#U$==K=zE=nrazF0#RgQd(h&n%^ z+LsA~wR=i(#9*zTQ$IZ>AI}N1NqgW6dfuj5{|()jKddi|Ra=1R%H9g;Q z9bPQtx#?@R{?6?L8IT56)AV_n7qc8BB`s9hZc`7?qRjI`#k*#+hz}LlcD;JBQA&nd zjzn6H1SaB^v%IYb&Ep=iA9M~6lb{6^dT6_Pz+0|o9*}|7buEMAx-(hry~g3P4teIt zKBzifVySNNGR7vf5`!0D_D$d4HM^!=>jb7(&o{u~N-keI1GD@An8KNus^bj!I?)IP ztE~40-^hVr&-E?J2+SVCEMZ7I=rrM6pp)z4XR z9Y4^WU`r45I5RhmBh!c0mqB4cxAkV=dMZ1l*0&5(LaO3929&;3C$hhVWV_=uGa_Ak zDBBQ^i@dnWt@a}CSWdu&0yE z^Pu^I3TV1c;JJ3&1!F%`Bv!~olh%5OnVXGgQV_@%kvl|UW`zwUSMXO2nOtdEk)HR2 zW-G*rA{YZj=+%JS%IGOka*}KJUS+{kC$Ei26UWPL9RLe)yabUqsDtuers)fkuF>an zmeaI5P{xgx8R#~=(}r!m-PC$=-)b34_>(){)U=Kdh2xIiGX1vRG`X;Xy_en?bd$%O zi#A|-%fkJqq`ud&UFxKtLoBh@PefaN?-f%IfB_E6iCfcFcXkxto1SCZJUUGB8LV>= zfjhSQR<~oPNxdE%d!}w2gareq2S=V|G#ZQCFuNA!C``%%_uE}q6Ii{4h)pk3Sj!?X zY+cb(0#$Vk%QKtMqMqLJ6@$mw0p>7O!f=l!ipe9@X+#cscL6--8dte=+##W}N>}@4;hTVODdt-Oeuw7QVoW?i{S#OtYy{Xy3>yWOfNq4be|3n? z>JXjPAvzJp_3@b(p~3b}%$^|@^Oh+lftXK;!%s+x=usraZR{B@!5#44NGCl-4J} zxQ5T+dx{#^&H!6P?}uqV!(FrH8m+56EGfP~jcd?kI~VJ99{Xk31>{@Cxi^t7s`&-2 zo3Wa|gzp9P+1`rrx51Ce)xYRZh4DWK8Kbp~o_`|r zTm6O5=sRVM-|O!bF^Sl}h|j*15DH@*-tSY~oI1OHe;l_qruy>=Ju8F5z1aiK9PZg`yn~m>@fw`Jsk>XM!y<|QZZbyp&~{#aTj|<< z;CQYqY~-aK7%lUI*9kL(hK^+ivMdW(Nl3|vTrJNsLyjoRymHB|Z54P^@mii=FPxQz zQyo_`9aj^HXLLQMA-T3p*7#^h8qT@QG+XbgZWqDT16k&H<<%S>#5uKt;K=UUty(9v z{d)e4@)UFV(lwm*8*nzRJdShCl=f~CQwgRVNYf4q^`-nPMJc(?Z zq4m8&-gI0mqps_iu^qKtlZ9RBwAWd^HKB2(370)UT3tOZN*lTM<=3fn>d1Q5^YrueYrR5NaDNMW Q*{Bb(?^2C_0oquX1XwtACIA2c 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 4ca3c35e9237e48f1162bf1233d8e9e109776dda..a6caab67c334baeff9c399bfcb3ce7d03b15b3ba 100644 GIT binary patch literal 2436 zcmV-~348Vc03kVeob6g|a}&uC9$N+n9R?0KLb%*zurEXcBP3ZQ#BXO}-kdX-fbY1R zq9|)=CH9bZb-OFSK)EXUkg9x2Rler8(4@ ztLCq3$c*uvnD5CcwcpR|3azaLge_85}HEpkw<@JeMHPgMSk8 zBJ76mb8DO|-xsFuYIz;9S!W`PzXJad*ze%aas=JaDd(n#3e54}q&WsG?~4=2 z*KN}B*tw5z?Q?xEA^wNZ-zMo}H4j~TmvAp(&wT>hUx{l{=)L|4{0;w4z<<`_m(Uyj zY4Be@z&`_<;r|Re_Wu)fKSkWnte8JT%qv#R9P}pURm996h#B(PjTP^AVz_>5(3^O_N4&fhuMWM5_Xp@|7WbCW z{maf%1A61vgm1&b@F~}|Fm2#k7I#PJUUtdy>q7q*tZx9FZ*ld&7g$`e(0$G3>O*h1 zwxP4RcEGo7<#-o)-a+j=gzi67;kWd$b^>Qk62?|Ua@Nh9nb~&3$P0WqS1w97%66Q+ zpdaVr&~yBD$BkxXqp>q9TcOj43FdU_L9%v(a$Ny>fV==)wP2)j%o>o?1!!ALqm!{NwsNfz^x zG;-T*%<;o&&~`Qa zC1*I7M+S(lB6~qZzPVhxQ}_I8u~wjm$3f&N(yNlBO*e{GgCJ}ccJuO@1hVv6);h4119sirb~_vW*j3}WAuEMqe#pM&`zbpWVm5nV zul!vkS7s{NE0t35{YH7eQQmJ9y;02fj@c;xM4hksy?#8EeKloOv)gRDQFX2+=jGF{ zRET@DVW>Q9kmpBU)76kQ^_&9k(>-N2(_=2T5s~J2DfCLt%f}l}*EZKS9xbE*4Yd~Y za&=>U3 z=94&Kuv70k-%=1wxvP2vp{bd?2wJVkjdP7+K9w?6erZG{jYx{|AdKoM8C|s*sO>pT zeQi2lo5~K-p!cnQ^!RtyB&5H+qiRmi_>N*&M^B)(82C{vsm91fE!N^-$!6u;vThiI z*;^3<<)j8?<)6B6?Cx6o)WoM9c~!G<4@uVH+5oVZ07|0)FB3p{06-e`Xa;UevfJ_LGL#?080aHWc1Xv^{{ylSyPi%)+Ai$ z#{JMAEX4klz40e-kjHi6Uo*YXX?Gpj=+H}1Ha(~92T|-bq(={-+mzGSpLM;6X5GHt z3xha__j+zLoqZd>EqcSUPj<%h8y&9a#jYFE`!>@@*1T5olK_MS z?Y{a|JImeeh1g?9h+_laGMf5ny4xP5?sXwFW?zS-d&o5c)0R?vFa%+HbG7IN^eadrYxToyTSyoM&aV+K=ePNrz9r zn6K3=!b<8KSFM_s%b=kEo}cE&iHh8um|lWgbT-Ppfqwv;%a4rF(=WVqAaxc?d6&{* ztRq_rRvs~$`;q^3F z75$V;O;DP@)hg|ewo9zF+U#IwQE6P^gE86lDM)2DIY-#%S8LgbMF*(W^2v9T83o@< z^3}ecX1YZ$D&ARHS(Zb`-8VXw(@yFPY&nfQKQEaC6l`;0_b`{zp)Pxexs(rec{%Ko z9NZ6a*-4d=`3ke3NBkN~dmQPjEbVf%uQOWf!W+NP4uIb6E3J_FQX5K$=@Cau#vy3y zD{d$mhq$P3Is>-8?TqSs{l>FeK4rsv^Bq%L5B~=7!1*uvFX+PhKMF?S)b&4bi88hu CkMITn literal 1522 zcmV$wjeYVffPz4HkDQcORXW9 z)X`+7Yi3efSbgxpSJ(2@ALsAz#ie_n`A+8K0*eP9nYB(j``dee`+8>~ZpFqrWB#(o06fq_+n=u@YaT4`RAjU~mZ-lY_G~UNi=UMnBp+ATCXW`HCc_SyT z`vP)e`-?(<3!Z1BcAml=6nV?+gwy}qd8oF4d?nH@H-@Nk3K@*P8eKV^T*KrPFyU10_;PB|5NZE zHTW~YHUBjDtv>!4=rn&882kSU>@&p88Zkdd%$yN34_wEbMa)89%sJ?E%z0pWBj#1a zW!)dZuA#SAkq7U_brIb|=D$S~x(++m^R6slK9-SZ5jf|0Q{gd(*~44VaZW6M317yK z5yIGQ>`EGX9rq6677V_-z<&c@3I2DDT$YSn%ze2BozCSxu#(}s+~>>vSca{h+gD<6 z7asy=8~$cgMRW)B%=o^n0_T~r{0*>&2KOT|xQA=NHTOEWEra`8;F|j}ur<`n*msEe z*ogN;4DR4l;5y#-h*vP;Z2;Hto&kGm#M?y7XXs-Y*grJw>uTa+6#o~HFrwFho<#M^ zuI&eo=gPU+ytIQl}jmB26Q>#1fwj~|6 zm`Vkq->HZ4d8e6rm6G(dYvH_X@9q?p)NFaRP=-$1HsxA-r{#p527w2}(Q%!o=eLpD=lG@MwLHH#+|F?$Gsjg{8=FM9>p2a{eaKkIbGx;* z>IDwmC~S_BmeVqm$&ku%H}ab~(`K4I*6koDd7j_M?OAe0!m{v2%YDl*oqeO}OUo+u zCzsokGmA14Q0kl{x02{ggMEv5Sl4YUSahGLzD!1UBfmMk6s<1|=uv<+m>n>9N%0O+ zslHYFQ*@t1rMqhLh5Wlcd$(u*)1L8vJ+twiCI7;jl--?9m`=S)8_Qrfw(VeJZc{GE zwa1F}KJ6P#*EDk6z-ic#Nm;%37#`3`>s?%}h4r6?M4FR3y6+2ewYpYbFIQKJJqC5v zt_4}DR;rH%c%41FHI!Got>1f6s#YH@|!!bar2;)rcxyQVbPbQu?ZRZV<|9CnOg&tef%3vU2Vg zB|~Cdy%Et-cnerXx1K&fPi8#0L9agTiowmtBL|*Z^&evWeXm zWxLbLL?g=dCY6-&(U{=RfA6|Q;eO=)L+McSF Ywj0j3!;bI9lQ>A5H(x#f0rev{u<*G9^#A|> diff --git a/tests/manual/rhi/tessellation/test.vert.qsb b/tests/manual/rhi/tessellation/test.vert.qsb index ac261d2b41a27ff8277ca7a08eefabe4cf623a0b..39734b6d5dd020174cb19ad52e32972803239fd0 100644 GIT binary patch literal 1106 zcmV-Y1g-l3026R{ob6cMZrnr^9`EL-F>OdFq|hd1sz_+Jt;}v3P{on}YEc@gQbM*fAUMZ!X2)xL!v&~PQAQe%=gfD$^J{M$ z#@Hg0qXu(@4Oz%yCRhS9WC6(Pc=#6$Rv0Gh0nCRP5!9FkHDE5Y5pzl^Gui*mFlfC( zYhNGLey`Uy7ZGB@@rNW`g$$t5TWld)_nvm2T5;01wm)gH%=h|)_-(e7-Os^1bp@u0 zU4!`!%ypO>FrTn_;F3eFN@kGE$jNFXLo0i5RkA&>g1onh1NZBN{RZuk?>cCq-W}km z6UQ3dEx^iQ-T*w-e1ZIb53X0}497j%FA?85>`lTiWp<>$io?uw=h#*99nU(nzeX~& zu}n4$vay`yQ4aKJh4|{kw@TO*;#;NsDt*Mhn`!^>1>!#--9^&BMSG=xi)vHP@of;_ zrw~((^1%r62T0~&-67wull^Z%x{$>rWh^IKBbkwt-5?qAAA)!2-}|J$M&}sESAfSD zZc^SbH@o1;O~NZbZj;}5ruyJdu=r56{2cWoY>Y3Nehz-y9C=YJ!hk>6YH=?%P2W8a z)1={tJvTx54sSKN2>8hBJ>bJI7AWmh!$2Z&Bc~VoVN?ZcYc|4|qfqoYI@oBMuS^c( z#CHzK6sQ1ryP-_q=WVOi>|`)90}!Gbn`R-{pO_xMPJ-QTxBgd=Vi#@cKx;q733$=t z13z?=?YxnWnLoWH7!<7a%p{NSv{4@Ui>u(<& z96Wp6>f`}3D1F+7z^d5~(xgPMl2Xk+6*geHFLFs3t71S$B+dmDkyO()8C+15vd^;K zrA%>1?1|?HyjP}&+HUUD(ltVmvYAJa{mdyHkQ7vSBvr#~BSCqIHJyQ}<|$!y4kjIg zn8-`gC@2^j=UWzEs#kT(8oz9cpy#K3kAHsZC9dzlkE}0_cTFvi#cBUzyjU-HO*4%} zaKZ!k)Qg91&*PbvZXPt#LetefW7}!$MfkV|t>)3u%)YU0m3;(_i~J&((ebwJCP^fY z)5L2O45+rxL)uM6kM;A;wEOa|q0Hec#kN)LkB(I9Xgtmx{%R~OhNXH^M~lF!deMYa zH3d+)sG+PS7IQu`Tc{?yjA}o>)@shG-fJn?cU}~DKA*hWXV>aJPelN1;NhMh9=ra2 zFruG}{l|bzUPhkVca%$@w6=C$y3?7iz@KY z1MWB-44~n+9yWE;6yYCH^GGGT(ogH0-z__0o$-6saam`SN-^LKH#l!pN58vMCT%t| zMcsP;ZA)L!!u87*zHH$)yM^a2Tllht-@F!{FMrLbcFzB~Sd!H$Wc3P}`j%JsRlKvf Y)r#Nf@_!)J_WlW{d@$7c-)Qpiwd%??lmGw# literal 714 zcmV;*0yX^r00>@qoZVAfYZE~fKATN1S#3+eqEMz&jJZ5TFFg09Rn1Hzuah3MJTAKHM1f_oJsl7EyYQQ=?U^I^{ zQW-a}W!hUO-IJ;x^s9|0H3-YoXQkeP%uM`a&!#!R2CmzK$m4*?nPG?Q_HyEQFSo(DGdIV2d7iI| zC+?jv`pZk=-{;shj^ANl#qaRl=rO$#>pjJuQV=~B;`~Bn1@}X)ZIkn#BDxy(Br%9# z1;(@(w#69fcd$CD?;gh&_?-H9jeP21o99Ned4ZK|GhfxQ%XQP4dI!HTIH-!idse3Lopdx2V&&#5sSF|5Bn)>PoScN4n9Jd!V;z^oD{3zaM5});zgjFMc;Ek<7bUVjq z=jM^;`o4KQ9E}6+YEE1{7w_MGIBK`g-qf0Lf@~@}tz%=$)!XA>L0%1Ixq6~)f~)KP)P0 wNCHbLlA&(sNKzcBCMS@1JPX`t2D2AK9B&Yez2RJ<>D-@l{@F_V3pPQd^%5&&>i_@% 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;