Warning: That file was not part of the compilation database. It may have many parsing errors.

1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Gui module
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qrhimetal_p_p.h"
38#include "qshader_p.h"
39#include "qshaderdescription_p.h"
40#include <QGuiApplication>
41#include <QWindow>
42#include <qmath.h>
43
44#ifdef Q_OS_MACOS
45#include <AppKit/AppKit.h>
46#endif
47
48#include <Metal/Metal.h>
49#include <QuartzCore/CAMetalLayer.h>
50
51QT_BEGIN_NAMESPACE
52
53/*
54 Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
55 Shared (host visible) and duplicated (to help having 2 frames in flight),
56 "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
57 Textures are Private (device local) and a host visible staging buffer is
58 used to upload data to them. Does not rely on strong objects refs from
59 command buffers but does rely on the automatic resource tracking of the
60 command encoders. Assumes that an autorelease pool (ideally per frame) is
61 available on the thread on which QRhi is used.
62*/
63
64#if __has_feature(objc_arc)
65#error ARC not supported
66#endif
67
68// Note: we expect everything here pass the Metal API validation when running
69// in Debug mode in XCode. Some of the issues that break validation are not
70// obvious and not visible when running outside XCode.
71//
72// An exception is the nextDrawable Called Early blah blah warning, which is
73// plain and simply false.
74
75/*!
76 \class QRhiMetalInitParams
77 \inmodule QtRhi
78 \brief Metal specific initialization parameters.
79
80 A Metal-based QRhi needs no special parameters for initialization.
81
82 \badcode
83 QRhiMetalInitParams params;
84 rhi = QRhi::create(QRhi::Metal, &params);
85 \endcode
86
87 \note Metal API validation cannot be enabled by the application. Instead,
88 run the debug build of the application in XCode. Generating a
89 \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
90 way to enable this.
91
92 \note QRhiSwapChain can only target QWindow instances that have their
93 surface type set to QSurface::MetalSurface.
94
95 \section2 Working with existing Metal devices
96
97 When interoperating with another graphics engine, it may be necessary to
98 get a QRhi instance that uses the same Metal device. This can be achieved
99 by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
100 device must be set to a non-null value then. Optionally, a command queue
101 object can be specified as well.
102
103 The QRhi does not take ownership of any of the external objects.
104 */
105
106/*!
107 \class QRhiMetalNativeHandles
108 \inmodule QtRhi
109 \brief Holds the Metal device used by the QRhi.
110
111 \note The class uses \c{void *} as the type since including the Objective C
112 headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
113 \c{id<MTLCommandQueue>}.
114 */
115
116/*!
117 \class QRhiMetalTextureNativeHandles
118 \inmodule QtRhi
119 \brief Holds the Metal texture object that is backing a QRhiTexture instance.
120
121 \note The class uses \c{void *} as the type since including the Objective C
122 headers is not acceptable here. The actual type is \c{id<MTLTexture>}.
123 */
124
125/*!
126 \class QRhiMetalCommandBufferNativeHandles
127 \inmodule QtRhi
128 \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
129
130 \note The command buffer object is only guaranteed to be valid while
131 recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
132 - \l{QRhi::endFrame()}{endFrame()} or
133 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
134 \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
135
136 \note The command encoder is only valid while recording a pass, that is,
137 between \l{QRhiCommandBuffer::beginPass()} -
138 \l{QRhiCommandBuffer::endPass()}.
139 */
140
141struct QRhiMetalData
142{
143 QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
144
145 id<MTLDevice> dev = nil;
146 id<MTLCommandQueue> cmdQueue = nil;
147
148 MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
149 const QColor &colorClearValue,
150 const QRhiDepthStencilClearValue &depthStencilClearValue,
151 int colorAttCount);
152 id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
153 QString *error, QByteArray *entryPoint);
154 id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
155
156 struct DeferredReleaseEntry {
157 enum Type {
158 Buffer,
159 RenderBuffer,
160 Texture,
161 Sampler,
162 StagingBuffer
163 };
164 Type type;
165 int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
166 union {
167 struct {
168 id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
169 } buffer;
170 struct {
171 id<MTLTexture> texture;
172 } renderbuffer;
173 struct {
174 id<MTLTexture> texture;
175 id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
176 id<MTLTexture> views[QRhi::MAX_LEVELS];
177 } texture;
178 struct {
179 id<MTLSamplerState> samplerState;
180 } sampler;
181 struct {
182 id<MTLBuffer> buffer;
183 } stagingBuffer;
184 };
185 };
186 QVector<DeferredReleaseEntry> releaseQueue;
187
188 struct OffscreenFrame {
189 OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
190 bool active = false;
191 QMetalCommandBuffer cbWrapper;
192 } ofr;
193
194 struct ActiveReadback {
195 int activeFrameSlot = -1;
196 QRhiReadbackDescription desc;
197 QRhiReadbackResult *result;
198 id<MTLBuffer> buf;
199 quint32 bufSize;
200 QSize pixelSize;
201 QRhiTexture::Format format;
202 };
203 QVector<ActiveReadback> activeReadbacks;
204
205 API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
206 API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
207
208 static const int TEXBUF_ALIGN = 256; // probably not accurate
209};
210
211Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
212Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
213
214struct QMetalBufferData
215{
216 bool managed;
217 bool slotted;
218 id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
219 QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
220};
221
222struct QMetalRenderBufferData
223{
224 MTLPixelFormat format;
225 id<MTLTexture> tex = nil;
226};
227
228struct QMetalTextureData
229{
230 QMetalTextureData(QMetalTexture *t) : q(t) { }
231
232 QMetalTexture *q;
233 MTLPixelFormat format;
234 id<MTLTexture> tex = nil;
235 id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
236 bool owns = true;
237 id<MTLTexture> perLevelViews[QRhi::MAX_LEVELS];
238
239 id<MTLTexture> viewForLevel(int level);
240};
241
242struct QMetalSamplerData
243{
244 id<MTLSamplerState> samplerState = nil;
245};
246
247struct QMetalCommandBufferData
248{
249 id<MTLCommandBuffer> cb;
250 id<MTLRenderCommandEncoder> currentRenderPassEncoder;
251 id<MTLComputeCommandEncoder> currentComputePassEncoder;
252 MTLRenderPassDescriptor *currentPassRpDesc;
253 int currentFirstVertexBinding;
254 QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
255 QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
256};
257
258struct QMetalRenderTargetData
259{
260 QSize pixelSize;
261 float dpr = 1;
262 int sampleCount = 1;
263 int colorAttCount = 0;
264 int dsAttCount = 0;
265
266 struct ColorAtt {
267 bool needsDrawableForTex = false;
268 id<MTLTexture> tex = nil;
269 int layer = 0;
270 int level = 0;
271 bool needsDrawableForResolveTex = false;
272 id<MTLTexture> resolveTex = nil;
273 int resolveLayer = 0;
274 int resolveLevel = 0;
275 };
276
277 struct {
278 ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
279 id<MTLTexture> dsTex = nil;
280 bool hasStencil = false;
281 bool depthNeedsStore = false;
282 } fb;
283};
284
285struct QMetalGraphicsPipelineData
286{
287 id<MTLRenderPipelineState> ps = nil;
288 id<MTLDepthStencilState> ds = nil;
289 MTLPrimitiveType primitiveType;
290 MTLWinding winding;
291 MTLCullMode cullMode;
292 id<MTLLibrary> vsLib = nil;
293 id<MTLFunction> vsFunc = nil;
294 id<MTLLibrary> fsLib = nil;
295 id<MTLFunction> fsFunc = nil;
296};
297
298struct QMetalComputePipelineData
299{
300 id<MTLComputePipelineState> ps = nil;
301 id<MTLLibrary> csLib = nil;
302 id<MTLFunction> csFunc = nil;
303 MTLSize localSize;
304};
305
306struct QMetalSwapChainData
307{
308 CAMetalLayer *layer = nullptr;
309 id<CAMetalDrawable> curDrawable;
310 dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
311 MTLRenderPassDescriptor *rp = nullptr;
312 id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
313 QRhiTexture::Format rhiColorFormat;
314 MTLPixelFormat colorFormat;
315};
316
317QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
318{
319 Q_UNUSED(params);
320
321 d = new QRhiMetalData(this);
322
323 importedDevice = importDevice != nullptr;
324 if (importedDevice) {
325 if (d->dev) {
326 d->dev = (id<MTLDevice>) importDevice->dev;
327 importedCmdQueue = importDevice->cmdQueue != nullptr;
328 if (importedCmdQueue)
329 d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
330 } else {
331 qWarning("No MTLDevice given, cannot import");
332 importedDevice = false;
333 }
334 }
335}
336
337QRhiMetal::~QRhiMetal()
338{
339 delete d;
340}
341
342static inline uint aligned(uint v, uint byteAlign)
343{
344 return (v + byteAlign - 1) & ~(byteAlign - 1);
345}
346
347bool QRhiMetal::create(QRhi::Flags flags)
348{
349 Q_UNUSED(flags);
350
351 if (importedDevice)
352 [d->dev retain];
353 else
354 d->dev = MTLCreateSystemDefaultDevice();
355
356 qDebug("Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
357
358 if (importedCmdQueue)
359 [d->cmdQueue retain];
360 else
361 d->cmdQueue = [d->dev newCommandQueue];
362
363 if (@available(macOS 10.13, iOS 11.0, *)) {
364 d->captureMgr = [MTLCaptureManager sharedCaptureManager];
365 // Have a custom capture scope as well which then shows up in XCode as
366 // an option when capturing, and becomes especially useful when having
367 // multiple windows with multiple QRhis.
368 d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
369 const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
370 d->captureScope.label = label.toNSString();
371 }
372
373#if defined(Q_OS_MACOS)
374 caps.maxTextureSize = 16384;
375#elif defined(Q_OS_TVOS)
376 if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
377 caps.maxTextureSize = 16384;
378 else
379 caps.maxTextureSize = 8192;
380#elif defined(Q_OS_IOS)
381 // welcome to feature set hell
382 if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
383 || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
384 || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
385 {
386 caps.maxTextureSize = 16384;
387 } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
388 || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
389 {
390 caps.maxTextureSize = 8192;
391 } else {
392 caps.maxTextureSize = 4096;
393 }
394#endif
395
396 nativeHandlesStruct.dev = d->dev;
397 nativeHandlesStruct.cmdQueue = d->cmdQueue;
398
399 return true;
400}
401
402void QRhiMetal::destroy()
403{
404 executeDeferredReleases(true);
405 finishActiveReadbacks(true);
406
407 if (@available(macOS 10.13, iOS 11.0, *)) {
408 [d->captureScope release];
409 d->captureScope = nil;
410 }
411
412 [d->cmdQueue release];
413 if (!importedCmdQueue)
414 d->cmdQueue = nil;
415
416 [d->dev release];
417 if (!importedDevice)
418 d->dev = nil;
419}
420
421QVector<int> QRhiMetal::supportedSampleCounts() const
422{
423 return { 1, 2, 4, 8 };
424}
425
426int QRhiMetal::effectiveSampleCount(int sampleCount) const
427{
428 // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
429 const int s = qBound(1, sampleCount, 64);
430 if (!supportedSampleCounts().contains(s)) {
431 qWarning("Attempted to set unsupported sample count %d", sampleCount);
432 return 1;
433 }
434 return s;
435}
436
437QRhiSwapChain *QRhiMetal::createSwapChain()
438{
439 return new QMetalSwapChain(this);
440}
441
442QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
443{
444 return new QMetalBuffer(this, type, usage, size);
445}
446
447int QRhiMetal::ubufAlignment() const
448{
449 return 256;
450}
451
452bool QRhiMetal::isYUpInFramebuffer() const
453{
454 return false;
455}
456
457bool QRhiMetal::isYUpInNDC() const
458{
459 return true;
460}
461
462bool QRhiMetal::isClipDepthZeroToOne() const
463{
464 return true;
465}
466
467QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
468{
469 // depth range 0..1
470 static QMatrix4x4 m;
471 if (m.isIdentity()) {
472 // NB the ctor takes row-major
473 m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
474 0.0f, 1.0f, 0.0f, 0.0f,
475 0.0f, 0.0f, 0.5f, 0.5f,
476 0.0f, 0.0f, 0.0f, 1.0f);
477 }
478 return m;
479}
480
481bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
482{
483 Q_UNUSED(flags);
484
485#ifdef Q_OS_MACOS
486 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
487 return false;
488 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
489 return false;
490#else
491 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
492 return false;
493#endif
494
495 return true;
496}
497
498bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
499{
500 switch (feature) {
501 case QRhi::MultisampleTexture:
502 return true;
503 case QRhi::MultisampleRenderBuffer:
504 return true;
505 case QRhi::DebugMarkers:
506 return true;
507 case QRhi::Timestamps:
508 return false;
509 case QRhi::Instancing:
510 return true;
511 case QRhi::CustomInstanceStepRate:
512 return true;
513 case QRhi::PrimitiveRestart:
514 return true;
515 case QRhi::NonDynamicUniformBuffers:
516 return true;
517 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
518 return false;
519 case QRhi::NPOTTextureRepeat:
520 return true;
521 case QRhi::RedOrAlpha8IsRed:
522 return true;
523 case QRhi::ElementIndexUint:
524 return true;
525 case QRhi::Compute:
526 return true;
527 case QRhi::WideLines:
528 return false;
529 case QRhi::VertexShaderPointSize:
530 return true;
531 case QRhi::BaseVertex:
532 return true;
533 case QRhi::BaseInstance:
534 return true;
535 default:
536 Q_UNREACHABLE();
537 return false;
538 }
539}
540
541int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
542{
543 switch (limit) {
544 case QRhi::TextureSizeMin:
545 return 1;
546 case QRhi::TextureSizeMax:
547 return caps.maxTextureSize;
548 case QRhi::MaxColorAttachments:
549 return 8;
550 case QRhi::FramesInFlight:
551 return QMTL_FRAMES_IN_FLIGHT;
552 default:
553 Q_UNREACHABLE();
554 return 0;
555 }
556}
557
558const QRhiNativeHandles *QRhiMetal::nativeHandles()
559{
560 return &nativeHandlesStruct;
561}
562
563void QRhiMetal::sendVMemStatsToProfiler()
564{
565 // nothing to do here
566}
567
568void QRhiMetal::makeThreadLocalNativeContextCurrent()
569{
570 // nothing to do here
571}
572
573QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
574 int sampleCount, QRhiRenderBuffer::Flags flags)
575{
576 return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
577}
578
579QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
580 int sampleCount, QRhiTexture::Flags flags)
581{
582 return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
583}
584
585QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
586 QRhiSampler::Filter mipmapMode,
587 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
588{
589 return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v);
590}
591
592QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
593 QRhiTextureRenderTarget::Flags flags)
594{
595 return new QMetalTextureRenderTarget(this, desc, flags);
596}
597
598QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
599{
600 return new QMetalGraphicsPipeline(this);
601}
602
603QRhiComputePipeline *QRhiMetal::createComputePipeline()
604{
605 return new QMetalComputePipeline(this);
606}
607
608QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
609{
610 return new QMetalShaderResourceBindings(this);
611}
612
613void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
614 int dynamicOffsetCount,
615 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
616 bool offsetOnlyChange)
617{
618 static const int KNOWN_STAGES = 3;
619 struct {
620 QRhiBatchedBindings<id<MTLBuffer> > buffers;
621 QRhiBatchedBindings<NSUInteger> bufferOffsets;
622 QRhiBatchedBindings<id<MTLTexture> > textures;
623 QRhiBatchedBindings<id<MTLSamplerState> > samplers;
624 } res[KNOWN_STAGES];
625
626 for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
627 const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
628 switch (b->type) {
629 case QRhiShaderResourceBinding::UniformBuffer:
630 {
631 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
632 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
633 uint offset = b->u.ubuf.offset;
634 for (int i = 0; i < dynamicOffsetCount; ++i) {
635 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
636 if (dynOfs.first == b->binding) {
637 offset = dynOfs.second;
638 break;
639 }
640 }
641 if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
642 res[0].buffers.feed(b->binding, mtlbuf);
643 res[0].bufferOffsets.feed(b->binding, offset);
644 }
645 if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
646 res[1].buffers.feed(b->binding, mtlbuf);
647 res[1].bufferOffsets.feed(b->binding, offset);
648 }
649 if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
650 res[2].buffers.feed(b->binding, mtlbuf);
651 res[2].bufferOffsets.feed(b->binding, offset);
652 }
653 }
654 break;
655 case QRhiShaderResourceBinding::SampledTexture:
656 {
657 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
658 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
659 if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
660 res[0].textures.feed(b->binding, texD->d->tex);
661 res[0].samplers.feed(b->binding, samplerD->d->samplerState);
662 }
663 if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
664 res[1].textures.feed(b->binding, texD->d->tex);
665 res[1].samplers.feed(b->binding, samplerD->d->samplerState);
666 }
667 if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
668 res[2].textures.feed(b->binding, texD->d->tex);
669 res[2].samplers.feed(b->binding, samplerD->d->samplerState);
670 }
671 }
672 break;
673 case QRhiShaderResourceBinding::ImageLoad:
674 Q_FALLTHROUGH();
675 case QRhiShaderResourceBinding::ImageStore:
676 Q_FALLTHROUGH();
677 case QRhiShaderResourceBinding::ImageLoadStore:
678 {
679 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
680 id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
681 if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage))
682 res[0].textures.feed(b->binding, t);
683 if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
684 res[1].textures.feed(b->binding, t);
685 if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
686 res[2].textures.feed(b->binding, t);
687 }
688 break;
689 case QRhiShaderResourceBinding::BufferLoad:
690 Q_FALLTHROUGH();
691 case QRhiShaderResourceBinding::BufferStore:
692 Q_FALLTHROUGH();
693 case QRhiShaderResourceBinding::BufferLoadStore:
694 {
695 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
696 id<MTLBuffer> mtlbuf = bufD->d->buf[0];
697 uint offset = b->u.sbuf.offset;
698 if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
699 res[0].buffers.feed(b->binding, mtlbuf);
700 res[0].bufferOffsets.feed(b->binding, offset);
701 }
702 if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
703 res[1].buffers.feed(b->binding, mtlbuf);
704 res[1].bufferOffsets.feed(b->binding, offset);
705 }
706 if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
707 res[2].buffers.feed(b->binding, mtlbuf);
708 res[2].bufferOffsets.feed(b->binding, offset);
709 }
710 }
711 break;
712 default:
713 Q_UNREACHABLE();
714 break;
715 }
716 }
717
718 for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
719 res[idx].buffers.finish();
720 res[idx].bufferOffsets.finish();
721
722 for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
723 const auto &bufferBatch(res[idx].buffers.batches[i]);
724 const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
725 switch (idx) {
726 case 0:
727 [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
728 offsets: offsetBatch.resources.constData()
729 withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
730 break;
731 case 1:
732 [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
733 offsets: offsetBatch.resources.constData()
734 withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
735 break;
736 case 2:
737 [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
738 offsets: offsetBatch.resources.constData()
739 withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
740 break;
741 default:
742 Q_UNREACHABLE();
743 break;
744 }
745 }
746
747 if (offsetOnlyChange)
748 continue;
749
750 res[idx].textures.finish();
751 res[idx].samplers.finish();
752
753 for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) {
754 const auto &batch(res[idx].textures.batches[i]);
755 switch (idx) {
756 case 0:
757 [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
758 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
759 break;
760 case 1:
761 [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData()
762 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
763 break;
764 case 2:
765 [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData()
766 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
767 break;
768 default:
769 Q_UNREACHABLE();
770 break;
771 }
772 }
773 for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) {
774 const auto &batch(res[idx].samplers.batches[i]);
775 switch (idx) {
776 case 0:
777 [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()
778 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
779 break;
780 case 1:
781 [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData()
782 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
783 break;
784 case 2:
785 [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData()
786 withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
787 break;
788 default:
789 Q_UNREACHABLE();
790 break;
791 }
792 }
793 }
794}
795
796void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
797{
798 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
799 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
800 QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
801
802 if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
803 cbD->currentGraphicsPipeline = ps;
804 cbD->currentComputePipeline = nullptr;
805 cbD->currentPipelineGeneration = psD->generation;
806
807 [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps];
808 [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds];
809 [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode];
810 [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding];
811 }
812
813 psD->lastActiveFrameSlot = currentFrameSlot;
814}
815
816void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
817 int dynamicOffsetCount,
818 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
819{
820 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
821 Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass);
822 QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline);
823 QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
824
825 if (!srb) {
826 if (gfxPsD)
827 srb = gfxPsD->m_shaderResourceBindings;
828 else
829 srb = compPsD->m_shaderResourceBindings;
830 }
831
832 QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
833 bool hasSlottedResourceInSrb = false;
834 bool hasDynamicOffsetInSrb = false;
835 bool resNeedsRebind = false;
836
837 // do buffer writes, figure out if we need to rebind, and mark as in-use
838 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
839 const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
840 QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
841 switch (b->type) {
842 case QRhiShaderResourceBinding::UniformBuffer:
843 {
844 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
845 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
846 executeBufferHostWritesForCurrentFrame(bufD);
847 if (bufD->d->slotted)
848 hasSlottedResourceInSrb = true;
849 if (b->u.ubuf.hasDynamicOffset)
850 hasDynamicOffsetInSrb = true;
851 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
852 resNeedsRebind = true;
853 bd.ubuf.id = bufD->m_id;
854 bd.ubuf.generation = bufD->generation;
855 }
856 bufD->lastActiveFrameSlot = currentFrameSlot;
857 }
858 break;
859 case QRhiShaderResourceBinding::SampledTexture:
860 {
861 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
862 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
863 if (texD->generation != bd.stex.texGeneration
864 || texD->m_id != bd.stex.texId
865 || samplerD->generation != bd.stex.samplerGeneration
866 || samplerD->m_id != bd.stex.samplerId)
867 {
868 resNeedsRebind = true;
869 bd.stex.texId = texD->m_id;
870 bd.stex.texGeneration = texD->generation;
871 bd.stex.samplerId = samplerD->m_id;
872 bd.stex.samplerGeneration = samplerD->generation;
873 }
874 texD->lastActiveFrameSlot = currentFrameSlot;
875 samplerD->lastActiveFrameSlot = currentFrameSlot;
876 }
877 break;
878 case QRhiShaderResourceBinding::ImageLoad:
879 Q_FALLTHROUGH();
880 case QRhiShaderResourceBinding::ImageStore:
881 Q_FALLTHROUGH();
882 case QRhiShaderResourceBinding::ImageLoadStore:
883 {
884 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
885 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
886 resNeedsRebind = true;
887 bd.simage.id = texD->m_id;
888 bd.simage.generation = texD->generation;
889 }
890 texD->lastActiveFrameSlot = currentFrameSlot;
891 }
892 break;
893 case QRhiShaderResourceBinding::BufferLoad:
894 Q_FALLTHROUGH();
895 case QRhiShaderResourceBinding::BufferStore:
896 Q_FALLTHROUGH();
897 case QRhiShaderResourceBinding::BufferLoadStore:
898 {
899 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
900 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
901 executeBufferHostWritesForCurrentFrame(bufD);
902 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
903 resNeedsRebind = true;
904 bd.sbuf.id = bufD->m_id;
905 bd.sbuf.generation = bufD->generation;
906 }
907 bufD->lastActiveFrameSlot = currentFrameSlot;
908 }
909 break;
910 default:
911 Q_UNREACHABLE();
912 break;
913 }
914 }
915
916 // make sure the resources for the correct slot get bound
917 const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
918 if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
919 resNeedsRebind = true;
920
921 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
922 const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
923
924 // dynamic uniform buffer offsets always trigger a rebind
925 if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
926 if (gfxPsD) {
927 cbD->currentGraphicsSrb = srb;
928 cbD->currentComputeSrb = nullptr;
929 } else {
930 cbD->currentGraphicsSrb = nullptr;
931 cbD->currentComputeSrb = srb;
932 }
933 cbD->currentSrbGeneration = srbD->generation;
934 cbD->currentResSlot = resSlot;
935
936 const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
937 enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange);
938 }
939}
940
941void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
942 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
943 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
944{
945 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
946 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
947
948 QRhiBatchedBindings<id<MTLBuffer> > buffers;
949 QRhiBatchedBindings<NSUInteger> offsets;
950 for (int i = 0; i < bindingCount; ++i) {
951 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
952 executeBufferHostWritesForCurrentFrame(bufD);
953 bufD->lastActiveFrameSlot = currentFrameSlot;
954 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
955 buffers.feed(startBinding + i, mtlbuf);
956 offsets.feed(startBinding + i, bindings[i].second);
957 }
958 buffers.finish();
959 offsets.finish();
960
961 // same binding space for vertex and constant buffers - work it around
962 QRhiShaderResourceBindings *srb = cbD->currentGraphicsSrb;
963 // There's nothing guaranteeing setShaderResources() was called before
964 // setVertexInput()... but whatever srb will get bound will have to be
965 // layout-compatible anyways so maxBinding is the same.
966 if (!srb)
967 srb = cbD->currentGraphicsPipeline->shaderResourceBindings();
968 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
969
970 if (firstVertexBinding != cbD->d->currentFirstVertexBinding
971 || buffers != cbD->d->currentVertexInputsBuffers
972 || offsets != cbD->d->currentVertexInputOffsets)
973 {
974 cbD->d->currentFirstVertexBinding = firstVertexBinding;
975 cbD->d->currentVertexInputsBuffers = buffers;
976 cbD->d->currentVertexInputOffsets = offsets;
977
978 for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
979 const auto &bufferBatch(buffers.batches[i]);
980 const auto &offsetBatch(offsets.batches[i]);
981 [cbD->d->currentRenderPassEncoder setVertexBuffers:
982 bufferBatch.resources.constData()
983 offsets: offsetBatch.resources.constData()
984 withRange: NSMakeRange(firstVertexBinding + bufferBatch.startBinding, bufferBatch.resources.count())];
985 }
986 }
987
988 if (indexBuf) {
989 QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
990 executeBufferHostWritesForCurrentFrame(ibufD);
991 ibufD->lastActiveFrameSlot = currentFrameSlot;
992 cbD->currentIndexBuffer = indexBuf;
993 cbD->currentIndexOffset = indexOffset;
994 cbD->currentIndexFormat = indexFormat;
995 } else {
996 cbD->currentIndexBuffer = nullptr;
997 }
998}
999
1000void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
1001{
1002 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1003 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1004 const QSize outputSize = cbD->currentTarget->pixelSize();
1005
1006 // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
1007 float x, y, w, h;
1008 if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
1009 return;
1010
1011 MTLViewport vp;
1012 vp.originX = x;
1013 vp.originY = y;
1014 vp.width = w;
1015 vp.height = h;
1016 vp.znear = viewport.minDepth();
1017 vp.zfar = viewport.maxDepth();
1018
1019 [cbD->d->currentRenderPassEncoder setViewport: vp];
1020
1021 if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
1022 MTLScissorRect s;
1023 s.x = x;
1024 s.y = y;
1025 s.width = w;
1026 s.height = h;
1027 [cbD->d->currentRenderPassEncoder setScissorRect: s];
1028 }
1029}
1030
1031void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
1032{
1033 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1034 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1035 Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
1036 const QSize outputSize = cbD->currentTarget->pixelSize();
1037
1038 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
1039 int x, y, w, h;
1040 if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
1041 return;
1042
1043 MTLScissorRect s;
1044 s.x = x;
1045 s.y = y;
1046 s.width = w;
1047 s.height = h;
1048
1049 [cbD->d->currentRenderPassEncoder setScissorRect: s];
1050}
1051
1052void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
1053{
1054 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1055 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1056
1057 [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF() green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
1058}
1059
1060void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
1061{
1062 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1063 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1064
1065 [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
1066}
1067
1068void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
1069 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
1070{
1071 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1072 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1073
1074 [cbD->d->currentRenderPassEncoder drawPrimitives:
1075 QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
1076 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
1077}
1078
1079void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
1080 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
1081{
1082 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1083 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1084
1085 if (!cbD->currentIndexBuffer)
1086 return;
1087
1088 const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
1089 Q_ASSERT(indexOffset == aligned(indexOffset, 4));
1090
1091 QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
1092 id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
1093
1094 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
1095 indexCount: indexCount
1096 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
1097 indexBuffer: mtlbuf
1098 indexBufferOffset: indexOffset
1099 instanceCount: instanceCount
1100 baseVertex: vertexOffset
1101 baseInstance: firstInstance];
1102}
1103
1104void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
1105{
1106 if (!debugMarkers)
1107 return;
1108
1109 NSString *str = [NSString stringWithUTF8String: name.constData()];
1110 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1111 if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
1112 [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
1113 } else {
1114 if (@available(macOS 10.13, iOS 11.0, *))
1115 [cbD->d->cb pushDebugGroup: str];
1116 }
1117}
1118
1119void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
1120{
1121 if (!debugMarkers)
1122 return;
1123
1124 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1125 if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
1126 [cbD->d->currentRenderPassEncoder popDebugGroup];
1127 } else {
1128 if (@available(macOS 10.13, iOS 11.0, *))
1129 [cbD->d->cb popDebugGroup];
1130 }
1131}
1132
1133void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
1134{
1135 if (!debugMarkers)
1136 return;
1137
1138 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1139 if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
1140 [cbD->d->currentRenderPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
1141}
1142
1143const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
1144{
1145 return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
1146}
1147
1148void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
1149{
1150 Q_UNUSED(cb);
1151}
1152
1153void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
1154{
1155 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1156 cbD->resetPerPassCachedState();
1157}
1158
1159QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
1160{
1161 Q_UNUSED(flags);
1162
1163 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
1164
1165 // This is a bit messed up since for this swapchain we want to wait for the
1166 // commands+present to complete, while for others just for the commands
1167 // (for this same frame slot) but not sure how to do that in a sane way so
1168 // wait for full cb completion for now.
1169 for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1170 dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
1171 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1172 if (sc != swapChainD)
1173 dispatch_semaphore_signal(sem);
1174 }
1175
1176 currentSwapChain = swapChainD;
1177 currentFrameSlot = swapChainD->currentFrameSlot;
1178 if (swapChainD->ds)
1179 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
1180
1181 if (@available(macOS 10.13, iOS 11.0, *))
1182 [d->captureScope beginScope];
1183
1184 // Do not let the command buffer mess with the refcount of objects. We do
1185 // have a proper render loop and will manage lifetimes similarly to other
1186 // backends (Vulkan).
1187 swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1188
1189 QMetalRenderTargetData::ColorAtt colorAtt;
1190 if (swapChainD->samples > 1) {
1191 colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
1192 colorAtt.needsDrawableForResolveTex = true;
1193 } else {
1194 colorAtt.needsDrawableForTex = true;
1195 }
1196
1197 swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
1198 swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
1199 swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
1200 swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
1201
1202 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1203 QRHI_PROF_F(beginSwapChainFrame(swapChain));
1204
1205 executeDeferredReleases();
1206 swapChainD->cbWrapper.resetState();
1207 finishActiveReadbacks();
1208
1209 return QRhi::FrameOpSuccess;
1210}
1211
1212QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
1213{
1214 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
1215 Q_ASSERT(currentSwapChain == swapChainD);
1216
1217 const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
1218 if (needsPresent)
1219 [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
1220
1221 // Must not hold on to the drawable, regardless of needsPresent.
1222 // (internally it is autoreleased or something, it seems)
1223 swapChainD->d->curDrawable = nil;
1224
1225 __block int thisFrameSlot = currentFrameSlot;
1226 [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
1227 dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
1228 }];
1229
1230 [swapChainD->cbWrapper.d->cb commit];
1231
1232 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1233 QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
1234
1235 if (@available(macOS 10.13, iOS 11.0, *))
1236 [d->captureScope endScope];
1237
1238 if (needsPresent)
1239 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
1240
1241 swapChainD->frameCount += 1;
1242 currentSwapChain = nullptr;
1243 return QRhi::FrameOpSuccess;
1244}
1245
1246QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
1247{
1248 currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
1249 if (swapchains.count() > 1) {
1250 for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1251 // wait+signal is the general pattern to ensure the commands for a
1252 // given frame slot have completed (if sem is 1, we go 0 then 1; if
1253 // sem is 0 we go -1, block, completion increments to 0, then us to 1)
1254 dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
1255 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1256 dispatch_semaphore_signal(sem);
1257 }
1258 }
1259
1260 d->ofr.active = true;
1261 *cb = &d->ofr.cbWrapper;
1262 d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1263
1264 executeDeferredReleases();
1265 d->ofr.cbWrapper.resetState();
1266 finishActiveReadbacks();
1267
1268 return QRhi::FrameOpSuccess;
1269}
1270
1271QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
1272{
1273 Q_ASSERT(d->ofr.active);
1274 d->ofr.active = false;
1275
1276 [d->ofr.cbWrapper.d->cb commit];
1277
1278 // offscreen frames wait for completion, unlike swapchain ones
1279 [d->ofr.cbWrapper.d->cb waitUntilCompleted];
1280
1281 finishActiveReadbacks(true);
1282
1283 return QRhi::FrameOpSuccess;
1284}
1285
1286QRhi::FrameOpResult QRhiMetal::finish()
1287{
1288 id<MTLCommandBuffer> cb = nil;
1289 QMetalSwapChain *swapChainD = nullptr;
1290 if (inFrame) {
1291 if (d->ofr.active) {
1292 Q_ASSERT(!currentSwapChain);
1293 Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
1294 cb = d->ofr.cbWrapper.d->cb;
1295 } else {
1296 Q_ASSERT(currentSwapChain);
1297 swapChainD = currentSwapChain;
1298 Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
1299 cb = swapChainD->cbWrapper.d->cb;
1300 }
1301 }
1302
1303 for (QMetalSwapChain *sc : qAsConst(swapchains)) {
1304 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
1305 if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
1306 // no wait as this is the thing we're going to be commit below and
1307 // beginFrame decremented sem already and going to be signaled by endFrame
1308 continue;
1309 }
1310 dispatch_semaphore_t sem = sc->d->sem[i];
1311 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
1312 dispatch_semaphore_signal(sem);
1313 }
1314 }
1315
1316 if (cb) {
1317 [cb commit];
1318 [cb waitUntilCompleted];
1319 }
1320
1321 if (inFrame) {
1322 if (d->ofr.active)
1323 d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1324 else
1325 swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
1326 }
1327
1328 executeDeferredReleases(true);
1329
1330 finishActiveReadbacks(true);
1331
1332 return QRhi::FrameOpSuccess;
1333}
1334
1335MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
1336 const QColor &colorClearValue,
1337 const QRhiDepthStencilClearValue &depthStencilClearValue,
1338 int colorAttCount)
1339{
1340 MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
1341 MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
1342 colorClearValue.alphaF());
1343
1344 for (int i = 0; i < colorAttCount; ++i) {
1345 rp.colorAttachments[i].loadAction = MTLLoadActionClear;
1346 rp.colorAttachments[i].storeAction = MTLStoreActionStore;
1347 rp.colorAttachments[i].clearColor = c;
1348 }
1349
1350 if (hasDepthStencil) {
1351 rp.depthAttachment.loadAction = MTLLoadActionClear;
1352 rp.depthAttachment.storeAction = MTLStoreActionDontCare;
1353 rp.stencilAttachment.loadAction = MTLLoadActionClear;
1354 rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
1355 rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
1356 rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
1357 }
1358
1359 return rp;
1360}
1361
1362qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
1363{
1364 qsizetype size = 0;
1365 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
1366 subresDesc.data().size() : subresDesc.image().sizeInBytes();
1367 if (imageSizeBytes > 0)
1368 size += aligned(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
1369 return size;
1370}
1371
1372void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
1373 int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
1374 qsizetype *curOfs)
1375{
1376 const QPoint dp = subresDesc.destinationTopLeft();
1377 const QByteArray rawData = subresDesc.data();
1378 QImage img = subresDesc.image();
1379 id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
1380
1381 if (!img.isNull()) {
1382 const qsizetype fullImageSizeBytes = img.sizeInBytes();
1383 int w = img.width();
1384 int h = img.height();
1385 int bpl = img.bytesPerLine();
1386 int srcOffset = 0;
1387
1388 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
1389 const int sx = subresDesc.sourceTopLeft().x();
1390 const int sy = subresDesc.sourceTopLeft().y();
1391 if (!subresDesc.sourceSize().isEmpty()) {
1392 w = subresDesc.sourceSize().width();
1393 h = subresDesc.sourceSize().height();
1394 }
1395 if (img.depth() == 32) {
1396 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
1397 srcOffset = sy * bpl + sx * 4;
1398 // bpl remains set to the original image's row stride
1399 } else {
1400 img = img.copy(sx, sy, w, h);
1401 bpl = img.bytesPerLine();
1402 Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
1403 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), img.sizeInBytes());
1404 }
1405 } else {
1406 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
1407 }
1408
1409 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1410 sourceOffset: *curOfs + srcOffset
1411 sourceBytesPerRow: bpl
1412 sourceBytesPerImage: 0
1413 sourceSize: MTLSizeMake(w, h, 1)
1414 toTexture: texD->d->tex
1415 destinationSlice: layer
1416 destinationLevel: level
1417 destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
1418 options: MTLBlitOptionNone];
1419
1420 *curOfs += aligned(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
1421 } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
1422 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
1423 const int subresw = subresSize.width();
1424 const int subresh = subresSize.height();
1425 int w, h;
1426 if (subresDesc.sourceSize().isEmpty()) {
1427 w = subresw;
1428 h = subresh;
1429 } else {
1430 w = subresDesc.sourceSize().width();
1431 h = subresDesc.sourceSize().height();
1432 }
1433
1434 quint32 bpl = 0;
1435 QSize blockDim;
1436 compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
1437
1438 const int dx = aligned(dp.x(), blockDim.width());
1439 const int dy = aligned(dp.y(), blockDim.height());
1440 if (dx + w != subresw)
1441 w = aligned(w, blockDim.width());
1442 if (dy + h != subresh)
1443 h = aligned(h, blockDim.height());
1444
1445 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
1446
1447 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1448 sourceOffset: *curOfs
1449 sourceBytesPerRow: bpl
1450 sourceBytesPerImage: 0
1451 sourceSize: MTLSizeMake(w, h, 1)
1452 toTexture: texD->d->tex
1453 destinationSlice: layer
1454 destinationLevel: level
1455 destinationOrigin: MTLOriginMake(dx, dy, 0)
1456 options: MTLBlitOptionNone];
1457
1458 *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
1459 } else if (!rawData.isEmpty()) {
1460 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
1461 const int subresw = subresSize.width();
1462 const int subresh = subresSize.height();
1463 int w, h;
1464 if (subresDesc.sourceSize().isEmpty()) {
1465 w = subresw;
1466 h = subresh;
1467 } else {
1468 w = subresDesc.sourceSize().width();
1469 h = subresDesc.sourceSize().height();
1470 }
1471
1472 quint32 bpl = 0;
1473 textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
1474 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
1475
1476 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
1477 sourceOffset: *curOfs
1478 sourceBytesPerRow: bpl
1479 sourceBytesPerImage: 0
1480 sourceSize: MTLSizeMake(w, h, 1)
1481 toTexture: texD->d->tex
1482 destinationSlice: layer
1483 destinationLevel: level
1484 destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
1485 options: MTLBlitOptionNone];
1486
1487 *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
1488 } else {
1489 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
1490 }
1491}
1492
1493void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1494{
1495 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1496 QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
1497 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1498
1499 for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
1500 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
1501 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
1502 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
1503 bufD->d->pendingUpdates[i].append(u);
1504 }
1505
1506 // Due to the Metal API the handling of static and dynamic buffers is
1507 // basically the same. So go through the same pendingUpdates machinery.
1508 for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
1509 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
1510 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
1511 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
1512 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
1513 bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
1514 }
1515
1516 id<MTLBlitCommandEncoder> blitEnc = nil;
1517 auto ensureBlit = [&blitEnc, cbD, this] {
1518 if (!blitEnc) {
1519 blitEnc = [cbD->d->cb blitCommandEncoder];
1520 if (debugMarkers)
1521 [blitEnc pushDebugGroup: @"Texture upload/copy"];
1522 }
1523 };
1524
1525 for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
1526 if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
1527 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex);
1528 qsizetype stagingSize = 0;
1529 for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
1530 for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
1531 for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
1532 stagingSize += subresUploadByteSize(subresDesc);
1533 }
1534 }
1535
1536 ensureBlit();
1537 Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
1538 utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: stagingSize
1539 options: MTLResourceStorageModeShared];
1540 QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
1541
1542 void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
1543 qsizetype curOfs = 0;
1544 for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
1545 for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
1546 for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
1547 enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
1548 }
1549 }
1550
1551 utexD->lastActiveFrameSlot = currentFrameSlot;
1552
1553 QRhiMetalData::DeferredReleaseEntry e;
1554 e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
1555 e.lastActiveFrameSlot = currentFrameSlot;
1556 e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
1557 utexD->d->stagingBuf[currentFrameSlot] = nil;
1558 d->releaseQueue.append(e);
1559 QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
1560 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
1561 Q_ASSERT(u.copy.src && u.copy.dst);
1562 QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src);
1563 QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst);
1564 const QPoint dp = u.copy.desc.destinationTopLeft();
1565 const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
1566 const QPoint sp = u.copy.desc.sourceTopLeft();
1567
1568 ensureBlit();
1569 [blitEnc copyFromTexture: srcD->d->tex
1570 sourceSlice: u.copy.desc.sourceLayer()
1571 sourceLevel: u.copy.desc.sourceLevel()
1572 sourceOrigin: MTLOriginMake(sp.x(), sp.y(), 0)
1573 sourceSize: MTLSizeMake(size.width(), size.height(), 1)
1574 toTexture: dstD->d->tex
1575 destinationSlice: u.copy.desc.destinationLayer()
1576 destinationLevel: u.copy.desc.destinationLevel()
1577 destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)];
1578
1579 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
1580 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
1581 QRhiMetalData::ActiveReadback aRb;
1582 aRb.activeFrameSlot = currentFrameSlot;
1583 aRb.desc = u.read.rb;
1584 aRb.result = u.read.result;
1585
1586 QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture());
1587 QMetalSwapChain *swapChainD = nullptr;
1588 id<MTLTexture> src;
1589 QSize srcSize;
1590 if (texD) {
1591 if (texD->samples > 1) {
1592 qWarning("Multisample texture cannot be read back");
1593 continue;
1594 }
1595 aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
1596 : texD->m_pixelSize;
1597 aRb.format = texD->m_format;
1598 src = texD->d->tex;
1599 srcSize = texD->m_pixelSize;
1600 texD->lastActiveFrameSlot = currentFrameSlot;
1601 } else {
1602 Q_ASSERT(currentSwapChain);
1603 swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
1604 aRb.pixelSize = swapChainD->pixelSize;
1605 aRb.format = swapChainD->d->rhiColorFormat;
1606 // Multisample swapchains need nothing special since resolving
1607 // happens when ending a renderpass.
1608 const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
1609 src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
1610 srcSize = swapChainD->rtWrapper.d->pixelSize;
1611 }
1612
1613 quint32 bpl = 0;
1614 textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
1615 aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
1616
1617 QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(aRb.buf)),
1618 texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
1619 aRb.bufSize));
1620
1621 ensureBlit();
1622 [blitEnc copyFromTexture: src
1623 sourceSlice: u.read.rb.layer()
1624 sourceLevel: u.read.rb.level()
1625 sourceOrigin: MTLOriginMake(0, 0, 0)
1626 sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
1627 toBuffer: aRb.buf
1628 destinationOffset: 0
1629 destinationBytesPerRow: bpl
1630 destinationBytesPerImage: 0
1631 options: MTLBlitOptionNone];
1632
1633 d->activeReadbacks.append(aRb);
1634 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
1635 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex);
1636 ensureBlit();
1637 [blitEnc generateMipmapsForTexture: utexD->d->tex];
1638 utexD->lastActiveFrameSlot = currentFrameSlot;
1639 }
1640 }
1641
1642 if (blitEnc) {
1643 if (debugMarkers)
1644 [blitEnc popDebugGroup];
1645 [blitEnc endEncoding];
1646 }
1647
1648 ud->free();
1649}
1650
1651// this handles all types of buffers, not just Dynamic
1652void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
1653{
1654 const int idx = bufD->d->slotted ? currentFrameSlot : 0;
1655 QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
1656 if (updates.isEmpty())
1657 return;
1658
1659 void *p = [bufD->d->buf[idx] contents];
1660 int changeBegin = -1;
1661 int changeEnd = -1;
1662 for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
1663 Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
1664 memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
1665 if (changeBegin == -1 || u.offset < changeBegin)
1666 changeBegin = u.offset;
1667 if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
1668 changeEnd = u.offset + u.data.size();
1669 }
1670 if (changeBegin >= 0 && bufD->d->managed)
1671 [bufD->d->buf[idx] didModifyRange: NSMakeRange(changeBegin, changeEnd - changeBegin)];
1672
1673 updates.clear();
1674}
1675
1676void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1677{
1678 Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
1679
1680 enqueueResourceUpdates(cb, resourceUpdates);
1681}
1682
1683void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
1684 QRhiRenderTarget *rt,
1685 const QColor &colorClearValue,
1686 const QRhiDepthStencilClearValue &depthStencilClearValue,
1687 QRhiResourceUpdateBatch *resourceUpdates)
1688{
1689 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1690 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
1691
1692 if (resourceUpdates)
1693 enqueueResourceUpdates(cb, resourceUpdates);
1694
1695 QMetalRenderTargetData *rtD = nullptr;
1696 switch (rt->resourceType()) {
1697 case QRhiResource::RenderTarget:
1698 rtD = QRHI_RES(QMetalReferenceRenderTarget, rt)->d;
1699 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
1700 if (rtD->colorAttCount) {
1701 QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
1702 if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) {
1703 Q_ASSERT(currentSwapChain);
1704 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
1705 if (!swapChainD->d->curDrawable)
1706 swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable];
1707 if (!swapChainD->d->curDrawable) {
1708 qWarning("No drawable");
1709 return;
1710 }
1711 id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
1712 if (color0.needsDrawableForTex) {
1713 color0.tex = scTex;
1714 color0.needsDrawableForTex = false;
1715 } else {
1716 color0.resolveTex = scTex;
1717 color0.needsDrawableForResolveTex = false;
1718 }
1719 }
1720 }
1721 break;
1722 case QRhiResource::TextureRenderTarget:
1723 {
1724 QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
1725 rtD = rtTex->d;
1726 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
1727 if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
1728 for (int i = 0; i < rtD->colorAttCount; ++i)
1729 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
1730 }
1731 if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
1732 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
1733 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
1734 }
1735 const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
1736 for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
1737 if (colorAttachment.texture())
1738 QRHI_RES(QMetalTexture, colorAttachment.texture())->lastActiveFrameSlot = currentFrameSlot;
1739 else if (colorAttachment.renderBuffer())
1740 QRHI_RES(QMetalRenderBuffer, colorAttachment.renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
1741 if (colorAttachment.resolveTexture())
1742 QRHI_RES(QMetalTexture, colorAttachment.resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
1743 }
1744 if (rtTex->m_desc.depthStencilBuffer())
1745 QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
1746 if (rtTex->m_desc.depthTexture())
1747 QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
1748 }
1749 break;
1750 default:
1751 Q_UNREACHABLE();
1752 break;
1753 }
1754
1755 for (int i = 0; i < rtD->colorAttCount; ++i) {
1756 cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
1757 cbD->d->currentPassRpDesc.colorAttachments[i].slice = rtD->fb.colorAtt[i].layer;
1758 cbD->d->currentPassRpDesc.colorAttachments[i].level = rtD->fb.colorAtt[i].level;
1759 if (rtD->fb.colorAtt[i].resolveTex) {
1760 cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
1761 cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
1762 cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = rtD->fb.colorAtt[i].resolveLayer;
1763 cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = rtD->fb.colorAtt[i].resolveLevel;
1764 }
1765 }
1766
1767 if (rtD->dsAttCount) {
1768 Q_ASSERT(rtD->fb.dsTex);
1769 cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
1770 cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
1771 if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
1772 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
1773 }
1774
1775 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
1776
1777 cbD->resetPerPassState();
1778
1779 cbD->recordingPass = QMetalCommandBuffer::RenderPass;
1780 cbD->currentTarget = rt;
1781}
1782
1783void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1784{
1785 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1786 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
1787
1788 [cbD->d->currentRenderPassEncoder endEncoding];
1789
1790 cbD->recordingPass = QMetalCommandBuffer::NoPass;
1791 cbD->currentTarget = nullptr;
1792
1793 if (resourceUpdates)
1794 enqueueResourceUpdates(cb, resourceUpdates);
1795}
1796
1797void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1798{
1799 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1800 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
1801
1802 if (resourceUpdates)
1803 enqueueResourceUpdates(cb, resourceUpdates);
1804
1805 cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
1806 cbD->resetPerPassState();
1807 cbD->recordingPass = QMetalCommandBuffer::ComputePass;
1808}
1809
1810void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
1811{
1812 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1813 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
1814
1815 [cbD->d->currentComputePassEncoder endEncoding];
1816 cbD->recordingPass = QMetalCommandBuffer::NoPass;
1817
1818 if (resourceUpdates)
1819 enqueueResourceUpdates(cb, resourceUpdates);
1820}
1821
1822void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
1823{
1824 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1825 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
1826 QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps);
1827
1828 if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
1829 cbD->currentGraphicsPipeline = nullptr;
1830 cbD->currentComputePipeline = ps;
1831 cbD->currentPipelineGeneration = psD->generation;
1832
1833 [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
1834 }
1835
1836 psD->lastActiveFrameSlot = currentFrameSlot;
1837}
1838
1839void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
1840{
1841 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1842 Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
1843 QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
1844
1845 [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(x, y, z)
1846 threadsPerThreadgroup: psD->d->localSize];
1847}
1848
1849static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
1850{
1851 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
1852 [e.buffer.buffers[i] release];
1853}
1854
1855static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
1856{
1857 [e.renderbuffer.texture release];
1858}
1859
1860static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
1861{
1862 [e.texture.texture release];
1863 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
1864 [e.texture.stagingBuffers[i] release];
1865 for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
1866 [e.texture.views[i] release];
1867}
1868
1869static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
1870{
1871 [e.sampler.samplerState release];
1872}
1873
1874void QRhiMetal::executeDeferredReleases(bool forced)
1875{
1876 for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
1877 const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
1878 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
1879 switch (e.type) {
1880 case QRhiMetalData::DeferredReleaseEntry::Buffer:
1881 qrhimtl_releaseBuffer(e);
1882 break;
1883 case QRhiMetalData::DeferredReleaseEntry::RenderBuffer:
1884 qrhimtl_releaseRenderBuffer(e);
1885 break;
1886 case QRhiMetalData::DeferredReleaseEntry::Texture:
1887 qrhimtl_releaseTexture(e);
1888 break;
1889 case QRhiMetalData::DeferredReleaseEntry::Sampler:
1890 qrhimtl_releaseSampler(e);
1891 break;
1892 case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
1893 [e.stagingBuffer.buffer release];
1894 break;
1895 default:
1896 break;
1897 }
1898 d->releaseQueue.removeAt(i);
1899 }
1900 }
1901}
1902
1903void QRhiMetal::finishActiveReadbacks(bool forced)
1904{
1905 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
1906 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1907
1908 for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) {
1909 const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]);
1910 if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
1911 aRb.result->format = aRb.format;
1912 aRb.result->pixelSize = aRb.pixelSize;
1913 aRb.result->data.resize(aRb.bufSize);
1914 void *p = [aRb.buf contents];
1915 memcpy(aRb.result->data.data(), p, aRb.bufSize);
1916 [aRb.buf release];
1917
1918 QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.buf))));
1919
1920 if (aRb.result->completed)
1921 completedCallbacks.append(aRb.result->completed);
1922
1923 d->activeReadbacks.removeAt(i);
1924 }
1925 }
1926
1927 for (auto f : completedCallbacks)
1928 f();
1929}
1930
1931QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
1932 : QRhiBuffer(rhi, type, usage, size),
1933 d(new QMetalBufferData)
1934{
1935 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
1936 d->buf[i] = nil;
1937}
1938
1939QMetalBuffer::~QMetalBuffer()
1940{
1941 release();
1942 delete d;
1943}
1944
1945void QMetalBuffer::release()
1946{
1947 if (!d->buf[0])
1948 return;
1949
1950 QRhiMetalData::DeferredReleaseEntry e;
1951 e.type = QRhiMetalData::DeferredReleaseEntry::Buffer;
1952 e.lastActiveFrameSlot = lastActiveFrameSlot;
1953
1954 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
1955 e.buffer.buffers[i] = d->buf[i];
1956 d->buf[i] = nil;
1957 d->pendingUpdates[i].clear();
1958 }
1959
1960 QRHI_RES_RHI(QRhiMetal);
1961 rhiD->d->releaseQueue.append(e);
1962 QRHI_PROF;
1963 QRHI_PROF_F(releaseBuffer(this));
1964 rhiD->unregisterResource(this);
1965}
1966
1967bool QMetalBuffer::build()
1968{
1969 if (d->buf[0])
1970 release();
1971
1972 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
1973 qWarning("StorageBuffer cannot be combined with Dynamic");
1974 return false;
1975 }
1976
1977 const int nonZeroSize = m_size <= 0 ? 256 : m_size;
1978 const int roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256) : nonZeroSize;
1979
1980 d->managed = false;
1981 MTLResourceOptions opts = MTLResourceStorageModeShared;
1982#ifdef Q_OS_MACOS
1983 if (m_type != Dynamic) {
1984 opts = MTLResourceStorageModeManaged;
1985 d->managed = true;
1986 }
1987#endif
1988
1989 // Immutable and Static only has buf[0] and pendingUpdates[0] in use.
1990 // Dynamic uses all.
1991 d->slotted = m_type == Dynamic;
1992
1993 QRHI_RES_RHI(QRhiMetal);
1994 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
1995 if (i == 0 || d->slotted) {
1996 d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
1997 d->pendingUpdates[i].reserve(16);
1998 if (!m_objectName.isEmpty()) {
1999 if (!d->slotted) {
2000 d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
2001 } else {
2002 const QByteArray name = m_objectName + '/' + QByteArray::number(i);
2003 d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
2004 }
2005 }
2006 }
2007 }
2008
2009 QRHI_PROF;
2010 QRHI_PROF_F(newBuffer(this, roundedSize, d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1, 0));
2011
2012 lastActiveFrameSlot = -1;
2013 generation += 1;
2014 rhiD->registerResource(this);
2015 return true;
2016}
2017
2018QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
2019 int sampleCount, QRhiRenderBuffer::Flags flags)
2020 : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),
2021 d(new QMetalRenderBufferData)
2022{
2023}
2024
2025QMetalRenderBuffer::~QMetalRenderBuffer()
2026{
2027 release();
2028 delete d;
2029}
2030
2031void QMetalRenderBuffer::release()
2032{
2033 if (!d->tex)
2034 return;
2035
2036 QRhiMetalData::DeferredReleaseEntry e;
2037 e.type = QRhiMetalData::DeferredReleaseEntry::RenderBuffer;
2038 e.lastActiveFrameSlot = lastActiveFrameSlot;
2039
2040 e.renderbuffer.texture = d->tex;
2041 d->tex = nil;
2042
2043 QRHI_RES_RHI(QRhiMetal);
2044 rhiD->d->releaseQueue.append(e);
2045 QRHI_PROF;
2046 QRHI_PROF_F(releaseRenderBuffer(this));
2047 rhiD->unregisterResource(this);
2048}
2049
2050bool QMetalRenderBuffer::build()
2051{
2052 if (d->tex)
2053 release();
2054
2055 if (m_pixelSize.isEmpty())
2056 return false;
2057
2058 QRHI_RES_RHI(QRhiMetal);
2059 samples = rhiD->effectiveSampleCount(m_sampleCount);
2060
2061 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
2062 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
2063 desc.width = m_pixelSize.width();
2064 desc.height = m_pixelSize.height();
2065 if (samples > 1)
2066 desc.sampleCount = samples;
2067 desc.resourceOptions = MTLResourceStorageModePrivate;
2068 desc.usage = MTLTextureUsageRenderTarget;
2069
2070 bool transientBacking = false;
2071 switch (m_type) {
2072 case DepthStencil:
2073#ifdef Q_OS_MACOS
2074 desc.storageMode = MTLStorageModePrivate;
2075#else
2076 desc.storageMode = MTLResourceStorageModeMemoryless;
2077 transientBacking = true;
2078#endif
2079 d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
2080 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
2081 desc.pixelFormat = d->format;
2082 break;
2083 case Color:
2084 desc.storageMode = MTLStorageModePrivate;
2085 d->format = MTLPixelFormatRGBA8Unorm;
2086 desc.pixelFormat = d->format;
2087 break;
2088 default:
2089 Q_UNREACHABLE();
2090 break;
2091 }
2092
2093 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
2094 [desc release];
2095
2096 if (!m_objectName.isEmpty())
2097 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
2098
2099 QRHI_PROF;
2100 QRHI_PROF_F(newRenderBuffer(this, transientBacking, false, samples));
2101
2102 lastActiveFrameSlot = -1;
2103 generation += 1;
2104 rhiD->registerResource(this);
2105 return true;
2106}
2107
2108QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
2109{
2110 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
2111}
2112
2113QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
2114 int sampleCount, Flags flags)
2115 : QRhiTexture(rhi, format, pixelSize, sampleCount, flags),
2116 d(new QMetalTextureData(this))
2117{
2118 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
2119 d->stagingBuf[i] = nil;
2120
2121 for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
2122 d->perLevelViews[i] = nil;
2123}
2124
2125QMetalTexture::~QMetalTexture()
2126{
2127 release();
2128 delete d;
2129}
2130
2131void QMetalTexture::release()
2132{
2133 if (!d->tex)
2134 return;
2135
2136 QRhiMetalData::DeferredReleaseEntry e;
2137 e.type = QRhiMetalData::DeferredReleaseEntry::Texture;
2138 e.lastActiveFrameSlot = lastActiveFrameSlot;
2139
2140 e.texture.texture = d->owns ? d->tex : nil;
2141 d->tex = nil;
2142 nativeHandlesStruct.texture = nullptr;
2143
2144 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2145 e.texture.stagingBuffers[i] = d->stagingBuf[i];
2146 d->stagingBuf[i] = nil;
2147 }
2148
2149 for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
2150 e.texture.views[i] = d->perLevelViews[i];
2151 d->perLevelViews[i] = nil;
2152 }
2153
2154 QRHI_RES_RHI(QRhiMetal);
2155 rhiD->d->releaseQueue.append(e);
2156 QRHI_PROF;
2157 QRHI_PROF_F(releaseTexture(this));
2158 rhiD->unregisterResource(this);
2159}
2160
2161static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
2162{
2163 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
2164 switch (format) {
2165 case QRhiTexture::RGBA8:
2166 return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
2167 case QRhiTexture::BGRA8:
2168 return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
2169 case QRhiTexture::R8:
2170#ifdef Q_OS_MACOS
2171 return MTLPixelFormatR8Unorm;
2172#else
2173 return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
2174#endif
2175 case QRhiTexture::R16:
2176 return MTLPixelFormatR16Unorm;
2177 case QRhiTexture::RED_OR_ALPHA8:
2178 return MTLPixelFormatR8Unorm;
2179
2180 case QRhiTexture::RGBA16F:
2181 return MTLPixelFormatRGBA16Float;
2182 case QRhiTexture::RGBA32F:
2183 return MTLPixelFormatRGBA32Float;
2184
2185 case QRhiTexture::D16:
2186#ifdef Q_OS_MACOS
2187 return MTLPixelFormatDepth16Unorm;
2188#else
2189 return MTLPixelFormatDepth32Float;
2190#endif
2191 case QRhiTexture::D32F:
2192 return MTLPixelFormatDepth32Float;
2193
2194#ifdef Q_OS_MACOS
2195 case QRhiTexture::BC1:
2196 return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
2197 case QRhiTexture::BC2:
2198 return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
2199 case QRhiTexture::BC3:
2200 return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
2201 case QRhiTexture::BC4:
2202 return MTLPixelFormatBC4_RUnorm;
2203 case QRhiTexture::BC5:
2204 qWarning("QRhiMetal does not support BC5");
2205 return MTLPixelFormatRGBA8Unorm;
2206 case QRhiTexture::BC6H:
2207 return MTLPixelFormatBC6H_RGBUfloat;
2208 case QRhiTexture::BC7:
2209 return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
2210#else
2211 case QRhiTexture::BC1:
2212 case QRhiTexture::BC2:
2213 case QRhiTexture::BC3:
2214 case QRhiTexture::BC4:
2215 case QRhiTexture::BC5:
2216 case QRhiTexture::BC6H:
2217 case QRhiTexture::BC7:
2218 qWarning("QRhiMetal: BCx compression not supported on this platform");
2219 return MTLPixelFormatRGBA8Unorm;
2220#endif
2221
2222#ifndef Q_OS_MACOS
2223 case QRhiTexture::ETC2_RGB8:
2224 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
2225 case QRhiTexture::ETC2_RGB8A1:
2226 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
2227 case QRhiTexture::ETC2_RGBA8:
2228 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
2229
2230 case QRhiTexture::ASTC_4x4:
2231 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
2232 case QRhiTexture::ASTC_5x4:
2233 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
2234 case QRhiTexture::ASTC_5x5:
2235 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
2236 case QRhiTexture::ASTC_6x5:
2237 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
2238 case QRhiTexture::ASTC_6x6:
2239 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
2240 case QRhiTexture::ASTC_8x5:
2241 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
2242 case QRhiTexture::ASTC_8x6:
2243 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
2244 case QRhiTexture::ASTC_8x8:
2245 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
2246 case QRhiTexture::ASTC_10x5:
2247 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
2248 case QRhiTexture::ASTC_10x6:
2249 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
2250 case QRhiTexture::ASTC_10x8:
2251 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
2252 case QRhiTexture::ASTC_10x10:
2253 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
2254 case QRhiTexture::ASTC_12x10:
2255 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
2256 case QRhiTexture::ASTC_12x12:
2257 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
2258#else
2259 case QRhiTexture::ETC2_RGB8:
2260 case QRhiTexture::ETC2_RGB8A1:
2261 case QRhiTexture::ETC2_RGBA8:
2262 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
2263 return MTLPixelFormatRGBA8Unorm;
2264
2265 case QRhiTexture::ASTC_4x4:
2266 case QRhiTexture::ASTC_5x4:
2267 case QRhiTexture::ASTC_5x5:
2268 case QRhiTexture::ASTC_6x5:
2269 case QRhiTexture::ASTC_6x6:
2270 case QRhiTexture::ASTC_8x5:
2271 case QRhiTexture::ASTC_8x6:
2272 case QRhiTexture::ASTC_8x8:
2273 case QRhiTexture::ASTC_10x5:
2274 case QRhiTexture::ASTC_10x6:
2275 case QRhiTexture::ASTC_10x8:
2276 case QRhiTexture::ASTC_10x10:
2277 case QRhiTexture::ASTC_12x10:
2278 case QRhiTexture::ASTC_12x12:
2279 qWarning("QRhiMetal: ASTC compression not supported on this platform");
2280 return MTLPixelFormatRGBA8Unorm;
2281#endif
2282
2283 default:
2284 Q_UNREACHABLE();
2285 return MTLPixelFormatRGBA8Unorm;
2286 }
2287}
2288
2289bool QMetalTexture::prepareBuild(QSize *adjustedSize)
2290{
2291 if (d->tex)
2292 release();
2293
2294 const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
2295 const bool isCube = m_flags.testFlag(CubeMap);
2296 const bool hasMipMaps = m_flags.testFlag(MipMapped);
2297
2298 QRHI_RES_RHI(QRhiMetal);
2299 d->format = toMetalTextureFormat(m_format, m_flags);
2300 mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
2301 samples = rhiD->effectiveSampleCount(m_sampleCount);
2302 if (samples > 1) {
2303 if (isCube) {
2304 qWarning("Cubemap texture cannot be multisample");
2305 return false;
2306 }
2307 if (hasMipMaps) {
2308 qWarning("Multisample texture cannot have mipmaps");
2309 return false;
2310 }
2311 }
2312
2313 if (adjustedSize)
2314 *adjustedSize = size;
2315
2316 return true;
2317}
2318
2319bool QMetalTexture::build()
2320{
2321 QSize size;
2322 if (!prepareBuild(&size))
2323 return false;
2324
2325 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
2326
2327 const bool isCube = m_flags.testFlag(CubeMap);
2328 if (isCube)
2329 desc.textureType = MTLTextureTypeCube;
2330 else
2331 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
2332 desc.pixelFormat = d->format;
2333 desc.width = size.width();
2334 desc.height = size.height();
2335 desc.mipmapLevelCount = mipLevelCount;
2336 if (samples > 1)
2337 desc.sampleCount = samples;
2338 desc.resourceOptions = MTLResourceStorageModePrivate;
2339 desc.storageMode = MTLStorageModePrivate;
2340 desc.usage = MTLTextureUsageShaderRead;
2341 if (m_flags.testFlag(RenderTarget))
2342 desc.usage |= MTLTextureUsageRenderTarget;
2343 if (m_flags.testFlag(UsedWithLoadStore))
2344 desc.usage |= MTLTextureUsageShaderWrite;
2345
2346 QRHI_RES_RHI(QRhiMetal);
2347 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
2348 [desc release];
2349
2350 if (!m_objectName.isEmpty())
2351 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
2352
2353 d->owns = true;
2354 nativeHandlesStruct.texture = d->tex;
2355
2356 QRHI_PROF;
2357 QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
2358
2359 lastActiveFrameSlot = -1;
2360 generation += 1;
2361 rhiD->registerResource(this);
2362 return true;
2363}
2364
2365bool QMetalTexture::buildFrom(const QRhiNativeHandles *src)
2366{
2367 const QRhiMetalTextureNativeHandles *h = static_cast<const QRhiMetalTextureNativeHandles *>(src);
2368 if (!h || !h->texture)
2369 return false;
2370
2371 if (!prepareBuild())
2372 return false;
2373
2374 d->tex = (id<MTLTexture>) h->texture;
2375
2376 d->owns = false;
2377 nativeHandlesStruct.texture = d->tex;
2378
2379 QRHI_PROF;
2380 QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
2381
2382 lastActiveFrameSlot = -1;
2383 generation += 1;
2384 QRHI_RES_RHI(QRhiMetal);
2385 rhiD->registerResource(this);
2386 return true;
2387}
2388
2389const QRhiNativeHandles *QMetalTexture::nativeHandles()
2390{
2391 return &nativeHandlesStruct;
2392}
2393
2394id<MTLTexture> QMetalTextureData::viewForLevel(int level)
2395{
2396 Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
2397 if (perLevelViews[level])
2398 return perLevelViews[level];
2399
2400 const MTLTextureType type = [tex textureType];
2401 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
2402 id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
2403 levels: NSMakeRange(level, 1) slices: NSMakeRange(0, isCube ? 6 : 1)];
2404
2405 perLevelViews[level] = view;
2406 return view;
2407}
2408
2409QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
2410 AddressMode u, AddressMode v)
2411 : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v),
2412 d(new QMetalSamplerData)
2413{
2414}
2415
2416QMetalSampler::~QMetalSampler()
2417{
2418 release();
2419 delete d;
2420}
2421
2422void QMetalSampler::release()
2423{
2424 if (!d->samplerState)
2425 return;
2426
2427 QRhiMetalData::DeferredReleaseEntry e;
2428 e.type = QRhiMetalData::DeferredReleaseEntry::Sampler;
2429 e.lastActiveFrameSlot = lastActiveFrameSlot;
2430
2431 e.sampler.samplerState = d->samplerState;
2432 d->samplerState = nil;
2433
2434 QRHI_RES_RHI(QRhiMetal);
2435 rhiD->d->releaseQueue.append(e);
2436 rhiD->unregisterResource(this);
2437}
2438
2439static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
2440{
2441 switch (f) {
2442 case QRhiSampler::Nearest:
2443 return MTLSamplerMinMagFilterNearest;
2444 case QRhiSampler::Linear:
2445 return MTLSamplerMinMagFilterLinear;
2446 default:
2447 Q_UNREACHABLE();
2448 return MTLSamplerMinMagFilterNearest;
2449 }
2450}
2451
2452static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
2453{
2454 switch (f) {
2455 case QRhiSampler::None:
2456 return MTLSamplerMipFilterNotMipmapped;
2457 case QRhiSampler::Nearest:
2458 return MTLSamplerMipFilterNearest;
2459 case QRhiSampler::Linear:
2460 return MTLSamplerMipFilterLinear;
2461 default:
2462 Q_UNREACHABLE();
2463 return MTLSamplerMipFilterNotMipmapped;
2464 }
2465}
2466
2467static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
2468{
2469 switch (m) {
2470 case QRhiSampler::Repeat:
2471 return MTLSamplerAddressModeRepeat;
2472 case QRhiSampler::ClampToEdge:
2473 return MTLSamplerAddressModeClampToEdge;
2474 case QRhiSampler::Border:
2475 return MTLSamplerAddressModeClampToBorderColor;
2476 case QRhiSampler::Mirror:
2477 return MTLSamplerAddressModeMirrorRepeat;
2478 case QRhiSampler::MirrorOnce:
2479 return MTLSamplerAddressModeMirrorClampToEdge;
2480 default:
2481 Q_UNREACHABLE();
2482 return MTLSamplerAddressModeClampToEdge;
2483 }
2484}
2485
2486static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
2487{
2488 switch (op) {
2489 case QRhiSampler::Never:
2490 return MTLCompareFunctionNever;
2491 case QRhiSampler::Less:
2492 return MTLCompareFunctionLess;
2493 case QRhiSampler::Equal:
2494 return MTLCompareFunctionEqual;
2495 case QRhiSampler::LessOrEqual:
2496 return MTLCompareFunctionLessEqual;
2497 case QRhiSampler::Greater:
2498 return MTLCompareFunctionGreater;
2499 case QRhiSampler::NotEqual:
2500 return MTLCompareFunctionNotEqual;
2501 case QRhiSampler::GreaterOrEqual:
2502 return MTLCompareFunctionGreaterEqual;
2503 case QRhiSampler::Always:
2504 return MTLCompareFunctionAlways;
2505 default:
2506 Q_UNREACHABLE();
2507 return MTLCompareFunctionNever;
2508 }
2509}
2510
2511bool QMetalSampler::build()
2512{
2513 if (d->samplerState)
2514 release();
2515
2516 MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
2517 desc.minFilter = toMetalFilter(m_minFilter);
2518 desc.magFilter = toMetalFilter(m_magFilter);
2519 desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
2520 desc.sAddressMode = toMetalAddressMode(m_addressU);
2521 desc.tAddressMode = toMetalAddressMode(m_addressV);
2522 desc.rAddressMode = toMetalAddressMode(m_addressW);
2523 desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
2524
2525 QRHI_RES_RHI(QRhiMetal);
2526 d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
2527 [desc release];
2528
2529 lastActiveFrameSlot = -1;
2530 generation += 1;
2531 rhiD->registerResource(this);
2532 return true;
2533}
2534
2535// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
2536// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
2537QMetalRenderPassDescriptor::QMetalRenderPassDescriptor(QRhiImplementation *rhi)
2538 : QRhiRenderPassDescriptor(rhi)
2539{
2540}
2541
2542QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
2543{
2544 release();
2545}
2546
2547void QMetalRenderPassDescriptor::release()
2548{
2549 // nothing to do here
2550}
2551
2552QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
2553 : QRhiRenderTarget(rhi),
2554 d(new QMetalRenderTargetData)
2555{
2556}
2557
2558QMetalReferenceRenderTarget::~QMetalReferenceRenderTarget()
2559{
2560 release();
2561 delete d;
2562}
2563
2564void QMetalReferenceRenderTarget::release()
2565{
2566 // nothing to do here
2567}
2568
2569QSize QMetalReferenceRenderTarget::pixelSize() const
2570{
2571 return d->pixelSize;
2572}
2573
2574float QMetalReferenceRenderTarget::devicePixelRatio() const
2575{
2576 return d->dpr;
2577}
2578
2579int QMetalReferenceRenderTarget::sampleCount() const
2580{
2581 return d->sampleCount;
2582}
2583
2584QMetalTextureRenderTarget::QMetalTextureRenderTarget(QRhiImplementation *rhi,
2585 const QRhiTextureRenderTargetDescription &desc,
2586 Flags flags)
2587 : QRhiTextureRenderTarget(rhi, desc, flags),
2588 d(new QMetalRenderTargetData)
2589{
2590}
2591
2592QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
2593{
2594 release();
2595 delete d;
2596}
2597
2598void QMetalTextureRenderTarget::release()
2599{
2600 // nothing to do here
2601}
2602
2603QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
2604{
2605 const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
2606 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
2607 rpD->colorAttachmentCount = colorAttachments.count();
2608 rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
2609
2610 for (int i = 0, ie = colorAttachments.count(); i != ie; ++i) {
2611 QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
2612 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
2613 rpD->colorFormat[i] = texD ? texD->d->format : rbD->d->format;
2614 }
2615
2616 if (m_desc.depthTexture())
2617 rpD->dsFormat = QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format;
2618 else if (m_desc.depthStencilBuffer())
2619 rpD->dsFormat = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format;
2620
2621 return rpD;
2622}
2623
2624bool QMetalTextureRenderTarget::build()
2625{
2626 const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
2627 Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
2628 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
2629 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
2630
2631 d->colorAttCount = colorAttachments.count();
2632 for (int i = 0; i < d->colorAttCount; ++i) {
2633 QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
2634 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
2635 Q_ASSERT(texD || rbD);
2636 id<MTLTexture> dst = nil;
2637 if (texD) {
2638 dst = texD->d->tex;
2639 if (i == 0) {
2640 d->pixelSize = texD->pixelSize();
2641 d->sampleCount = texD->samples;
2642 }
2643 } else if (rbD) {
2644 dst = rbD->d->tex;
2645 if (i == 0) {
2646 d->pixelSize = rbD->pixelSize();
2647 d->sampleCount = rbD->samples;
2648 }
2649 }
2650 QMetalRenderTargetData::ColorAtt colorAtt;
2651 colorAtt.tex = dst;
2652 colorAtt.layer = colorAttachments[i].layer();
2653 colorAtt.level = colorAttachments[i].level();
2654 QMetalTexture *resTexD = QRHI_RES(QMetalTexture, colorAttachments[i].resolveTexture());
2655 colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
2656 colorAtt.resolveLayer = colorAttachments[i].resolveLayer();
2657 colorAtt.resolveLevel = colorAttachments[i].resolveLevel();
2658 d->fb.colorAtt[i] = colorAtt;
2659 }
2660 d->dpr = 1;
2661
2662 if (hasDepthStencil) {
2663 if (m_desc.depthTexture()) {
2664 QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
2665 d->fb.dsTex = depthTexD->d->tex;
2666 d->fb.hasStencil = false;
2667 d->fb.depthNeedsStore = true;
2668 if (d->colorAttCount == 0) {
2669 d->pixelSize = depthTexD->pixelSize();
2670 d->sampleCount = depthTexD->samples;
2671 }
2672 } else {
2673 QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
2674 d->fb.dsTex = depthRbD->d->tex;
2675 d->fb.hasStencil = true;
2676 d->fb.depthNeedsStore = false;
2677 if (d->colorAttCount == 0) {
2678 d->pixelSize = depthRbD->pixelSize();
2679 d->sampleCount = depthRbD->samples;
2680 }
2681 }
2682 d->dsAttCount = 1;
2683 } else {
2684 d->dsAttCount = 0;
2685 }
2686
2687 return true;
2688}
2689
2690QSize QMetalTextureRenderTarget::pixelSize() const
2691{
2692 return d->pixelSize;
2693}
2694
2695float QMetalTextureRenderTarget::devicePixelRatio() const
2696{
2697 return d->dpr;
2698}
2699
2700int QMetalTextureRenderTarget::sampleCount() const
2701{
2702 return d->sampleCount;
2703}
2704
2705QMetalShaderResourceBindings::QMetalShaderResourceBindings(QRhiImplementation *rhi)
2706 : QRhiShaderResourceBindings(rhi)
2707{
2708}
2709
2710QMetalShaderResourceBindings::~QMetalShaderResourceBindings()
2711{
2712 release();
2713}
2714
2715void QMetalShaderResourceBindings::release()
2716{
2717 sortedBindings.clear();
2718 maxBinding = -1;
2719}
2720
2721bool QMetalShaderResourceBindings::build()
2722{
2723 if (!sortedBindings.isEmpty())
2724 release();
2725
2726 sortedBindings = m_bindings;
2727 std::sort(sortedBindings.begin(), sortedBindings.end(),
2728 [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
2729 {
2730 return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
2731 });
2732 if (!sortedBindings.isEmpty())
2733 maxBinding = QRhiShaderResourceBindingPrivate::get(&sortedBindings.last())->binding;
2734 else
2735 maxBinding = -1;
2736
2737 boundResourceData.resize(sortedBindings.count());
2738
2739 for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) {
2740 const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&sortedBindings[i]);
2741 QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]);
2742 switch (b->type) {
2743 case QRhiShaderResourceBinding::UniformBuffer:
2744 {
2745 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
2746 bd.ubuf.id = bufD->m_id;
2747 bd.ubuf.generation = bufD->generation;
2748 }
2749 break;
2750 case QRhiShaderResourceBinding::SampledTexture:
2751 {
2752 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
2753 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
2754 bd.stex.texId = texD->m_id;
2755 bd.stex.texGeneration = texD->generation;
2756 bd.stex.samplerId = samplerD->m_id;
2757 bd.stex.samplerGeneration = samplerD->generation;
2758 }
2759 break;
2760 case QRhiShaderResourceBinding::ImageLoad:
2761 Q_FALLTHROUGH();
2762 case QRhiShaderResourceBinding::ImageStore:
2763 Q_FALLTHROUGH();
2764 case QRhiShaderResourceBinding::ImageLoadStore:
2765 {
2766 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
2767 bd.simage.id = texD->m_id;
2768 bd.simage.generation = texD->generation;
2769 }
2770 break;
2771 case QRhiShaderResourceBinding::BufferLoad:
2772 Q_FALLTHROUGH();
2773 case QRhiShaderResourceBinding::BufferStore:
2774 Q_FALLTHROUGH();
2775 case QRhiShaderResourceBinding::BufferLoadStore:
2776 {
2777 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
2778 bd.sbuf.id = bufD->m_id;
2779 bd.sbuf.generation = bufD->generation;
2780 }
2781 break;
2782 default:
2783 Q_UNREACHABLE();
2784 break;
2785 }
2786 }
2787
2788 generation += 1;
2789 return true;
2790}
2791
2792QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
2793 : QRhiGraphicsPipeline(rhi),
2794 d(new QMetalGraphicsPipelineData)
2795{
2796}
2797
2798QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
2799{
2800 release();
2801 delete d;
2802}
2803
2804void QMetalGraphicsPipeline::release()
2805{
2806 QRHI_RES_RHI(QRhiMetal);
2807
2808 if (!d->ps)
2809 return;
2810
2811 if (d->ps) {
2812 [d->ps release];
2813 d->ps = nil;
2814 }
2815
2816 if (d->ds) {
2817 [d->ds release];
2818 d->ds = nil;
2819 }
2820
2821 if (d->vsFunc) {
2822 [d->vsFunc release];
2823 d->vsFunc = nil;
2824 }
2825 if (d->vsLib) {
2826 [d->vsLib release];
2827 d->vsLib = nil;
2828 }
2829
2830 if (d->fsFunc) {
2831 [d->fsFunc release];
2832 d->fsFunc = nil;
2833 }
2834 if (d->fsLib) {
2835 [d->fsLib release];
2836 d->fsLib = nil;
2837 }
2838
2839 rhiD->unregisterResource(this);
2840}
2841
2842static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
2843{
2844 switch (format) {
2845 case QRhiVertexInputAttribute::Float4:
2846 return MTLVertexFormatFloat4;
2847 case QRhiVertexInputAttribute::Float3:
2848 return MTLVertexFormatFloat3;
2849 case QRhiVertexInputAttribute::Float2:
2850 return MTLVertexFormatFloat2;
2851 case QRhiVertexInputAttribute::Float:
2852 return MTLVertexFormatFloat;
2853 case QRhiVertexInputAttribute::UNormByte4:
2854 return MTLVertexFormatUChar4Normalized;
2855 case QRhiVertexInputAttribute::UNormByte2:
2856 return MTLVertexFormatUChar2Normalized;
2857 case QRhiVertexInputAttribute::UNormByte:
2858 if (@available(macOS 10.13, iOS 11.0, *))
2859 return MTLVertexFormatUCharNormalized;
2860 else
2861 Q_UNREACHABLE();
2862 default:
2863 Q_UNREACHABLE();
2864 return MTLVertexFormatFloat4;
2865 }
2866}
2867
2868static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
2869{
2870 switch (f) {
2871 case QRhiGraphicsPipeline::Zero:
2872 return MTLBlendFactorZero;
2873 case QRhiGraphicsPipeline::One:
2874 return MTLBlendFactorOne;
2875 case QRhiGraphicsPipeline::SrcColor:
2876 return MTLBlendFactorSourceColor;
2877 case QRhiGraphicsPipeline::OneMinusSrcColor:
2878 return MTLBlendFactorOneMinusSourceColor;
2879 case QRhiGraphicsPipeline::