diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 0dc88a3556a..07bda4922dc 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -279,7 +279,7 @@ struct QMetalShaderResourceBindingsData { QRhiBatchedBindings > textureBatches; QRhiBatchedBindings > samplerBatches; } res[QRhiMetal::SUPPORTED_STAGES]; - enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; + enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2, TESSCTRL = 3, TESSEVAL = 4 }; }; struct QMetalCommandBufferData @@ -380,7 +380,8 @@ struct QMetalGraphicsPipelineData QVector deviceLocalWorkBuffers; QVector hostVisibleWorkBuffers; } tess; - template void setupVertexOrStageInputDescriptor(T *desc); + void setupVertexInputDescriptor(MTLVertexDescriptor *desc); + void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc); }; struct QMetalComputePipelineData @@ -1071,6 +1072,10 @@ static inline void bindStageBuffers(QMetalCommandBuffer *cbD, offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; break; + case QMetalShaderResourceBindingsData::TESSCTRL: + case QMetalShaderResourceBindingsData::TESSEVAL: + // do nothing. These are used later for tessellation + break; default: Q_UNREACHABLE(); break; @@ -1094,6 +1099,10 @@ static inline void bindStageTextures(QMetalCommandBuffer *cbD, [cbD->d->currentComputePassEncoder setTextures: textureBatch.resources.constData() withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))]; break; + case QMetalShaderResourceBindingsData::TESSCTRL: + case QMetalShaderResourceBindingsData::TESSEVAL: + // do nothing. These are used later for tessellation + break; default: Q_UNREACHABLE(); break; @@ -1117,6 +1126,10 @@ static inline void bindStageSamplers(QMetalCommandBuffer *cbD, [cbD->d->currentComputePassEncoder setSamplerStates: samplerBatch.resources.constData() withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))]; break; + case QMetalShaderResourceBindingsData::TESSCTRL: + case QMetalShaderResourceBindingsData::TESSEVAL: + // do nothing. These are used later for tessellation + break; default: Q_UNREACHABLE(); break; @@ -1150,17 +1163,22 @@ static inline void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceS } } -// 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) +static inline QRhiShaderResourceBinding::StageFlag toRhiSrbStage(int stage) { - return stages.testAnyFlags(QRhiShaderResourceBinding::VertexStage - | QRhiShaderResourceBinding::TessellationControlStage - | QRhiShaderResourceBinding::TessellationEvaluationStage); + switch (stage) { + case QMetalShaderResourceBindingsData::VERTEX: + return QRhiShaderResourceBinding::StageFlag::VertexStage; + case QMetalShaderResourceBindingsData::TESSCTRL: + return QRhiShaderResourceBinding::StageFlag::TessellationControlStage; + case QMetalShaderResourceBindingsData::TESSEVAL: + return QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage; + case QMetalShaderResourceBindingsData::FRAGMENT: + return QRhiShaderResourceBinding::StageFlag::FragmentStage; + case QMetalShaderResourceBindingsData::COMPUTE: + return QRhiShaderResourceBinding::StageFlag::ComputeStage; + } + + Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::StageFlag::VertexStage); } void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, @@ -1187,20 +1205,13 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; } } - 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 }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); + + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (b->stage.testFlag(toRhiSrbStage(stage))) { + const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset }); + } } } break; @@ -1212,36 +1223,21 @@ 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 (isVertexishResource(b->stage)) { - // Must handle all three cases (combined, separate, separate): - // first = texture binding, second = sampler binding - // first = texture binding - // first = sampler binding (i.e. BindingType::Texture...) - const int textureBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Texture); - const int samplerBinding = texD && samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Sampler) - : (samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::VERTEX, nativeResourceBindingMaps, BindingType::Texture) : -1); - if (textureBinding >= 0 && texD) - bindingData.res[QMetalShaderResourceBindingsData::VERTEX].textures.append({ textureBinding + elem, texD->d->tex }); - if (samplerBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::VERTEX].samplers.append({ samplerBinding + elem, samplerD->d->samplerState }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - const int textureBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); - const int samplerBinding = texD && samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler) - : (samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Texture) : -1); - if (textureBinding >= 0 && texD) - bindingData.res[QMetalShaderResourceBindingsData::FRAGMENT].textures.append({ textureBinding + elem, texD->d->tex }); - if (samplerBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::FRAGMENT].samplers.append({ samplerBinding + elem, samplerD->d->samplerState }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - const int textureBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Texture); - const int samplerBinding = texD && samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Sampler) - : (samplerD ? mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Texture) : -1); - if (textureBinding >= 0 && texD) - bindingData.res[QMetalShaderResourceBindingsData::COMPUTE].textures.append({ textureBinding + elem, texD->d->tex }); - if (samplerBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::COMPUTE].samplers.append({ samplerBinding + elem, samplerD->d->samplerState }); + + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (b->stage.testFlag(toRhiSrbStage(stage))) { + // Must handle all three cases (combined, separate, separate): + // first = texture binding, second = sampler binding + // first = texture binding + // first = sampler binding (i.e. BindingType::Texture...) + const int textureBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture); + const int samplerBinding = texD && samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Sampler) + : (samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture) : -1); + if (textureBinding >= 0 && texD) + bindingData.res[stage].textures.append({ textureBinding + elem, texD->d->tex }); + if (samplerBinding >= 0) + bindingData.res[stage].samplers.append({ samplerBinding + elem, samplerD->d->samplerState }); + } } } } @@ -1252,20 +1248,13 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD { QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); id t = texD->d->viewForLevel(b->u.simage.level); - 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 }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::FRAGMENT].textures.append({ nativeBinding, t }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Texture); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::COMPUTE].textures.append({ nativeBinding, t }); + + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (b->stage.testFlag(toRhiSrbStage(stage))) { + const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture); + if (nativeBinding >= 0) + bindingData.res[stage].textures.append({ nativeBinding, t }); + } } } break; @@ -1276,20 +1265,12 @@ 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 (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 }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - const int nativeBinding = mapBinding(b->binding, QMetalShaderResourceBindingsData::COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) - bindingData.res[QMetalShaderResourceBindingsData::COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (b->stage.testFlag(toRhiSrbStage(stage))) { + const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset }); + } } } break; @@ -1300,9 +1281,10 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { - if (cbD->recordingPass != QMetalCommandBuffer::RenderPass && (stage == QMetalShaderResourceBindingsData::VERTEX || stage == QMetalShaderResourceBindingsData::FRAGMENT)) + if (cbD->recordingPass != QMetalCommandBuffer::RenderPass && (stage == QMetalShaderResourceBindingsData::VERTEX || stage == QMetalShaderResourceBindingsData::FRAGMENT + || stage == QMetalShaderResourceBindingsData::TESSCTRL || stage == QMetalShaderResourceBindingsData::TESSEVAL)) continue; - if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == QMetalShaderResourceBindingsData::COMPUTE) + if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && (stage == QMetalShaderResourceBindingsData::COMPUTE)) continue; // QRhiBatchedBindings works with the native bindings and expects @@ -1565,16 +1547,26 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind // dynamic uniform buffer offsets always trigger a rebind if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) { - const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr }; + const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr }; if (gfxPsD) { cbD->currentGraphicsSrb = srbD; cbD->currentComputeSrb = nullptr; - resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap; - resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap; + if (gfxPsD->d->tess.enabled) { + // If tessellating, we don't know which compVs shader to use until the draw call is + // made. They should all have the same native resource binding map, so pick one. + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap); + resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->tess.compVs[0].nativeResourceBindingMap; + resBindMaps[QMetalShaderResourceBindingsData::TESSCTRL] = &gfxPsD->d->tess.compTesc.nativeResourceBindingMap; + resBindMaps[QMetalShaderResourceBindingsData::TESSEVAL] = &gfxPsD->d->tess.vertTese.nativeResourceBindingMap; + } else { + resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs.nativeResourceBindingMap; + } + resBindMaps[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs.nativeResourceBindingMap; } else { cbD->currentGraphicsSrb = nullptr; cbD->currentComputeSrb = srbD; - resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap; + resBindMaps[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs.nativeResourceBindingMap; } cbD->currentSrbGeneration = srbD->generation; cbD->currentResSlot = resSlot; @@ -1734,8 +1726,52 @@ static void endTessellationComputeEncoding(QMetalCommandBuffer *cbD) cbD->d->tessellationComputeEncoder = nil; } + QMetalRenderTargetData * rtD = nullptr; + + switch (cbD->currentTarget->resourceType()) { + case QRhiResource::SwapChainRenderTarget: + rtD = QRHI_RES(QMetalSwapChainRenderTarget, cbD->currentTarget)->d; + break; + case QRhiResource::TextureRenderTarget: + rtD = QRHI_RES(QMetalTextureRenderTarget, cbD->currentTarget)->d; + break; + default: + break; + } + + Q_ASSERT(rtD); + + QVarLengthArray oldColorLoad; + for (uint i = 0; i < uint(rtD->colorAttCount); ++i) { + oldColorLoad.append(cbD->d->currentPassRpDesc.colorAttachments[i].loadAction); + if (cbD->d->currentPassRpDesc.colorAttachments[i].storeAction != MTLStoreActionDontCare) + cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad; + } + + MTLLoadAction oldDepthLoad; + MTLLoadAction oldStencilLoad; + if (rtD->dsAttCount) { + oldDepthLoad = cbD->d->currentPassRpDesc.depthAttachment.loadAction; + if (cbD->d->currentPassRpDesc.depthAttachment.storeAction != MTLStoreActionDontCare) + cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad; + + oldStencilLoad = cbD->d->currentPassRpDesc.stencilAttachment.loadAction; + if (cbD->d->currentPassRpDesc.stencilAttachment.storeAction != MTLStoreActionDontCare) + cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad; + } + cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc]; cbD->resetPerPassCachedState(); + + for (uint i = 0; i < uint(rtD->colorAttCount); ++i) { + cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = oldColorLoad[i]; + } + + if (rtD->dsAttCount) { + cbD->d->currentPassRpDesc.depthAttachment.loadAction = oldDepthLoad; + cbD->d->currentPassRpDesc.stencilAttachment.loadAction = oldStencilLoad; + } + } void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) @@ -1772,7 +1808,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) // 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). + // "vertex as compute" shader cbD->d->currentComputePassEncoder = computeEncoder; rebindShaderResources(cbD, QMetalShaderResourceBindingsData::VERTEX, QMetalShaderResourceBindingsData::COMPUTE); cbD->d->currentComputePassEncoder = nil; @@ -1818,9 +1854,9 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) 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) + cbD->d->currentComputePassEncoder = computeEncoder; + rebindShaderResources(cbD, QMetalShaderResourceBindingsData::TESSCTRL, QMetalShaderResourceBindingsData::COMPUTE); + cbD->d->currentComputePassEncoder = nil; const QMap &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings); const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1); @@ -1895,7 +1931,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args) graphicsPipeline->makeActiveForCurrentRenderPassEncoder(cbD); id renderEncoder = cbD->d->currentRenderPassEncoder; - rebindShaderResources(cbD, QMetalShaderResourceBindingsData::VERTEX, QMetalShaderResourceBindingsData::VERTEX, &resourceBindings); + rebindShaderResources(cbD, QMetalShaderResourceBindingsData::TESSEVAL, QMetalShaderResourceBindingsData::VERTEX, &resourceBindings); rebindShaderResources(cbD, QMetalShaderResourceBindingsData::FRAGMENT, QMetalShaderResourceBindingsData::FRAGMENT, &resourceBindings); const QMap &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings); @@ -4460,10 +4496,10 @@ void QMetalGraphicsPipeline::mapStates() d->slopeScaledDepthBias = m_slopeScaledDepthBias; } -template -void QMetalGraphicsPipelineData::setupVertexOrStageInputDescriptor(T *desc) +void QMetalGraphicsPipelineData::setupVertexInputDescriptor(MTLVertexDescriptor *desc) { // same binding space for vertex and constant buffers - work it around + // should be in native resource binding not SPIR-V, but this will work anyway const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1; QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout(); @@ -4471,7 +4507,6 @@ void QMetalGraphicsPipelineData::setupVertexOrStageInputDescriptor(T *desc) it != itEnd; ++it) { const uint loc = uint(it->location()); - // 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()); @@ -4481,15 +4516,42 @@ void QMetalGraphicsPipelineData::setupVertexOrStageInputDescriptor(T *desc) it != itEnd; ++it, ++bindingIndex) { const uint layoutIdx = uint(firstVertexBinding + bindingIndex); - using StepT = decltype(desc.layouts[layoutIdx].stepFunction); - if (std::is_same_v) { - desc.layouts[layoutIdx].stepFunction = StepT( + desc.layouts[layoutIdx].stepFunction = it->classification() == QRhiVertexInputBinding::PerInstance - ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX); + ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex; + desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate()); + desc.layouts[layoutIdx].stride = it->stride(); + } +} + +void QMetalGraphicsPipelineData::setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc) +{ + // same binding space for vertex and constant buffers - work it around + // should be in native resource binding not SPIR-V, but this will work anyway + const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1; + + QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout(); + for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes(); + it != itEnd; ++it) + { + const uint loc = uint(it->location()); + 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 = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings(); + it != itEnd; ++it, ++bindingIndex) + { + const uint layoutIdx = uint(firstVertexBinding + bindingIndex); + if (desc.indexBufferIndex) { + desc.layouts[layoutIdx].stepFunction = + it->classification() == QRhiVertexInputBinding::PerInstance + ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridXIndexed; } else { - desc.layouts[layoutIdx].stepFunction = StepT( + desc.layouts[layoutIdx].stepFunction = it->classification() == QRhiVertexInputBinding::PerInstance - ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex); + ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX; } desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate()); desc.layouts[layoutIdx].stride = it->stride(); @@ -4547,7 +4609,7 @@ bool QMetalGraphicsPipeline::createVertexFragmentPipeline() QRHI_RES_RHI(QRhiMetal); MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor]; - d->setupVertexOrStageInputDescriptor(vertexDesc); + d->setupVertexInputDescriptor(vertexDesc); MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; rpDesc.vertexDescriptor = vertexDesc; @@ -4701,7 +4763,7 @@ id QMetalGraphicsPipelineData::Tessellation::vsCompPipe cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding; } } - q->setupVertexOrStageInputDescriptor(cpDesc.stageInputDescriptor); + q->setupStageInputDescriptor(cpDesc.stageInputDescriptor); rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc); @@ -4759,6 +4821,13 @@ static inline bool hasBuiltin(const QVector [builtin](const QShaderDescription::BuiltinVariable &b) { return b.type == builtin; }) != builtinList.cend(); } +static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b) +{ + return a.location == b.location + && a.type == b.type + && a.perPatch == b.perPatch; +} + id QMetalGraphicsPipelineData::Tessellation::teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline) { if (pipeline->d->ps) @@ -4773,19 +4842,28 @@ id QMetalGraphicsPipelineData::Tessellation::teseFragRen 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); + QMap teseInVars; + for (const QShaderDescription::InOutVariable &teseInVar : vertTese.desc.inputVariables()) + teseInVars[teseInVar.location] = teseInVar; quint32 offsetInTescOutput = 0; quint32 offsetInTescPatchOutput = 0; int lastLocation = -1; - for (const QShaderDescription::InOutVariable &tescOutVar : compTesc.desc.outputVariables()) { + // these need to be sorted in location order so that lastLocation is calculated correctly - use QMap. + QMap tescOutVars; + for (const QShaderDescription::InOutVariable &tescOutVar : compTesc.desc.outputVariables()) + tescOutVars[tescOutVar.location] = tescOutVar; + + for (const QShaderDescription::InOutVariable &tescOutVar : tescOutVars) { const int location = tescOutVar.location; lastLocation = location; const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(tescOutVar.type); - if (teseInputLocations.contains(location)) { + if (teseInVars.contains(location)) { + if (!matches(teseInVars[location], tescOutVar)) { + qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << location; + qWarning() << "tesc out:" << tescOutVar << "tese in:" << teseInVars[location]; + } if (tescOutVar.perPatch) { if (tescPatchOutputBufferBinding >= 0) { vertexDesc.attributes[location].bufferIndex = tescPatchOutputBufferBinding; @@ -4799,6 +4877,8 @@ id QMetalGraphicsPipelineData::Tessellation::teseFragRen vertexDesc.attributes[location].offset = offsetInTescOutput; } } + } else { + qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar; } if (tescOutVar.perPatch) offsetInTescPatchOutput += rhiD->byteSizePerVertexForVertexInputFormat(format); diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 84c746e830b..ff57e84ae9c 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -446,7 +446,7 @@ public: void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); void executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot); void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); - static const int SUPPORTED_STAGES = 3; + static const int SUPPORTED_STAGES = 5; void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, int dynamicOffsetCount,