diff options
Diffstat (limited to 'src/gui/rhi/qrhimetal.mm')
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 439 |
1 files changed, 312 insertions, 127 deletions
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 83fec31081..0806c8a052 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -41,6 +41,8 @@ #ifdef Q_OS_MACOS #include <AppKit/AppKit.h> +#else +#include <UIKit/UIKit.h> #endif #include <Metal/Metal.h> @@ -112,15 +114,6 @@ QT_BEGIN_NAMESPACE */ /*! - \class QRhiMetalTextureNativeHandles - \inmodule QtRhi - \brief Holds the Metal texture object that is backing a QRhiTexture instance. - - \note The class uses \c{void *} as the type since including the Objective C - headers is not acceptable here. The actual type is \c{id<MTLTexture>}. - */ - -/*! \class QRhiMetalCommandBufferNativeHandles \inmodule QtRhi \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer. @@ -305,6 +298,8 @@ struct QMetalGraphicsPipelineData MTLPrimitiveType primitiveType; MTLWinding winding; MTLCullMode cullMode; + float depthBias; + float slopeScaledDepthBias; QMetalShader vs; QMetalShader fs; }; @@ -318,7 +313,13 @@ struct QMetalComputePipelineData struct QMetalSwapChainData { + // The iOS simulator's headers mark CAMetalLayer as iOS 13.0+ only. + // (for real device SDKs it is 8.0+) +#ifdef TARGET_IPHONE_SIMULATOR + API_AVAILABLE(ios(13.0)) CAMetalLayer *layer = nullptr; +#else CAMetalLayer *layer = nullptr; +#endif id<CAMetalDrawable> curDrawable; dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT]; MTLRenderPassDescriptor *rp = nullptr; @@ -561,6 +562,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ReadBackNonBaseMipLevel: return true; + case QRhi::TexelFetch: + return true; default: Q_UNREACHABLE(); return false; @@ -578,6 +581,8 @@ int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const return 8; case QRhi::FramesInFlight: return QMTL_FRAMES_IN_FLIGHT; + case QRhi::MaxAsyncReadbackFrames: + return QMTL_FRAMES_IN_FLIGHT; default: Q_UNREACHABLE(); return 0; @@ -627,9 +632,9 @@ QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &p QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -665,12 +670,17 @@ static inline int mapBinding(int binding, BindingType type) { const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex]; - if (map) { - auto it = map->constFind(binding); - if (it != map->cend()) - return type == BindingType::Sampler ? it->second : it->first; - } - return binding; + if (!map || map->isEmpty()) + return binding; // old QShader versions do not have this map, assume 1:1 mapping then + + auto it = map->constFind(binding); + if (it != map->cend()) + return type == BindingType::Sampler ? it->second : it->first; // may be -1, if the resource is inactive + + // Hitting this path is normal too. It is not given that the resource (for + // example, a uniform block) is present in the shaders for all the stages + // specified by the visibility mask in the QRhiShaderResourceBinding. + return -1; } void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, @@ -680,11 +690,27 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD bool offsetOnlyChange, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]) { - struct { - QRhiBatchedBindings<id<MTLBuffer> > buffers; - QRhiBatchedBindings<NSUInteger> bufferOffsets; - QRhiBatchedBindings<id<MTLTexture> > textures; - QRhiBatchedBindings<id<MTLSamplerState> > samplers; + struct Stage { + struct Buffer { + int nativeBinding; + id<MTLBuffer> mtlbuf; + uint offset; + }; + struct Texture { + int nativeBinding; + id<MTLTexture> mtltex; + }; + struct Sampler { + int nativeBinding; + id<MTLSamplerState> mtlsampler; + }; + QVarLengthArray<Buffer, 8> buffers; + QVarLengthArray<Texture, 8> textures; + QVarLengthArray<Sampler, 8> samplers; + QRhiBatchedBindings<id<MTLBuffer> > bufferBatches; + QRhiBatchedBindings<NSUInteger> bufferOffsetBatches; + QRhiBatchedBindings<id<MTLTexture> > textureBatches; + QRhiBatchedBindings<id<MTLSamplerState> > samplerBatches; } res[SUPPORTED_STAGES]; enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; @@ -704,73 +730,99 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[VERTEX].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[FRAGMENT].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[COMPUTE].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); } } break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[VERTEX].samplers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[FRAGMENT].samplers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[COMPUTE].samplers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + 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)) { + const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); + const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler); + if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { + res[VERTEX].textures.append({ nativeBindingTexture + elem, texD->d->tex }); + res[VERTEX].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState }); + } + } + if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { + const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); + const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler); + if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { + res[FRAGMENT].textures.append({ nativeBindingTexture + elem, texD->d->tex }); + res[FRAGMENT].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState }); + } + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); + const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler); + if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { + res[COMPUTE].textures.append({ nativeBindingTexture + elem, texD->d->tex }); + res[COMPUTE].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState }); + } + } } } break; case QRhiShaderResourceBinding::ImageLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageLoadStore: { QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level); - if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) - res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), t); - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) - res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), t); - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) - res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), t); + if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { + const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); + if (nativeBinding >= 0) + res[VERTEX].textures.append({ nativeBinding, t }); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { + const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); + if (nativeBinding >= 0) + res[FRAGMENT].textures.append({ nativeBinding, t }); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); + if (nativeBinding >= 0) + res[COMPUTE].textures.append({ nativeBinding, t }); + } } break; case QRhiShaderResourceBinding::BufferLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferLoadStore: { QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); id<MTLBuffer> mtlbuf = bufD->d->buf[0]; uint offset = uint(b->u.sbuf.offset); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[VERTEX].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[FRAGMENT].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[COMPUTE].bufferOffsets.feed(b->binding, offset); + const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); + if (nativeBinding >= 0) + res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); } } break; @@ -786,12 +838,26 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE) continue; - res[stage].buffers.finish(); - res[stage].bufferOffsets.finish(); + // QRhiBatchedBindings works with the native bindings and expects + // sorted input. The pre-sorted QRhiShaderResourceBinding list (based + // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we + // have to sort here every time. + + std::sort(res[stage].buffers.begin(), res[stage].buffers.end(), [](const Stage::Buffer &a, const Stage::Buffer &b) { + return a.nativeBinding < b.nativeBinding; + }); + + for (const Stage::Buffer &buf : qAsConst(res[stage].buffers)) { + res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf); + res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset); + } + + res[stage].bufferBatches.finish(); + res[stage].bufferOffsetBatches.finish(); - for (int i = 0, ie = res[stage].buffers.batches.count(); i != ie; ++i) { - const auto &bufferBatch(res[stage].buffers.batches[i]); - const auto &offsetBatch(res[stage].bufferOffsets.batches[i]); + for (int i = 0, ie = res[stage].bufferBatches.batches.count(); i != ie; ++i) { + const auto &bufferBatch(res[stage].bufferBatches.batches[i]); + const auto &offsetBatch(res[stage].bufferOffsetBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() @@ -817,11 +883,25 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (offsetOnlyChange) continue; - res[stage].textures.finish(); - res[stage].samplers.finish(); + std::sort(res[stage].textures.begin(), res[stage].textures.end(), [](const Stage::Texture &a, const Stage::Texture &b) { + return a.nativeBinding < b.nativeBinding; + }); + + std::sort(res[stage].samplers.begin(), res[stage].samplers.end(), [](const Stage::Sampler &a, const Stage::Sampler &b) { + return a.nativeBinding < b.nativeBinding; + }); + + for (const Stage::Texture &t : qAsConst(res[stage].textures)) + res[stage].textureBatches.feed(t.nativeBinding, t.mtltex); + + for (const Stage::Sampler &s : qAsConst(res[stage].samplers)) + res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler); - for (int i = 0, ie = res[stage].textures.batches.count(); i != ie; ++i) { - const auto &batch(res[stage].textures.batches[i]); + res[stage].textureBatches.finish(); + res[stage].samplerBatches.finish(); + + for (int i = 0, ie = res[stage].textureBatches.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].textureBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData() @@ -840,8 +920,8 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; } } - for (int i = 0, ie = res[stage].samplers.batches.count(); i != ie; ++i) { - const auto &batch(res[stage].samplers.batches[i]); + for (int i = 0, ie = res[stage].samplerBatches.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].samplerBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() @@ -885,6 +965,14 @@ void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline [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 }; + } } psD->lastActiveFrameSlot = currentFrameSlot; @@ -935,27 +1023,32 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - if (texD->generation != bd.stex.texGeneration - || texD->m_id != bd.stex.texId - || samplerD->generation != bd.stex.samplerGeneration - || samplerD->m_id != bd.stex.samplerId) - { + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + if (bd.stex.count != data->count) { + bd.stex.count = data->count; resNeedsRebind = true; - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; } - texD->lastActiveFrameSlot = currentFrameSlot; - samplerD->lastActiveFrameSlot = currentFrameSlot; + for (int elem = 0; elem < data->count; ++elem) { + QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex); + QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler); + if (texD->generation != bd.stex.d[elem].texGeneration + || texD->m_id != bd.stex.d[elem].texId + || samplerD->generation != bd.stex.d[elem].samplerGeneration + || samplerD->m_id != bd.stex.d[elem].samplerId) + { + resNeedsRebind = true; + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + } + texD->lastActiveFrameSlot = currentFrameSlot; + samplerD->lastActiveFrameSlot = currentFrameSlot; + } } break; case QRhiShaderResourceBinding::ImageLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageLoadStore: { QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); @@ -968,9 +1061,7 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind } break; case QRhiShaderResourceBinding::BufferLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferLoadStore: { QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); @@ -1746,16 +1837,15 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } // this handles all types of buffers, not just Dynamic -void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) +void QRhiMetal::executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot) { - const int idx = bufD->d->slotted ? currentFrameSlot : 0; - if (bufD->d->pendingUpdates[idx].isEmpty()) + if (bufD->d->pendingUpdates[slot].isEmpty()) return; - void *p = [bufD->d->buf[idx] contents]; + void *p = [bufD->d->buf[slot] contents]; int changeBegin = -1; int changeEnd = -1; - for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[idx])) { + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[slot])) { Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf)); memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); if (changeBegin == -1 || u.offset < changeBegin) @@ -1763,10 +1853,17 @@ void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) if (changeEnd == -1 || u.offset + u.data.size() > changeEnd) changeEnd = u.offset + u.data.size(); } +#ifdef Q_OS_MACOS if (changeBegin >= 0 && bufD->d->managed) - [bufD->d->buf[idx] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))]; + [bufD->d->buf[slot] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))]; +#endif - bufD->d->pendingUpdates[idx].clear(); + bufD->d->pendingUpdates[slot].clear(); +} + +void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) +{ + executeBufferHostWritesForSlot(bufD, bufD->d->slotted ? currentFrameSlot : 0); } void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -1798,8 +1895,12 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) { Q_ASSERT(currentSwapChain); QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain); - if (!swapChainD->d->curDrawable) - swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable]; + if (!swapChainD->d->curDrawable) { +#ifdef TARGET_IPHONE_SIMULATOR + if (@available(ios 13.0, *)) +#endif + swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable]; + } if (!swapChainD->d->curDrawable) { qWarning("No drawable"); return; @@ -2113,6 +2214,22 @@ bool QMetalBuffer::build() return true; } +QRhiBuffer::NativeBuffer QMetalBuffer::nativeBuffer() +{ + if (d->slotted) { + NativeBuffer b; + Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT)); + for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { + QRHI_RES_RHI(QRhiMetal); + rhiD->executeBufferHostWritesForSlot(this, i); + b.objects[i] = &d->buf[i]; + } + b.slotCount = QMTL_FRAMES_IN_FLIGHT; + return b; + } + return { { &d->buf[0] }, 1 }; +} + QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags), @@ -2170,12 +2287,13 @@ bool QMetalRenderBuffer::build() case DepthStencil: #ifdef Q_OS_MACOS desc.storageMode = MTLStorageModePrivate; + d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported + ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8; #else - desc.storageMode = MTLResourceStorageModeMemoryless; + desc.storageMode = MTLStorageModeMemoryless; transientBacking = true; + d->format = MTLPixelFormatDepth32Float_Stencil8; #endif - d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported - ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8; desc.pixelFormat = d->format; break; case Color: @@ -2237,7 +2355,6 @@ void QMetalTexture::release() e.texture.texture = d->owns ? d->tex : nil; d->tex = nil; - nativeHandlesStruct.texture = nullptr; for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { e.texture.stagingBuffers[i] = d->stagingBuf[i]; @@ -2279,6 +2396,10 @@ static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QR return MTLPixelFormatRGBA16Float; case QRhiTexture::RGBA32F: return MTLPixelFormatRGBA32Float; + case QRhiTexture::R16F: + return MTLPixelFormatR16Float; + case QRhiTexture::R32F: + return MTLPixelFormatR32Float; case QRhiTexture::D16: #ifdef Q_OS_MACOS @@ -2449,7 +2570,6 @@ bool QMetalTexture::build() d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()]; d->owns = true; - nativeHandlesStruct.texture = d->tex; QRHI_PROF; QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples)); @@ -2460,19 +2580,18 @@ bool QMetalTexture::build() return true; } -bool QMetalTexture::buildFrom(const QRhiNativeHandles *src) +bool QMetalTexture::buildFrom(QRhiTexture::NativeTexture src) { - const QRhiMetalTextureNativeHandles *h = static_cast<const QRhiMetalTextureNativeHandles *>(src); - if (!h || !h->texture) + void * const * tex = (void * const *) src.object; + if (!tex || !*tex) return false; if (!prepareBuild()) return false; - d->tex = (id<MTLTexture>) h->texture; + d->tex = (id<MTLTexture>) *tex; d->owns = false; - nativeHandlesStruct.texture = d->tex; QRHI_PROF; QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples)); @@ -2484,9 +2603,9 @@ bool QMetalTexture::buildFrom(const QRhiNativeHandles *src) return true; } -const QRhiNativeHandles *QMetalTexture::nativeHandles() +QRhiTexture::NativeTexture QMetalTexture::nativeTexture() { - return &nativeHandlesStruct; + return {&d->tex, 0}; } id<MTLTexture> QMetalTextureData::viewForLevel(int level) @@ -2505,8 +2624,8 @@ id<MTLTexture> QMetalTextureData::viewForLevel(int level) } QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v), + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w), d(new QMetalSamplerData) { } @@ -2569,12 +2688,8 @@ static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode return MTLSamplerAddressModeRepeat; case QRhiSampler::ClampToEdge: return MTLSamplerAddressModeClampToEdge; - case QRhiSampler::Border: - return MTLSamplerAddressModeClampToBorderColor; case QRhiSampler::Mirror: return MTLSamplerAddressModeMirrorRepeat; - case QRhiSampler::MirrorOnce: - return MTLSamplerAddressModeMirrorClampToEdge; default: Q_UNREACHABLE(); return MTLSamplerAddressModeClampToEdge; @@ -2647,6 +2762,32 @@ void QMetalRenderPassDescriptor::release() // nothing to do here } +bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const +{ + if (!other) + return false; + + const QMetalRenderPassDescriptor *o = QRHI_RES(const QMetalRenderPassDescriptor, other); + + if (colorAttachmentCount != o->colorAttachmentCount) + return false; + + if (hasDepthStencil != o->hasDepthStencil) + return false; + + for (int i = 0; i < colorAttachmentCount; ++i) { + if (colorFormat[i] != o->colorFormat[i]) + return false; + } + + if (hasDepthStencil) { + if (dsFormat != o->dsFormat) + return false; + } + + return true; +} + QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi) : QRhiRenderTarget(rhi), d(new QMetalRenderTargetData) @@ -2850,18 +2991,20 @@ bool QMetalShaderResourceBindings::build() break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + bd.stex.count = data->count; + for (int elem = 0; elem < data->count; ++elem) { + QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex); + QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler); + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + } } break; case QRhiShaderResourceBinding::ImageLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::ImageLoadStore: { QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); @@ -2870,9 +3013,7 @@ bool QMetalShaderResourceBindings::build() } break; case QRhiShaderResourceBinding::BufferLoad: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferStore: - Q_FALLTHROUGH(); case QRhiShaderResourceBinding::BufferLoadStore: { QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); @@ -3114,8 +3255,12 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c) id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { - QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant }; + QShaderKey key = { QShader::MetalLibShader, 20, shaderVariant }; QShaderCode mtllib = shader.shader(key); + if (mtllib.shader().isEmpty()) { + key.setSourceVersion(12); + mtllib = shader.shader(key); + } if (!mtllib.shader().isEmpty()) { dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), size_t(mtllib.shader().size()), @@ -3134,16 +3279,20 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } } - key = { QShader::MslShader, 12, shaderVariant }; + key = { QShader::MslShader, 20, shaderVariant }; QShaderCode mslSource = shader.shader(key); if (mslSource.shader().isEmpty()) { - qWarning() << "No MSL 1.2 code found in baked shader" << shader; + key.setSourceVersion(12); + mslSource = shader.shader(key); + } + if (mslSource.shader().isEmpty()) { + qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader; return nil; } NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()]; MTLCompileOptions *opts = [[MTLCompileOptions alloc] init]; - opts.languageVersion = MTLLanguageVersion1_2; + opts.languageVersion = key.sourceVersion() == 20 ? MTLLanguageVersion2_0 : MTLLanguageVersion1_2; NSError *err = nil; id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err]; [opts release]; @@ -3314,7 +3463,11 @@ bool QMetalGraphicsPipeline::build() // validation blows up otherwise. MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat); rpDesc.depthAttachmentPixelFormat = fmt; +#ifdef Q_OS_MACOS if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float) +#else + if (fmt != MTLPixelFormatDepth32Float) +#endif rpDesc.stencilAttachmentPixelFormat = fmt; } @@ -3357,6 +3510,8 @@ bool QMetalGraphicsPipeline::build() d->primitiveType = toMetalPrimitiveType(m_topology); d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise; d->cullMode = toMetalCullMode(m_cullMode); + d->depthBias = float(m_depthBias); + d->slopeScaledDepthBias = m_slopeScaledDepthBias; lastActiveFrameSlot = -1; generation += 1; @@ -3505,6 +3660,7 @@ void QMetalCommandBuffer::resetPerPassCachedState() currentIndexFormat = QRhiCommandBuffer::IndexUInt16; currentCullMode = -1; currentFrontFaceWinding = -1; + currentDepthBiasValues = { 0.0f, 0.0f }; d->currentFirstVertexBinding = -1; d->currentVertexInputsBuffers.clear(); @@ -3531,6 +3687,10 @@ QMetalSwapChain::~QMetalSwapChain() void QMetalSwapChain::release() { +#ifdef TARGET_IPHONE_SIMULATOR + if (@available(ios 13.0, *)) { +#endif + if (!d->layer) return; @@ -3559,6 +3719,10 @@ void QMetalSwapChain::release() QRHI_PROF_F(releaseSwapChain(this)); rhiD->unregisterResource(this); + +#ifdef TARGET_IPHONE_SIMULATOR + } +#endif } QRhiCommandBuffer *QMetalSwapChain::currentFrameCommandBuffer() @@ -3581,16 +3745,20 @@ QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor() { chooseFormats(); // ensure colorFormat and similar are filled out - QRHI_RES_RHI(QRhiMetal); QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi); rpD->colorAttachmentCount = 1; rpD->hasDepthStencil = m_depthStencil != nullptr; rpD->colorFormat[0] = int(d->colorFormat); +#ifdef Q_OS_MACOS // m_depthStencil may not be built yet so cannot rely on computed fields in it + QRHI_RES_RHI(QRhiMetal); rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8; +#else + rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8; +#endif return rpD; } @@ -3606,6 +3774,10 @@ void QMetalSwapChain::chooseFormats() bool QMetalSwapChain::buildOrResize() { +#ifdef TARGET_IPHONE_SIMULATOR + if (@available(ios 13.0, *)) { +#endif + Q_ASSERT(m_window); const bool needsRegistration = !window || window != m_window; @@ -3625,7 +3797,11 @@ bool QMetalSwapChain::buildOrResize() return false; } +#ifdef Q_OS_MACOS NSView *view = reinterpret_cast<NSView *>(window->winId()); +#else + UIView *view = reinterpret_cast<UIView *>(window->winId()); +#endif Q_ASSERT(view); d->layer = static_cast<CAMetalLayer *>(view.layer); Q_ASSERT(d->layer); @@ -3729,6 +3905,15 @@ bool QMetalSwapChain::buildOrResize() rhiD->registerResource(this); return true; + +#ifdef TARGET_IPHONE_SIMULATOR + } else { + // Won't ever get here in a normal app because MTLDevice creation would + // fail too. Print a warning, just in case. + qWarning("No CAMetalLayer support in this version of the iOS Simulator"); + return false; + } +#endif } QT_END_NAMESPACE |