Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qrhimetal.mm
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qrhimetal_p.h"
7#include "qshader_p.h"
8#include <QGuiApplication>
9#include <QWindow>
10#include <QUrl>
11#include <QFile>
12#include <QTemporaryFile>
13#include <QFileInfo>
14#include <qmath.h>
15#include <QtCore/qendian.h>
16#include <QOperatingSystemVersion>
17
18#include <QtCore/private/qcore_mac_p.h>
19#include <QtGui/private/qmetallayer_p.h>
20#include <QtGui/qpa/qplatformwindow_p.h>
21
22#ifdef Q_OS_MACOS
23#include <AppKit/AppKit.h>
24#else
25#include <UIKit/UIKit.h>
26#endif
27
28#include <QuartzCore/CATransaction.h>
29
30#include <Metal/Metal.h>
31
32#include <utility> // for std::pair
33
35
36/*
37 Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
38 Shared (host visible) and duplicated (to help having 2 frames in flight),
39 "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
40 Textures are Private (device local) and a host visible staging buffer is
41 used to upload data to them. Does not rely on strong objects refs from
42 command buffers but does rely on the automatic resource tracking of the
43 command encoders. Assumes that an autorelease pool (ideally per frame) is
44 available on the thread on which QRhi is used.
45*/
46
47#if __has_feature(objc_arc)
48#error ARC not supported
49#endif
50
51// Even though the macOS 13 MTLBinaryArchive problem (QTBUG-106703) seems
52// to be solved in later 13.x releases, we have reports from old Intel hardware
53// and older macOS versions where this causes problems (QTBUG-114338).
54// Thus we no longer do OS version based differentiation, but rather have a
55// single toggle that is currently on, and so QRhi::(set)pipelineCache()
56// does nothing with Metal.
57#define QRHI_METAL_DISABLE_BINARY_ARCHIVE
58
59// We should be able to operate with command buffers that do not automatically
60// retain/release the resources used by them. (since we have logic that mirrors
61// other backends such as the Vulkan one anyway)
62#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
63
64/*!
65 \class QRhiMetalInitParams
66 \inmodule QtGuiPrivate
67 \inheaderfile rhi/qrhi.h
68 \since 6.6
69 \brief Metal specific initialization parameters.
70
71 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
72 for details.
73
74 A Metal-based QRhi needs no special parameters for initialization.
75
76 \badcode
77 QRhiMetalInitParams params;
78 rhi = QRhi::create(QRhi::Metal, &params);
79 \endcode
80
81 \note Metal API validation cannot be enabled programmatically by the QRhi.
82 Instead, either run the debug build of the application in XCode, by
83 generating a \c{.xcodeproj} file via \c{cmake -G Xcode}, or set the
84 environment variable \c{METAL_DEVICE_WRAPPER_TYPE=1}. The variable needs to
85 be set early on in the environment, preferably before starting the process;
86 attempting to set it at QRhi creation time is not functional in practice.
87 (too late probably)
88
89 \note QRhiSwapChain can only target QWindow instances that have their
90 surface type set to QSurface::MetalSurface.
91
92 \section2 Working with existing Metal devices
93
94 When interoperating with another graphics engine, it may be necessary to
95 get a QRhi instance that uses the same Metal device. This can be achieved
96 by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
97 device must be set to a non-null value then. Optionally, a command queue
98 object can be specified as well.
99
100 The QRhi does not take ownership of any of the external objects.
101 */
102
103/*!
104 \class QRhiMetalNativeHandles
105 \inmodule QtGuiPrivate
106 \inheaderfile rhi/qrhi.h
107 \since 6.6
108 \brief Holds the Metal device used by the QRhi.
109
110 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
111 for details.
112 */
113
114/*!
115 \variable QRhiMetalNativeHandles::dev
116
117 Set to a valid MTLDevice to import an existing device.
118*/
119
120/*!
121 \variable QRhiMetalNativeHandles::cmdQueue
122
123 Set to a valid MTLCommandQueue when importing an existing command queue.
124 When \nullptr, QRhi will create a new command queue.
125*/
126
127/*!
128 \class QRhiMetalCommandBufferNativeHandles
129 \inmodule QtGuiPrivate
130 \inheaderfile rhi/qrhi.h
131 \since 6.6
132 \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
133
134 \note The command buffer object is only guaranteed to be valid while
135 recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
136 - \l{QRhi::endFrame()}{endFrame()} or
137 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
138 \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
139
140 \note The command encoder is only valid while recording a pass, that is,
141 between \l{QRhiCommandBuffer::beginPass()} -
142 \l{QRhiCommandBuffer::endPass()}.
143
144 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
145 for details.
146 */
147
148/*!
149 \variable QRhiMetalCommandBufferNativeHandles::commandBuffer
150*/
151
152/*!
153 \variable QRhiMetalCommandBufferNativeHandles::encoder
154*/
155
157{
160 std::array<uint, 3> localSize = {};
167
168 void destroy() {
169 nativeResourceBindingMap.clear();
170 [lib release];
171 lib = nil;
172 [func release];
173 func = nil;
174 [argumentEncoder release];
175 argumentEncoder = nil;
177 }
178};
179
181{
182 QRhiMetalData(QRhiMetal *rhi) : q(rhi), ofr(rhi) { }
183
188
191 const QColor &colorClearValue,
192 const QRhiDepthStencilClearValue &depthStencilClearValue,
193 int colorAttCount,
194 QRhiShadingRateMap *shadingRateMap);
195 id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
196 bool preferArgumentBuffers,
197 QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
198 id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
199 bool setupBinaryArchive(NSURL *sourceFileUrl = nil);
200 void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
201 void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
202 void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
203 void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
204
218 int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
219 union {
220 struct {
221 id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
222 } buffer;
223 struct {
224 id<MTLTexture> texture;
225 } renderbuffer;
226 struct {
227 id<MTLTexture> texture;
228 id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
229 id<MTLTexture> views[QRhi::MAX_MIP_LEVELS];
230 id<MTLTexture> samplingView;
231 id<MTLTexture> writeView;
232 } texture;
233 struct {
234 id<MTLSamplerState> samplerState;
235 } sampler;
236 struct {
237 id<MTLBuffer> buffer;
238 } stagingBuffer;
239 struct {
240 id<MTLRenderPipelineState> pipelineState;
241 id<MTLDepthStencilState> depthStencilState;
242 std::array<id<MTLComputePipelineState>, 3> tessVertexComputeState;
243 id<MTLComputePipelineState> tessTessControlComputeState;
244 } graphicsPipeline;
245 struct {
246 id<MTLComputePipelineState> pipelineState;
247 } computePipeline;
248 struct {
249 id<MTLRasterizationRateMap> rateMap;
250 } shadingRateMap;
251 struct {
252 id<MTLIndirectCommandBuffer> icb;
253 id<MTLBuffer> argBuffer;
254 } stagingIcbBuffer;
255 };
256 };
258
260 OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
261 bool active = false;
262 double lastGpuTime = 0;
264 } ofr;
265
274 // Only used for depth(-stencil) textures whose Metal pixel format does
275 // not match the layout of the readback result, where the staging
276 // buffer holds a densely packed depth plane, followed by a densely
277 // packed stencil plane when the format has stencil. See
278 // enqueueResourceUpdates() and finishActiveReadbacks().
281 // Whether the depth plane holds 32-bit floats (as opposed to a
282 // 24-bit unorm value in the low bits of each 32-bit word).
283 bool depthPlaneIsFloat = false;
284 };
286
294
296
299
300 // Indirect Command Buffer (ICB) infrastructure for GPU-driven multi-draw
310 // Written by the encode kernel, consumed by executeCommandsInBuffer.
312 // Holds 0xFFFFFFFF, stands in for the count buffer when there is none.
314 bool icbSetupFailed = false;
315
316 static const int TEXBUF_ALIGN = 256; // probably not accurate
317
318 using ShaderCacheKey = std::pair<QRhiShaderStage, bool>; // stage, argument_buffers
320
329 StagingArea argBufPool[QMTL_FRAMES_IN_FLIGHT];
330 StagingArea bufStagingPool[QMTL_FRAMES_IN_FLIGHT];
331 id<MTLBuffer> allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment,
332 int frameSlot, quint32 minBlockSize, quint32 *offset);
333 id<MTLBuffer> allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset);
334 id<MTLBuffer> allocBufferStaging(quint32 size, int frameSlot, quint32 *offset);
335 id<MTLBuffer> newOneShotStagingBuffer(quint32 size, int frameSlot);
337
338 // Uploads larger than this always get their own one-shot MTLBuffer and do
339 // not count towards the area's size, so that a one-off large upload cannot
340 // inflate it.
341 static constexpr quint32 LARGE_STAGING_ALLOC = 512 * 1024;
342 static constexpr quint32 STAGING_AREA_MIN = 64 * 1024;
343 static constexpr quint32 STAGING_AREA_MAX = 16 * 1024 * 1024;
344 static constexpr int STAGING_AREA_LOW_DEMAND_FRAMES = 60;
345 static constexpr int STAGING_AREA_HIGH_DEMAND_FRAMES = 3;
346
347 // Counts both swapchain and offscreen frames. Never 0 once a frame started,
348 // so that a default-initialized "used in frame" cannot match.
350};
351
354
356{
357 bool managed = false;
358 bool slotted = false;
359 bool isDeviceLocal = false; // true = MTLResourceStorageModePrivate buf[0], no slotting, non-host visible
360 id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
365 QVarLengthArray<BufferUpdate, 16> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
366};
367
373
395
400
405
433
456
458{
460 float dpr = 1;
461 int sampleCount = 1;
463 int dsAttCount = 0;
464
476
477 struct {
479 id<MTLTexture> dsTex = nil;
480 id<MTLTexture> dsResolveTex = nil;
481 bool hasStencil = false;
482 bool depthNeedsStore = false;
483 bool preserveColor = false;
484 bool preserveDs = false;
485 } fb;
486
488};
489
491{
495 bool icbCapable = false;
516 bool enabled = false;
517 bool failed = false;
525 quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
526 {
527 // max vertex output components = resourceLimit(MaxVertexOutputs) * 4 = 60
528 return vertexOrIndexCount * instanceCount * sizeof(float) * 60;
529 }
530 quint32 tescCompOutputBufferSize(quint32 patchCount) const
531 {
532 return outControlPointCount * patchCount * sizeof(float) * 60;
533 }
534 quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
535 {
536 // assume maxTessellationControlPerPatchOutputComponents is 128
537 return patchCount * sizeof(float) * 128;
538 }
539 quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
540 {
541 return ((vertexOrIndexCount + inControlPointCount - 1) / inControlPointCount) * instanceCount;
542 }
547 } tess;
548 void setupVertexInputDescriptor(MTLVertexDescriptor *desc);
549 void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc);
550
551 // SPIRV-Cross buffer size buffers
553};
554
556{
560
561 // SPIRV-Cross buffer size buffers
563};
564
576
577QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
578{
579 Q_UNUSED(params);
580
581 d = new QRhiMetalData(this);
582
583 importedDevice = importDevice != nullptr;
584 if (importedDevice) {
585 if (importDevice->dev) {
586 d->dev = (id<MTLDevice>) importDevice->dev;
587 importedCmdQueue = importDevice->cmdQueue != nullptr;
588 if (importedCmdQueue)
589 d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
590 } else {
591 qWarning("No MTLDevice given, cannot import");
592 importedDevice = false;
593 }
594 }
595}
596
598{
599 delete d;
600}
601
602template <class Int>
603inline Int aligned(Int v, Int byteAlign)
604{
605 return (v + byteAlign - 1) & ~(byteAlign - 1);
606}
607
608bool QRhiMetal::probe(QRhiMetalInitParams *params)
609{
610 QMacAutoReleasePool pool;
611
612 Q_UNUSED(params);
613 id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
614 if (dev) {
615 [dev release];
616 return true;
617 }
618 return false;
619}
620
622{
624 // Do not let the command buffer mess with the refcount of objects. We do
625 // have a proper render loop and will manage lifetimes similarly to other
626 // backends (Vulkan).
627 return [cmdQueue commandBufferWithUnretainedReferences];
628#else
629 return [cmdQueue commandBuffer];
630#endif
631}
632
633bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl)
634{
636 return false;
637#endif
638
639 [binArch release];
640 MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new];
641 binArchDesc.url = sourceFileUrl;
642 NSError *err = nil;
643 binArch = [dev newBinaryArchiveWithDescriptor: binArchDesc error: &err];
644 [binArchDesc release];
645 if (!binArch) {
646 const QString msg = QString::fromNSString(err.localizedDescription);
647 qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg));
648 return false;
649 }
650 return true;
651}
652
653bool QRhiMetal::create(QRhi::Flags flags)
654{
655 rhiFlags = flags;
656
657 if (importedDevice)
658 [d->dev retain];
659 else
660 d->dev = MTLCreateSystemDefaultDevice();
661
662 if (!d->dev) {
663 qWarning("No MTLDevice");
664 return false;
665 }
666
667 const QString deviceName = QString::fromNSString([d->dev name]);
668 qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(deviceName));
669 driverInfoStruct.deviceName = deviceName.toUtf8();
670
671 // deviceId and vendorId stay unset for now. Note that registryID is not
672 // suitable as deviceId because it does not seem stable on macOS and can
673 // apparently change when the system is rebooted.
674
675#ifdef Q_OS_MACOS
676 const MTLDeviceLocation deviceLocation = [d->dev location];
677 switch (deviceLocation) {
678 case MTLDeviceLocationBuiltIn:
679 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
680 break;
681 case MTLDeviceLocationSlot:
682 driverInfoStruct.deviceType = QRhiDriverInfo::DiscreteDevice;
683 break;
684 case MTLDeviceLocationExternal:
685 driverInfoStruct.deviceType = QRhiDriverInfo::ExternalDevice;
686 break;
687 default:
688 break;
689 }
690#else
691 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
692#endif
693
694 const QOperatingSystemVersion ver = QOperatingSystemVersion::current();
695 osMajor = ver.majorVersion();
696 osMinor = ver.minorVersion();
697
699 [d->cmdQueue retain];
700 else
701 d->cmdQueue = [d->dev newCommandQueue];
702
703 d->captureMgr = [MTLCaptureManager sharedCaptureManager];
704 // Have a custom capture scope as well which then shows up in XCode as
705 // an option when capturing, and becomes especially useful when having
706 // multiple windows with multiple QRhis.
707 d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
708 const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
709 d->captureScope.label = label.toNSString();
710
711#if defined(Q_OS_MACOS) || defined(Q_OS_VISIONOS)
712 caps.maxTextureSize = 16384;
713 caps.baseVertexAndInstance = true;
714 caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7];
715 caps.maxThreadGroupSize = 1024;
716 caps.multiView = true;
717#elif defined(Q_OS_TVOS)
718 if ([d->dev supportsFamily:MTLGPUFamilyApple3])
719 caps.maxTextureSize = 16384;
720 else
721 caps.maxTextureSize = 8192;
722 caps.baseVertexAndInstance = false;
723 caps.isAppleGPU = true;
724#elif defined(Q_OS_IOS)
725 if ([d->dev supportsFamily:MTLGPUFamilyApple3]) {
726 caps.maxTextureSize = 16384;
727 caps.baseVertexAndInstance = true;
728 } else if ([d->dev supportsFamily:MTLGPUFamilyApple2]) {
729 caps.maxTextureSize = 8192;
730 caps.baseVertexAndInstance = false;
731 } else {
732 caps.maxTextureSize = 4096;
733 caps.baseVertexAndInstance = false;
734 }
735 caps.isAppleGPU = true;
736 if ([d->dev supportsFamily:MTLGPUFamilyApple4])
737 caps.maxThreadGroupSize = 1024;
738 if ([d->dev supportsFamily:MTLGPUFamilyApple5])
739 caps.multiView = true;
740#endif
741
742 // MTLBlitCommandEncoder's buffer-to-buffer copy requires 4 byte aligned
743 // offsets and size on non-Apple GPUs, whereas uploadStaticBuffer() takes
744 // any offset and size. Keep the legacy host-write path there.
745 caps.usePrivateStaticBuffers = caps.isAppleGPU;
746 if (qEnvironmentVariableIntValue("QT_METAL_NO_PRIVATE_STATIC_BUFFERS"))
747 caps.usePrivateStaticBuffers = false;
748
749 caps.supportedSampleCounts = { 1 };
750 for (int sampleCount : { 2, 4, 8 }) {
751 if ([d->dev supportsTextureSampleCount: sampleCount])
752 caps.supportedSampleCounts.append(sampleCount);
753 }
754
755 caps.indirectCommandBuffers = ([d->dev supportsFamily:MTLGPUFamilyApple5]
756 || [d->dev supportsFamily:MTLGPUFamilyMac2])
757 && [d->dev supportsFamily:MTLGPUFamilyMetal3];
758
759 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 1];
760 if (caps.shadingRateMap && caps.multiView)
761 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 2];
762
763 // QTBUG-144444: setDepthClipMode is not available on the Simulator
764 caps.depthClamp = [d->dev supportsFamily:MTLGPUFamilyApple3];
765
766#if defined(Q_OS_MACOS) || defined(Q_OS_VISIONOS)
767 caps.fragmentStoresAndAtomics = true;
768#else
769 caps.fragmentStoresAndAtomics = [d->dev supportsFamily:MTLGPUFamilyApple3];
770#endif
771
772 if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
773 d->setupBinaryArchive();
774
775 nativeHandlesStruct.dev = (MTLDevice *) d->dev;
776 nativeHandlesStruct.cmdQueue = (MTLCommandQueue *) d->cmdQueue;
777
778 return true;
779}
780
782{
785
786 for (QMetalShader &s : d->shaderCache)
787 s.destroy();
788 d->shaderCache.clear();
789
790 [d->captureScope release];
791 d->captureScope = nil;
792
793 for (auto *pools : { d->argBufPool, d->bufStagingPool }) {
794 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
795 [pools[i].buf release];
796 pools[i].buf = nil;
797 pools[i].capacity = 0;
798 pools[i].offset = 0;
799 }
800 }
801
802 [d->icbArgumentBuffer release];
803 d->icbArgumentBuffer = nil;
804
805 [d->icbRangeBuffer release];
806 d->icbRangeBuffer = nil;
807
808 [d->icbNoCountBuffer release];
809 d->icbNoCountBuffer = nil;
810
811 [d->icbEncodeFunction release];
812 d->icbEncodeFunction = nil;
813
814 [d->icbEncodeFunctionU32 release];
815 d->icbEncodeFunctionU32 = nil;
816
817 [d->icbEncodeFunctionU16 release];
818 d->icbEncodeFunctionU16 = nil;
819
820 [d->icbEncodePipeline release];
821 d->icbEncodePipeline = nil;
822
823 [d->icbEncodePipelineU32 release];
824 d->icbEncodePipelineU32 = nil;
825
826 [d->icbEncodePipelineU16 release];
827 d->icbEncodePipelineU16 = nil;
828
829 [d->icb release];
830 d->icb = nil;
831
832 d->icbCapacity = 0;
833 d->icbSetupFailed = false;
834
835 [d->binArch release];
836 d->binArch = nil;
837
838 [d->cmdQueue release];
839 if (!importedCmdQueue)
840 d->cmdQueue = nil;
841
842 [d->dev release];
843 if (!importedDevice)
844 d->dev = nil;
845}
846
848{
849 return caps.supportedSampleCounts;
850}
851
853{
854 Q_UNUSED(sampleCount);
855 return { QSize(1, 1) };
856}
857
858QRhiSwapChain *QRhiMetal::createSwapChain()
859{
860 return new QMetalSwapChain(this);
861}
862
863QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
864{
865 return new QMetalBuffer(this, type, usage, size);
866}
867
869{
870 return 256;
871}
872
874{
875 return false;
876}
877
879{
880 return true;
881}
882
884{
885 return true;
886}
887
889{
890 // depth range 0..1
891 // NB the ctor takes row-major
892 static constexpr QMatrix4x4 m(1.0f, 0.0f, 0.0f, 0.0f,
893 0.0f, 1.0f, 0.0f, 0.0f,
894 0.0f, 0.0f, 0.5f, 0.5f,
895 0.0f, 0.0f, 0.0f, 1.0f);
896 return m;
897}
898
899bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
900{
901 Q_UNUSED(flags);
902
903 bool supportsFamilyMac2 = false; // needed for BC* formats
904 bool supportsFamilyApple3 = false;
905
906#ifdef Q_OS_MACOS
907 supportsFamilyMac2 = true;
908 if (caps.isAppleGPU)
909 supportsFamilyApple3 = true;
910#else
911 supportsFamilyApple3 = true;
912#endif
913
914 // BC5 is not available for any Apple hardare
915 if (format == QRhiTexture::BC5)
916 return false;
917
918 if (!supportsFamilyApple3) {
919 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
920 return false;
921 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
922 return false;
923 }
924
925 if (!supportsFamilyMac2)
926 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
927 return false;
928
929 return true;
930}
931
932bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
933{
934 switch (feature) {
935 case QRhi::MultisampleTexture:
936 return true;
937 case QRhi::MultisampleRenderBuffer:
938 return true;
939 case QRhi::DebugMarkers:
940 return true;
941 case QRhi::Timestamps:
942 return true;
943 case QRhi::Instancing:
944 return true;
945 case QRhi::CustomInstanceStepRate:
946 return true;
947 case QRhi::PrimitiveRestart:
948 return true;
949 case QRhi::NonDynamicUniformBuffers:
950 return true;
951 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
952 return false;
953 case QRhi::NPOTTextureRepeat:
954 return true;
955 case QRhi::RedOrAlpha8IsRed:
956 return true;
957 case QRhi::ElementIndexUint:
958 return true;
959 case QRhi::Compute:
960 return true;
961 case QRhi::FragmentStoresAndAtomics:
962 return caps.fragmentStoresAndAtomics;
963 case QRhi::WideLines:
964 return false;
965 case QRhi::VertexShaderPointSize:
966 return true;
967 case QRhi::BaseVertex:
968 return caps.baseVertexAndInstance;
969 case QRhi::BaseInstance:
970 return caps.baseVertexAndInstance;
971 case QRhi::TriangleFanTopology:
972 return false;
973 case QRhi::ReadBackNonUniformBuffer:
974 return true;
975 case QRhi::ReadBackNonBaseMipLevel:
976 return true;
977 case QRhi::TexelFetch:
978 return true;
979 case QRhi::RenderToNonBaseMipLevel:
980 return true;
981 case QRhi::IntAttributes:
982 return true;
983 case QRhi::ScreenSpaceDerivatives:
984 return true;
985 case QRhi::ReadBackAnyTextureFormat:
986 return true;
987 case QRhi::PipelineCacheDataLoadSave:
989 return false;
990#else
991 return true;
992#endif
993 case QRhi::ImageDataStride:
994 return true;
995 case QRhi::RenderBufferImport:
996 return false;
997 case QRhi::ThreeDimensionalTextures:
998 return true;
999 case QRhi::RenderTo3DTextureSlice:
1000 return true;
1001 case QRhi::TextureArrays:
1002 return true;
1003 case QRhi::Tessellation:
1004 return true;
1005 case QRhi::GeometryShader:
1006 return false;
1007 case QRhi::TextureArrayRange:
1008 return true;
1009 case QRhi::NonFillPolygonMode:
1010 return true;
1011 case QRhi::OneDimensionalTextures:
1012 return true;
1013 case QRhi::OneDimensionalTextureMipmaps:
1014 return false;
1015 case QRhi::HalfAttributes:
1016 return true;
1017 case QRhi::RenderToOneDimensionalTexture:
1018 return false;
1019 case QRhi::ThreeDimensionalTextureMipmaps:
1020 return true;
1021 case QRhi::MultiView:
1022 return caps.multiView;
1023 case QRhi::TextureViewFormat:
1024 return true;
1025 case QRhi::ResolveDepthStencil:
1026 return true;
1027 case QRhi::VariableRateShading:
1028 return false;
1029 case QRhi::VariableRateShadingMap:
1030 return caps.shadingRateMap;
1031 case QRhi::VariableRateShadingMapWithTexture:
1032 return false;
1033 case QRhi::PerRenderTargetBlending:
1034 case QRhi::SampleVariables:
1035 return true;
1036 case QRhi::InstanceIndexIncludesBaseInstance:
1037 return true;
1038 case QRhi::DepthClamp:
1039 return caps.depthClamp;
1040 case QRhi::DrawIndirect:
1041 return true;
1042 case QRhi::DrawIndirectMulti:
1043 return caps.indirectCommandBuffers;
1044 case QRhi::ShaderDrawParameters:
1045 return false;
1046 case QRhi::PushConstants:
1047 return true;
1048 case QRhi::DrawIndirectCount:
1049 return caps.indirectCommandBuffers;
1050 case QRhi::DispatchIndirect:
1051 return true;
1052 case QRhi::BufferToBufferCopy:
1053 return true;
1054 case QRhi::StaticBuffersOnGpuTimeline:
1055 return caps.usePrivateStaticBuffers;
1056 default:
1057 Q_UNREACHABLE();
1058 return false;
1059 }
1060}
1061
1062int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
1063{
1064 switch (limit) {
1065 case QRhi::TextureSizeMin:
1066 return 1;
1067 case QRhi::TextureSizeMax:
1068 return caps.maxTextureSize;
1069 case QRhi::MaxColorAttachments:
1070 return 8;
1071 case QRhi::FramesInFlight:
1072 return QMTL_FRAMES_IN_FLIGHT;
1073 case QRhi::MaxAsyncReadbackFrames:
1074 return QMTL_FRAMES_IN_FLIGHT;
1075 case QRhi::MaxThreadGroupsPerDimension:
1076 return 65535;
1077 case QRhi::MaxThreadsPerThreadGroup:
1078 Q_FALLTHROUGH();
1079 case QRhi::MaxThreadGroupX:
1080 Q_FALLTHROUGH();
1081 case QRhi::MaxThreadGroupY:
1082 Q_FALLTHROUGH();
1083 case QRhi::MaxThreadGroupZ:
1084 return caps.maxThreadGroupSize;
1085 case QRhi::TextureArraySizeMax:
1086 return 2048;
1087 case QRhi::MaxUniformBufferRange:
1088 return 65536;
1089 case QRhi::MaxVertexInputs:
1090 return 31;
1091 case QRhi::MaxVertexOutputs:
1092 return 15; // use the minimum from MTLGPUFamily1/2/3
1093 case QRhi::MaxPushConstantsSize:
1094 return 4096; // the limit for setVertexBytes() and friends
1095 case QRhi::MaxVertexStorageBuffers:
1096 case QRhi::MaxFragmentStorageBuffers:
1097 return 31;
1098 case QRhi::ShadingRateImageTileSize:
1099 return 0;
1100 default:
1101 Q_UNREACHABLE();
1102 return 0;
1103 }
1104}
1105
1107{
1108 return &nativeHandlesStruct;
1109}
1110
1112{
1113 return driverInfoStruct;
1114}
1115
1117{
1118 QRhiStats result;
1119 result.totalPipelineCreationTime = totalPipelineCreationTime();
1120 return result;
1121}
1122
1124{
1125 // not applicable
1126 return false;
1127}
1128
1129void QRhiMetal::setQueueSubmitParams(QRhiNativeHandles *)
1130{
1131 // not applicable
1132}
1133
1135{
1136 for (QMetalShader &s : d->shaderCache)
1137 s.destroy();
1138
1139 d->shaderCache.clear();
1140}
1141
1143{
1144 return false;
1145}
1146
1156
1158{
1159 Q_STATIC_ASSERT(sizeof(QMetalPipelineCacheDataHeader) == 256);
1160 QByteArray data;
1161 if (!d->binArch || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
1162 return data;
1163
1164 QTemporaryFile tmp;
1165 if (!tmp.open()) {
1166 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1167 return data;
1168 }
1169 tmp.close(); // the file exists until the tmp dtor runs
1170
1171 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1172 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1173 NSError *err = nil;
1174 if (![d->binArch serializeToURL: url error: &err]) {
1175 const QString msg = QString::fromNSString(err.localizedDescription);
1176 // Some of these "errors" are not actual errors. (think of "Nothing to serialize")
1177 qCDebug(QRHI_LOG_INFO, "Failed to serialize MTLBinaryArchive: %s", qPrintable(msg));
1178 return data;
1179 }
1180
1181 QFile f(fn);
1182 if (!f.open(QIODevice::ReadOnly)) {
1183 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to reopen temporary file");
1184 return data;
1185 }
1186 const QByteArray blob = f.readAll();
1187 f.close();
1188
1189 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1190 const quint32 dataSize = quint32(blob.size());
1191
1192 data.resize(headerSize + dataSize);
1193
1195 header.rhiId = pipelineCacheRhiId();
1196 header.arch = quint32(sizeof(void*));
1197 header.dataSize = quint32(dataSize);
1198 header.osMajor = osMajor;
1199 header.osMinor = osMinor;
1200 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1201 if (driverStrLen)
1202 memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen);
1203 header.driver[driverStrLen] = '\0';
1204
1205 memcpy(data.data(), &header, headerSize);
1206 memcpy(data.data() + headerSize, blob.constData(), dataSize);
1207 return data;
1208}
1209
1210void QRhiMetal::setPipelineCacheData(const QByteArray &data)
1211{
1212 if (data.isEmpty())
1213 return;
1214
1215 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1216 if (data.size() < qsizetype(headerSize)) {
1217 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)");
1218 return;
1219 }
1220
1221 const size_t dataOffset = headerSize;
1223 memcpy(&header, data.constData(), headerSize);
1224
1225 const quint32 rhiId = pipelineCacheRhiId();
1226 if (header.rhiId != rhiId) {
1227 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
1228 rhiId, header.rhiId);
1229 return;
1230 }
1231
1232 const quint32 arch = quint32(sizeof(void*));
1233 if (header.arch != arch) {
1234 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
1235 arch, header.arch);
1236 return;
1237 }
1238
1239 if (header.osMajor != osMajor || header.osMinor != osMinor) {
1240 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: OS version does not match (%u.%u, %u.%u)",
1241 osMajor, osMinor, header.osMajor, header.osMinor);
1242 return;
1243 }
1244
1245 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1246 if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) {
1247 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Metal device name does not match");
1248 return;
1249 }
1250
1251 if (quint64(data.size()) < quint64(dataOffset) + header.dataSize) {
1252 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)");
1253 return;
1254 }
1255
1256 const char *p = data.constData() + dataOffset;
1257
1258 QTemporaryFile tmp;
1259 if (!tmp.open()) {
1260 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1261 return;
1262 }
1263 tmp.write(p, header.dataSize);
1264 tmp.close(); // the file exists until the tmp dtor runs
1265
1266 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1267 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1268 if (d->setupBinaryArchive(url))
1269 qCDebug(QRHI_LOG_INFO, "Created MTLBinaryArchive with initial data of %u bytes", header.dataSize);
1270}
1271
1272QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
1273 int sampleCount, QRhiRenderBuffer::Flags flags,
1274 QRhiTexture::Format backingFormatHint)
1275{
1276 return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
1277}
1278
1279QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format,
1280 const QSize &pixelSize, int depth, int arraySize,
1281 int sampleCount, QRhiTexture::Flags flags)
1282{
1283 return new QMetalTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
1284}
1285
1286QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
1287 QRhiSampler::Filter mipmapMode,
1288 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
1289{
1290 return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
1291}
1292
1293QRhiShadingRateMap *QRhiMetal::createShadingRateMap()
1294{
1295 return new QMetalShadingRateMap(this);
1296}
1297
1298QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
1299 QRhiTextureRenderTarget::Flags flags)
1300{
1301 return new QMetalTextureRenderTarget(this, desc, flags);
1302}
1303
1305{
1306 return new QMetalGraphicsPipeline(this);
1307}
1308
1310{
1311 return new QMetalComputePipeline(this);
1312}
1313
1315{
1316 return new QMetalShaderResourceBindings(this);
1317}
1318
1324
1325static inline int mapBinding(int binding,
1326 int stageIndex,
1327 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[],
1328 BindingType type)
1329{
1330 const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
1331 if (!map || map->isEmpty())
1332 return binding; // old QShader versions do not have this map, assume 1:1 mapping then
1333
1334 auto it = map->constFind(binding);
1335 if (it != map->cend())
1336 return type == BindingType::Sampler ? it->second : it->first; // may be -1, if the resource is inactive
1337
1338 // Hitting this path is normal too. It is not given that the resource (for
1339 // example, a uniform block) is present in the shaders for all the stages
1340 // specified by the visibility mask in the QRhiShaderResourceBinding.
1341 return -1;
1342}
1343
1344static inline MTLResourceUsage storageImageUsage(QRhiShaderResourceBinding::Type type)
1345{
1346 switch (type) {
1347 case QRhiShaderResourceBinding::ImageLoad:
1348 return MTLResourceUsageRead;
1349 case QRhiShaderResourceBinding::ImageStore:
1350 return MTLResourceUsageWrite;
1351 default:
1352 return MTLResourceUsageRead | MTLResourceUsageWrite;
1353 }
1354}
1355
1357 int encoderStage,
1359{
1360 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : res.textures) {
1361 switch (encoderStage) {
1362 case QMetalShaderResourceBindingsData::VERTEX:
1363 [cbD->d->currentRenderPassEncoder useResource: t.mtltex usage: t.usage stages: MTLRenderStageVertex];
1364 break;
1365 case QMetalShaderResourceBindingsData::FRAGMENT:
1366 [cbD->d->currentRenderPassEncoder useResource: t.mtltex usage: t.usage stages: MTLRenderStageFragment];
1367 break;
1368 case QMetalShaderResourceBindingsData::COMPUTE:
1369 [cbD->d->currentComputePassEncoder useResource: t.mtltex usage: t.usage];
1370 break;
1371 default:
1372 break;
1373 }
1374 }
1375}
1376
1378 int stage,
1379 const QRhiBatchedBindings<id<MTLBuffer>>::Batch &bufferBatch,
1380 const QRhiBatchedBindings<NSUInteger>::Batch &offsetBatch)
1381{
1382 switch (stage) {
1383 case QMetalShaderResourceBindingsData::VERTEX:
1384 [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
1385 offsets: offsetBatch.resources.constData()
1386 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1387 break;
1388 case QMetalShaderResourceBindingsData::FRAGMENT:
1389 [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
1390 offsets: offsetBatch.resources.constData()
1391 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1392 break;
1393 case QMetalShaderResourceBindingsData::COMPUTE:
1394 [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
1395 offsets: offsetBatch.resources.constData()
1396 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1397 break;
1400 // do nothing. These are used later for tessellation
1401 break;
1402 default:
1403 Q_UNREACHABLE();
1404 break;
1405 }
1406}
1407
1409 int stage,
1410 const QRhiBatchedBindings<id<MTLTexture>>::Batch &textureBatch)
1411{
1412 switch (stage) {
1413 case QMetalShaderResourceBindingsData::VERTEX:
1414 [cbD->d->currentRenderPassEncoder setVertexTextures: textureBatch.resources.constData()
1415 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1416 break;
1417 case QMetalShaderResourceBindingsData::FRAGMENT:
1418 [cbD->d->currentRenderPassEncoder setFragmentTextures: textureBatch.resources.constData()
1419 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1420 break;
1421 case QMetalShaderResourceBindingsData::COMPUTE:
1422 [cbD->d->currentComputePassEncoder setTextures: textureBatch.resources.constData()
1423 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1424 break;
1427 // do nothing. These are used later for tessellation
1428 break;
1429 default:
1430 Q_UNREACHABLE();
1431 break;
1432 }
1433}
1434
1436 int encoderStage,
1437 const QRhiBatchedBindings<id<MTLSamplerState>>::Batch &samplerBatch)
1438{
1439 switch (encoderStage) {
1440 case QMetalShaderResourceBindingsData::VERTEX:
1441 [cbD->d->currentRenderPassEncoder setVertexSamplerStates: samplerBatch.resources.constData()
1442 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1443 break;
1444 case QMetalShaderResourceBindingsData::FRAGMENT:
1445 [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: samplerBatch.resources.constData()
1446 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1447 break;
1448 case QMetalShaderResourceBindingsData::COMPUTE:
1449 [cbD->d->currentComputePassEncoder setSamplerStates: samplerBatch.resources.constData()
1450 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1451 break;
1454 // do nothing. These are used later for tessellation
1455 break;
1456 default:
1457 Q_UNREACHABLE();
1458 break;
1459 }
1460}
1461
1462// Helper that is not used during the common vertex+fragment and compute
1463// pipelines, but is necessary when tessellation is involved and so the
1464// graphics pipeline is under the hood a combination of multiple compute and
1465// render pipelines. We need to be able to set the buffers, textures, samplers
1466// when a switching between render and compute encoders.
1467static inline void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceStage, int encoderStage,
1468 const QMetalShaderResourceBindingsData *customBindingState = nullptr)
1469{
1470 const QMetalShaderResourceBindingsData *bindingData = customBindingState ? customBindingState : &cbD->d->currentShaderResourceBindingState;
1471
1472 for (int i = 0, ie = bindingData->res[resourceStage].bufferBatches.batches.count(); i != ie; ++i) {
1473 const auto &bufferBatch(bindingData->res[resourceStage].bufferBatches.batches[i]);
1474 const auto &offsetBatch(bindingData->res[resourceStage].bufferOffsetBatches.batches[i]);
1475 bindStageBuffers(cbD, encoderStage, bufferBatch, offsetBatch);
1476 }
1477
1478 for (int i = 0, ie = bindingData->res[resourceStage].textureBatches.batches.count(); i != ie; ++i) {
1479 const auto &batch(bindingData->res[resourceStage].textureBatches.batches[i]);
1480 bindStageTextures(cbD, encoderStage, batch);
1481 }
1482
1483 for (int i = 0, ie = bindingData->res[resourceStage].samplerBatches.batches.count(); i != ie; ++i) {
1484 const auto &batch(bindingData->res[resourceStage].samplerBatches.batches[i]);
1485 bindStageSamplers(cbD, encoderStage, batch);
1486 }
1487
1488 if (bindingData->res[resourceStage].usesArgumentBuffer)
1489 declareStageArgumentBufferResources(cbD, encoderStage, bindingData->res[resourceStage]);
1490}
1491
1493{
1494 switch (stage) {
1495 case QMetalShaderResourceBindingsData::VERTEX:
1496 return QRhiShaderResourceBinding::StageFlag::VertexStage;
1497 case QMetalShaderResourceBindingsData::TESSCTRL:
1498 return QRhiShaderResourceBinding::StageFlag::TessellationControlStage;
1499 case QMetalShaderResourceBindingsData::TESSEVAL:
1500 return QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage;
1501 case QMetalShaderResourceBindingsData::FRAGMENT:
1502 return QRhiShaderResourceBinding::StageFlag::FragmentStage;
1503 case QMetalShaderResourceBindingsData::COMPUTE:
1504 return QRhiShaderResourceBinding::StageFlag::ComputeStage;
1505 }
1506
1507 Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::StageFlag::VertexStage);
1508}
1509
1512 int dynamicOffsetCount,
1513 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
1514 bool offsetOnlyChange,
1515 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES],
1516 const QMetalShader *shaders[SUPPORTED_STAGES])
1517{
1519
1520 for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) {
1521 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding);
1522 switch (b->type()) {
1523 case QRhiShaderResourceBinding::UniformBuffer:
1524 {
1525 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->ubuf().buf);
1526 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1527 quint32 offset = b->ubuf().offset;
1528 for (int i = 0; i < dynamicOffsetCount; ++i) {
1529 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
1530 if (dynOfs.first == b->binding) {
1531 offset = dynOfs.second;
1532 break;
1533 }
1534 }
1535
1536 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1537 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1538 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1539 if (nativeBinding >= 0)
1540 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1541 }
1542 }
1543 }
1544 break;
1545 case QRhiShaderResourceBinding::SampledTexture:
1546 case QRhiShaderResourceBinding::Texture:
1547 case QRhiShaderResourceBinding::Sampler:
1548 {
1549 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->stex();
1550 for (int elem = 0; elem < data->count(); ++elem) {
1551 QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
1552 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
1553
1554 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1555 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1556 // Must handle all three cases (combined, separate, separate):
1557 // first = texture binding, second = sampler binding
1558 // first = texture binding
1559 // first = sampler binding (i.e. BindingType::Texture...)
1560 const int textureBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1561 const int samplerBinding = texD && samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Sampler)
1562 : (samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture) : -1);
1563 if (textureBinding >= 0 && texD)
1564 bindingData.res[stage].textures.append({ textureBinding + elem, texD->d->textureForSampling(), MTLResourceUsageRead });
1565 if (samplerBinding >= 0)
1566 bindingData.res[stage].samplers.append({ samplerBinding + elem, samplerD->d->samplerState });
1567 }
1568 }
1569 }
1570 }
1571 break;
1572 case QRhiShaderResourceBinding::ImageLoad:
1573 case QRhiShaderResourceBinding::ImageStore:
1574 case QRhiShaderResourceBinding::ImageLoadStore:
1575 {
1576 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->simage().tex);
1577 id<MTLTexture> t = texD->d->viewForLevel(b->simage().level);
1578
1579 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1580 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1581 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1582 if (nativeBinding >= 0)
1583 bindingData.res[stage].textures.append({ nativeBinding, t, storageImageUsage(b->type()) });
1584 }
1585 }
1586 }
1587 break;
1588 case QRhiShaderResourceBinding::BufferLoad:
1589 case QRhiShaderResourceBinding::BufferStore:
1590 case QRhiShaderResourceBinding::BufferLoadStore:
1591 {
1592 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->sbuf().buf);
1593 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1594 quint32 offset = b->sbuf().offset;
1595 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1596 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1597 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1598 if (nativeBinding >= 0)
1599 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1600 }
1601 }
1602 }
1603 break;
1604 default:
1605 Q_UNREACHABLE();
1606 break;
1607 }
1608 }
1609
1610 // With the argument buffer shader variant the texture and sampler values
1611 // collected above are ids within the argument buffer, so encode them into
1612 // one and bind that instead of binding them individually.
1613 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1614 const QMetalShader *shader = shaders[stage];
1615 if (!shader || !shader->argumentEncoder)
1616 continue;
1617 QMetalShaderResourceBindingsData::Stage &res(bindingData.res[stage]);
1618
1619 // offsetOnlyChange means same srb, same pipeline, and no resource
1620 // changed, so what is in the argument buffer cannot have changed
1621 // either. Carry the encoded one over instead of building it again.
1622 if (offsetOnlyChange) {
1624 cbD->d->currentShaderResourceBindingState.res[stage]);
1625 if (prev.usesArgumentBuffer) {
1626 for (const QMetalShaderResourceBindingsData::Stage::Buffer &b : prev.buffers) {
1627 if (b.nativeBinding == shader->argumentBufferIndex) {
1628 res.samplers.clear();
1629 res.buffers.append(b);
1630 res.usesArgumentBuffer = true;
1631 break;
1632 }
1633 }
1634 if (res.usesArgumentBuffer)
1635 continue;
1636 }
1637 }
1638
1639 // The argument buffer is in the constant address space, and it is bound
1640 // like any other buffer, so the offset must satisfy the same 256 byte
1641 // requirement on macOS that makes ubufAlignment() 256. The encoder's
1642 // own alignment is typically well below that.
1643 const quint32 argBufAlignment = qMax(quint32(shader->argumentEncoder.alignment),
1644 quint32(ubufAlignment()));
1645 quint32 argBufOffset = 0;
1646 id<MTLBuffer> argBuf = d->allocArgumentBuffer(quint32(shader->argumentEncoder.encodedLength),
1647 argBufAlignment, currentFrameSlot, &argBufOffset);
1648 if (!argBuf) {
1649 // Nothing gets bound at the argument buffer's index then, so the
1650 // shader will dereference garbage. Acceptable: failing to
1651 // sub-allocate from a small shared memory buffer means the process
1652 // is out of memory anyway.
1653 qWarning("Failed to allocate Metal argument buffer");
1654 res.textures.clear();
1655 res.samplers.clear();
1656 continue;
1657 }
1658 [shader->argumentEncoder setArgumentBuffer: argBuf offset: argBufOffset];
1659 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : std::as_const(res.textures))
1660 [shader->argumentEncoder setTexture: t.mtltex atIndex: NSUInteger(t.nativeBinding)];
1661 for (const QMetalShaderResourceBindingsData::Stage::Sampler &sm : std::as_const(res.samplers))
1662 [shader->argumentEncoder setSamplerState: sm.mtlsampler atIndex: NSUInteger(sm.nativeBinding)];
1663 res.samplers.clear();
1664 res.buffers.append({ shader->argumentBufferIndex, argBuf, argBufOffset });
1665 res.usesArgumentBuffer = true;
1666 }
1667
1668 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1671 continue;
1673 continue;
1674
1675 // QRhiBatchedBindings works with the native bindings and expects
1676 // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
1677 // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
1678 // have to sort here every time.
1679
1680 std::sort(bindingData.res[stage].buffers.begin(), bindingData.res[stage].buffers.end(), [](const QMetalShaderResourceBindingsData::Stage::Buffer &a, const QMetalShaderResourceBindingsData::Stage::Buffer &b) {
1681 return a.nativeBinding < b.nativeBinding;
1682 });
1683
1684 for (const QMetalShaderResourceBindingsData::Stage::Buffer &buf : std::as_const(bindingData.res[stage].buffers)) {
1685 bindingData.res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf);
1686 bindingData.res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset);
1687 }
1688
1689 bindingData.res[stage].bufferBatches.finish();
1690 bindingData.res[stage].bufferOffsetBatches.finish();
1691
1692 for (int i = 0, ie = bindingData.res[stage].bufferBatches.batches.count(); i != ie; ++i) {
1693 const auto &bufferBatch(bindingData.res[stage].bufferBatches.batches[i]);
1694 const auto &offsetBatch(bindingData.res[stage].bufferOffsetBatches.batches[i]);
1695 // skip setting Buffer binding if the current state is already correct
1696 if (cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches.count() > i
1697 && cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches.count() > i
1698 && bufferBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches[i]
1699 && offsetBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches[i])
1700 {
1701 continue;
1702 }
1703 bindStageBuffers(cbD, stage, bufferBatch, offsetBatch);
1704 }
1705
1706 if (offsetOnlyChange)
1707 continue;
1708
1709 if (bindingData.res[stage].usesArgumentBuffer) {
1710 declareStageArgumentBufferResources(cbD, stage, bindingData.res[stage]);
1711 continue;
1712 }
1713
1714 std::sort(bindingData.res[stage].textures.begin(), bindingData.res[stage].textures.end(), [](const QMetalShaderResourceBindingsData::Stage::Texture &a, const QMetalShaderResourceBindingsData::Stage::Texture &b) {
1715 return a.nativeBinding < b.nativeBinding;
1716 });
1717
1718 std::sort(bindingData.res[stage].samplers.begin(), bindingData.res[stage].samplers.end(), [](const QMetalShaderResourceBindingsData::Stage::Sampler &a, const QMetalShaderResourceBindingsData::Stage::Sampler &b) {
1719 return a.nativeBinding < b.nativeBinding;
1720 });
1721
1722 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : std::as_const(bindingData.res[stage].textures))
1723 bindingData.res[stage].textureBatches.feed(t.nativeBinding, t.mtltex);
1724
1725 for (const QMetalShaderResourceBindingsData::Stage::Sampler &s : std::as_const(bindingData.res[stage].samplers))
1726 bindingData.res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler);
1727
1728 bindingData.res[stage].textureBatches.finish();
1729 bindingData.res[stage].samplerBatches.finish();
1730
1731 for (int i = 0, ie = bindingData.res[stage].textureBatches.batches.count(); i != ie; ++i) {
1732 const auto &batch(bindingData.res[stage].textureBatches.batches[i]);
1733 // skip setting Texture binding if the current state is already correct
1734 if (cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches.count() > i
1735 && batch == cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches[i])
1736 {
1737 continue;
1738 }
1739 bindStageTextures(cbD, stage, batch);
1740 }
1741
1742 for (int i = 0, ie = bindingData.res[stage].samplerBatches.batches.count(); i != ie; ++i) {
1743 const auto &batch(bindingData.res[stage].samplerBatches.batches[i]);
1744 // skip setting Sampler State if the current state is already correct
1745 if (cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches.count() > i
1746 && batch == cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches[i])
1747 {
1748 continue;
1749 }
1750 bindStageSamplers(cbD, stage, batch);
1751 }
1752 }
1753
1754 cbD->d->currentShaderResourceBindingState = bindingData;
1755}
1756
1758{
1759 const QList<QShaderDescription::PushConstantBlock> blocks = s.desc.pushConstantBlocks();
1760 return blocks.isEmpty() ? 0 : quint32(blocks.first().size);
1761}
1762
1763static inline int mtlPushConstantBufferIndex(const QMetalShader &s)
1764{
1765 return s.nativeShaderInfo.extraBufferBindings.value(QShaderPrivate::MslPushConstantBufferBinding, -1);
1766}
1767
1769{
1770 QRHI_RES_RHI(QRhiMetal);
1771
1772 // Also update the tracked state, so that the callers that reactivate a
1773 // pipeline on a new encoder after interrupting the pass do not have to.
1774 cbD->currentGraphicsPipeline = this;
1775 cbD->currentComputePipeline = nullptr;
1777
1778 [cbD->d->currentRenderPassEncoder setRenderPipelineState: d->ps];
1779
1780 if (cbD->d->currentDepthStencilState != d->ds) {
1781 [cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
1782 cbD->d->currentDepthStencilState = d->ds;
1783 }
1784 if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
1785 [cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
1786 cbD->currentCullMode = int(d->cullMode);
1787 }
1788 if (cbD->currentTriangleFillMode == -1 || d->triangleFillMode != uint(cbD->currentTriangleFillMode)) {
1789 [cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
1790 cbD->currentTriangleFillMode = int(d->triangleFillMode);
1791 }
1792 if (rhiD->caps.depthClamp) {
1793 if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) {
1794 [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode];
1795 cbD->currentDepthClipMode = int(d->depthClipMode);
1796 }
1797 }
1798 if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
1799 [cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
1800 cbD->currentFrontFaceWinding = int(d->winding);
1801 }
1802 if (!qFuzzyCompare(d->depthBias, cbD->currentDepthBiasValues.first)
1803 || !qFuzzyCompare(d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
1804 {
1805 [cbD->d->currentRenderPassEncoder setDepthBias: d->depthBias
1806 slopeScale: d->slopeScaledDepthBias
1807 clamp: 0.0f];
1808 cbD->currentDepthBiasValues = { d->depthBias, d->slopeScaledDepthBias };
1809 }
1810
1811 if (m_stencilTest) {
1812 const quint32 ref = m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef)
1813 ? cbD->currentStencilRef : 0;
1814 if (cbD->appliedStencilRef != ref) {
1815 [cbD->d->currentRenderPassEncoder setStencilReferenceValue: ref];
1816 cbD->appliedStencilRef = ref;
1817 }
1818 }
1819
1820 if (cbD->pushConstantsNeedRebind && !d->tess.enabled) {
1821 const int vsIdx = mtlPushConstantBufferIndex(d->vs);
1822 const int fsIdx = mtlPushConstantBufferIndex(d->fs);
1823 if (vsIdx >= 0 || fsIdx >= 0) {
1824 const quint32 blockSize = qMax(mtlPushConstantBlockSize(d->vs), mtlPushConstantBlockSize(d->fs));
1825 if (quint32(cbD->pushConstantData.size()) < blockSize)
1826 cbD->pushConstantData.resize(int(blockSize), 0);
1827 const NSUInteger total = NSUInteger(cbD->pushConstantData.size());
1828 if (vsIdx >= 0)
1829 [cbD->d->currentRenderPassEncoder setVertexBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(vsIdx)];
1830 if (fsIdx >= 0)
1831 [cbD->d->currentRenderPassEncoder setFragmentBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(fsIdx)];
1832 cbD->pushConstantsNeedRebind = false;
1833 }
1834 }
1835}
1836
1837void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
1838{
1839 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1842
1844 return;
1845
1847 cbD->currentComputePipeline = nullptr;
1849
1850 if (cbD->hasCustomScissorSet && !psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
1852
1853 if (!psD->d->tess.enabled && !psD->d->tess.failed)
1855
1856 // mark work buffers that can now be safely reused as reusable
1857 // NOTE: These are usually empty unless tessellation or mutiview is used.
1858 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.deviceLocalWorkBuffers) {
1859 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1860 workBuf->lastActiveFrameSlot = -1;
1861 }
1862 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.hostVisibleWorkBuffers) {
1863 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1864 workBuf->lastActiveFrameSlot = -1;
1865 }
1866
1867 psD->lastActiveFrameSlot = currentFrameSlot;
1868}
1869
1870void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
1871 int dynamicOffsetCount,
1872 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
1873{
1874 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1878
1879 if (!srb) {
1880 if (gfxPsD)
1881 srb = gfxPsD->m_shaderResourceBindings;
1882 else
1883 srb = compPsD->m_shaderResourceBindings;
1884 }
1885
1887 bool hasSlottedResourceInSrb = false;
1888 bool hasDynamicOffsetInSrb = false;
1889 bool resNeedsRebind = false;
1890
1891 bool pipelineChanged = false;
1892 if (gfxPsD) {
1893 pipelineChanged = srbD->lastUsedGraphicsPipeline != gfxPsD;
1894 srbD->lastUsedGraphicsPipeline = gfxPsD;
1895 } else {
1896 pipelineChanged = srbD->lastUsedComputePipeline != compPsD;
1897 srbD->lastUsedComputePipeline = compPsD;
1898 }
1899
1900 // SPIRV-Cross buffer size buffers
1901 // Need to determine storage buffer sizes here as this is the last opportunity for storage
1902 // buffer bindings (offset, size) to be specified before draw / dispatch call
1903 const bool needsBufferSizeBuffer = (compPsD && compPsD->d->bufferSizeBuffer) || (gfxPsD && gfxPsD->d->bufferSizeBuffer);
1904 QMap<QRhiShaderResourceBinding::StageFlag, QMap<int, quint32>> storageBufferSizes;
1905
1906 // do buffer writes, figure out if we need to rebind, and mark as in-use
1907 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
1908 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
1909 QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
1910 switch (b->type()) {
1911 case QRhiShaderResourceBinding::UniformBuffer:
1912 {
1913 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->ubuf().buf);
1914 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
1915 sanityCheckResourceOwnership(bufD);
1917 if (bufD->d->slotted)
1918 hasSlottedResourceInSrb = true;
1919 if (b->ubuf().hasDynamicOffset)
1920 hasDynamicOffsetInSrb = true;
1921 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
1922 resNeedsRebind = true;
1923 bd.ubuf.id = bufD->m_id;
1924 bd.ubuf.generation = bufD->generation;
1925 }
1926 bufD->lastActiveFrameSlot = currentFrameSlot;
1927 }
1928 break;
1929 case QRhiShaderResourceBinding::SampledTexture:
1930 case QRhiShaderResourceBinding::Texture:
1931 case QRhiShaderResourceBinding::Sampler:
1932 {
1933 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->stex();
1934 if (bd.stex.d.size() != data->count()) {
1935 bd.stex.d.resize(data->count());
1936 resNeedsRebind = true;
1937 }
1938 for (int elem = 0; elem < data->count(); ++elem) {
1939 QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
1940 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
1941 Q_ASSERT(texD || samplerD);
1942 sanityCheckResourceOwnership(texD);
1943 sanityCheckResourceOwnership(samplerD);
1944 const quint64 texId = texD ? texD->m_id : 0;
1945 const uint texGen = texD ? texD->generation : 0;
1946 const quint64 samplerId = samplerD ? samplerD->m_id : 0;
1947 const uint samplerGen = samplerD ? samplerD->generation : 0;
1948 if (texGen != bd.stex.d[elem].texGeneration
1949 || texId != bd.stex.d[elem].texId
1950 || samplerGen != bd.stex.d[elem].samplerGeneration
1951 || samplerId != bd.stex.d[elem].samplerId)
1952 {
1953 resNeedsRebind = true;
1954 bd.stex.d[elem].texId = texId;
1955 bd.stex.d[elem].texGeneration = texGen;
1956 bd.stex.d[elem].samplerId = samplerId;
1957 bd.stex.d[elem].samplerGeneration = samplerGen;
1958 }
1959 if (texD)
1960 texD->lastActiveFrameSlot = currentFrameSlot;
1961 if (samplerD)
1962 samplerD->lastActiveFrameSlot = currentFrameSlot;
1963 }
1964 }
1965 break;
1966 case QRhiShaderResourceBinding::ImageLoad:
1967 case QRhiShaderResourceBinding::ImageStore:
1968 case QRhiShaderResourceBinding::ImageLoadStore:
1969 {
1970 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->simage().tex);
1971 sanityCheckResourceOwnership(texD);
1972 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
1973 resNeedsRebind = true;
1974 bd.simage.id = texD->m_id;
1975 bd.simage.generation = texD->generation;
1976 }
1977 texD->lastActiveFrameSlot = currentFrameSlot;
1978 }
1979 break;
1980 case QRhiShaderResourceBinding::BufferLoad:
1981 case QRhiShaderResourceBinding::BufferStore:
1982 case QRhiShaderResourceBinding::BufferLoadStore:
1983 {
1984 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->sbuf().buf);
1985 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
1986 sanityCheckResourceOwnership(bufD);
1987
1988 if (needsBufferSizeBuffer) {
1989 for (int i = 0; i < 6; ++i) {
1990 const QRhiShaderResourceBinding::StageFlag stage =
1991 QRhiShaderResourceBinding::StageFlag(1 << i);
1992 if (b->stage.testFlag(stage)) {
1993 storageBufferSizes[stage][b->binding] = b->sbuf().maybeSize ? b->sbuf().maybeSize : bufD->size();
1994 }
1995 }
1996 }
1997
1999 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
2000 resNeedsRebind = true;
2001 bd.sbuf.id = bufD->m_id;
2002 bd.sbuf.generation = bufD->generation;
2003 }
2004 bufD->lastActiveFrameSlot = currentFrameSlot;
2005 }
2006 break;
2007 default:
2008 Q_UNREACHABLE();
2009 break;
2010 }
2011 }
2012
2013 if (needsBufferSizeBuffer) {
2014 QMetalBuffer *bufD = nullptr;
2015 QVarLengthArray<std::pair<QMetalShader *, QRhiShaderResourceBinding::StageFlag>, 4> shaders;
2016
2017 if (compPsD) {
2018 bufD = compPsD->d->bufferSizeBuffer;
2019 Q_ASSERT(compPsD->d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2020 shaders.append({&compPsD->d->cs, QRhiShaderResourceBinding::StageFlag::ComputeStage});
2021 } else {
2022 bufD = gfxPsD->d->bufferSizeBuffer;
2023 if (gfxPsD->d->tess.enabled) {
2024
2025 // Assumptions
2026 // * We only use one of the compute vertex shader variants in a pipeline at any one time
2027 // * The vertex shader variants all have the same storage block bindings
2028 // * The vertex shader variants all have the same native resource binding map
2029 // * The vertex shader variants all have the same MslBufferSizeBufferBinding requirement
2030 // * The vertex shader variants all have the same MslBufferSizeBufferBinding binding
2031 // => We only need to use one vertex shader variant to generate the identical shader
2032 // resource bindings
2033 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[1].desc.storageBlocks());
2034 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[2].desc.storageBlocks());
2035 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
2036 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
2037 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
2038 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2039 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
2040 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2041 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
2042 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
2043 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
2044 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
2045
2046 if (gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2047 shaders.append({&gfxPsD->d->tess.compVs[0], QRhiShaderResourceBinding::StageFlag::VertexStage});
2048
2049 if (gfxPsD->d->tess.compTesc.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2050 shaders.append({&gfxPsD->d->tess.compTesc, QRhiShaderResourceBinding::StageFlag::TessellationControlStage});
2051
2052 if (gfxPsD->d->tess.vertTese.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2053 shaders.append({&gfxPsD->d->tess.vertTese, QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage});
2054
2055 } else {
2056 if (gfxPsD->d->vs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2057 shaders.append({&gfxPsD->d->vs, QRhiShaderResourceBinding::StageFlag::VertexStage});
2058 }
2059 if (gfxPsD->d->fs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2060 shaders.append({&gfxPsD->d->fs, QRhiShaderResourceBinding::StageFlag::FragmentStage});
2061 }
2062
2063 quint32 offset = 0;
2064 for (const auto &shader : shaders) {
2065
2066 const int binding = shader.first->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
2067
2068 // if we don't have a srb entry for the buffer size buffer
2069 if (!(storageBufferSizes.contains(shader.second) && storageBufferSizes[shader.second].contains(binding))) {
2070
2071 int maxNativeBinding = 0;
2072 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks())
2073 maxNativeBinding = qMax(maxNativeBinding, shader.first->nativeResourceBindingMap[block.binding].first);
2074
2075 const int size = (maxNativeBinding + 1) * sizeof(int);
2076
2077 Q_ASSERT(offset + size <= bufD->size());
2078 srbD->sortedBindings.append(QRhiShaderResourceBinding::bufferLoad(binding, shader.second, bufD, offset, size));
2079
2080 QMetalShaderResourceBindings::BoundResourceData bd;
2081 bd.sbuf.id = bufD->m_id;
2082 bd.sbuf.generation = bufD->generation;
2083 srbD->boundResourceData.append(bd);
2084 }
2085
2086 // create the buffer size buffer data
2087 QVarLengthArray<int, 8> bufferSizeBufferData;
2088 Q_ASSERT(storageBufferSizes.contains(shader.second));
2089 const QMap<int, quint32> &sizes(storageBufferSizes[shader.second]);
2090 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks()) {
2091 const int index = shader.first->nativeResourceBindingMap[block.binding].first;
2092
2093 // if the native binding is -1, the buffer is present but not accessed in the shader
2094 if (index < 0)
2095 continue;
2096
2097 if (bufferSizeBufferData.size() <= index)
2098 bufferSizeBufferData.resize(index + 1);
2099
2100 Q_ASSERT(sizes.contains(block.binding));
2101 bufferSizeBufferData[index] = sizes[block.binding];
2102 }
2103
2104 QRhiBufferData data;
2105 const quint32 size = bufferSizeBufferData.size() * sizeof(int);
2106 data.assign(reinterpret_cast<const char *>(bufferSizeBufferData.constData()), size);
2107 Q_ASSERT(offset + size <= bufD->size());
2108 bufD->d->pendingUpdates[bufD->d->slotted ? currentFrameSlot : 0].append({ offset, data });
2109
2110 // buffer offsets must be 32byte aligned
2111 offset += ((size + 31) / 32) * 32;
2112 }
2113
2115 bufD->lastActiveFrameSlot = currentFrameSlot;
2116 }
2117
2118 // make sure the resources for the correct slot get bound
2119 const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
2120 if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
2121 resNeedsRebind = true;
2122
2123 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srbD) : (cbD->currentComputeSrb != srbD);
2124 const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
2125
2126 // dynamic uniform buffer offsets always trigger a rebind
2127 if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt || pipelineChanged) {
2128 const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr };
2129 const QMetalShader *shaders[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr };
2130 if (gfxPsD) {
2131 cbD->currentGraphicsSrb = srbD;
2132 cbD->currentComputeSrb = nullptr;
2133 if (gfxPsD->d->tess.enabled) {
2134 // If tessellating, we don't know which compVs shader to use until the draw call is
2135 // made. They should all have the same native resource binding map, so pick one.
2136 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
2137 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
2138 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->tess.compVs[0].nativeResourceBindingMap;
2139 resBindMaps[QMetalShaderResourceBindingsData::TESSCTRL] = &gfxPsD->d->tess.compTesc.nativeResourceBindingMap;
2140 resBindMaps[QMetalShaderResourceBindingsData::TESSEVAL] = &gfxPsD->d->tess.vertTese.nativeResourceBindingMap;
2141 } else {
2142 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs.nativeResourceBindingMap;
2143 shaders[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs;
2144 }
2145 resBindMaps[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs.nativeResourceBindingMap;
2146 shaders[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs;
2147 } else {
2148 cbD->currentGraphicsSrb = nullptr;
2149 cbD->currentComputeSrb = srbD;
2150 resBindMaps[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs.nativeResourceBindingMap;
2151 shaders[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs;
2152 }
2154 cbD->currentResSlot = resSlot;
2155
2156 const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind
2157 && !srbChanged && !srbRebuilt && !pipelineChanged;
2158 enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange,
2159 resBindMaps, shaders);
2160 }
2161}
2162
2163void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
2164 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
2165 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
2166{
2167 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2169
2170 QRhiBatchedBindings<id<MTLBuffer> > buffers;
2171 QRhiBatchedBindings<NSUInteger> offsets;
2172 for (int i = 0; i < bindingCount; ++i) {
2173 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
2175 bufD->lastActiveFrameSlot = currentFrameSlot;
2176 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
2177 buffers.feed(startBinding + i, mtlbuf);
2178 offsets.feed(startBinding + i, bindings[i].second);
2179 }
2180 buffers.finish();
2181 offsets.finish();
2182
2183 // same binding space for vertex and constant buffers - work it around
2185 // There's nothing guaranteeing setShaderResources() was called before
2186 // setVertexInput()... but whatever srb will get bound will have to be
2187 // layout-compatible anyways so maxBinding is the same.
2188 if (!srbD)
2189 srbD = QRHI_RES(QMetalShaderResourceBindings, cbD->currentGraphicsPipeline->shaderResourceBindings());
2190 const int firstVertexBinding = srbD->maxBinding + 1;
2191
2192 if (firstVertexBinding != cbD->d->currentFirstVertexBinding
2193 || buffers != cbD->d->currentVertexInputsBuffers
2194 || offsets != cbD->d->currentVertexInputOffsets)
2195 {
2196 cbD->d->currentFirstVertexBinding = firstVertexBinding;
2197 cbD->d->currentVertexInputsBuffers = buffers;
2198 cbD->d->currentVertexInputOffsets = offsets;
2199
2200 for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
2201 const auto &bufferBatch(buffers.batches[i]);
2202 const auto &offsetBatch(offsets.batches[i]);
2203 [cbD->d->currentRenderPassEncoder setVertexBuffers:
2204 bufferBatch.resources.constData()
2205 offsets: offsetBatch.resources.constData()
2206 withRange: NSMakeRange(uint(firstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
2207 }
2208 }
2209
2210 if (indexBuf) {
2211 QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
2213 ibufD->lastActiveFrameSlot = currentFrameSlot;
2214 cbD->currentIndexBuffer = ibufD;
2215 cbD->currentIndexOffset = indexOffset;
2216 cbD->currentIndexFormat = indexFormat;
2217 } else {
2218 cbD->currentIndexBuffer = nullptr;
2219 }
2220}
2221
2222// The size of the coordinate space viewports and scissors are specified in.
2223// Normally this is the pixel size of the render target. When a rasterization
2224// rate map is attached, Metal interprets viewports and scissors in the logical
2225// (screen space) coordinate system of the map, which is typically larger than
2226// the physical texture size. This is what foveated rendering on visionOS uses.
2227QSize QRhiMetal::outputSizeForTarget(QRhiRenderTarget *target)
2228{
2229 QRhiShadingRateMap *srm = nullptr;
2230 switch (target->resourceType()) {
2231 case QRhiResource::TextureRenderTarget:
2232 srm = QRHI_RES(QMetalTextureRenderTarget, target)->m_desc.shadingRateMap();
2233 break;
2234 case QRhiResource::SwapChainRenderTarget:
2235 srm = QRHI_RES(QMetalSwapChainRenderTarget, target)->swapChain()->shadingRateMap();
2236 break;
2237 default:
2238 break;
2239 }
2240
2241 if (srm) {
2242 const QSize logicalSize = srm->logicalSize();
2243 if (logicalSize.isValid())
2244 return logicalSize;
2245 }
2246
2247 return target->pixelSize();
2248}
2249
2251{
2252 cbD->hasCustomScissorSet = false;
2253
2254 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2255 std::array<float, 4> vp = cbD->currentViewport.viewport();
2256 float x = 0, y = 0, w = 0, h = 0;
2257
2258 if (qFuzzyIsNull(vp[2]) && qFuzzyIsNull(vp[3])) {
2259 x = 0;
2260 y = 0;
2261 w = outputSize.width();
2262 h = outputSize.height();
2263 } else {
2264 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
2265 qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, vp, &x, &y, &w, &h);
2266 }
2267
2268 MTLScissorRect s;
2269 s.x = NSUInteger(x);
2270 s.y = NSUInteger(y);
2271 s.width = NSUInteger(w);
2272 s.height = NSUInteger(h);
2273 [cbD->d->currentRenderPassEncoder setScissorRect: s];
2274}
2275
2276void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
2277{
2278 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2280 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2281
2282 // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
2283 float x, y, w, h;
2284 if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
2285 return;
2286
2287 MTLViewport vp;
2288 vp.originX = double(x);
2289 vp.originY = double(y);
2290 vp.width = double(w);
2291 vp.height = double(h);
2292 vp.znear = double(viewport.minDepth());
2293 vp.zfar = double(viewport.maxDepth());
2294
2295 [cbD->d->currentRenderPassEncoder setViewport: vp];
2296
2297 cbD->currentViewport = viewport;
2299 && !cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
2300 {
2302 }
2303}
2304
2305void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
2306{
2307 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2309 Q_ASSERT(!cbD->currentGraphicsPipeline
2310 || cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
2311 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2312
2313 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
2314 int x, y, w, h;
2315 if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
2316 return;
2317
2318 MTLScissorRect s;
2319 s.x = NSUInteger(x);
2320 s.y = NSUInteger(y);
2321 s.width = NSUInteger(w);
2322 s.height = NSUInteger(h);
2323
2324 [cbD->d->currentRenderPassEncoder setScissorRect: s];
2325
2326 cbD->hasCustomScissorSet = true;
2327 cbD->currentScissor = scissor;
2328}
2329
2330void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
2331{
2332 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2334
2335 [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF()
2336 green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
2337
2338 cbD->hasBlendConstantsSet = true;
2339 cbD->currentBlendConstants = c;
2340}
2341
2342void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
2343{
2344 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2346
2347 [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
2348
2349 cbD->currentStencilRef = refValue;
2350 cbD->appliedStencilRef = refValue;
2351}
2352
2353void QRhiMetal::setPushConstants(QRhiCommandBuffer *cb, quint32 offset, quint32 size, const void *data)
2354{
2355 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2357
2358 // setVertexBytes() and friends take the whole block every time, so a shadow
2359 // copy is needed to honour an update of part of it.
2360 auto patch = [cbD, offset, size, data](quint32 blockSize) {
2361 const quint32 total = qMax(blockSize, offset + size);
2362 if (quint32(cbD->pushConstantData.size()) < total)
2363 cbD->pushConstantData.resize(int(total), 0);
2364 memcpy(cbD->pushConstantData.data() + offset, data, size);
2365 return total;
2366 };
2367
2370 if (!psD)
2371 return;
2372 const int idx = mtlPushConstantBufferIndex(psD->d->cs);
2373 if (idx < 0) {
2374 qWarning("No pipeline with a push constant block is active; setPushConstants ignored");
2375 return;
2376 }
2377 const quint32 total = patch(mtlPushConstantBlockSize(psD->d->cs));
2378 [cbD->d->currentComputePassEncoder setBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(idx)];
2379 } else {
2381 if (!psD)
2382 return;
2383 if (psD->d->tess.enabled) {
2384 qWarning("Push constants are not supported with tessellation on Metal");
2385 return;
2386 }
2387 const int vsIdx = mtlPushConstantBufferIndex(psD->d->vs);
2388 const int fsIdx = mtlPushConstantBufferIndex(psD->d->fs);
2389 if (vsIdx < 0 && fsIdx < 0) {
2390 qWarning("No pipeline with a push constant block is active; setPushConstants ignored");
2391 return;
2392 }
2393 const quint32 total = patch(qMax(mtlPushConstantBlockSize(psD->d->vs), mtlPushConstantBlockSize(psD->d->fs)));
2394 if (vsIdx >= 0)
2395 [cbD->d->currentRenderPassEncoder setVertexBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(vsIdx)];
2396 if (fsIdx >= 0)
2397 [cbD->d->currentRenderPassEncoder setFragmentBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(fsIdx)];
2398 }
2399}
2400
2401void QRhiMetal::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize)
2402{
2403 Q_UNUSED(cb);
2404 Q_UNUSED(coarsePixelSize);
2405}
2406
2408{
2409 switch (cbD->currentTarget->resourceType()) {
2410 case QRhiResource::SwapChainRenderTarget:
2411 return QRHI_RES(QMetalSwapChainRenderTarget, cbD->currentTarget)->d;
2412 case QRhiResource::TextureRenderTarget:
2413 return QRHI_RES(QMetalTextureRenderTarget, cbD->currentTarget)->d;
2414 default:
2415 return nullptr;
2416 }
2417}
2418
2419// Memoryless attachments only exist for the duration of a single encoder, there
2420// is nowhere to store them to.
2421static inline bool canStoreAttachment(id<MTLTexture> tex)
2422{
2423 return tex && tex.storageMode != MTLStorageModeMemoryless;
2424}
2425
2426// When the pass continues on a new encoder afterwards, the contents have to be
2427// stored, not just resolved or discarded. An attachment with a resolve texture
2428// can only use the resolving store actions.
2429static inline MTLStoreAction interruptionStoreAction(MTLStoreAction finalAction, bool passIsEnding)
2430{
2431 if (passIsEnding)
2432 return finalAction;
2433 return finalAction == MTLStoreActionDontCare ? MTLStoreActionStore
2434 : MTLStoreActionStoreAndMultisampleResolve;
2435}
2436
2437// Store actions are deferred (MTLStoreActionUnknown) for attachments whose
2438// contents may need to outlive the encoder, so they have to be finalized before
2439// the encoder ends.
2440// Store actions cannot be mutated otherwise, and for a resolve attachment the
2441// only legal choices are the two resolving ones.
2443{
2444 for (const auto &[index, finalAction] : std::as_const(cbD->d->deferredColorStoreActions))
2445 [cbD->d->currentRenderPassEncoder setColorStoreAction:
2446 interruptionStoreAction(finalAction, passIsEnding) atIndex: index];
2447
2448 if (cbD->d->deferredDepthStoreAction != MTLStoreActionUnknown) {
2449 [cbD->d->currentRenderPassEncoder setDepthStoreAction:
2450 interruptionStoreAction(cbD->d->deferredDepthStoreAction, passIsEnding)];
2451 }
2452 if (cbD->d->deferredStencilStoreAction != MTLStoreActionUnknown) {
2453 [cbD->d->currentRenderPassEncoder setStencilStoreAction:
2454 interruptionStoreAction(cbD->d->deferredStencilStoreAction, passIsEnding)];
2455 }
2456}
2457
2458// Ends the render command encoder such that the pass can be continued on a new
2459// encoder afterwards.
2461{
2463 for (qsizetype i = 0; i < cbD->d->openDebugGroups.size(); ++i)
2464 [cbD->d->currentRenderPassEncoder popDebugGroup];
2465 [cbD->d->currentRenderPassEncoder endEncoding];
2466 cbD->d->currentRenderPassEncoder = nil;
2467}
2468
2471 id<MTLComputeCommandEncoder> maybeComputeEncoder)
2472{
2473 if (cbD->d->currentRenderPassEncoder)
2475
2476 if (!maybeComputeEncoder)
2477 maybeComputeEncoder = [cbD->d->cb computeCommandEncoder];
2478
2479 return maybeComputeEncoder;
2480}
2481
2483 id<MTLComputeCommandEncoder> computeEncoder)
2484{
2485 if (computeEncoder) {
2486 [computeEncoder endEncoding];
2487 computeEncoder = nil;
2488 }
2489
2491 Q_ASSERT(rtD);
2492
2493 // A memoryless attachment cannot be loaded, its contents are lost (see
2494 // NoTransientBacking), even when the store action is not DontCare, which is
2495 // the case with a resolve.
2496 const auto canLoad = [](MTLRenderPassAttachmentDescriptor *att) {
2497 return att.storeAction != MTLStoreActionDontCare && canStoreAttachment(att.texture);
2498 };
2499
2500 QVarLengthArray<MTLLoadAction, 4> oldColorLoad;
2501 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2502 oldColorLoad.append(cbD->d->currentPassRpDesc.colorAttachments[i].loadAction);
2503 if (canLoad(cbD->d->currentPassRpDesc.colorAttachments[i]))
2504 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
2505 }
2506
2507 MTLLoadAction oldDepthLoad;
2508 MTLLoadAction oldStencilLoad;
2509 if (rtD->dsAttCount) {
2510 oldDepthLoad = cbD->d->currentPassRpDesc.depthAttachment.loadAction;
2511 if (canLoad(cbD->d->currentPassRpDesc.depthAttachment))
2512 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
2513
2514 oldStencilLoad = cbD->d->currentPassRpDesc.stencilAttachment.loadAction;
2515 if (canLoad(cbD->d->currentPassRpDesc.stencilAttachment))
2516 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
2517 }
2518
2519 // The state below is not tied to the pipeline, so it is not restored by the
2520 // callers when they reactivate it on the new encoder. Preserve it here,
2521 // otherwise the pass would silently continue with a full-target viewport,
2522 // no scissor, and default blend constants and stencil reference.
2523 const QRhiViewport prevViewport = cbD->currentViewport;
2524 const bool prevHasScissor = cbD->hasCustomScissorSet;
2525 const QRhiScissor prevScissor = cbD->currentScissor;
2526 const bool prevHasBlendConstants = cbD->hasBlendConstantsSet;
2527 const QColor prevBlendConstants = cbD->currentBlendConstants;
2528 const quint32 prevStencilRef = cbD->currentStencilRef;
2529 // Whether the pipeline was relying on the viewport-derived default scissor,
2530 // which setViewport() below cannot reapply on its own: it only does so when
2531 // a pipeline is already current, and there is none at that point.
2532 const bool prevHasDefaultScissor = cbD->currentGraphicsPipeline
2533 && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor);
2534 // Rebound when the callers reactivate the pipeline, or when the next
2535 // pipeline with a push constant block is set.
2536 const QVarLengthArray<char, 128> prevPushConstantData = cbD->pushConstantData;
2537
2538 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
2540
2541 for (const QByteArray &name : std::as_const(cbD->d->openDebugGroups))
2542 [cbD->d->currentRenderPassEncoder pushDebugGroup: [NSString stringWithUTF8String: name.constData()]];
2543
2544 cbD->pushConstantData = prevPushConstantData;
2545 cbD->pushConstantsNeedRebind = !prevPushConstantData.isEmpty();
2546
2547 // Must come before the callers reactivate the pipeline: setScissor()
2548 // expects no pipeline to be current yet, and setDefaultScissor() consults
2549 // the viewport.
2550 if (!qFuzzyIsNull(prevViewport.viewport()[2]) || !qFuzzyIsNull(prevViewport.viewport()[3]))
2551 rhiD->setViewport(cbD, prevViewport);
2552 if (prevHasScissor)
2553 rhiD->setScissor(cbD, prevScissor);
2554 else if (prevHasDefaultScissor)
2555 rhiD->setDefaultScissor(cbD);
2556 if (prevHasBlendConstants)
2557 rhiD->setBlendConstants(cbD, prevBlendConstants);
2558 cbD->currentStencilRef = prevStencilRef;
2559
2560 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2561 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = oldColorLoad[i];
2562 }
2563
2564 if (rtD->dsAttCount) {
2565 cbD->d->currentPassRpDesc.depthAttachment.loadAction = oldDepthLoad;
2566 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = oldStencilLoad;
2567 }
2568
2569}
2570
2572{
2573 QMetalCommandBuffer *cbD = args.cbD;
2575 if (graphicsPipeline->d->tess.failed)
2576 return;
2577
2578 const bool indexed = args.type != TessDrawArgs::NonIndexed;
2579 const quint32 instanceCount = indexed ? args.drawIndexed.instanceCount : args.draw.instanceCount;
2580 const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount;
2581
2582 QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess);
2583 QMetalGraphicsPipelineData::ExtraBufferManager &extraBufMgr(graphicsPipeline->d->extraBufMgr);
2584 const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount);
2585 QMetalBuffer *vertOutBuf = nullptr;
2586 QMetalBuffer *tescOutBuf = nullptr;
2587 QMetalBuffer *tescPatchOutBuf = nullptr;
2588 QMetalBuffer *tescFactorBuf = nullptr;
2589 QMetalBuffer *tescParamsBuf = nullptr;
2590 id<MTLComputeCommandEncoder> vertTescComputeEncoder
2591 = tempComputeEncoder(this, cbD, cbD->d->tessellationComputeEncoder);
2592 cbD->d->tessellationComputeEncoder = vertTescComputeEncoder;
2593
2594 // Step 1: vertex shader (as compute)
2595 {
2596 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2597 QShader::Variant shaderVariant = QShader::NonIndexedVertexAsComputeShader;
2598 if (args.type == TessDrawArgs::U16Indexed)
2599 shaderVariant = QShader::UInt16IndexedVertexAsComputeShader;
2600 else if (args.type == TessDrawArgs::U32Indexed)
2601 shaderVariant = QShader::UInt32IndexedVertexAsComputeShader;
2602 const int varIndex = QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(shaderVariant);
2603 id<MTLComputePipelineState> computePipelineState = tess.vsCompPipeline(this, shaderVariant);
2604 [computeEncoder setComputePipelineState: computePipelineState];
2605
2606 // Make uniform buffers, textures, and samplers (meant for the
2607 // vertex stage from the client's point of view) visible in the
2608 // "vertex as compute" shader
2609 cbD->d->currentComputePassEncoder = computeEncoder;
2611 cbD->d->currentComputePassEncoder = nil;
2612
2613 const QMap<int, int> &ebb(tess.compVs[varIndex].nativeShaderInfo.extraBufferBindings);
2614 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2615 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
2616
2617 if (outputBufferBinding >= 0) {
2618 const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount);
2619 vertOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2620 if (!vertOutBuf)
2621 return;
2622 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2623 }
2624
2625 if (indexBufferBinding >= 0)
2626 [computeEncoder setBuffer: (id<MTLBuffer>) args.drawIndexed.indexBuffer offset: 0 atIndex: indexBufferBinding];
2627
2628 for (int i = 0, ie = cbD->d->currentVertexInputsBuffers.batches.count(); i != ie; ++i) {
2629 const auto &bufferBatch(cbD->d->currentVertexInputsBuffers.batches[i]);
2630 const auto &offsetBatch(cbD->d->currentVertexInputOffsets.batches[i]);
2631 [computeEncoder setBuffers: bufferBatch.resources.constData()
2632 offsets: offsetBatch.resources.constData()
2633 withRange: NSMakeRange(uint(cbD->d->currentFirstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
2634 }
2635
2636 if (indexed) {
2637 [computeEncoder setStageInRegion: MTLRegionMake2D(args.drawIndexed.vertexOffset, args.drawIndexed.firstInstance,
2638 args.drawIndexed.indexCount, args.drawIndexed.instanceCount)];
2639 } else {
2640 [computeEncoder setStageInRegion: MTLRegionMake2D(args.draw.firstVertex, args.draw.firstInstance,
2641 args.draw.vertexCount, args.draw.instanceCount)];
2642 }
2643
2644 [computeEncoder dispatchThreads: MTLSizeMake(vertexOrIndexCount, instanceCount, 1)
2645 threadsPerThreadgroup: MTLSizeMake(computePipelineState.threadExecutionWidth, 1, 1)];
2646 }
2647
2648 // Step 2: tessellation control shader (as compute)
2649 {
2650 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2651 id<MTLComputePipelineState> computePipelineState = tess.tescCompPipeline(this);
2652 [computeEncoder setComputePipelineState: computePipelineState];
2653
2654 cbD->d->currentComputePassEncoder = computeEncoder;
2656 cbD->d->currentComputePassEncoder = nil;
2657
2658 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2659 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2660 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2661 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2662 const int paramsBufferBinding = ebb.value(QShaderPrivate::MslTessTescParamsBufferBinding, -1);
2663 const int inputBufferBinding = ebb.value(QShaderPrivate::MslTessTescInputBufferBinding, -1);
2664
2665 if (outputBufferBinding >= 0) {
2666 const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount);
2667 tescOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2668 if (!tescOutBuf)
2669 return;
2670 [computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2671 }
2672
2673 if (patchOutputBufferBinding >= 0) {
2674 const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount);
2675 tescPatchOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2676 if (!tescPatchOutBuf)
2677 return;
2678 [computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2679 }
2680
2681 if (tessFactorBufferBinding >= 0) {
2682 tescFactorBuf = extraBufMgr.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
2683 [computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2684 }
2685
2686 if (paramsBufferBinding >= 0) {
2687 struct {
2688 quint32 inControlPointCount;
2689 quint32 patchCount;
2690 } params;
2691 tescParamsBuf = extraBufMgr.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible);
2692 if (!tescParamsBuf)
2693 return;
2694 params.inControlPointCount = tess.inControlPointCount;
2695 params.patchCount = patchCount;
2696 id<MTLBuffer> paramsBuf = tescParamsBuf->d->buf[0];
2697 char *p = reinterpret_cast<char *>([paramsBuf contents]);
2698 memcpy(p, &params, sizeof(params));
2699 [computeEncoder setBuffer: paramsBuf offset: 0 atIndex: paramsBufferBinding];
2700 }
2701
2702 if (vertOutBuf && inputBufferBinding >= 0)
2703 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: inputBufferBinding];
2704
2705 int sgSize = int(computePipelineState.threadExecutionWidth);
2706 int wgSize = std::lcm(tess.outControlPointCount, sgSize);
2707 while (wgSize > caps.maxThreadGroupSize) {
2708 sgSize /= 2;
2709 wgSize = std::lcm(tess.outControlPointCount, sgSize);
2710 }
2711 [computeEncoder dispatchThreads: MTLSizeMake(patchCount * tess.outControlPointCount, 1, 1)
2712 threadsPerThreadgroup: MTLSizeMake(wgSize, 1, 1)];
2713 }
2714
2715 // Much of the state in the QMetalCommandBuffer is going to be reset
2716 // when we get a new render encoder. Save what we need. (cheaper than
2717 // starting to walk over the srb again)
2718 const QMetalShaderResourceBindingsData resourceBindings = cbD->d->currentShaderResourceBindingState;
2719
2720 endTempComputeEncoding(this, cbD, cbD->d->tessellationComputeEncoder);
2721 cbD->d->tessellationComputeEncoder = nil;
2722
2723 // Step 3: tessellation evaluation (as vertex) + fragment shader
2724 {
2725 // No need to call tess.teseFragRenderPipeline because it was done
2726 // once and we know the result is stored in the standard place
2727 // (graphicsPipeline->d->ps).
2728
2730 id<MTLRenderCommandEncoder> renderEncoder = cbD->d->currentRenderPassEncoder;
2731
2734
2735 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2736 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2737 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2738 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2739
2740 if (outputBufferBinding >= 0 && tescOutBuf)
2741 [renderEncoder setVertexBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2742
2743 if (patchOutputBufferBinding >= 0 && tescPatchOutBuf)
2744 [renderEncoder setVertexBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2745
2746 if (tessFactorBufferBinding >= 0 && tescFactorBuf) {
2747 [renderEncoder setTessellationFactorBuffer: tescFactorBuf->d->buf[0] offset: 0 instanceStride: 0];
2748 [renderEncoder setVertexBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2749 }
2750
2751 [cbD->d->currentRenderPassEncoder drawPatches: tess.outControlPointCount
2752 patchStart: 0
2753 patchCount: patchCount
2754 patchIndexBuffer: nil
2755 patchIndexBufferOffset: 0
2756 instanceCount: 1
2757 baseInstance: 0];
2758 }
2759}
2760
2761void QRhiMetal::adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb)
2762{
2763 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2764 const int multiViewCount = cbD->currentGraphicsPipeline->m_multiViewCount;
2765 if (multiViewCount <= 1)
2766 return;
2767
2768 const QMap<int, int> &ebb(cbD->currentGraphicsPipeline->d->vs.nativeShaderInfo.extraBufferBindings);
2769 const int viewMaskBufBinding = ebb.value(QShaderPrivate::MslMultiViewMaskBufferBinding, -1);
2770 if (viewMaskBufBinding == -1) {
2771 qWarning("No extra buffer for multiview in the vertex shader; was it built with --view-count specified?");
2772 return;
2773 }
2774 struct {
2775 quint32 viewOffset;
2776 quint32 viewCount;
2777 } multiViewInfo;
2778 multiViewInfo.viewOffset = 0;
2779 multiViewInfo.viewCount = quint32(multiViewCount);
2780 QMetalBuffer *buf = cbD->currentGraphicsPipeline->d->extraBufMgr.acquireWorkBuffer(this, sizeof(multiViewInfo),
2782 if (buf) {
2783 id<MTLBuffer> mtlbuf = buf->d->buf[0];
2784 char *p = reinterpret_cast<char *>([mtlbuf contents]);
2785 memcpy(p, &multiViewInfo, sizeof(multiViewInfo));
2786 [cbD->d->currentRenderPassEncoder setVertexBuffer: mtlbuf offset: 0 atIndex: viewMaskBufBinding];
2787 // The instance count is adjusted for layered rendering. The vertex shader is expected to contain something like:
2788 // uint gl_ViewIndex = spvViewMask[0] + (gl_InstanceIndex - gl_BaseInstance) % spvViewMask[1];
2789 // where spvViewMask is the buffer with multiViewInfo passed in above.
2790 *instanceCount *= multiViewCount;
2791 }
2792}
2793
2794void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
2795 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
2796{
2797 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2799
2800 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2801 TessDrawArgs a;
2802 a.cbD = cbD;
2803 a.type = TessDrawArgs::NonIndexed;
2804 a.draw.vertexCount = vertexCount;
2805 a.draw.instanceCount = instanceCount;
2806 a.draw.firstVertex = firstVertex;
2807 a.draw.firstInstance = firstInstance;
2809 return;
2810 }
2811
2812 adjustForMultiViewDraw(&instanceCount, cb);
2813
2814 if (caps.baseVertexAndInstance) {
2815 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2816 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
2817 } else {
2818 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2819 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount];
2820 }
2821}
2822
2823void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
2824 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
2825{
2826 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2828
2829 if (!cbD->currentIndexBuffer)
2830 return;
2831
2832 const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
2833 Q_ASSERT(indexOffset == aligned(indexOffset, 4u));
2834
2836 id<MTLBuffer> mtlibuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
2837
2838 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2839 TessDrawArgs a;
2840 a.cbD = cbD;
2841 a.type = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? TessDrawArgs::U16Indexed : TessDrawArgs::U32Indexed;
2842 a.drawIndexed.indexCount = indexCount;
2843 a.drawIndexed.instanceCount = instanceCount;
2844 a.drawIndexed.firstIndex = firstIndex;
2845 a.drawIndexed.vertexOffset = vertexOffset;
2846 a.drawIndexed.firstInstance = firstInstance;
2847 a.drawIndexed.indexBuffer = mtlibuf;
2849 return;
2850 }
2851
2852 adjustForMultiViewDraw(&instanceCount, cb);
2853
2854 if (caps.baseVertexAndInstance) {
2855 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2856 indexCount: indexCount
2857 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2858 indexBuffer: mtlibuf
2859 indexBufferOffset: indexOffset
2860 instanceCount: instanceCount
2861 baseVertex: vertexOffset
2862 baseInstance: firstInstance];
2863 } else {
2864 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2865 indexCount: indexCount
2866 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2867 indexBuffer: mtlibuf
2868 indexBufferOffset: indexOffset
2869 instanceCount: instanceCount];
2870 }
2871}
2872
2873// Returns null when the ICB path is usable, otherwise the reason why not.
2875{
2876 if (!caps.indirectCommandBuffers)
2877 return "indirect command buffers are not supported on this device";
2879 || !cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesIndirectDraws))
2880 {
2881 return "the current graphics pipeline was not created with UsesIndirectDraws";
2882 }
2883 if (cbD->currentGraphicsPipeline->d->tess.enabled)
2884 return "the current graphics pipeline uses tessellation";
2886 return "the shaders of the current graphics pipeline sample textures but have no "
2887 "argument buffer variant, which Metal requires for a pipeline that supports "
2888 "indirect command buffers; rebuild them with qsb --msl-argument-buffers "
2889 "(or use MSLARGUMENTBUFFERS with qt_add_shaders)";
2890 }
2891 return nullptr;
2892}
2893
2895{
2897 return false;
2898
2899 if (!d->icbEncodePipeline) {
2900 NSError *err = nil;
2901 NSString *src = [NSString stringWithUTF8String:s_icbEncodeMsl];
2902 MTLCompileOptions *opts = [MTLCompileOptions new];
2903 opts.languageVersion = MTLLanguageVersion2_1;
2904 id<MTLLibrary> lib = [d->dev newLibraryWithSource:src options:opts error:&err];
2905 [opts release];
2906 if (!lib) {
2907 qWarning("Failed to compile ICB encode kernel: %s",
2908 qPrintable(QString::fromNSString(err.localizedDescription)));
2909 d->icbSetupFailed = true;
2910 return false;
2911 }
2912 d->icbEncodeFunction = [lib newFunctionWithName:@"encode_icb"];
2913 d->icbEncodeFunctionU32 = [lib newFunctionWithName:@"encode_icb_indexed_u32"];
2914 d->icbEncodeFunctionU16 = [lib newFunctionWithName:@"encode_icb_indexed_u16"];
2915 [lib release];
2916 if (!d->icbEncodeFunction || !d->icbEncodeFunctionU32 || !d->icbEncodeFunctionU16) {
2917 qWarning("ICB encode kernel functions not found");
2918 d->icbSetupFailed = true;
2919 return false;
2920 }
2921 NSError *errU32 = nil;
2922 NSError *errU16 = nil;
2923 d->icbEncodePipeline = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunction error:&err];
2924 d->icbEncodePipelineU32 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU32 error:&errU32];
2925 d->icbEncodePipelineU16 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU16 error:&errU16];
2926 if (!d->icbEncodePipeline || !d->icbEncodePipelineU32 || !d->icbEncodePipelineU16) {
2927 NSError *firstErr = !d->icbEncodePipeline ? err
2928 : (!d->icbEncodePipelineU32 ? errU32 : errU16);
2929 qWarning("Failed to create ICB encode compute pipeline: %s",
2930 qPrintable(QString::fromNSString(firstErr.localizedDescription)));
2931 d->icbSetupFailed = true;
2932 return false;
2933 }
2934 }
2935
2936 if (!d->icbRangeBuffer) {
2937 d->icbRangeBuffer = [d->dev newBufferWithLength:sizeof(MTLIndirectCommandBufferExecutionRange)
2938 options:MTLResourceStorageModePrivate];
2939 static constexpr quint32 noCount = 0xFFFFFFFFu;
2940 d->icbNoCountBuffer = [d->dev newBufferWithBytes:&noCount
2941 length:sizeof(noCount)
2942 options:MTLResourceStorageModeShared];
2943 if (!d->icbRangeBuffer || !d->icbNoCountBuffer) {
2944 qWarning("Failed to create ICB helper buffers");
2945 d->icbSetupFailed = true;
2946 return false;
2947 }
2948 }
2949
2950 return true;
2951}
2952
2953bool QRhiMetal::prepareIcb(quint32 maxDrawCount)
2954{
2956 return false;
2957
2958 if (!d->icb || d->icbCapacity < maxDrawCount) {
2959 if (d->icb) {
2962 e.lastActiveFrameSlot = currentFrameSlot;
2963 e.stagingIcbBuffer.icb = d->icb;
2964 e.stagingIcbBuffer.argBuffer = d->icbArgumentBuffer;
2965 d->releaseQueue.append(e);
2966 }
2967 d->icb = nil;
2968 d->icbArgumentBuffer = nil;
2969
2970 MTLIndirectCommandBufferDescriptor *icbDesc = [MTLIndirectCommandBufferDescriptor new];
2971 icbDesc.commandTypes = MTLIndirectCommandTypeDraw | MTLIndirectCommandTypeDrawIndexed;
2972 icbDesc.inheritPipelineState = YES;
2973 icbDesc.inheritBuffers = YES;
2974 icbDesc.maxVertexBufferBindCount = 0;
2975 icbDesc.maxFragmentBufferBindCount = 0;
2976 d->icb = [d->dev newIndirectCommandBufferWithDescriptor:icbDesc
2977 maxCommandCount:maxDrawCount
2978 options:MTLResourceStorageModePrivate];
2979 [icbDesc release];
2980 if (!d->icb) {
2981 qWarning("Failed to create MTLIndirectCommandBuffer");
2982 d->icbCapacity = 0;
2983 return false;
2984 }
2985 d->icbCapacity = maxDrawCount;
2986
2987 id<MTLArgumentEncoder> argEnc = [d->icbEncodeFunction newArgumentEncoderWithBufferIndex:1];
2988 d->icbArgumentBuffer = [d->dev newBufferWithLength:argEnc.encodedLength
2989 options:MTLResourceStorageModeShared];
2990 [argEnc setArgumentBuffer:d->icbArgumentBuffer offset:0];
2991 [argEnc setIndirectCommandBuffer:d->icb atIndex:0];
2992 [argEnc release];
2993 }
2994
2995 return true;
2996}
2997
2998// Encodes maxDrawCount entries of indirectBufMtl into targetIcb on an already
2999// open compute encoder, writing the encoded count to targetRangeBuffer.
3000// countBufMtl is optional: when null all maxDrawCount commands are encoded.
3002 id<MTLComputeCommandEncoder> computeEncoder,
3003 id<MTLIndirectCommandBuffer> targetIcb,
3004 id<MTLBuffer> targetArgBuffer,
3005 id<MTLBuffer> targetRangeBuffer,
3006 bool indexed,
3007 QRhiCommandBuffer::IndexFormat indexFormat,
3008 MTLPrimitiveType primitiveType,
3009 id<MTLBuffer> indirectBufMtl, quint32 indirectBufferOffset,
3010 id<MTLBuffer> indexBufMtl, quint32 indexBufferOffset,
3011 id<MTLBuffer> countBufMtl, quint32 countBufferOffset,
3012 quint32 maxDrawCount, quint32 stride)
3013{
3014 id<MTLComputePipelineState> computePipeline = d->icbEncodePipeline;
3015 if (indexed) {
3016 computePipeline = indexFormat == QRhiCommandBuffer::IndexUInt16
3017 ? d->icbEncodePipelineU16 : d->icbEncodePipelineU32;
3018 }
3019 uint32_t maxDrawCountVal = maxDrawCount;
3020 uint32_t metalPrimType = uint32_t(primitiveType);
3021 uint32_t strideVal = stride;
3022
3023 [computeEncoder setComputePipelineState:computePipeline];
3024 [computeEncoder setBuffer:indirectBufMtl offset:indirectBufferOffset atIndex:0];
3025 [computeEncoder setBuffer:targetArgBuffer offset:0 atIndex:1];
3026 [computeEncoder setBytes:&maxDrawCountVal length:sizeof(uint32_t) atIndex:2];
3027 if (indexed)
3028 [computeEncoder setBuffer:indexBufMtl offset:indexBufferOffset atIndex:3];
3029 [computeEncoder setBytes:&metalPrimType length:sizeof(uint32_t) atIndex:4];
3030 [computeEncoder setBytes:&strideVal length:sizeof(uint32_t) atIndex:5];
3031 [computeEncoder setBuffer:countBufMtl ? countBufMtl : d->icbNoCountBuffer
3032 offset:countBufMtl ? countBufferOffset : 0
3033 atIndex:6];
3034 [computeEncoder setBuffer:targetRangeBuffer offset:0 atIndex:7];
3035 [computeEncoder useResource:targetIcb usage:MTLResourceUsageWrite];
3036 [computeEncoder useResource:indirectBufMtl usage:MTLResourceUsageRead];
3037 if (indexed)
3038 [computeEncoder useResource:indexBufMtl usage:MTLResourceUsageRead];
3039
3040 NSUInteger tw = computePipeline.threadExecutionWidth;
3041 [computeEncoder dispatchThreads:MTLSizeMake(maxDrawCount, 1, 1)
3042 threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
3043}
3044
3045// Encodes up to maxDrawCount commands into the shared ICB with a compute
3046// dispatch, then executes them on the render encoder. countBufMtl is optional:
3047// when null, all maxDrawCount commands are executed, otherwise the number of
3048// draws is min(maxDrawCount, <uint32 at countBufferOffset>), computed on the
3049// GPU. Returns false when the ICB could not be set up, in which case nothing
3050// was recorded and the pass is left untouched.
3051bool QRhiMetal::icbDraw(QMetalCommandBuffer *cbD, bool indexed,
3052 QMetalBuffer *indirectBufD, quint32 indirectBufferOffset,
3053 QMetalBuffer *countBufD, quint32 countBufferOffset,
3054 quint32 maxDrawCount, quint32 stride)
3055{
3056 if (!maxDrawCount)
3057 return true;
3058
3059 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
3060 if (indexed && !indexBufD)
3061 return false;
3062
3063 if (!prepareIcb(maxDrawCount))
3064 return false;
3065
3067 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3068 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3069
3070 id<MTLBuffer> countBufMtl = nil;
3071 if (countBufD) {
3073 countBufD->lastActiveFrameSlot = currentFrameSlot;
3074 countBufMtl = countBufD->d->buf[countBufD->d->slotted ? currentFrameSlot : 0];
3075 }
3076
3077 // Everything below survives the interruption only if saved and restored.
3079 const QMetalShaderResourceBindingsData savedResourceBindings = cbD->d->currentShaderResourceBindingState;
3080 const int savedFirstVertexBinding = cbD->d->currentFirstVertexBinding;
3081 const auto savedVertexBuffers = cbD->d->currentVertexInputsBuffers;
3082 const auto savedVertexOffsets = cbD->d->currentVertexInputOffsets;
3083 const quint32 savedIndexOffset = cbD->currentIndexOffset;
3084 const QRhiCommandBuffer::IndexFormat savedIndexFormat = cbD->currentIndexFormat;
3085 id<MTLBuffer> indexBufMtl = indexed
3086 ? indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0] : nil;
3087
3088 // End the current render encoder to make room for the compute pass.
3090
3091 id<MTLComputeCommandEncoder> computeEncoder = [cbD->d->cb computeCommandEncoder];
3092 encodeIcbWithCompute(d, computeEncoder, d->icb, d->icbArgumentBuffer, d->icbRangeBuffer,
3093 indexed, savedIndexFormat, savedPipeline->d->primitiveType,
3094 indirectBufMtl, indirectBufferOffset,
3095 indexBufMtl, savedIndexOffset,
3096 countBufMtl, countBufferOffset,
3097 maxDrawCount, stride);
3098
3099 // Restart the render pass with Load actions to preserve existing content.
3100 endTempComputeEncoding(this, cbD, computeEncoder);
3101
3102 // Restore pipeline, shader resources, and vertex bindings on the new encoder.
3105 QMetalShaderResourceBindingsData::VERTEX, &savedResourceBindings);
3107 QMetalShaderResourceBindingsData::FRAGMENT, &savedResourceBindings);
3108
3109 if (savedFirstVertexBinding >= 0) {
3110 cbD->d->currentFirstVertexBinding = savedFirstVertexBinding;
3111 cbD->d->currentVertexInputsBuffers = savedVertexBuffers;
3112 cbD->d->currentVertexInputOffsets = savedVertexOffsets;
3113 for (int i = 0, ie = savedVertexBuffers.batches.count(); i != ie; ++i) {
3114 const auto &bufferBatch(savedVertexBuffers.batches[i]);
3115 const auto &offsetBatch(savedVertexOffsets.batches[i]);
3116 [cbD->d->currentRenderPassEncoder setVertexBuffers:
3117 bufferBatch.resources.constData()
3118 offsets: offsetBatch.resources.constData()
3119 withRange: NSMakeRange(uint(savedFirstVertexBinding) + bufferBatch.startBinding,
3120 NSUInteger(bufferBatch.resources.count()))];
3121 }
3122 }
3123
3124 cbD->currentIndexBuffer = indexBufD;
3125 cbD->currentIndexOffset = savedIndexOffset;
3126 cbD->currentIndexFormat = savedIndexFormat;
3127
3128 // Declare buffer dependencies and execute the GPU-encoded ICB. The range to
3129 // execute is read from icbRangeBuffer, which the kernel just wrote.
3130 [cbD->d->currentRenderPassEncoder useResource:indirectBufMtl
3131 usage:MTLResourceUsageRead
3132 stages:MTLRenderStageVertex | MTLRenderStageFragment];
3133 if (indexed) {
3134 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
3135 usage:MTLResourceUsageRead
3136 stages:MTLRenderStageVertex | MTLRenderStageFragment];
3137 }
3138 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:d->icb
3139 indirectBuffer:d->icbRangeBuffer
3140 indirectBufferOffset:0];
3141 return true;
3142}
3143
3144// The ICB (Indirect Command Buffer) path encodes the draw commands on the GPU
3145// and executes them with a single executeCommandsInBuffer, which removes the
3146// per-draw CPU overhead. The encoding needs a compute pass, so the render pass
3147// has to be interrupted and restarted: that costs about 100-150 microseconds,
3148// whereas an individual indirect draw call takes 1-2. Hence only taking this
3149// path for large batches.
3150static constexpr quint32 ICB_DRAW_COUNT_THRESHOLD = 128;
3151
3152void QRhiMetal::drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
3153 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
3154{
3155 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3157
3158 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
3160 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3161 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3162
3163 if (drawCount > ICB_DRAW_COUNT_THRESHOLD && !icbUnavailableReason(cbD)
3164 && icbDraw(cbD, false, indirectBufD, indirectBufferOffset, nullptr, 0, drawCount, stride))
3165 {
3166 return;
3167 }
3168
3169 // CPU-side for-loop fallback: used when ICB is not applicable or setup failed.
3170 NSUInteger offset = indirectBufferOffset;
3171 for (quint32 i = 0; i < drawCount; ++i) {
3172 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
3173 indirectBuffer: indirectBufMtl
3174 indirectBufferOffset: offset];
3175 offset += stride;
3176 }
3177}
3178
3179void QRhiMetal::drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
3180 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
3181{
3182 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3184
3185 if (!cbD->currentIndexBuffer)
3186 return;
3187
3188 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
3189 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
3190
3191 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
3193 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3194 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3195
3196 if (drawCount > ICB_DRAW_COUNT_THRESHOLD && !icbUnavailableReason(cbD)
3197 && icbDraw(cbD, true, indirectBufD, indirectBufferOffset, nullptr, 0, drawCount, stride))
3198 {
3199 return;
3200 }
3201
3202 // CPU-side for-loop fallback: used when ICB is not applicable or setup failed.
3203 NSUInteger offset = indirectBufferOffset;
3204 for (quint32 i = 0; i < drawCount; ++i) {
3205 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
3206 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
3207 indexBuffer: indexBufMtl
3208 indexBufferOffset: cbD->currentIndexOffset
3209 indirectBuffer: indirectBufMtl
3210 indirectBufferOffset: offset];
3211 offset += stride;
3212 }
3213}
3214
3215void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
3216{
3217 if (!debugMarkers)
3218 return;
3219
3220 NSString *str = [NSString stringWithUTF8String: name.constData()];
3221 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3222 switch (cbD->recordingPass) {
3223 case QMetalCommandBuffer::RenderPass:
3224 [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
3225 cbD->d->openDebugGroups.append(name);
3226 break;
3227 case QMetalCommandBuffer::ComputePass:
3228 [cbD->d->currentComputePassEncoder pushDebugGroup: str];
3229 break;
3230 default:
3231 [cbD->d->cb pushDebugGroup: str];
3232 break;
3233 }
3234}
3235
3236void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
3237{
3238 if (!debugMarkers)
3239 return;
3240
3241 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3242 switch (cbD->recordingPass) {
3243 case QMetalCommandBuffer::RenderPass:
3244 [cbD->d->currentRenderPassEncoder popDebugGroup];
3245 if (!cbD->d->openDebugGroups.isEmpty())
3246 cbD->d->openDebugGroups.removeLast();
3247 break;
3248 case QMetalCommandBuffer::ComputePass:
3249 [cbD->d->currentComputePassEncoder popDebugGroup];
3250 break;
3251 default:
3252 [cbD->d->cb popDebugGroup];
3253 break;
3254 }
3255}
3256
3257void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
3258{
3259 if (!debugMarkers)
3260 return;
3261
3262 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3263 NSString *str = [NSString stringWithUTF8String: msg.constData()];
3264 switch (cbD->recordingPass) {
3265 case QMetalCommandBuffer::RenderPass:
3266 [cbD->d->currentRenderPassEncoder insertDebugSignpost: str];
3267 break;
3268 case QMetalCommandBuffer::ComputePass:
3269 [cbD->d->currentComputePassEncoder insertDebugSignpost: str];
3270 break;
3271 default:
3272 break;
3273 }
3274}
3275
3276const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
3277{
3278 return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
3279}
3280
3281void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
3282{
3283 Q_UNUSED(cb);
3284}
3285
3286void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
3287{
3288 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3290}
3291
3292double QRhiMetal::lastCompletedGpuTime(QRhiCommandBuffer *cb)
3293{
3294 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3295 return cbD->d->lastGpuTime;
3296}
3297
3298QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
3299{
3300 Q_UNUSED(flags);
3301
3302 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
3303 currentSwapChain = swapChainD;
3304 currentFrameSlot = swapChainD->currentFrameSlot;
3305
3306 // If we are too far ahead, block. This is also what ensures that any
3307 // resource used in the previous frame for this slot is now not in use
3308 // anymore by the GPU.
3309 dispatch_semaphore_wait(swapChainD->d->sem[currentFrameSlot], DISPATCH_TIME_FOREVER);
3310
3311 // Do this also for any other swapchain's commands with the same frame slot
3312 // While this reduces concurrency, it keeps resource usage safe: swapchain
3313 // A starting its frame 0, followed by swapchain B starting its own frame 0
3314 // will make B wait for A's frame 0 commands, so if a resource is written
3315 // in B's frame or when B checks for pending resource releases, that won't
3316 // mess up A's in-flight commands (as they are not in flight anymore).
3317 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
3318 if (sc != swapChainD)
3319 sc->waitUntilCompleted(currentFrameSlot); // wait+signal
3320 }
3321
3322 [d->captureScope beginScope];
3323
3324 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
3325
3327 if (swapChainD->samples > 1) {
3328 colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
3329 colorAtt.needsDrawableForResolveTex = true;
3330 } else {
3331 colorAtt.needsDrawableForTex = true;
3332 }
3333
3334 swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
3335 swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
3336 swapChainD->rtWrapper.d->fb.dsResolveTex = nil;
3337 swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
3338 swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
3339
3340 if (swapChainD->ds)
3341 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
3342
3343 d->argBufPool[currentFrameSlot].offset = 0;
3344 d->resetAndResizeBufferStagingArea(currentFrameSlot);
3345 d->globalFrameId += 1;
3346
3348 swapChainD->cbWrapper.resetState(swapChainD->d->lastGpuTime[currentFrameSlot]);
3349 swapChainD->d->lastGpuTime[currentFrameSlot] = 0;
3351
3352 return QRhi::FrameOpSuccess;
3353}
3354
3355QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
3356{
3357 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
3358 Q_ASSERT(currentSwapChain == swapChainD);
3359
3360 // Keep strong reference to command buffer
3361 id<MTLCommandBuffer> commandBuffer = swapChainD->cbWrapper.d->cb;
3362
3363 __block int thisFrameSlot = currentFrameSlot;
3364 [commandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> cb) {
3365 swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
3366 dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
3367 }];
3368
3370 // When Metal API validation diagnostics is enabled in Xcode the texture is
3371 // released before the command buffer is done with it. Manually keep it alive
3372 // to work around this.
3373 id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain];
3374 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
3375 [drawableTexture release];
3376 }];
3377#endif
3378
3379 if (flags.testFlag(QRhi::SkipPresent)) {
3380 // Just need to commit, that's it
3381 [commandBuffer commit];
3382 } else {
3383 if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) {
3384 // Got something to present
3385 if (swapChainD->d->layer.presentsWithTransaction) {
3386 [commandBuffer commit];
3387 // Keep strong reference to Metal layer
3388 auto *metalLayer = swapChainD->d->layer;
3389 auto presentWithTransaction = ^{
3390 [commandBuffer waitUntilScheduled];
3391 // If the layer has been resized while we waited to be scheduled we bail out,
3392 // as the drawable is no longer valid for the layer, and we'll get a follow-up
3393 // display with the right size. We know we are on the main thread here, which
3394 // means we can access the layer directly. We also know that the layer is valid,
3395 // since the block keeps a strong reference to it, compared to the QRhiSwapChain
3396 // that can go away under our feet by the time we're scheduled.
3397 const auto surfaceSize = QSizeF::fromCGSize(metalLayer.bounds.size) * metalLayer.contentsScale;
3398 const auto textureSize = QSizeF(drawable.texture.width, drawable.texture.height);
3399 if (textureSize == surfaceSize) {
3400 [drawable present];
3401 } else {
3402 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable << "due to texture size"
3403 << textureSize << "not matching surface size" << surfaceSize;
3404 }
3405 };
3406
3407 if (NSThread.currentThread == NSThread.mainThread) {
3408 presentWithTransaction();
3409 } else {
3410 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
3411 Q_ASSERT(qtMetalLayer);
3412 // Let the main thread present the drawable from displayLayer
3413 qtMetalLayer.mainThreadPresentation = presentWithTransaction;
3414 }
3415 } else {
3416 // Keep strong reference to Metal layer so it's valid in the block
3417 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
3418 [commandBuffer addScheduledHandler:^(id<MTLCommandBuffer>) {
3419 if (qtMetalLayer) {
3420 // The schedule handler comes in on the com.Metal.CompletionQueueDispatch
3421 // thread, which means we might be racing against a display cycle on the
3422 // main thread. If the displayLayer is already in progress, we don't want
3423 // to step on its toes.
3424 if (qtMetalLayer.displayLock.tryLockForRead()) {
3425 [drawable present];
3426 qtMetalLayer.displayLock.unlock();
3427 } else {
3428 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable
3429 << "due to" << qtMetalLayer << "needing display";
3430 }
3431 } else {
3432 [drawable present];
3433 }
3434 }];
3435 [commandBuffer commit];
3436 }
3437 } else {
3438 // Still need to commit, even if we don't have a drawable
3439 [commandBuffer commit];
3440 }
3441
3442 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
3443 }
3444
3445 // Must not hold on to the drawable, regardless of needsPresent
3446 [swapChainD->d->curDrawable release];
3447 swapChainD->d->curDrawable = nil;
3448
3449 [d->captureScope endScope];
3450
3451 swapChainD->frameCount += 1;
3452 currentSwapChain = nullptr;
3453 return QRhi::FrameOpSuccess;
3454}
3455
3456QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
3457{
3458 Q_UNUSED(flags);
3459
3460 currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
3461
3462 for (QMetalSwapChain *sc : std::as_const(swapchains))
3463 sc->waitUntilCompleted(currentFrameSlot);
3464
3465 d->ofr.active = true;
3466 *cb = &d->ofr.cbWrapper;
3467 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
3468
3469 d->argBufPool[currentFrameSlot].offset = 0;
3470 d->resetAndResizeBufferStagingArea(currentFrameSlot);
3471 d->globalFrameId += 1;
3472
3474 d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime);
3475 d->ofr.lastGpuTime = 0;
3477
3478 return QRhi::FrameOpSuccess;
3479}
3480
3481QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags)
3482{
3483 Q_UNUSED(flags);
3484 Q_ASSERT(d->ofr.active);
3485 d->ofr.active = false;
3486
3487 id<MTLCommandBuffer> cb = d->ofr.cbWrapper.d->cb;
3488 [cb commit];
3489
3490 // offscreen frames wait for completion, unlike swapchain ones
3491 [cb waitUntilCompleted];
3492
3493 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
3494
3496
3497 return QRhi::FrameOpSuccess;
3498}
3499
3501{
3502 id<MTLCommandBuffer> cb = nil;
3503 QMetalSwapChain *swapChainD = nullptr;
3504 if (inFrame) {
3505 if (d->ofr.active) {
3506 Q_ASSERT(!currentSwapChain);
3507 Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
3508 cb = d->ofr.cbWrapper.d->cb;
3509 } else {
3510 Q_ASSERT(currentSwapChain);
3511 swapChainD = currentSwapChain;
3512 Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
3513 cb = swapChainD->cbWrapper.d->cb;
3514 }
3515 }
3516
3517 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
3518 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3519 if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
3520 // no wait as this is the thing we're going to be commit below and
3521 // beginFrame decremented sem already and going to be signaled by endFrame
3522 continue;
3523 }
3524 sc->waitUntilCompleted(i);
3525 }
3526 }
3527
3528 if (cb) {
3529 [cb commit];
3530 [cb waitUntilCompleted];
3531 }
3532
3533 if (inFrame) {
3534 if (d->ofr.active) {
3535 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
3536 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
3537 } else {
3538 swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
3539 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
3540 }
3541 }
3542
3544
3546
3547 return QRhi::FrameOpSuccess;
3548}
3549
3551 const QColor &colorClearValue,
3552 const QRhiDepthStencilClearValue &depthStencilClearValue,
3553 int colorAttCount,
3554 QRhiShadingRateMap *shadingRateMap)
3555{
3556 MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
3557 MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
3558 colorClearValue.alphaF());
3559
3560 for (uint i = 0; i < uint(colorAttCount); ++i) {
3561 rp.colorAttachments[i].loadAction = MTLLoadActionClear;
3562 rp.colorAttachments[i].storeAction = MTLStoreActionStore;
3563 rp.colorAttachments[i].clearColor = c;
3564 }
3565
3566 if (hasDepthStencil) {
3567 rp.depthAttachment.loadAction = MTLLoadActionClear;
3568 rp.depthAttachment.storeAction = MTLStoreActionDontCare;
3569 rp.stencilAttachment.loadAction = MTLLoadActionClear;
3570 rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
3571 rp.depthAttachment.clearDepth = double(depthStencilClearValue.depthClearValue());
3572 rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
3573 }
3574
3575 if (shadingRateMap)
3576 rp.rasterizationRateMap = QRHI_RES(QMetalShadingRateMap, shadingRateMap)->d->rateMap;
3577
3578 return rp;
3579}
3580
3581qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
3582{
3583 qsizetype size = 0;
3584 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
3585 subresDesc.data().size() : subresDesc.image().sizeInBytes();
3586 if (imageSizeBytes > 0)
3587 size += aligned<qsizetype>(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3588 return size;
3589}
3590
3591void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
3592 int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
3593 qsizetype *curOfs)
3594{
3595 const QPoint dp = subresDesc.destinationTopLeft();
3596 const QByteArray rawData = subresDesc.data();
3597 QImage img = subresDesc.image();
3598 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3599 id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
3600
3601 if (!img.isNull()) {
3602 const qsizetype fullImageSizeBytes = img.sizeInBytes();
3603 QSize size = img.size();
3604 int bpl = img.bytesPerLine();
3605
3606 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
3607 const int sx = subresDesc.sourceTopLeft().x();
3608 const int sy = subresDesc.sourceTopLeft().y();
3609 if (!subresDesc.sourceSize().isEmpty())
3610 size = subresDesc.sourceSize();
3611 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3612 if (size.width() == img.width()) {
3613 const int bpc = qMax(1, img.depth() / 8);
3614 Q_ASSERT(size.height() * img.bytesPerLine() <= fullImageSizeBytes);
3615 memcpy(reinterpret_cast<char *>(mp) + *curOfs,
3616 img.constBits() + sy * img.bytesPerLine() + sx * bpc,
3617 size.height() * img.bytesPerLine());
3618 } else {
3619 img = img.copy(sx, sy, size.width(), size.height());
3620 bpl = img.bytesPerLine();
3621 Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
3622 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(img.sizeInBytes()));
3623 }
3624 } else {
3625 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3626 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
3627 }
3628
3629 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3630 sourceOffset: NSUInteger(*curOfs)
3631 sourceBytesPerRow: NSUInteger(bpl)
3632 sourceBytesPerImage: 0
3633 sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1)
3634 toTexture: texD->d->tex
3635 destinationSlice: NSUInteger(is3D ? 0 : layer)
3636 destinationLevel: NSUInteger(level)
3637 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3638 options: MTLBlitOptionNone];
3639
3640 *curOfs += aligned<qsizetype>(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3641 } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
3642 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3643 const int subresw = subresSize.width();
3644 const int subresh = subresSize.height();
3645 int w, h;
3646 if (subresDesc.sourceSize().isEmpty()) {
3647 w = subresw;
3648 h = subresh;
3649 } else {
3650 w = subresDesc.sourceSize().width();
3651 h = subresDesc.sourceSize().height();
3652 }
3653
3654 quint32 bpl = 0;
3655 QSize blockDim;
3656 compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
3657
3658 const int dx = aligned(dp.x(), blockDim.width());
3659 const int dy = aligned(dp.y(), blockDim.height());
3660 if (dx + w != subresw)
3661 w = aligned(w, blockDim.width());
3662 if (dy + h != subresh)
3663 h = aligned(h, blockDim.height());
3664
3665 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3666
3667 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3668 sourceOffset: NSUInteger(*curOfs)
3669 sourceBytesPerRow: bpl
3670 sourceBytesPerImage: 0
3671 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3672 toTexture: texD->d->tex
3673 destinationSlice: NSUInteger(is3D ? 0 : layer)
3674 destinationLevel: NSUInteger(level)
3675 destinationOrigin: MTLOriginMake(NSUInteger(dx), NSUInteger(dy), NSUInteger(is3D ? layer : 0))
3676 options: MTLBlitOptionNone];
3677
3678 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3679 } else if (!rawData.isEmpty()) {
3680 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3681 const int subresw = subresSize.width();
3682 const int subresh = subresSize.height();
3683 int w, h;
3684 if (subresDesc.sourceSize().isEmpty()) {
3685 w = subresw;
3686 h = subresh;
3687 } else {
3688 w = subresDesc.sourceSize().width();
3689 h = subresDesc.sourceSize().height();
3690 }
3691
3692 QSize size = clampedSubResourceUploadSize(QSize(w, h), dp, level, texD->m_pixelSize);
3693 quint32 bytesPerPixel = 0;
3694 textureFormatInfo(texD->m_format, size, nullptr, nullptr, &bytesPerPixel);
3695 size = clampedSubResourceUploadSizeForSourceData(size, subresDesc.dataStride(),
3696 bytesPerPixel, rawData.size());
3697 w = size.width();
3698 h = size.height();
3699
3700 quint32 bpl = 0;
3701 if (subresDesc.dataStride())
3702 bpl = subresDesc.dataStride();
3703 else
3704 textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, nullptr);
3705
3706 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3707
3708 if (!size.isEmpty()) {
3709 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3710 sourceOffset: NSUInteger(*curOfs)
3711 sourceBytesPerRow: bpl
3712 sourceBytesPerImage: 0
3713 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3714 toTexture: texD->d->tex
3715 destinationSlice: NSUInteger(is3D ? 0 : layer)
3716 destinationLevel: NSUInteger(level)
3717 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3718 options: MTLBlitOptionNone];
3719 }
3720
3721 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3722 } else {
3723 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
3724 }
3725}
3726
3727void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3728{
3729 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3731
3732 id<MTLBlitCommandEncoder> blitEnc = nil;
3733 auto ensureBlit = [&blitEnc, cbD, this]() {
3734 if (!blitEnc) {
3735 blitEnc = [cbD->d->cb blitCommandEncoder];
3736 if (debugMarkers)
3737 [blitEnc pushDebugGroup: @"Resource updates"];
3738 }
3739 };
3740
3741 for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
3742 const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
3744 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3745 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
3746 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) {
3747 if (u.offset == 0 && u.data.size() == bufD->m_size)
3748 bufD->d->pendingUpdates[i].clear();
3749 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3750 }
3752 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3753 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
3754 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
3755 if (bufD->d->isDeviceLocal) {
3756 const quint32 size = quint32(u.data.size());
3757 if (size) { // a zero size blit is invalid
3758 quint32 stagingOffset = 0;
3759 id<MTLBuffer> stagingBuf = d->allocBufferStaging(size, currentFrameSlot, &stagingOffset);
3760 if (stagingBuf) {
3761 char *p = reinterpret_cast<char *>([stagingBuf contents]);
3762 memcpy(p + stagingOffset, u.data.constData(), size);
3763 ensureBlit();
3764 [blitEnc copyFromBuffer: stagingBuf
3765 sourceOffset: stagingOffset
3766 toBuffer: bufD->d->buf[0]
3767 destinationOffset: u.offset
3768 size: size];
3769 } else {
3770 qWarning("Failed to allocate staging buffer of size %u for buffer upload", size);
3771 }
3772 }
3773 bufD->lastActiveFrameSlot = currentFrameSlot;
3774 } else {
3775 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
3776 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3777 }
3779 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3781 const int idx = bufD->d->slotted ? currentFrameSlot : 0;
3782 if (bufD->m_type == QRhiBuffer::Dynamic) {
3783 char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]);
3784 if (p) {
3785 u.result->data.resize(u.readSize);
3786 memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize));
3787 }
3788 if (u.result->completed)
3789 u.result->completed();
3790 } else {
3791 // Copy into a dedicated staging buffer, here and now, instead
3792 // of holding on to the buffer and reading it when the readback
3793 // completes. The contents have to be the ones at this point in
3794 // the command stream: a buffer that is not slotted - which is
3795 // every buffer with StorageBuffer usage - has one native buffer
3796 // shared by all frames, so by the time the readback completes a
3797 // later frame may well be writing it.
3798 QRhiMetalData::BufferReadback readback;
3799 readback.activeFrameSlot = currentFrameSlot;
3800 readback.readSize = u.readSize;
3801 readback.result = u.result;
3802 readback.buf = [d->dev newBufferWithLength: u.readSize
3803 options: MTLResourceStorageModeShared];
3804
3805 ensureBlit();
3806 [blitEnc copyFromBuffer: bufD->d->buf[idx]
3807 sourceOffset: u.offset
3808 toBuffer: readback.buf
3809 destinationOffset: 0
3810 size: u.readSize];
3811
3812 d->activeBufferReadbacks.append(readback);
3813 bufD->lastActiveFrameSlot = currentFrameSlot;
3814 }
3816 QMetalBuffer *dstD = QRHI_RES(QMetalBuffer, u.buf);
3817 QMetalBuffer *srcD = QRHI_RES(QMetalBuffer, u.src);
3818 Q_ASSERT(dstD->m_type != QRhiBuffer::Dynamic && srcD->m_type != QRhiBuffer::Dynamic);
3819
3821 const int srcIdx = srcD->d->slotted ? currentFrameSlot : 0;
3822 const int dstSlotCount = dstD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1;
3823 // With device private buffers dstSlotCount is 1 and there are no
3824 // pending host writes at all, so this is a no-op. On the host
3825 // visible path every slot has to be flushed, not just the current
3826 // one: a partial copy cannot discard the pending writes outside the
3827 // copied range, and leaving them queued would let them land on top
3828 // of the copy in a later frame. Writing the slot that is not the
3829 // current one races a still in-flight frame reading it, which is
3830 // why mixing uploadStaticBuffer() and copyBuffer() on one buffer
3831 // within a frame stays unsupported there.
3832 for (int i = 0; i != dstSlotCount; ++i)
3834
3835 ensureBlit();
3836 for (int i = 0; i != dstSlotCount; ++i) {
3837 [blitEnc copyFromBuffer: srcD->d->buf[srcIdx]
3838 sourceOffset: u.srcOffset
3839 toBuffer: dstD->d->buf[i]
3840 destinationOffset: u.offset
3841 size: u.readSize];
3842 }
3843
3844 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3846 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3847 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic && !bufD->d->slotted);
3849 ensureBlit();
3850 [blitEnc fillBuffer: bufD->d->buf[0]
3851 range: NSMakeRange(u.offset, u.readSize)
3852 value: u.fillValue];
3853 bufD->lastActiveFrameSlot = currentFrameSlot;
3854 }
3855 }
3856
3857 for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
3858 const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
3860 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
3861 qsizetype stagingSize = 0;
3862 for (const auto &subres : u.subresDesc)
3863 stagingSize += subresUploadByteSize(subres.desc);
3864
3865 ensureBlit();
3866 Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
3867 utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: NSUInteger(stagingSize)
3868 options: MTLResourceStorageModeShared];
3869
3870 void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
3871 qsizetype curOfs = 0;
3872 for (const auto &subres : u.subresDesc)
3873 enqueueSubresUpload(utexD, mp, blitEnc, subres.layer, subres.level, subres.desc, &curOfs);
3874
3875 utexD->lastActiveFrameSlot = currentFrameSlot;
3876
3879 e.lastActiveFrameSlot = currentFrameSlot;
3880 e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
3881 utexD->d->stagingBuf[currentFrameSlot] = nil;
3882 d->releaseQueue.append(e);
3884 Q_ASSERT(u.src && u.dst);
3885 QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
3886 QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
3887 const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3888 const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3889 const QPoint dp = u.desc.destinationTopLeft();
3890 const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
3891 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
3892 const QPoint sp = u.desc.sourceTopLeft();
3893
3894 ensureBlit();
3895 [blitEnc copyFromTexture: srcD->d->tex
3896 sourceSlice: NSUInteger(srcIs3D ? 0 : u.desc.sourceLayer())
3897 sourceLevel: NSUInteger(u.desc.sourceLevel())
3898 sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), NSUInteger(srcIs3D ? u.desc.sourceLayer() : 0))
3899 sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1)
3900 toTexture: dstD->d->tex
3901 destinationSlice: NSUInteger(dstIs3D ? 0 : u.desc.destinationLayer())
3902 destinationLevel: NSUInteger(u.desc.destinationLevel())
3903 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(dstIs3D ? u.desc.destinationLayer() : 0))];
3904
3905 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3908 readback.activeFrameSlot = currentFrameSlot;
3909 readback.desc = u.rb;
3910 readback.result = u.result;
3911
3912 QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
3913 QMetalSwapChain *swapChainD = nullptr;
3914 id<MTLTexture> src;
3915 QRect rect;
3916 bool is3D = false;
3917 if (texD) {
3918 if (texD->samples > 1) {
3919 qWarning("Multisample texture cannot be read back");
3920 continue;
3921 }
3922 is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3923 if (u.rb.rect().isValid())
3924 rect = u.rb.rect();
3925 else
3926 rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
3927 readback.format = texD->m_format;
3928 src = texD->d->tex;
3929 texD->lastActiveFrameSlot = currentFrameSlot;
3930 } else {
3931 Q_ASSERT(currentSwapChain);
3933 if (u.rb.rect().isValid())
3934 rect = u.rb.rect();
3935 else
3936 rect = QRect({0, 0}, swapChainD->pixelSize);
3937 readback.format = swapChainD->d->rhiColorFormat;
3938 // Multisample swapchains need nothing special since resolving
3939 // happens when ending a renderpass.
3940 const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
3941 src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
3942 }
3943 readback.pixelSize = rect.size();
3944
3945 quint32 bpl = 0;
3946 textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize, nullptr);
3947
3948 // The depth and stencil aspects of a texture with a combined
3949 // depth/stencil Metal pixel format cannot be copied out with
3950 // MTLBlitOptionNone, they need separate blits, each into its own
3951 // densely packed plane of the staging buffer. In addition, the
3952 // layout of what the blit produces does not necessarily match
3953 // what QRhi returns for the format (e.g. D16 and D24 may be backed
3954 // by Depth32Float). The decisions are thus based on the actual Metal
3955 // pixel format. finishActiveReadbacks() then converts the planes
3956 // into the layout of the result.
3957 const MTLPixelFormat mtlFormat = texD ? texD->d->format : MTLPixelFormatInvalid;
3958 const bool isDepthFormat = texD && (readback.format == QRhiTexture::D16
3959 || readback.format == QRhiTexture::D24
3960 || readback.format == QRhiTexture::D24S8
3961 || readback.format == QRhiTexture::D32F
3962 || readback.format == QRhiTexture::D32FS8);
3963 const bool nativeLayout = (readback.format == QRhiTexture::D16 && mtlFormat == MTLPixelFormatDepth16Unorm)
3964 || (readback.format == QRhiTexture::D32F && mtlFormat == MTLPixelFormatDepth32Float);
3965 const bool usePlanes = isDepthFormat && !nativeLayout;
3966 bool combinedMtlFormat = mtlFormat == MTLPixelFormatDepth32Float_Stencil8;
3967#ifdef Q_OS_MACOS
3968 if (mtlFormat == MTLPixelFormatDepth24Unorm_Stencil8)
3969 combinedMtlFormat = true;
3970#endif
3971 const bool hasStencil = isStencilSupportingFormat(readback.format);
3972 quint32 depthPlaneBytesPerRow = 0;
3973 if (usePlanes) {
3974 // The depth blit writes 4 bytes per pixel (a float, or a 24-bit
3975 // unorm value in the low bits), the stencil blit 1 byte per pixel.
3976 depthPlaneBytesPerRow = quint32(rect.width()) * 4;
3977 readback.depthPlaneByteSize = depthPlaneBytesPerRow * quint32(rect.height());
3978 if (hasStencil)
3979 readback.stencilPlaneByteSize = quint32(rect.width()) * quint32(rect.height());
3980 readback.depthPlaneIsFloat = mtlFormat == MTLPixelFormatDepth32Float
3981 || mtlFormat == MTLPixelFormatDepth32Float_Stencil8;
3982 }
3983 const quint32 stagingBufSize = usePlanes
3984 ? readback.depthPlaneByteSize + readback.stencilPlaneByteSize
3985 : readback.bufSize;
3986 readback.buf = [d->dev newBufferWithLength: stagingBufSize options: MTLResourceStorageModeShared];
3987
3988 ensureBlit();
3989 const auto blit = [&](quint32 destinationOffset, quint32 destinationBytesPerRow, MTLBlitOption options) {
3990 [blitEnc copyFromTexture: src
3991 sourceSlice: NSUInteger(is3D ? 0 : u.rb.layer())
3992 sourceLevel: NSUInteger(u.rb.level())
3993 sourceOrigin: MTLOriginMake(NSUInteger(rect.x()), NSUInteger(rect.y()), NSUInteger(is3D ? u.rb.layer() : 0))
3994 sourceSize: MTLSizeMake(NSUInteger(rect.width()), NSUInteger(rect.height()), 1)
3995 toBuffer: readback.buf
3996 destinationOffset: destinationOffset
3997 destinationBytesPerRow: destinationBytesPerRow
3998 destinationBytesPerImage: 0
3999 options: options];
4000 };
4001 if (usePlanes) {
4002 blit(0, depthPlaneBytesPerRow,
4003 combinedMtlFormat ? MTLBlitOptionDepthFromDepthStencil : MTLBlitOptionNone);
4004 if (hasStencil)
4005 blit(readback.depthPlaneByteSize, quint32(rect.width()), MTLBlitOptionStencilFromDepthStencil);
4006 } else {
4007 blit(0, bpl, MTLBlitOptionNone);
4008 }
4009
4010 d->activeTextureReadbacks.append(readback);
4012 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
4013 ensureBlit();
4014 [blitEnc generateMipmapsForTexture: utexD->d->tex];
4015 utexD->lastActiveFrameSlot = currentFrameSlot;
4016 }
4017 }
4018
4019 if (blitEnc) {
4020 if (debugMarkers)
4021 [blitEnc popDebugGroup];
4022 [blitEnc endEncoding];
4023 }
4024
4025 ud->free();
4026}
4027
4028// this handles all types of buffers, not just Dynamic
4030{
4031 if (bufD->d->pendingUpdates[slot].isEmpty())
4032 return;
4033
4034 Q_ASSERT(!bufD->d->isDeviceLocal);
4035
4036 void *p = [bufD->d->buf[slot] contents];
4037 quint32 changeBegin = UINT32_MAX;
4038 quint32 changeEnd = 0;
4039 for (const QMetalBufferData::BufferUpdate &u : std::as_const(bufD->d->pendingUpdates[slot])) {
4040 memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
4041 if (u.offset < changeBegin)
4042 changeBegin = u.offset;
4043 if (u.offset + u.data.size() > changeEnd)
4044 changeEnd = u.offset + u.data.size();
4045 }
4046#ifdef Q_OS_MACOS
4047 if (changeBegin < UINT32_MAX && changeBegin < changeEnd && bufD->d->managed)
4048 [bufD->d->buf[slot] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))];
4049#endif
4050
4051 bufD->d->pendingUpdates[slot].clear();
4052}
4053
4055{
4056 executeBufferHostWritesForSlot(bufD, bufD->d->slotted ? currentFrameSlot : 0);
4057}
4058
4059void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4060{
4061 Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
4062
4063 enqueueResourceUpdates(cb, resourceUpdates);
4064}
4065
4066void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
4067 QRhiRenderTarget *rt,
4068 const QColor &colorClearValue,
4069 const QRhiDepthStencilClearValue &depthStencilClearValue,
4070 QRhiResourceUpdateBatch *resourceUpdates,
4072{
4073 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4075
4076 if (resourceUpdates)
4077 enqueueResourceUpdates(cb, resourceUpdates);
4078
4079 QMetalRenderTargetData *rtD = nullptr;
4080 switch (rt->resourceType()) {
4081 case QRhiResource::SwapChainRenderTarget:
4082 {
4084 rtD = rtSc->d;
4085 QRhiShadingRateMap *shadingRateMap = rtSc->swapChain()->shadingRateMap();
4086 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
4087 colorClearValue,
4088 depthStencilClearValue,
4089 rtD->colorAttCount,
4090 shadingRateMap);
4091 if (rtD->colorAttCount) {
4092 QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
4094 Q_ASSERT(currentSwapChain);
4096 if (!swapChainD->d->curDrawable) {
4097 QMacAutoReleasePool pool;
4098 swapChainD->d->curDrawable = [[swapChainD->d->layer nextDrawable] retain];
4099 }
4100 if (!swapChainD->d->curDrawable) {
4101 qWarning("No drawable");
4102 return;
4103 }
4104 id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
4105 if (color0.needsDrawableForTex) {
4106 color0.tex = scTex;
4107 color0.needsDrawableForTex = false;
4108 } else {
4109 color0.resolveTex = scTex;
4110 color0.needsDrawableForResolveTex = false;
4111 }
4112 }
4113 }
4114 if (shadingRateMap)
4115 QRHI_RES(QMetalShadingRateMap, shadingRateMap)->lastActiveFrameSlot = currentFrameSlot;
4116 }
4117 break;
4118 case QRhiResource::TextureRenderTarget:
4119 {
4121 rtD = rtTex->d;
4122 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList))
4123 rtTex->create();
4124 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
4125 colorClearValue,
4126 depthStencilClearValue,
4127 rtD->colorAttCount,
4128 rtTex->m_desc.shadingRateMap());
4129 if (rtD->fb.preserveColor) {
4130 for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
4131 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
4132 }
4133 if (rtD->dsAttCount && rtD->fb.preserveDs) {
4134 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
4135 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
4136 }
4137 int colorAttCount = 0;
4138 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
4139 it != itEnd; ++it)
4140 {
4141 colorAttCount += 1;
4142 if (it->texture()) {
4143 QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
4144 if (it->multiViewCount() >= 2)
4145 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(it->multiViewCount());
4146 } else if (it->renderBuffer()) {
4147 QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
4148 }
4149 if (it->resolveTexture())
4150 QRHI_RES(QMetalTexture, it->resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
4151 }
4152 if (rtTex->m_desc.depthStencilBuffer())
4153 QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
4154 if (rtTex->m_desc.depthTexture()) {
4155 QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture());
4156 depthTexture->lastActiveFrameSlot = currentFrameSlot;
4157 if (depthTexture->arraySize() >= 2) {
4158 const int depthLayer = rtTex->m_desc.depthLayer();
4159 if (depthLayer >= 0) {
4160 cbD->d->currentPassRpDesc.depthAttachment.slice = NSUInteger(depthLayer);
4161 cbD->d->currentPassRpDesc.stencilAttachment.slice = NSUInteger(depthLayer);
4162 if (colorAttCount == 0)
4163 cbD->d->currentPassRpDesc.renderTargetArrayLength = 1;
4164 } else if (colorAttCount == 0) {
4165 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize());
4166 }
4167 }
4168 }
4169 if (rtTex->m_desc.depthResolveTexture())
4170 QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
4171 if (rtTex->m_desc.shadingRateMap())
4172 QRHI_RES(QMetalShadingRateMap, rtTex->m_desc.shadingRateMap())->lastActiveFrameSlot = currentFrameSlot;
4173 }
4174 break;
4175 default:
4176 Q_UNREACHABLE();
4177 break;
4178 }
4179
4180 cbD->d->deferredColorStoreActions.clear();
4181 cbD->d->deferredDepthStoreAction = MTLStoreActionUnknown;
4182 cbD->d->deferredStencilStoreAction = MTLStoreActionUnknown;
4183 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
4184 cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
4185 cbD->d->currentPassRpDesc.colorAttachments[i].slice = NSUInteger(rtD->fb.colorAtt[i].arrayLayer);
4186 cbD->d->currentPassRpDesc.colorAttachments[i].depthPlane = NSUInteger(rtD->fb.colorAtt[i].slice);
4187 cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level);
4188 if (rtD->fb.colorAtt[i].resolveTex) {
4189 const MTLStoreAction storeAction = rtD->fb.preserveColor ? MTLStoreActionStoreAndMultisampleResolve
4190 : MTLStoreActionMultisampleResolve;
4191 // Defer, so that the multisample contents can be kept if the pass
4192 // ends up being interrupted. finalizeDeferredStoreActions() sets the
4193 // real action before the encoder ends.
4194 cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionUnknown;
4195 cbD->d->deferredColorStoreActions.append({ i, storeAction });
4196 cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
4197 cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer);
4198 cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel);
4199 }
4200 }
4201
4202 if (rtD->dsAttCount) {
4203 Q_ASSERT(rtD->fb.dsTex);
4204 cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
4205 cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
4206 if (rtD->fb.depthNeedsStore) { // Depth/Stencil is set to DontCare by default, override if needed
4207 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
4208 if (rtD->fb.hasStencil)
4209 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = MTLStoreActionStore;
4210 } else if (canStoreAttachment(rtD->fb.dsTex)) {
4211 // Would be discarded at the end of the pass, but an interruption in
4212 // the middle of it still has to be able to keep the contents. Defer,
4213 // so that nothing is stored unless that actually happens.
4214 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionUnknown;
4215 cbD->d->deferredDepthStoreAction = MTLStoreActionDontCare;
4216 if (rtD->fb.hasStencil) {
4217 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = MTLStoreActionUnknown;
4218 cbD->d->deferredStencilStoreAction = MTLStoreActionDontCare;
4219 }
4220 }
4221 if (rtD->fb.dsResolveTex) {
4222 const MTLStoreAction dsStoreAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve
4223 : MTLStoreActionMultisampleResolve;
4224 // Deferred for the same reason as the color attachments above, but
4225 // only when there is something to defer to: a memoryless
4226 // depth-stencil buffer, which is what a QRhiRenderBuffer is on Apple
4227 // GPUs, cannot be stored at all, and rejects the store-and-resolve
4228 // action that an interruption would need.
4229 const bool deferrable = canStoreAttachment(rtD->fb.dsTex);
4230 cbD->d->currentPassRpDesc.depthAttachment.storeAction = deferrable ? MTLStoreActionUnknown
4231 : dsStoreAction;
4232 if (deferrable)
4233 cbD->d->deferredDepthStoreAction = dsStoreAction;
4234 cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex;
4235 if (rtD->fb.hasStencil) {
4236 cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex;
4237 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = deferrable ? MTLStoreActionUnknown
4238 : dsStoreAction;
4239 if (deferrable)
4240 cbD->d->deferredStencilStoreAction = dsStoreAction;
4241 }
4242 }
4243 }
4244
4245 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
4246
4248
4250 cbD->currentTarget = rt;
4251}
4252
4253void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4254{
4255 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4257
4259 [cbD->d->currentRenderPassEncoder endEncoding];
4260
4262 cbD->currentTarget = nullptr;
4263
4264 if (resourceUpdates)
4265 enqueueResourceUpdates(cb, resourceUpdates);
4266}
4267
4268void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb,
4269 QRhiResourceUpdateBatch *resourceUpdates,
4271{
4272 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4274
4275 if (resourceUpdates)
4276 enqueueResourceUpdates(cb, resourceUpdates);
4277
4278 cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
4281}
4282
4283void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4284{
4285 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4287
4288 [cbD->d->currentComputePassEncoder endEncoding];
4290
4291 if (resourceUpdates)
4292 enqueueResourceUpdates(cb, resourceUpdates);
4293}
4294
4295void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
4296{
4297 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4300
4302 cbD->currentGraphicsPipeline = nullptr;
4303 cbD->currentComputePipeline = psD;
4305
4306 [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
4307 }
4308
4309 psD->lastActiveFrameSlot = currentFrameSlot;
4310}
4311
4312void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
4313{
4314 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4317
4318 [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(NSUInteger(x), NSUInteger(y), NSUInteger(z))
4319 threadsPerThreadgroup: psD->d->localSize];
4320}
4321
4322void QRhiMetal::dispatchIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
4323 quint32 indirectBufferOffset)
4324{
4325 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4328
4329 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
4331 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
4332 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
4333
4334 // dispatchThreadgroups still wants threadsPerThreadgroup explicitly; the
4335 // indirect buffer only supplies the grid size in threadgroups. The
4336 // threads-per-threadgroup value comes from the bound compute pipeline
4337 // (set in QMetalComputePipeline::create() from the SPIR-V shader's
4338 // local_size_x/y/z layout qualifiers).
4339 [cbD->d->currentComputePassEncoder
4340 dispatchThreadgroupsWithIndirectBuffer: indirectBufMtl
4341 indirectBufferOffset: indirectBufferOffset
4342 threadsPerThreadgroup: psD->d->localSize];
4343}
4344
4345void QRhiMetal::drawIndirectCount(QRhiCommandBuffer *cb,
4346 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
4347 QRhiBuffer *countBuffer, quint32 countBufferOffset,
4348 quint32 maxDrawCount, quint32 stride)
4349{
4350 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4352
4353 // Implemented on top of indirect command buffers. There can be no CPU-side
4354 // fallback, the count is only known to the device.
4355 if (const char *reason = icbUnavailableReason(cbD)) {
4356 qWarning("drawIndirectCount is not available because %s; skipping", reason);
4357 return;
4358 }
4359
4360 icbDraw(cbD, false, QRHI_RES(QMetalBuffer, indirectBuffer), indirectBufferOffset,
4361 QRHI_RES(QMetalBuffer, countBuffer), countBufferOffset, maxDrawCount, stride);
4362}
4363
4364void QRhiMetal::drawIndexedIndirectCount(QRhiCommandBuffer *cb,
4365 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
4366 QRhiBuffer *countBuffer, quint32 countBufferOffset,
4367 quint32 maxDrawCount, quint32 stride)
4368{
4369 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4371
4372 // Implemented on top of indirect command buffers. There can be no CPU-side
4373 // fallback, the count is only known to the device.
4374 if (const char *reason = icbUnavailableReason(cbD)) {
4375 qWarning("drawIndexedIndirectCount is not available because %s; skipping", reason);
4376 return;
4377 }
4378
4379 if (!cbD->currentIndexBuffer) {
4380 qWarning("drawIndexedIndirectCount called without an index buffer bound; skipping");
4381 return;
4382 }
4383
4384 icbDraw(cbD, true, QRHI_RES(QMetalBuffer, indirectBuffer), indirectBufferOffset,
4385 QRHI_RES(QMetalBuffer, countBuffer), countBufferOffset, maxDrawCount, stride);
4386}
4387
4388static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t);
4389
4391{
4404 Slot frameSlots[QMTL_FRAMES_IN_FLIGHT];
4405
4406 enum class Fill { None, Cpu, Gpu };
4408 bool slotSetupFailed = false;
4409 bool created = false;
4410
4411 // Set by buildIndirect(), which makes the CPU-side recording irrelevant:
4412 // gpuFilled means the kernel encoded into frameSlots[builtSlot], fallbackBuild
4413 // that there was no ICB to encode into and executeIndirect() has to use the
4414 // plain indirect draw entry points.
4415 bool gpuFilled = false;
4416 bool fallbackBuild = false;
4417 QRhiIndirectCommandBufferBuildInfo buildInfo;
4418 int builtSlot = -1;
4419};
4420
4422{
4423 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
4424 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[i]);
4425 if (slot.icb) {
4429 e.stagingIcbBuffer.icb = slot.icb;
4430 e.stagingIcbBuffer.argBuffer = slot.argBuffer;
4431 rhiD->d->releaseQueue.append(e);
4432 }
4433 if (slot.rangeBuffer) {
4437 e.stagingBuffer.buffer = slot.rangeBuffer;
4438 rhiD->d->releaseQueue.append(e);
4439 }
4440 slot = {};
4441 }
4443}
4444
4447{
4449
4450 if (icbD->d->slotSetupFailed)
4451 return false;
4452
4453 if (icbD->d->fill == fill)
4454 return icbD->d->frameSlots[0].icb != nil;
4455
4458 icbD->d->gpuFilled = false;
4459 icbD->d->builtSlot = -1;
4460 }
4461
4462 if (!rhiD->caps.indirectCommandBuffers)
4463 return false;
4464
4465 const bool gpu = fill == QMetalIndirectCommandBufferData::Fill::Gpu;
4466 if (gpu && !rhiD->prepareIcbKernels())
4467 return false;
4468
4469 MTLIndirectCommandBufferDescriptor *icbDesc = [MTLIndirectCommandBufferDescriptor new];
4470 icbDesc.commandTypes = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws
4471 ? MTLIndirectCommandTypeDrawIndexed : MTLIndirectCommandTypeDraw;
4472 // All state except the primitive type and the index buffer is inherited.
4473 icbDesc.inheritPipelineState = YES;
4474 icbDesc.inheritBuffers = YES;
4475 icbDesc.maxVertexBufferBindCount = 0;
4476 icbDesc.maxFragmentBufferBindCount = 0;
4477
4478 bool ok = true;
4479 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT && ok; ++i) {
4480 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[i]);
4481 slot.icb = [rhiD->d->dev newIndirectCommandBufferWithDescriptor:icbDesc
4482 maxCommandCount:icbD->maxCommandCount()
4483 options:gpu ? MTLResourceStorageModePrivate
4484 : MTLResourceStorageModeShared];
4485 if (!slot.icb) {
4486 qWarning("Failed to create MTLIndirectCommandBuffer");
4487 ok = false;
4488 break;
4489 }
4490 if (gpu) {
4491 slot.rangeBuffer = [rhiD->d->dev newBufferWithLength:sizeof(MTLIndirectCommandBufferExecutionRange)
4492 options:MTLResourceStorageModePrivate];
4493 id<MTLArgumentEncoder> argEnc = [rhiD->d->icbEncodeFunction newArgumentEncoderWithBufferIndex:1];
4494 slot.argBuffer = [rhiD->d->dev newBufferWithLength:argEnc.encodedLength
4495 options:MTLResourceStorageModeShared];
4496 if (slot.rangeBuffer && slot.argBuffer) {
4497 [argEnc setArgumentBuffer:slot.argBuffer offset:0];
4498 [argEnc setIndirectCommandBuffer:slot.icb atIndex:0];
4499 } else {
4500 qWarning("Failed to create MTLIndirectCommandBuffer helper buffers");
4501 ok = false;
4502 }
4503 [argEnc release];
4504 if (!ok)
4505 break;
4506 }
4507 }
4508 [icbDesc release];
4509
4510 if (!ok) {
4511 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
4512 [icbD->d->frameSlots[i].icb release];
4513 [icbD->d->frameSlots[i].rangeBuffer release];
4514 [icbD->d->frameSlots[i].argBuffer release];
4515 icbD->d->frameSlots[i] = {};
4516 }
4517 icbD->d->slotSetupFailed = true;
4518 return false;
4519 }
4520
4521 icbD->d->fill = fill;
4522 return true;
4523}
4524
4526 quint32 maxCommandCount)
4529{
4530}
4531
4537
4539{
4540 clear();
4541
4542 if (!d->created)
4543 return;
4544
4545 d->slotSetupFailed = false;
4546 d->gpuFilled = false;
4547 d->fallbackBuild = false;
4548 d->buildInfo = {};
4549 d->builtSlot = -1;
4550 d->created = false;
4551 m_gpuBuilt = false;
4552 m_gpuBuiltCommandCount = 0;
4553
4554 QRHI_RES_RHI(QRhiMetal);
4555 if (rhiD) {
4557 rhiD->unregisterResource(this);
4558 }
4559}
4560
4562{
4563 if (d->created)
4564 destroy();
4565 else
4566 clear();
4567
4568 if (!m_maxCommandCount) {
4569 qWarning("QRhiIndirectCommandBuffer: maxCommandCount is 0");
4570 return false;
4571 }
4572
4573 QRHI_RES_RHI(QRhiMetal);
4575 d->created = true;
4576 rhiD->registerResource(this);
4577 return true;
4578}
4579
4580QRhiIndirectCommandBuffer *QRhiMetal::createIndirectCommandBuffer(QRhiIndirectCommandBuffer::Type type,
4581 quint32 maxCommandCount)
4582{
4583 return new QMetalIndirectCommandBuffer(this, type, maxCommandCount);
4584}
4585
4586void QRhiMetal::commitIndirectCommandBuffer(QRhiResourceUpdateBatch *u,
4587 QRhiIndirectCommandBuffer *icb)
4588{
4589 // Nothing to upload: executeIndirect() encodes straight into the ICB, and
4590 // only there are the primitive type and index buffer known.
4591 Q_UNUSED(u);
4592 Q_UNUSED(icb);
4593}
4594
4595// True when the slot holds an encoding that is already good for these
4596// contents and this state, and so does not need to be re-encoded.
4599 MTLPrimitiveType primitiveType,
4600 id<MTLBuffer> indexBufMtl, quint32 indexOffset,
4601 QRhiCommandBuffer::IndexFormat indexFormat)
4602{
4603 const bool indexed = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4604
4605 return slot.encoded
4606 && slot.generation == icbD->contentsGeneration()
4607 && slot.primitiveType == primitiveType
4608 && (!indexed || (slot.indexBuf == indexBufMtl
4609 && slot.indexOffset == indexOffset
4610 && slot.indexFormat == indexFormat));
4611}
4612
4613// Re-encodes the CPU-recorded commands unless the slot already matches.
4616 MTLPrimitiveType primitiveType,
4617 id<MTLBuffer> indexBufMtl, quint32 indexOffset,
4618 QRhiCommandBuffer::IndexFormat indexFormat)
4619{
4620 const bool indexed = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4621
4622 if (qrhimtl_icbSlotMatches(icbD, slot, primitiveType, indexBufMtl, indexOffset, indexFormat))
4623 return;
4624
4625 const quint32 count = icbD->recordedCommandCount();
4626 if (indexed) {
4627 const MTLIndexType indexType = indexFormat == QRhiCommandBuffer::IndexUInt16
4628 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
4629 const quint32 indexSize = indexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4;
4631 for (quint32 i = 0; i < count; ++i) {
4632 const QRhiIndexedIndirectDrawCommand &c(cmds[i]);
4633 id<MTLIndirectRenderCommand> rc = [slot.icb indirectRenderCommandAtIndex:i];
4634 [rc drawIndexedPrimitives:primitiveType
4635 indexCount:c.indexCount
4636 indexType:indexType
4637 indexBuffer:indexBufMtl
4638 indexBufferOffset:indexOffset + c.firstIndex * indexSize
4639 instanceCount:c.instanceCount
4640 baseVertex:c.vertexOffset
4641 baseInstance:c.firstInstance];
4642 }
4643 } else {
4644 const QRhiIndirectDrawCommand *cmds = icbD->drawCommands();
4645 for (quint32 i = 0; i < count; ++i) {
4646 const QRhiIndirectDrawCommand &c(cmds[i]);
4647 id<MTLIndirectRenderCommand> rc = [slot.icb indirectRenderCommandAtIndex:i];
4648 [rc drawPrimitives:primitiveType
4649 vertexStart:c.firstVertex
4650 vertexCount:c.vertexCount
4651 instanceCount:c.instanceCount
4652 baseInstance:c.firstInstance];
4653 }
4654 }
4655
4656 // Commands past the current count may be left over from a longer batch.
4657 if (count < icbD->maxCommandCount())
4658 [slot.icb resetWithRange:NSMakeRange(count, icbD->maxCommandCount() - count)];
4659
4660 slot.generation = icbD->contentsGeneration();
4661 slot.primitiveType = primitiveType;
4662 slot.indexBuf = indexBufMtl;
4663 slot.indexOffset = indexOffset;
4664 slot.indexFormat = indexFormat;
4665 slot.encoded = true;
4666}
4667
4668// Fallback for when there is no usable ICB.
4670 quint32 firstCommand, quint32 count,
4671 int currentFrameSlot)
4672{
4673 const MTLPrimitiveType primitiveType = cbD->currentGraphicsPipeline->d->primitiveType;
4674
4675 if (icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws) {
4676 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
4677 if (!indexBufD)
4678 return;
4679 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4680 const MTLIndexType indexType = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16
4681 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
4682 const quint32 indexSize = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4;
4684 for (quint32 i = 0; i < count; ++i) {
4685 const QRhiIndexedIndirectDrawCommand &c(cmds[firstCommand + i]);
4686 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: primitiveType
4687 indexCount: c.indexCount
4688 indexType: indexType
4689 indexBuffer: indexBufMtl
4690 indexBufferOffset: cbD->currentIndexOffset + c.firstIndex * indexSize
4691 instanceCount: c.instanceCount
4692 baseVertex: c.vertexOffset
4693 baseInstance: c.firstInstance];
4694 }
4695 } else {
4696 const QRhiIndirectDrawCommand *cmds = icbD->drawCommands();
4697 for (quint32 i = 0; i < count; ++i) {
4698 const QRhiIndirectDrawCommand &c(cmds[firstCommand + i]);
4699 [cbD->d->currentRenderPassEncoder drawPrimitives: primitiveType
4700 vertexStart: c.firstVertex
4701 vertexCount: c.vertexCount
4702 instanceCount: c.instanceCount
4703 baseInstance: c.firstInstance];
4704 }
4705 }
4706}
4707
4708void QRhiMetal::executeIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb,
4709 quint32 firstCommand, quint32 commandCount)
4710{
4711 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4713
4714 QMetalIndirectCommandBuffer *icbD = QRHI_RES(QMetalIndirectCommandBuffer, icb);
4715 icbD->lastActiveFrameSlot = currentFrameSlot;
4716
4717 const bool indexed = icb->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4718
4719 if (icbD->d->gpuFilled) {
4720 if (icbD->d->buildInfo.topology != cbD->currentGraphicsPipeline->topology()) {
4721 qWarning("executeIndirect: the indirect command buffer was built for a different "
4722 "topology than the current graphics pipeline uses; skipping");
4723 return;
4724 }
4725 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[icbD->d->builtSlot]);
4726
4727 const quint32 total = icbD->commandCount();
4728 const bool deviceCount = icbD->d->buildInfo.countBuffer != nullptr;
4729 NSRange range = NSMakeRange(0, total);
4730 if (deviceCount) {
4731 // The count is only known to the device, and applying it is what
4732 // the compute kernel-written range is for. That form of
4733 // executeCommandsInBuffer takes no CPU-side range, so a subrange
4734 // and a device-side count cannot be combined.
4735 if (firstCommand != 0 || commandCount < total) {
4736 qWarning("executeIndirect: firstCommand and commandCount cannot be honoured "
4737 "together with a device-side count; executing all %u command(s)", total);
4738 }
4739 } else {
4740 if (firstCommand >= total)
4741 return;
4742 const quint32 count = qMin(commandCount, total - firstCommand);
4743 if (!count)
4744 return;
4745 range = NSMakeRange(firstCommand, count);
4746 }
4747
4748 if (indexed && icbD->d->buildInfo.indexBuffer) {
4749 QMetalBuffer *indexBufD = QRHI_RES(QMetalBuffer, icbD->d->buildInfo.indexBuffer);
4750 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4751 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
4752 usage:MTLResourceUsageRead
4753 stages:MTLRenderStageVertex | MTLRenderStageFragment];
4754 }
4755 if (deviceCount) {
4756 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb
4757 indirectBuffer:slot.rangeBuffer
4758 indirectBufferOffset:0];
4759 } else {
4760 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb withRange:range];
4761 }
4762 return;
4763 }
4764
4765 if (icbD->d->fallbackBuild) {
4766 const QRhiIndirectCommandBufferBuildInfo &info(icbD->d->buildInfo);
4767 if (info.countBuffer) {
4768 qWarning("executeIndirect: a device-side count needs an indirect command buffer, "
4769 "which is not available here; skipping");
4770 return;
4771 }
4772 const quint32 canonicalStride = indexed ? sizeof(QRhiIndexedIndirectDrawCommand)
4773 : sizeof(QRhiIndirectDrawCommand);
4774 const quint32 stride = info.stride ? info.stride : canonicalStride;
4775 const quint32 total = icbD->commandCount();
4776 if (firstCommand >= total)
4777 return;
4778 const quint32 count = qMin(commandCount, total - firstCommand);
4779 if (!count)
4780 return;
4781 const quint32 offset = info.sourceBufferOffset + firstCommand * stride;
4782 if (indexed)
4783 drawIndexedIndirect(cb, info.sourceBuffer, offset, count, stride);
4784 else
4785 drawIndirect(cb, info.sourceBuffer, offset, count, stride);
4786 return;
4787 }
4788
4789 const quint32 total = icbD->recordedCommandCount();
4790 if (firstCommand >= total)
4791 return;
4792 const quint32 count = qMin(commandCount, total - firstCommand);
4793 if (!count)
4794 return;
4795
4798 {
4799 qrhimtl_replayIcbOnCpu(cbD, icbD, firstCommand, count, currentFrameSlot);
4800 return;
4801 }
4802 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[currentFrameSlot]);
4803
4804 id<MTLBuffer> indexBufMtl = nil;
4805 quint32 indexOffset = 0;
4806 if (indexed) {
4807 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
4808 if (!indexBufD)
4809 return;
4810 indexBufD->lastActiveFrameSlot = currentFrameSlot;
4811 indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4812 indexOffset = cbD->currentIndexOffset;
4813 }
4814
4815 const MTLPrimitiveType primitiveType = cbD->currentGraphicsPipeline->d->primitiveType;
4816 if (slot.usedInFrameId == d->globalFrameId
4817 && !qrhimtl_icbSlotMatches(icbD, slot, primitiveType, indexBufMtl, indexOffset,
4818 cbD->currentIndexFormat))
4819 {
4820 // An executeCommandsInBuffer recorded earlier in this frame references
4821 // this slot, and re-encoding it now would change what that one executes
4822 // once submitted. Replaying as ordinary draws keeps both correct.
4823 qWarning("executeIndirect: the same indirect command buffer is executed more than once "
4824 "in a frame, with different contents, topology or index buffer state; "
4825 "falling back to individual draw calls");
4826 qrhimtl_replayIcbOnCpu(cbD, icbD, firstCommand, count, currentFrameSlot);
4827 return;
4828 }
4829
4830 qrhimtl_encodeIcbFromCpu(icbD, slot, primitiveType,
4831 indexBufMtl, indexOffset, cbD->currentIndexFormat);
4832
4833 if (indexed) {
4834 // Index buffer is not inherited from the encoder, and the ICB commands reference it directly,
4835 // so it needs the useResource.
4836 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
4837 usage:MTLResourceUsageRead
4838 stages:MTLRenderStageVertex | MTLRenderStageFragment];
4839 }
4840 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb
4841 withRange:NSMakeRange(firstCommand, count)];
4842 slot.usedInFrameId = d->globalFrameId;
4843}
4844
4845void QRhiMetal::buildIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb,
4846 const QRhiIndirectCommandBufferBuildInfo &info)
4847{
4848 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4850
4851 QMetalIndirectCommandBuffer *icbD = QRHI_RES(QMetalIndirectCommandBuffer, icb);
4852 icbD->lastActiveFrameSlot = currentFrameSlot;
4853
4854 const bool indexed = icb->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4855
4856 if (indexed && !info.indexBuffer) {
4857 qWarning("buildIndirect: an IndexedDraws indirect command buffer needs an "
4858 "index buffer in QRhiIndirectCommandBufferBuildInfo; skipping");
4859 return;
4860 }
4861
4862 quint32 count = info.commandCount ? info.commandCount : icbD->m_maxCommandCount;
4863 if (count > icbD->m_maxCommandCount) {
4864 qWarning("QRhiIndirectCommandBuffer: buildIndirect() with commandCount %u exceeds "
4865 "maxCommandCount %u; clamping", count, icbD->m_maxCommandCount);
4866 count = icbD->m_maxCommandCount;
4867 }
4868 icbD->m_gpuBuilt = true;
4869 icbD->m_gpuBuiltCommandCount = count;
4870
4872 icbD->d->gpuFilled = false;
4873 icbD->d->fallbackBuild = true;
4874 icbD->d->buildInfo = info;
4875 icbD->d->builtSlot = -1;
4876 return;
4877 }
4878
4879 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[currentFrameSlot]);
4880
4881 QMetalBuffer *srcBufD = QRHI_RES(QMetalBuffer, info.sourceBuffer);
4883 srcBufD->lastActiveFrameSlot = currentFrameSlot;
4884 id<MTLBuffer> srcBufMtl = srcBufD->d->buf[srcBufD->d->slotted ? currentFrameSlot : 0];
4885
4886 id<MTLBuffer> indexBufMtl = nil;
4887 if (indexed) {
4888 QMetalBuffer *indexBufD = QRHI_RES(QMetalBuffer, info.indexBuffer);
4889 indexBufD->lastActiveFrameSlot = currentFrameSlot;
4890 indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4891 }
4892
4893 id<MTLBuffer> countBufMtl = nil;
4894 if (info.countBuffer) {
4895 QMetalBuffer *countBufD = QRHI_RES(QMetalBuffer, info.countBuffer);
4897 countBufD->lastActiveFrameSlot = currentFrameSlot;
4898 countBufMtl = countBufD->d->buf[countBufD->d->slotted ? currentFrameSlot : 0];
4899 }
4900
4901 const quint32 stride = info.stride ? info.stride
4902 : (indexed ? sizeof(QRhiIndexedIndirectDrawCommand)
4903 : sizeof(QRhiIndirectDrawCommand));
4904
4905 // Outside a render pass, so the compute encoder costs no interruption, no
4906 // store action juggling and no per-pass state to restore.
4907 id<MTLComputeCommandEncoder> computeEncoder = [cbD->d->cb computeCommandEncoder];
4908 encodeIcbWithCompute(d, computeEncoder, slot.icb, slot.argBuffer, slot.rangeBuffer,
4909 indexed, info.indexFormat,
4910 toMetalPrimitiveType(info.topology),
4911 srcBufMtl, info.sourceBufferOffset,
4912 indexBufMtl, info.indexBufferOffset,
4913 countBufMtl, info.countBufferOffset,
4914 icbD->commandCount(), stride);
4915 [computeEncoder endEncoding];
4916
4917 icbD->d->gpuFilled = true;
4918 icbD->d->fallbackBuild = false;
4919 icbD->d->buildInfo = info;
4920 icbD->d->builtSlot = currentFrameSlot;
4921 // The CPU-encoded contents of this slot, if any, are gone now.
4922 slot.encoded = false;
4923}
4924
4926{
4927 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
4928 [e.buffer.buffers[i] release];
4929}
4930
4932{
4933 [e.renderbuffer.texture release];
4934}
4935
4937{
4938 [e.texture.texture release];
4939 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
4940 [e.texture.stagingBuffers[i] release];
4941 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
4942 [e.texture.views[i] release];
4943 [e.texture.samplingView release];
4944 [e.texture.writeView release];
4945}
4946
4948{
4949 [e.sampler.samplerState release];
4950}
4951
4953{
4954 qsizetype keepBegin = d->releaseQueue.size();
4955 for (qsizetype i = keepBegin - 1; i >= 0; --i) {
4956 const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
4957 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
4958 switch (e.type) {
4961 break;
4964 break;
4967 break;
4970 break;
4971 case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
4972 [e.stagingBuffer.buffer release];
4973 break;
4974 case QRhiMetalData::DeferredReleaseEntry::GraphicsPipeline:
4975 [e.graphicsPipeline.pipelineState release];
4976 [e.graphicsPipeline.depthStencilState release];
4977 [e.graphicsPipeline.tessVertexComputeState[0] release];
4978 [e.graphicsPipeline.tessVertexComputeState[1] release];
4979 [e.graphicsPipeline.tessVertexComputeState[2] release];
4980 [e.graphicsPipeline.tessTessControlComputeState release];
4981 break;
4982 case QRhiMetalData::DeferredReleaseEntry::ComputePipeline:
4983 [e.computePipeline.pipelineState release];
4984 break;
4985 case QRhiMetalData::DeferredReleaseEntry::ShadingRateMap:
4986 [e.shadingRateMap.rateMap release];
4987 break;
4988 case QRhiMetalData::DeferredReleaseEntry::StagingIcbBuffer:
4989 [e.stagingIcbBuffer.icb release];
4990 [e.stagingIcbBuffer.argBuffer release];
4991 break;
4992 default:
4993 break;
4994 }
4995 } else if (--keepBegin != i) {
4996 d->releaseQueue[keepBegin] = std::move(d->releaseQueue[i]);
4997 }
4998 }
4999 if (keepBegin)
5000 d->releaseQueue.remove(0, keepBegin);
5001}
5002
5004{
5005 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
5006
5007 for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) {
5008 const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]);
5009 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
5010 readback.result->format = readback.format;
5011 readback.result->pixelSize = readback.pixelSize;
5012 readback.result->data.resize(int(readback.bufSize));
5013 const void *p = [readback.buf contents];
5014 if (readback.depthPlaneByteSize) {
5015 // The staging buffer holds a densely packed depth plane, followed
5016 // by a densely packed stencil plane when the format has stencil
5017 // (see enqueueResourceUpdates()). Convert into the layout QRhi
5018 // guarantees: D16 is a 16-bit unorm value, D24 and D24S8 have the
5019 // depth in the upper 24 bits and the stencil (or zero) in the lower
5020 // 8 bits, and D32FS8 is the float depth followed by a 32-bit word
5021 // with the stencil in its low 8 bits.
5022 const quint32 pixelCount = quint32(readback.pixelSize.width()) * quint32(readback.pixelSize.height());
5023 const char *depthPlane = static_cast<const char *>(p);
5024 const quint8 *stencilPlane = reinterpret_cast<const quint8 *>(depthPlane + readback.depthPlaneByteSize);
5025 char *dst = readback.result->data.data();
5026 for (quint32 px = 0; px < pixelCount; ++px) {
5027 const quint32 depthWord = qFromUnaligned<quint32>(depthPlane + px * 4);
5028 const float depthF = readback.depthPlaneIsFloat ? qFromUnaligned<float>(depthPlane + px * 4) : 0.0f;
5029 const quint32 stencil = readback.stencilPlaneByteSize ? stencilPlane[px] : 0;
5030 switch (readback.format) {
5031 case QRhiTexture::D16:
5032 qToUnaligned(quint16(qRound(qBound(0.0f, depthF, 1.0f) * 65535.0f)), dst + px * 2);
5033 break;
5034 case QRhiTexture::D32FS8:
5035 qToUnaligned(depthWord, dst + px * 8);
5036 qToUnaligned(stencil, dst + px * 8 + 4);
5037 break;
5038 default: // D24, D24S8
5039 {
5040 const quint32 depth24 = readback.depthPlaneIsFloat
5041 ? quint32(qBound(0.0f, depthF, 1.0f) * 16777215.0f + 0.5f)
5042 : depthWord & 0x00FFFFFFu;
5043 qToUnaligned((depth24 << 8) | stencil, dst + px * 4);
5044 }
5045 break;
5046 }
5047 }
5048 } else {
5049 memcpy(readback.result->data.data(), p, readback.bufSize);
5050 }
5051 [readback.buf release];
5052
5053 if (readback.result->completed)
5054 completedCallbacks.append(readback.result->completed);
5055
5056 d->activeTextureReadbacks.remove(i);
5057 }
5058 }
5059
5060 for (int i = d->activeBufferReadbacks.count() - 1; i >= 0; --i) {
5061 const QRhiMetalData::BufferReadback &readback(d->activeBufferReadbacks[i]);
5062 if (forced || currentFrameSlot == readback.activeFrameSlot
5063 || readback.activeFrameSlot < 0) {
5064 readback.result->data.resize(readback.readSize);
5065 char *p = reinterpret_cast<char *>([readback.buf contents]);
5066 Q_ASSERT(p);
5067 memcpy(readback.result->data.data(), p, size_t(readback.readSize));
5068 [readback.buf release];
5069
5070 if (readback.result->completed)
5071 completedCallbacks.append(readback.result->completed);
5072
5073 d->activeBufferReadbacks.remove(i);
5074 }
5075 }
5076
5077 for (auto f : completedCallbacks)
5078 f();
5079}
5080
5081QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
5083 d(new QMetalBufferData)
5084{
5085 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
5086 d->buf[i] = nil;
5087}
5088
5090{
5091 destroy();
5092 delete d;
5093}
5094
5096{
5097 if (!d->buf[0])
5098 return;
5099
5103
5104 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5105 e.buffer.buffers[i] = d->buf[i];
5106 d->buf[i] = nil;
5107 d->pendingUpdates[i].clear();
5108 }
5109
5110 QRHI_RES_RHI(QRhiMetal);
5111 if (rhiD) {
5112 rhiD->d->releaseQueue.append(e);
5113 rhiD->unregisterResource(this);
5114 }
5115}
5116
5118{
5119 if (d->buf[0])
5120 destroy();
5121
5122 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
5123 qWarning("StorageBuffer cannot be combined with Dynamic");
5124 return false;
5125 }
5126
5127 const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
5128 const quint32 roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256u) : nonZeroSize;
5129
5130 d->managed = false;
5131 d->isDeviceLocal = false;
5132 MTLResourceOptions opts = MTLResourceStorageModeShared;
5133
5134 QRHI_RES_RHI(QRhiMetal);
5135
5136 const bool internalHostWritable = (int(m_usage) & (WorkBufPoolUsage | InternalHostWritable)) != 0;
5137
5138 if (m_type != Dynamic && !internalHostWritable && rhiD->caps.usePrivateStaticBuffers) {
5139 opts = MTLResourceStorageModePrivate;
5140 d->isDeviceLocal = true;
5141 d->slotted = false;
5142 } else {
5143#ifdef Q_OS_MACOS
5144 if (!rhiD->caps.isAppleGPU && m_type != Dynamic) {
5145 opts = MTLResourceStorageModeManaged;
5146 d->managed = true;
5147 }
5148#endif
5149 // Have QMTL_FRAMES_IN_FLIGHT versions regardless of the type. This is
5150 // because writing to a host visible buffer is not safe when another
5151 // frame reading from the same buffer is still in flight.
5152 d->slotted = !m_usage.testFlag(QRhiBuffer::StorageBuffer) // except for SSBOs written in the shader
5153 && !internalHostWritable; // and the internal buffers
5154 }
5155
5156 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5157 if (i == 0 || d->slotted) {
5158 d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
5159 if (!m_objectName.isEmpty()) {
5160 if (!d->slotted) {
5161 d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
5162 } else {
5163 const QByteArray name = m_objectName + '/' + QByteArray::number(i);
5164 d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
5165 }
5166 }
5167 }
5168 }
5169
5171 generation += 1;
5172 rhiD->registerResource(this);
5173 return true;
5174}
5175
5177{
5178 if (d->slotted) {
5179 NativeBuffer b;
5180 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT));
5181 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5182 QRHI_RES_RHI(QRhiMetal);
5184 b.objects[i] = &d->buf[i];
5185 }
5186 b.slotCount = QMTL_FRAMES_IN_FLIGHT;
5187 return b;
5188 }
5189 if (!d->isDeviceLocal) {
5190 QRHI_RES_RHI(QRhiMetal);
5192 }
5193 return { { &d->buf[0] }, 1 };
5194}
5195
5197{
5198 // Shortcut the entire buffer update mechanism and allow the client to do
5199 // the host writes directly to the buffer. This will lead to unexpected
5200 // results when combined with QRhiResourceUpdateBatch-based updates for the
5201 // buffer, but provides a fast path for dynamic buffers that have all their
5202 // content changed in every frame.
5203 Q_ASSERT(m_type == Dynamic);
5204 Q_ASSERT(!d->isDeviceLocal);
5205 QRHI_RES_RHI(QRhiMetal);
5206 Q_ASSERT(rhiD->inFrame);
5207 const int slot = d->slotted ? rhiD->currentFrameSlot : 0;
5208 void *p = [d->buf[slot] contents];
5209 return static_cast<char *>(p);
5210}
5211
5213{
5214 // Nothing to do: managed is never set for Dynamic buffers, and so there is
5215 // no didModifyRange: to issue.
5216}
5217
5218static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags, const QRhiMetal *d)
5219{
5220#ifndef Q_OS_MACOS
5221 Q_UNUSED(d);
5222#endif
5223
5224 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
5225 switch (format) {
5226 case QRhiTexture::RGBA8:
5227 return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
5228 case QRhiTexture::BGRA8:
5229 return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
5230 case QRhiTexture::R8:
5231#ifdef Q_OS_MACOS
5232 return MTLPixelFormatR8Unorm;
5233#else
5234 return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
5235#endif
5236 case QRhiTexture::R8SI:
5237 return MTLPixelFormatR8Sint;
5238 case QRhiTexture::R8UI:
5239 return MTLPixelFormatR8Uint;
5240 case QRhiTexture::RG8:
5241#ifdef Q_OS_MACOS
5242 return MTLPixelFormatRG8Unorm;
5243#else
5244 return srgb ? MTLPixelFormatRG8Unorm_sRGB : MTLPixelFormatRG8Unorm;
5245#endif
5246 case QRhiTexture::R16:
5247 return MTLPixelFormatR16Unorm;
5248 case QRhiTexture::RG16:
5249 return MTLPixelFormatRG16Unorm;
5250 case QRhiTexture::RED_OR_ALPHA8:
5251 return MTLPixelFormatR8Unorm;
5252
5253 case QRhiTexture::RGBA16F:
5254 return MTLPixelFormatRGBA16Float;
5255 case QRhiTexture::RGBA32F:
5256 return MTLPixelFormatRGBA32Float;
5257 case QRhiTexture::R16F:
5258 return MTLPixelFormatR16Float;
5259 case QRhiTexture::R32F:
5260 return MTLPixelFormatR32Float;
5261
5262 case QRhiTexture::RGB10A2:
5263 return MTLPixelFormatRGB10A2Unorm;
5264
5265 case QRhiTexture::R32SI:
5266 return MTLPixelFormatR32Sint;
5267 case QRhiTexture::R32UI:
5268 return MTLPixelFormatR32Uint;
5269 case QRhiTexture::RG32SI:
5270 return MTLPixelFormatRG32Sint;
5271 case QRhiTexture::RG32UI:
5272 return MTLPixelFormatRG32Uint;
5273 case QRhiTexture::RGBA32SI:
5274 return MTLPixelFormatRGBA32Sint;
5275 case QRhiTexture::RGBA32UI:
5276 return MTLPixelFormatRGBA32Uint;
5277
5278#ifdef Q_OS_MACOS
5279 case QRhiTexture::D16:
5280 return MTLPixelFormatDepth16Unorm;
5281 case QRhiTexture::D24:
5282 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
5283 case QRhiTexture::D24S8:
5284 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
5285#else
5286 case QRhiTexture::D16:
5287 return MTLPixelFormatDepth32Float;
5288 case QRhiTexture::D24:
5289 return MTLPixelFormatDepth32Float;
5290 case QRhiTexture::D24S8:
5291 return MTLPixelFormatDepth32Float_Stencil8;
5292#endif
5293 case QRhiTexture::D32F:
5294 return MTLPixelFormatDepth32Float;
5295 case QRhiTexture::D32FS8:
5296 return MTLPixelFormatDepth32Float_Stencil8;
5297
5298#ifdef Q_OS_MACOS
5299 case QRhiTexture::BC1:
5300 return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
5301 case QRhiTexture::BC2:
5302 return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
5303 case QRhiTexture::BC3:
5304 return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
5305 case QRhiTexture::BC4:
5306 return MTLPixelFormatBC4_RUnorm;
5307 case QRhiTexture::BC5:
5308 qWarning("QRhiMetal does not support BC5");
5309 return MTLPixelFormatInvalid;
5310 case QRhiTexture::BC6H:
5311 return MTLPixelFormatBC6H_RGBUfloat;
5312 case QRhiTexture::BC7:
5313 return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
5314#else
5315 case QRhiTexture::BC1:
5316 case QRhiTexture::BC2:
5317 case QRhiTexture::BC3:
5318 case QRhiTexture::BC4:
5319 case QRhiTexture::BC5:
5320 case QRhiTexture::BC6H:
5321 case QRhiTexture::BC7:
5322 qWarning("QRhiMetal: BCx compression not supported on this platform");
5323 return MTLPixelFormatInvalid;
5324#endif
5325
5326#ifndef Q_OS_MACOS
5327 case QRhiTexture::ETC2_RGB8:
5328 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
5329 case QRhiTexture::ETC2_RGB8A1:
5330 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
5331 case QRhiTexture::ETC2_RGBA8:
5332 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
5333
5334 case QRhiTexture::ASTC_4x4:
5335 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
5336 case QRhiTexture::ASTC_5x4:
5337 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
5338 case QRhiTexture::ASTC_5x5:
5339 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
5340 case QRhiTexture::ASTC_6x5:
5341 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
5342 case QRhiTexture::ASTC_6x6:
5343 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
5344 case QRhiTexture::ASTC_8x5:
5345 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
5346 case QRhiTexture::ASTC_8x6:
5347 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
5348 case QRhiTexture::ASTC_8x8:
5349 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
5350 case QRhiTexture::ASTC_10x5:
5351 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
5352 case QRhiTexture::ASTC_10x6:
5353 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
5354 case QRhiTexture::ASTC_10x8:
5355 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
5356 case QRhiTexture::ASTC_10x10:
5357 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
5358 case QRhiTexture::ASTC_12x10:
5359 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
5360 case QRhiTexture::ASTC_12x12:
5361 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
5362#else
5363 case QRhiTexture::ETC2_RGB8:
5364 if (d->caps.isAppleGPU)
5365 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
5366 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5367 return MTLPixelFormatInvalid;
5368 case QRhiTexture::ETC2_RGB8A1:
5369 if (d->caps.isAppleGPU)
5370 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
5371 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5372 return MTLPixelFormatInvalid;
5373 case QRhiTexture::ETC2_RGBA8:
5374 if (d->caps.isAppleGPU)
5375 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
5376 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5377 return MTLPixelFormatInvalid;
5378 case QRhiTexture::ASTC_4x4:
5379 if (d->caps.isAppleGPU)
5380 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
5381 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5382 return MTLPixelFormatInvalid;
5383 case QRhiTexture::ASTC_5x4:
5384 if (d->caps.isAppleGPU)
5385 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
5386 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5387 return MTLPixelFormatInvalid;
5388 case QRhiTexture::ASTC_5x5:
5389 if (d->caps.isAppleGPU)
5390 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
5391 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5392 return MTLPixelFormatInvalid;
5393 case QRhiTexture::ASTC_6x5:
5394 if (d->caps.isAppleGPU)
5395 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
5396 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5397 return MTLPixelFormatInvalid;
5398 case QRhiTexture::ASTC_6x6:
5399 if (d->caps.isAppleGPU)
5400 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
5401 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5402 return MTLPixelFormatInvalid;
5403 case QRhiTexture::ASTC_8x5:
5404 if (d->caps.isAppleGPU)
5405 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
5406 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5407 return MTLPixelFormatInvalid;
5408 case QRhiTexture::ASTC_8x6:
5409 if (d->caps.isAppleGPU)
5410 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
5411 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5412 return MTLPixelFormatInvalid;
5413 case QRhiTexture::ASTC_8x8:
5414 if (d->caps.isAppleGPU)
5415 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
5416 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5417 return MTLPixelFormatInvalid;
5418 case QRhiTexture::ASTC_10x5:
5419 if (d->caps.isAppleGPU)
5420 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
5421 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5422 return MTLPixelFormatInvalid;
5423 case QRhiTexture::ASTC_10x6:
5424 if (d->caps.isAppleGPU)
5425 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
5426 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5427 return MTLPixelFormatInvalid;
5428 case QRhiTexture::ASTC_10x8:
5429 if (d->caps.isAppleGPU)
5430 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
5431 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5432 return MTLPixelFormatInvalid;
5433 case QRhiTexture::ASTC_10x10:
5434 if (d->caps.isAppleGPU)
5435 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
5436 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5437 return MTLPixelFormatInvalid;
5438 case QRhiTexture::ASTC_12x10:
5439 if (d->caps.isAppleGPU)
5440 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
5441 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5442 return MTLPixelFormatInvalid;
5443 case QRhiTexture::ASTC_12x12:
5444 if (d->caps.isAppleGPU)
5445 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
5446 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5447 return MTLPixelFormatInvalid;
5448#endif
5449
5450 default:
5451 Q_UNREACHABLE();
5452 return MTLPixelFormatInvalid;
5453 }
5454}
5455
5456QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
5457 int sampleCount, QRhiRenderBuffer::Flags flags,
5458 QRhiTexture::Format backingFormatHint)
5461{
5462}
5463
5465{
5466 destroy();
5467 delete d;
5468}
5469
5471{
5472 if (!d->tex)
5473 return;
5474
5478
5479 e.renderbuffer.texture = d->tex;
5480 d->tex = nil;
5481
5482 QRHI_RES_RHI(QRhiMetal);
5483 if (rhiD) {
5484 rhiD->d->releaseQueue.append(e);
5485 rhiD->unregisterResource(this);
5486 }
5487}
5488
5490{
5491 if (d->tex)
5492 destroy();
5493
5494 if (m_pixelSize.isEmpty())
5495 return false;
5496
5497 QRHI_RES_RHI(QRhiMetal);
5498 samples = rhiD->effectiveSampleCount(m_sampleCount);
5499
5500 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
5501 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
5502 desc.width = NSUInteger(m_pixelSize.width());
5503 desc.height = NSUInteger(m_pixelSize.height());
5504 if (samples > 1)
5505 desc.sampleCount = NSUInteger(samples);
5506 desc.resourceOptions = MTLResourceStorageModePrivate;
5507 desc.usage = MTLTextureUsageRenderTarget;
5508
5509 // Memoryless contents cannot survive the render pass getting interrupted and
5510 // continued on another command encoder, which is what NoTransientBacking is
5511 // there to avoid.
5512 const bool canBeMemoryless = !m_flags.testFlag(QRhiRenderBuffer::NoTransientBacking);
5513
5514 switch (m_type) {
5515 case DepthStencil:
5516#ifdef Q_OS_MACOS
5517 if (rhiD->caps.isAppleGPU && canBeMemoryless) {
5518 desc.storageMode = MTLStorageModeMemoryless;
5519 d->format = MTLPixelFormatDepth32Float_Stencil8;
5520 } else {
5521 desc.storageMode = MTLStorageModePrivate;
5522 d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
5523 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
5524 }
5525#else
5526 desc.storageMode = canBeMemoryless ? MTLStorageModeMemoryless : MTLStorageModePrivate;
5527 d->format = MTLPixelFormatDepth32Float_Stencil8;
5528#endif
5529 desc.pixelFormat = d->format;
5530 break;
5531 case Color:
5532 desc.storageMode = MTLStorageModePrivate;
5533 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5534 d->format = toMetalTextureFormat(m_backingFormatHint, {}, rhiD);
5535 else
5536 d->format = MTLPixelFormatRGBA8Unorm;
5537 desc.pixelFormat = d->format;
5538 break;
5539 default:
5540 Q_UNREACHABLE();
5541 break;
5542 }
5543
5544 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
5545 [desc release];
5546
5547 if (!m_objectName.isEmpty())
5548 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
5549
5551 generation += 1;
5552 rhiD->registerResource(this);
5553 return true;
5554}
5555
5557{
5558 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5559 return m_backingFormatHint;
5560 else
5561 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
5562}
5563
5564QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
5565 int arraySize, int sampleCount, Flags flags)
5567 d(new QMetalTextureData(this))
5568{
5569 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
5570 d->stagingBuf[i] = nil;
5571
5572 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
5573 d->perLevelViews[i] = nil;
5574}
5575
5577{
5578 destroy();
5579 delete d;
5580}
5581
5583{
5584 if (!d->tex)
5585 return;
5586
5590
5591 e.texture.texture = d->owns ? d->tex : nil;
5592 d->tex = nil;
5593
5594 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5595 e.texture.stagingBuffers[i] = d->stagingBuf[i];
5596 d->stagingBuf[i] = nil;
5597 }
5598
5599 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
5600 e.texture.views[i] = d->perLevelViews[i];
5601 d->perLevelViews[i] = nil;
5602 }
5603
5604 e.texture.samplingView = d->samplingView;
5605 d->samplingView = nil;
5606 e.texture.writeView = d->writeView;
5607 d->writeView = nil;
5608
5609 QRHI_RES_RHI(QRhiMetal);
5610 if (rhiD) {
5611 rhiD->d->releaseQueue.append(e);
5612 rhiD->unregisterResource(this);
5613 }
5614}
5615
5616bool QMetalTexture::prepareCreate(QSize *adjustedSize)
5617{
5618 if (d->tex)
5619 destroy();
5620
5621 const bool isCube = m_flags.testFlag(CubeMap);
5622 const bool is3D = m_flags.testFlag(ThreeDimensional);
5623 const bool isArray = m_flags.testFlag(TextureArray);
5624 const bool hasMipMaps = m_flags.testFlag(MipMapped);
5625 const bool is1D = m_flags.testFlag(OneDimensional);
5626
5627 const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
5628 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
5629
5630 QRHI_RES_RHI(QRhiMetal);
5631 d->format = toMetalTextureFormat(m_format, m_flags, rhiD);
5632 if (m_writeViewFormat.format != UnknownFormat) {
5633 d->viewFormat = toMetalTextureFormat(m_writeViewFormat.format,
5634 m_writeViewFormat.srgb ? sRGB : Flags(), rhiD);
5635 } else {
5636 d->viewFormat = d->format;
5637 }
5638 if (m_readViewFormat.format != UnknownFormat) {
5639 d->viewFormatForSampling = toMetalTextureFormat(m_readViewFormat.format,
5640 m_readViewFormat.srgb ? sRGB : Flags(), rhiD);
5641 } else {
5642 d->viewFormatForSampling = d->format;
5643 }
5644 mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
5645 samples = rhiD->effectiveSampleCount(m_sampleCount);
5646 if (samples > 1) {
5647 if (isCube) {
5648 qWarning("Cubemap texture cannot be multisample");
5649 return false;
5650 }
5651 if (is3D) {
5652 qWarning("3D texture cannot be multisample");
5653 return false;
5654 }
5655 if (hasMipMaps) {
5656 qWarning("Multisample texture cannot have mipmaps");
5657 return false;
5658 }
5659 }
5660 if (isCube && is3D) {
5661 qWarning("Texture cannot be both cube and 3D");
5662 return false;
5663 }
5664 if (isArray && is3D) {
5665 qWarning("Texture cannot be both array and 3D");
5666 return false;
5667 }
5668 if (is1D && is3D) {
5669 qWarning("Texture cannot be both 1D and 3D");
5670 return false;
5671 }
5672 if (is1D && isCube) {
5673 qWarning("Texture cannot be both 1D and cube");
5674 return false;
5675 }
5676 if (m_depth > 1 && !is3D) {
5677 qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
5678 return false;
5679 }
5680 if (m_arraySize > 0 && !isArray) {
5681 qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
5682 return false;
5683 }
5684 if (m_arraySize < 1 && isArray) {
5685 qWarning("Texture is an array but array size is %d", m_arraySize);
5686 return false;
5687 }
5688
5689 if (!rhiD->textureFormatInfo(m_format, size, nullptr, nullptr, nullptr))
5690 return false;
5691
5692 if (adjustedSize)
5693 *adjustedSize = size;
5694
5695 return true;
5696}
5697
5699{
5700 QSize size;
5701 if (!prepareCreate(&size))
5702 return false;
5703
5704 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
5705
5706 const bool isCube = m_flags.testFlag(CubeMap);
5707 const bool is3D = m_flags.testFlag(ThreeDimensional);
5708 const bool isArray = m_flags.testFlag(TextureArray);
5709 const bool is1D = m_flags.testFlag(OneDimensional);
5710 if (isCube) {
5711 desc.textureType = MTLTextureTypeCube;
5712 } else if (is3D) {
5713 desc.textureType = MTLTextureType3D;
5714 } else if (is1D) {
5715 desc.textureType = isArray ? MTLTextureType1DArray : MTLTextureType1D;
5716 } else if (isArray) {
5717 desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
5718 } else {
5719 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
5720 }
5721 desc.pixelFormat = d->format;
5722 desc.width = NSUInteger(size.width());
5723 desc.height = NSUInteger(size.height());
5724 desc.depth = is3D ? qMax(1, m_depth) : 1;
5725 desc.mipmapLevelCount = NSUInteger(mipLevelCount);
5726 if (samples > 1)
5727 desc.sampleCount = NSUInteger(samples);
5728 if (isArray)
5729 desc.arrayLength = NSUInteger(qMax(0, m_arraySize));
5730 desc.resourceOptions = MTLResourceStorageModePrivate;
5731 desc.storageMode = MTLStorageModePrivate;
5732 desc.usage = MTLTextureUsageShaderRead;
5733 if (m_flags.testFlag(RenderTarget))
5734 desc.usage |= MTLTextureUsageRenderTarget;
5735 if (m_flags.testFlag(UsedWithLoadStore))
5736 desc.usage |= MTLTextureUsageShaderWrite;
5737
5738 // Metal requires this only when the view changes the component layout, and
5739 // explicitly says not to set it for linear <-> sRGB views. A view of the
5740 // same QRhi format with a different sRGB setting, or over an array slice
5741 // range, therefore does not need it.
5742 const bool writeViewChangesFormat = m_writeViewFormat.format != UnknownFormat
5743 && m_writeViewFormat.format != m_format;
5744 const bool readViewChangesFormat = m_readViewFormat.format != UnknownFormat
5745 && m_readViewFormat.format != m_format;
5746 if (writeViewChangesFormat || readViewChangesFormat)
5747 desc.usage |= MTLTextureUsagePixelFormatView;
5748
5749 QRHI_RES_RHI(QRhiMetal);
5750 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
5751 [desc release];
5752
5753 if (!m_objectName.isEmpty())
5754 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
5755
5756 d->owns = true;
5757
5758 if (!d->createViews())
5759 return false;
5760
5762 generation += 1;
5763 rhiD->registerResource(this);
5764 return true;
5765}
5766
5767bool QMetalTexture::createFrom(QRhiTexture::NativeTexture src)
5768{
5769 id<MTLTexture> tex = id<MTLTexture>(src.object);
5770 if (tex == 0)
5771 return false;
5772
5773 if (!prepareCreate())
5774 return false;
5775
5776 d->tex = tex;
5777
5778 d->owns = false;
5779
5780 if (!d->createViews())
5781 return false;
5782
5784 generation += 1;
5785 QRHI_RES_RHI(QRhiMetal);
5786 rhiD->registerResource(this);
5787 return true;
5788}
5789
5791{
5792 return {quint64(d->tex), 0};
5793}
5794
5796{
5797 Q_ASSERT(!samplingView && !writeView);
5798
5799 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
5800 const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
5801 const bool hasArrayRange = isArray && q->m_arrayRangeStart >= 0 && q->m_arrayRangeLength >= 0;
5802 const NSUInteger sliceCount = isCube ? 6 : (isArray ? NSUInteger(qMax(0, q->m_arraySize)) : 1);
5803 const NSRange levels = NSMakeRange(0, NSUInteger(q->mipLevelCount));
5804 const MTLTextureType type = [tex textureType];
5805
5806 id<MTLTexture> newSamplingView = nil;
5807 id<MTLTexture> newWriteView = nil;
5808
5809 if (viewFormatForSampling != format || hasArrayRange) {
5810 const NSRange slices = hasArrayRange
5811 ? NSMakeRange(NSUInteger(q->m_arrayRangeStart), NSUInteger(q->m_arrayRangeLength))
5812 : NSMakeRange(0, sliceCount);
5813 newSamplingView = [tex newTextureViewWithPixelFormat: viewFormatForSampling
5814 textureType: type levels: levels slices: slices];
5815 if (!newSamplingView) {
5816 qWarning("QRhiMetal: Failed to create texture view used for sampling");
5817 return false;
5818 }
5819 }
5820
5821 if (viewFormat != format) {
5822 newWriteView = [tex newTextureViewWithPixelFormat: viewFormat
5823 textureType: type levels: levels
5824 slices: NSMakeRange(0, sliceCount)];
5825 if (!newWriteView) {
5826 qWarning("QRhiMetal: Failed to create texture view used for rendering");
5827 [newSamplingView release];
5828 return false;
5829 }
5830 }
5831
5832 samplingView = newSamplingView;
5833 writeView = newWriteView;
5834 return true;
5835}
5836
5838{
5839 Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
5840 if (perLevelViews[level])
5841 return perLevelViews[level];
5842
5843 const MTLTextureType type = [tex textureType];
5844 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
5845 const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
5846 id<MTLTexture> view = [tex newTextureViewWithPixelFormat: viewFormat textureType: type
5847 levels: NSMakeRange(NSUInteger(level), 1)
5848 slices: NSMakeRange(0, isCube ? 6 : (isArray ? qMax(0, q->m_arraySize) : 1))];
5849
5850 perLevelViews[level] = view;
5851 return view;
5852}
5853
5854QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
5855 AddressMode u, AddressMode v, AddressMode w)
5857 d(new QMetalSamplerData)
5858{
5859}
5860
5862{
5863 destroy();
5864 delete d;
5865}
5866
5868{
5869 if (!d->samplerState)
5870 return;
5871
5875
5876 e.sampler.samplerState = d->samplerState;
5877 d->samplerState = nil;
5878
5879 QRHI_RES_RHI(QRhiMetal);
5880 if (rhiD) {
5881 rhiD->d->releaseQueue.append(e);
5882 rhiD->unregisterResource(this);
5883 }
5884}
5885
5886static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
5887{
5888 switch (f) {
5889 case QRhiSampler::Nearest:
5890 return MTLSamplerMinMagFilterNearest;
5891 case QRhiSampler::Linear:
5892 return MTLSamplerMinMagFilterLinear;
5893 default:
5894 Q_UNREACHABLE();
5895 return MTLSamplerMinMagFilterNearest;
5896 }
5897}
5898
5899static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
5900{
5901 switch (f) {
5902 case QRhiSampler::None:
5903 return MTLSamplerMipFilterNotMipmapped;
5904 case QRhiSampler::Nearest:
5905 return MTLSamplerMipFilterNearest;
5906 case QRhiSampler::Linear:
5907 return MTLSamplerMipFilterLinear;
5908 default:
5909 Q_UNREACHABLE();
5910 return MTLSamplerMipFilterNotMipmapped;
5911 }
5912}
5913
5914static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
5915{
5916 switch (m) {
5917 case QRhiSampler::Repeat:
5918 return MTLSamplerAddressModeRepeat;
5919 case QRhiSampler::ClampToEdge:
5920 return MTLSamplerAddressModeClampToEdge;
5921 case QRhiSampler::Mirror:
5922 return MTLSamplerAddressModeMirrorRepeat;
5923 default:
5924 Q_UNREACHABLE();
5925 return MTLSamplerAddressModeClampToEdge;
5926 }
5927}
5928
5929static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
5930{
5931 switch (op) {
5932 case QRhiSampler::Never:
5933 return MTLCompareFunctionNever;
5934 case QRhiSampler::Less:
5935 return MTLCompareFunctionLess;
5936 case QRhiSampler::Equal:
5937 return MTLCompareFunctionEqual;
5938 case QRhiSampler::LessOrEqual:
5939 return MTLCompareFunctionLessEqual;
5940 case QRhiSampler::Greater:
5941 return MTLCompareFunctionGreater;
5942 case QRhiSampler::NotEqual:
5943 return MTLCompareFunctionNotEqual;
5944 case QRhiSampler::GreaterOrEqual:
5945 return MTLCompareFunctionGreaterEqual;
5946 case QRhiSampler::Always:
5947 return MTLCompareFunctionAlways;
5948 default:
5949 Q_UNREACHABLE();
5950 return MTLCompareFunctionNever;
5951 }
5952}
5953
5955{
5956 if (d->samplerState)
5957 destroy();
5958
5959 MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
5960 desc.minFilter = toMetalFilter(m_minFilter);
5961 desc.magFilter = toMetalFilter(m_magFilter);
5962 desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
5963 desc.sAddressMode = toMetalAddressMode(m_addressU);
5964 desc.tAddressMode = toMetalAddressMode(m_addressV);
5965 desc.rAddressMode = toMetalAddressMode(m_addressW);
5966 desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
5967 QRHI_RES_RHI(QRhiMetal);
5968 // Whether this sampler ends up in an argument buffer is not known here, and
5969 // a sampler state cannot be changed afterwards. Metal gives no warning when
5970 // one without this is used with an argument buffer, it just faults the GPU.
5971 // So set it always as long as ICBs are supported.
5972 desc.supportArgumentBuffers = rhiD->caps.indirectCommandBuffers ? YES : NO;
5973 d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
5974 [desc release];
5975 if (!d->samplerState) {
5976 // Sampler states with supportArgumentBuffers set draw on a per-process
5977 // quota (MTLDevice.maxArgumentBufferSamplerCount), so mention that as
5978 // the likely reason for a failure that is otherwise hard to explain.
5979 qWarning("Failed to create Metal sampler state. The number of unique sampler "
5980 "states with argument buffer support may have exceeded the limit of %u.",
5981 uint(rhiD->d->dev.maxArgumentBufferSamplerCount));
5982 return false;
5983 }
5984
5986 generation += 1;
5987 rhiD->registerResource(this);
5988 return true;
5989}
5990
5994{
5995}
5996
5998{
5999 destroy();
6000 delete d;
6001}
6002
6004{
6005 if (!d->rateMap)
6006 return;
6007
6011
6012 e.shadingRateMap.rateMap = d->rateMap;
6013 d->rateMap = nil;
6014
6015 QRHI_RES_RHI(QRhiMetal);
6016 if (rhiD) {
6017 rhiD->d->releaseQueue.append(e);
6018 rhiD->unregisterResource(this);
6019 }
6020}
6021
6022bool QMetalShadingRateMap::createFrom(NativeShadingRateMap src)
6023{
6024 if (d->rateMap)
6025 destroy();
6026
6027 d->rateMap = (id<MTLRasterizationRateMap>) (quintptr(src.object));
6028 if (!d->rateMap)
6029 return false;
6030
6031 [d->rateMap retain];
6032
6034 generation += 1;
6035 QRHI_RES_RHI(QRhiMetal);
6036 rhiD->registerResource(this);
6037 return true;
6038}
6039
6041{
6042 if (!d->rateMap)
6043 return QSize();
6044
6045 const MTLSize screenSize = [d->rateMap screenSize];
6046 return QSize(int(screenSize.width), int(screenSize.height));
6047}
6048
6049// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
6050// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
6053{
6054 serializedFormatData.reserve(16);
6055}
6056
6061
6063{
6064 QRHI_RES_RHI(QRhiMetal);
6065 if (rhiD)
6066 rhiD->unregisterResource(this);
6067}
6068
6069bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
6070{
6071 if (!other)
6072 return false;
6073
6075
6077 return false;
6078
6080 return false;
6081
6082 for (int i = 0; i < colorAttachmentCount; ++i) {
6083 if (colorFormat[i] != o->colorFormat[i])
6084 return false;
6085 }
6086
6087 if (hasDepthStencil) {
6088 if (dsFormat != o->dsFormat)
6089 return false;
6090 }
6091
6093 return false;
6094
6095 return true;
6096}
6097
6099{
6100 serializedFormatData.clear();
6101 auto p = std::back_inserter(serializedFormatData);
6102
6103 *p++ = colorAttachmentCount;
6104 *p++ = hasDepthStencil;
6105 for (int i = 0; i < colorAttachmentCount; ++i)
6106 *p++ = colorFormat[i];
6107 *p++ = hasDepthStencil ? dsFormat : 0;
6108 *p++ = hasShadingRateMap;
6109}
6110
6112{
6113 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
6116 memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
6117 rpD->dsFormat = dsFormat;
6119
6121
6122 QRHI_RES_RHI(QRhiMetal);
6123 rhiD->registerResource(rpD, false);
6124 return rpD;
6125}
6126
6128{
6129 return serializedFormatData;
6130}
6131
6132QMetalSwapChainRenderTarget::QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
6135{
6136}
6137
6143
6145{
6146 // nothing to do here
6147}
6148
6150{
6151 return d->pixelSize;
6152}
6153
6155{
6156 return d->dpr;
6157}
6158
6160{
6161 return d->sampleCount;
6162}
6163
6165 const QRhiTextureRenderTargetDescription &desc,
6166 Flags flags)
6169{
6170}
6171
6177
6179{
6180 QRHI_RES_RHI(QRhiMetal);
6181 if (rhiD)
6182 rhiD->unregisterResource(this);
6183}
6184
6186{
6187 const int colorAttachmentCount = int(m_desc.colorAttachmentCount());
6188 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
6189 rpD->colorAttachmentCount = colorAttachmentCount;
6190 rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
6191
6192 for (int i = 0; i < colorAttachmentCount; ++i) {
6193 const QRhiColorAttachment *colorAtt = m_desc.colorAttachmentAt(i);
6194 QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAtt->texture());
6195 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAtt->renderBuffer());
6196 rpD->colorFormat[i] = int(texD ? texD->d->viewFormat : rbD->d->format);
6197 }
6198
6199 if (m_desc.depthTexture())
6200 rpD->dsFormat = int(QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->viewFormat);
6201 else if (m_desc.depthStencilBuffer())
6202 rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format);
6203
6204 rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr;
6205
6207
6208 QRHI_RES_RHI(QRhiMetal);
6209 rhiD->registerResource(rpD, false);
6210 return rpD;
6211}
6212
6214{
6215 QRHI_RES_RHI(QRhiMetal);
6216 Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
6217 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
6218 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
6219
6220 d->colorAttCount = 0;
6221 int attIndex = 0;
6222 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
6223 d->colorAttCount += 1;
6224 QMetalTexture *texD = QRHI_RES(QMetalTexture, it->texture());
6225 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, it->renderBuffer());
6226 Q_ASSERT(texD || rbD);
6227 id<MTLTexture> dst = nil;
6228 bool is3D = false;
6229 if (texD) {
6230 dst = texD->d->textureForWrite();
6231 if (attIndex == 0) {
6232 d->pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
6234 }
6235 is3D = texD->flags().testFlag(QRhiTexture::ThreeDimensional);
6236 } else if (rbD) {
6237 dst = rbD->d->tex;
6238 if (attIndex == 0) {
6239 d->pixelSize = rbD->pixelSize();
6241 }
6242 }
6244 colorAtt.tex = dst;
6245 colorAtt.arrayLayer = is3D ? 0 : it->layer();
6246 colorAtt.slice = is3D ? it->layer() : 0;
6247 colorAtt.level = it->level();
6248 QMetalTexture *resTexD = QRHI_RES(QMetalTexture, it->resolveTexture());
6249 colorAtt.resolveTex = resTexD ? resTexD->d->textureForWrite() : nil;
6250 colorAtt.resolveLayer = it->resolveLayer();
6251 colorAtt.resolveLevel = it->resolveLevel();
6252 d->fb.colorAtt[attIndex] = colorAtt;
6253 }
6254 d->dpr = 1;
6255
6256 if (hasDepthStencil) {
6257 if (m_desc.depthTexture()) {
6258 QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
6259 d->fb.dsTex = depthTexD->d->textureForWrite();
6260 d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format());
6261 d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture();
6262 d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
6263 if (d->colorAttCount == 0) {
6264 d->pixelSize = depthTexD->pixelSize();
6265 d->sampleCount = depthTexD->samples;
6266 }
6267 } else {
6268 QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
6269 d->fb.dsTex = depthRbD->d->tex;
6270 d->fb.hasStencil = true;
6271 d->fb.depthNeedsStore = false;
6272 d->fb.preserveDs = false;
6273 if (d->colorAttCount == 0) {
6274 d->pixelSize = depthRbD->pixelSize();
6275 d->sampleCount = depthRbD->samples;
6276 }
6277 }
6278 if (m_desc.depthResolveTexture()) {
6279 QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture());
6280 d->fb.dsResolveTex = depthResolveTexD->d->textureForWrite();
6281 }
6282 d->dsAttCount = 1;
6283 } else {
6284 d->dsAttCount = 0;
6285 }
6286
6287 if (d->colorAttCount > 0)
6288 d->fb.preserveColor = m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
6289
6290 QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList);
6291
6292 rhiD->registerResource(this, false);
6293 return true;
6294}
6295
6297{
6298 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(m_desc, d->currentResIdList))
6299 const_cast<QMetalTextureRenderTarget *>(this)->create();
6300
6301 return d->pixelSize;
6302}
6303
6305{
6306 return d->dpr;
6307}
6308
6310{
6311 return d->sampleCount;
6312}
6313
6318
6323
6325{
6326 sortedBindings.clear();
6327 maxBinding = -1;
6328
6329 QRHI_RES_RHI(QRhiMetal);
6330 if (rhiD)
6331 rhiD->unregisterResource(this);
6332}
6333
6335{
6336 if (!sortedBindings.isEmpty())
6337 destroy();
6338
6339 QRHI_RES_RHI(QRhiMetal);
6340 if (!rhiD->sanityCheckShaderResourceBindings(this))
6341 return false;
6342
6343 rhiD->updateLayoutDesc(this);
6344
6345 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
6346 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
6347 if (!sortedBindings.isEmpty())
6348 maxBinding = QRhiImplementation::shaderResourceBindingData(sortedBindings.last())->binding;
6349 else
6350 maxBinding = -1;
6351
6352 boundResourceData.resize(sortedBindings.count());
6353
6354 for (BoundResourceData &bd : boundResourceData)
6355 bd = {};
6356
6357 generation += 1;
6358 rhiD->registerResource(this, false);
6359 return true;
6360}
6361
6363{
6364 sortedBindings.clear();
6365 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
6366 if (!flags.testFlag(BindingsAreSorted))
6367 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
6368
6369 for (BoundResourceData &bd : boundResourceData)
6370 bd = {};
6371
6372 generation += 1;
6373}
6374
6378{
6379 d->q = this;
6380 d->tess.q = d;
6381}
6382
6388
6390{
6391 d->vs.destroy();
6392 d->fs.destroy();
6393
6394 d->icbCapable = false;
6395
6396 d->tess.compVs[0].destroy();
6397 d->tess.compVs[1].destroy();
6398 d->tess.compVs[2].destroy();
6399
6400 d->tess.compTesc.destroy();
6401 d->tess.vertTese.destroy();
6402
6403 qDeleteAll(d->extraBufMgr.deviceLocalWorkBuffers);
6404 d->extraBufMgr.deviceLocalWorkBuffers.clear();
6405 qDeleteAll(d->extraBufMgr.hostVisibleWorkBuffers);
6406 d->extraBufMgr.hostVisibleWorkBuffers.clear();
6407
6408 delete d->bufferSizeBuffer;
6409 d->bufferSizeBuffer = nullptr;
6410
6411 if (!d->ps && !d->ds
6412 && !d->tess.vertexComputeState[0] && !d->tess.vertexComputeState[1] && !d->tess.vertexComputeState[2]
6413 && !d->tess.tessControlComputeState)
6414 {
6415 return;
6416 }
6417
6421 e.graphicsPipeline.pipelineState = d->ps;
6422 e.graphicsPipeline.depthStencilState = d->ds;
6423 e.graphicsPipeline.tessVertexComputeState = d->tess.vertexComputeState;
6424 e.graphicsPipeline.tessTessControlComputeState = d->tess.tessControlComputeState;
6425 d->ps = nil;
6426 d->ds = nil;
6427 d->tess.vertexComputeState = {};
6428 d->tess.tessControlComputeState = nil;
6429
6430 QRHI_RES_RHI(QRhiMetal);
6431 if (rhiD) {
6432 rhiD->d->releaseQueue.append(e);
6433 rhiD->unregisterResource(this);
6434 }
6435}
6436
6437static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
6438{
6439 switch (format) {
6440 case QRhiVertexInputAttribute::Float4:
6441 return MTLVertexFormatFloat4;
6442 case QRhiVertexInputAttribute::Float3:
6443 return MTLVertexFormatFloat3;
6444 case QRhiVertexInputAttribute::Float2:
6445 return MTLVertexFormatFloat2;
6446 case QRhiVertexInputAttribute::Float:
6447 return MTLVertexFormatFloat;
6448 case QRhiVertexInputAttribute::UNormByte4:
6449 return MTLVertexFormatUChar4Normalized;
6450 case QRhiVertexInputAttribute::UNormByte2:
6451 return MTLVertexFormatUChar2Normalized;
6452 case QRhiVertexInputAttribute::UNormByte:
6453 return MTLVertexFormatUCharNormalized;
6454 case QRhiVertexInputAttribute::UInt4:
6455 return MTLVertexFormatUInt4;
6456 case QRhiVertexInputAttribute::UInt3:
6457 return MTLVertexFormatUInt3;
6458 case QRhiVertexInputAttribute::UInt2:
6459 return MTLVertexFormatUInt2;
6460 case QRhiVertexInputAttribute::UInt:
6461 return MTLVertexFormatUInt;
6462 case QRhiVertexInputAttribute::SInt4:
6463 return MTLVertexFormatInt4;
6464 case QRhiVertexInputAttribute::SInt3:
6465 return MTLVertexFormatInt3;
6466 case QRhiVertexInputAttribute::SInt2:
6467 return MTLVertexFormatInt2;
6468 case QRhiVertexInputAttribute::SInt:
6469 return MTLVertexFormatInt;
6470 case QRhiVertexInputAttribute::Half4:
6471 return MTLVertexFormatHalf4;
6472 case QRhiVertexInputAttribute::Half3:
6473 return MTLVertexFormatHalf3;
6474 case QRhiVertexInputAttribute::Half2:
6475 return MTLVertexFormatHalf2;
6476 case QRhiVertexInputAttribute::Half:
6477 return MTLVertexFormatHalf;
6478 case QRhiVertexInputAttribute::UShort4:
6479 return MTLVertexFormatUShort4;
6480 case QRhiVertexInputAttribute::UShort3:
6481 return MTLVertexFormatUShort3;
6482 case QRhiVertexInputAttribute::UShort2:
6483 return MTLVertexFormatUShort2;
6484 case QRhiVertexInputAttribute::UShort:
6485 return MTLVertexFormatUShort;
6486 case QRhiVertexInputAttribute::SShort4:
6487 return MTLVertexFormatShort4;
6488 case QRhiVertexInputAttribute::SShort3:
6489 return MTLVertexFormatShort3;
6490 case QRhiVertexInputAttribute::SShort2:
6491 return MTLVertexFormatShort2;
6492 case QRhiVertexInputAttribute::SShort:
6493 return MTLVertexFormatShort;
6494 default:
6495 Q_UNREACHABLE();
6496 return MTLVertexFormatFloat4;
6497 }
6498}
6499
6500static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
6501{
6502 switch (f) {
6503 case QRhiGraphicsPipeline::Zero:
6504 return MTLBlendFactorZero;
6505 case QRhiGraphicsPipeline::One:
6506 return MTLBlendFactorOne;
6507 case QRhiGraphicsPipeline::SrcColor:
6508 return MTLBlendFactorSourceColor;
6509 case QRhiGraphicsPipeline::OneMinusSrcColor:
6510 return MTLBlendFactorOneMinusSourceColor;
6511 case QRhiGraphicsPipeline::DstColor:
6512 return MTLBlendFactorDestinationColor;
6513 case QRhiGraphicsPipeline::OneMinusDstColor:
6514 return MTLBlendFactorOneMinusDestinationColor;
6515 case QRhiGraphicsPipeline::SrcAlpha:
6516 return MTLBlendFactorSourceAlpha;
6517 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
6518 return MTLBlendFactorOneMinusSourceAlpha;
6519 case QRhiGraphicsPipeline::DstAlpha:
6520 return MTLBlendFactorDestinationAlpha;
6521 case QRhiGraphicsPipeline::OneMinusDstAlpha:
6522 return MTLBlendFactorOneMinusDestinationAlpha;
6523 case QRhiGraphicsPipeline::ConstantColor:
6524 return MTLBlendFactorBlendColor;
6525 case QRhiGraphicsPipeline::ConstantAlpha:
6526 return MTLBlendFactorBlendAlpha;
6527 case QRhiGraphicsPipeline::OneMinusConstantColor:
6528 return MTLBlendFactorOneMinusBlendColor;
6529 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
6530 return MTLBlendFactorOneMinusBlendAlpha;
6531 case QRhiGraphicsPipeline::SrcAlphaSaturate:
6532 return MTLBlendFactorSourceAlphaSaturated;
6533 case QRhiGraphicsPipeline::Src1Color:
6534 return MTLBlendFactorSource1Color;
6535 case QRhiGraphicsPipeline::OneMinusSrc1Color:
6536 return MTLBlendFactorOneMinusSource1Color;
6537 case QRhiGraphicsPipeline::Src1Alpha:
6538 return MTLBlendFactorSource1Alpha;
6539 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
6540 return MTLBlendFactorOneMinusSource1Alpha;
6541 default:
6542 Q_UNREACHABLE();
6543 return MTLBlendFactorZero;
6544 }
6545}
6546
6547static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
6548{
6549 switch (op) {
6550 case QRhiGraphicsPipeline::Add:
6551 return MTLBlendOperationAdd;
6552 case QRhiGraphicsPipeline::Subtract:
6553 return MTLBlendOperationSubtract;
6554 case QRhiGraphicsPipeline::ReverseSubtract:
6555 return MTLBlendOperationReverseSubtract;
6556 case QRhiGraphicsPipeline::Min:
6557 return MTLBlendOperationMin;
6558 case QRhiGraphicsPipeline::Max:
6559 return MTLBlendOperationMax;
6560 default:
6561 Q_UNREACHABLE();
6562 return MTLBlendOperationAdd;
6563 }
6564}
6565
6566static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
6567{
6568 uint f = 0;
6569 if (c.testFlag(QRhiGraphicsPipeline::R))
6570 f |= MTLColorWriteMaskRed;
6571 if (c.testFlag(QRhiGraphicsPipeline::G))
6572 f |= MTLColorWriteMaskGreen;
6573 if (c.testFlag(QRhiGraphicsPipeline::B))
6574 f |= MTLColorWriteMaskBlue;
6575 if (c.testFlag(QRhiGraphicsPipeline::A))
6576 f |= MTLColorWriteMaskAlpha;
6577 return f;
6578}
6579
6580static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
6581{
6582 switch (op) {
6583 case QRhiGraphicsPipeline::Never:
6584 return MTLCompareFunctionNever;
6585 case QRhiGraphicsPipeline::Less:
6586 return MTLCompareFunctionLess;
6587 case QRhiGraphicsPipeline::Equal:
6588 return MTLCompareFunctionEqual;
6589 case QRhiGraphicsPipeline::LessOrEqual:
6590 return MTLCompareFunctionLessEqual;
6591 case QRhiGraphicsPipeline::Greater:
6592 return MTLCompareFunctionGreater;
6593 case QRhiGraphicsPipeline::NotEqual:
6594 return MTLCompareFunctionNotEqual;
6595 case QRhiGraphicsPipeline::GreaterOrEqual:
6596 return MTLCompareFunctionGreaterEqual;
6597 case QRhiGraphicsPipeline::Always:
6598 return MTLCompareFunctionAlways;
6599 default:
6600 Q_UNREACHABLE();
6601 return MTLCompareFunctionAlways;
6602 }
6603}
6604
6605static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
6606{
6607 switch (op) {
6608 case QRhiGraphicsPipeline::StencilZero:
6609 return MTLStencilOperationZero;
6610 case QRhiGraphicsPipeline::Keep:
6611 return MTLStencilOperationKeep;
6612 case QRhiGraphicsPipeline::Replace:
6613 return MTLStencilOperationReplace;
6614 case QRhiGraphicsPipeline::IncrementAndClamp:
6615 return MTLStencilOperationIncrementClamp;
6616 case QRhiGraphicsPipeline::DecrementAndClamp:
6617 return MTLStencilOperationDecrementClamp;
6618 case QRhiGraphicsPipeline::Invert:
6619 return MTLStencilOperationInvert;
6620 case QRhiGraphicsPipeline::IncrementAndWrap:
6621 return MTLStencilOperationIncrementWrap;
6622 case QRhiGraphicsPipeline::DecrementAndWrap:
6623 return MTLStencilOperationDecrementWrap;
6624 default:
6625 Q_UNREACHABLE();
6626 return MTLStencilOperationKeep;
6627 }
6628}
6629
6630static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
6631{
6632 switch (t) {
6633 case QRhiGraphicsPipeline::Triangles:
6634 return MTLPrimitiveTypeTriangle;
6635 case QRhiGraphicsPipeline::TriangleStrip:
6636 return MTLPrimitiveTypeTriangleStrip;
6637 case QRhiGraphicsPipeline::Lines:
6638 return MTLPrimitiveTypeLine;
6639 case QRhiGraphicsPipeline::LineStrip:
6640 return MTLPrimitiveTypeLineStrip;
6641 case QRhiGraphicsPipeline::Points:
6642 return MTLPrimitiveTypePoint;
6643 default:
6644 Q_UNREACHABLE();
6645 return MTLPrimitiveTypeTriangle;
6646 }
6647}
6648
6649static inline MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t)
6650{
6651 switch (t) {
6652 case QRhiGraphicsPipeline::Triangles:
6653 case QRhiGraphicsPipeline::TriangleStrip:
6654 case QRhiGraphicsPipeline::TriangleFan:
6655 return MTLPrimitiveTopologyClassTriangle;
6656 case QRhiGraphicsPipeline::Lines:
6657 case QRhiGraphicsPipeline::LineStrip:
6658 return MTLPrimitiveTopologyClassLine;
6659 case QRhiGraphicsPipeline::Points:
6660 return MTLPrimitiveTopologyClassPoint;
6661 default:
6662 Q_UNREACHABLE();
6663 return MTLPrimitiveTopologyClassTriangle;
6664 }
6665}
6666
6667static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
6668{
6669 switch (c) {
6670 case QRhiGraphicsPipeline::None:
6671 return MTLCullModeNone;
6672 case QRhiGraphicsPipeline::Front:
6673 return MTLCullModeFront;
6674 case QRhiGraphicsPipeline::Back:
6675 return MTLCullModeBack;
6676 default:
6677 Q_UNREACHABLE();
6678 return MTLCullModeNone;
6679 }
6680}
6681
6682static inline MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::PolygonMode mode)
6683{
6684 switch (mode) {
6685 case QRhiGraphicsPipeline::Fill:
6686 return MTLTriangleFillModeFill;
6687 case QRhiGraphicsPipeline::Line:
6688 return MTLTriangleFillModeLines;
6689 default:
6690 Q_UNREACHABLE();
6691 return MTLTriangleFillModeFill;
6692 }
6693}
6694
6695static inline MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
6696{
6697 switch (w) {
6698 case QShaderDescription::CwTessellationWindingOrder:
6699 return MTLWindingClockwise;
6700 case QShaderDescription::CcwTessellationWindingOrder:
6701 return MTLWindingCounterClockwise;
6702 default:
6703 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
6704 return MTLWindingCounterClockwise;
6705 }
6706}
6707
6708static inline MTLTessellationPartitionMode toMetalTessellationPartitionMode(QShaderDescription::TessellationPartitioning p)
6709{
6710 switch (p) {
6711 case QShaderDescription::EqualTessellationPartitioning:
6712 return MTLTessellationPartitionModePow2;
6713 case QShaderDescription::FractionalEvenTessellationPartitioning:
6714 return MTLTessellationPartitionModeFractionalEven;
6715 case QShaderDescription::FractionalOddTessellationPartitioning:
6716 return MTLTessellationPartitionModeFractionalOdd;
6717 default:
6718 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
6719 return MTLTessellationPartitionModePow2;
6720 }
6721}
6722
6723static inline MTLLanguageVersion toMetalLanguageVersion(const QShaderVersion &version)
6724{
6725 int v = version.version();
6726 return MTLLanguageVersion(((v / 10) << 16) + (v % 10));
6727}
6728
6729id<MTLBuffer> QRhiMetalData::allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment,
6730 int frameSlot, quint32 minBlockSize, quint32 *offset)
6731{
6732 auto &pool(*area);
6733 const quint32 alignedSize = aligned<quint32>(size, alignment);
6734 if (pool.offset + alignedSize > pool.capacity) {
6735 if (pool.buf) {
6736 // Reusing the StagingBuffer entry type: the handler just releases
6737 // the buffer. lastActiveFrameSlot = frameSlot is what keeps the old
6738 // buffer alive for the rest of this frame, since
6739 // executeDeferredReleases() only gets to this slot after the
6740 // semaphore wait in the next beginFrame() for it.
6743 e.lastActiveFrameSlot = frameSlot;
6744 e.stagingBuffer.buffer = pool.buf;
6745 releaseQueue.append(e);
6746 }
6747 pool.capacity = qMax(pool.capacity * 2, qMax(alignedSize, minBlockSize));
6748 pool.buf = [dev newBufferWithLength: pool.capacity options: MTLResourceStorageModeShared];
6749 pool.offset = 0;
6750 if (!pool.buf) {
6751 pool.capacity = 0;
6752 return nil;
6753 }
6754 }
6755 *offset = pool.offset;
6756 pool.offset += alignedSize;
6757 return pool.buf;
6758}
6759
6760id<MTLBuffer> QRhiMetalData::allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset)
6761{
6762 return allocFromStagingArea(&argBufPool[frameSlot], size, alignment, frameSlot, 16384, offset);
6763}
6764
6766{
6767 id<MTLBuffer> buf = [dev newBufferWithLength: size options: MTLResourceStorageModeShared];
6768 if (!buf)
6769 return nil;
6772 e.lastActiveFrameSlot = frameSlot;
6773 e.stagingBuffer.buffer = buf;
6774 releaseQueue.append(e);
6775 return buf;
6776}
6777
6778id<MTLBuffer> QRhiMetalData::allocBufferStaging(quint32 size, int frameSlot, quint32 *offset)
6779{
6780 if (size > LARGE_STAGING_ALLOC) {
6781 *offset = 0;
6782 return newOneShotStagingBuffer(size, frameSlot);
6783 }
6784
6785 StagingArea &area(bufStagingPool[frameSlot]);
6786 // 4 byte alignment is what MTLBlitCommandEncoder wants on non-Apple GPUs.
6787 const quint32 alignedSize = aligned<quint32>(size, 4u);
6788 area.bytesNeeded += alignedSize;
6789
6790 if (area.offset + alignedSize > area.capacity) {
6791 *offset = 0;
6792 return newOneShotStagingBuffer(size, frameSlot);
6793 }
6794
6795 *offset = area.offset;
6796 area.offset += alignedSize;
6797 return area.buf;
6798}
6799
6801{
6802 StagingArea &area(bufStagingPool[frameSlot]);
6803 const quint32 needed = area.bytesNeeded;
6804 area.bytesNeeded = 0;
6805 area.offset = 0;
6806
6807 bool resize = false;
6808 quint32 newCapacity = 0;
6809 if (needed > area.capacity) {
6810 // Fell back to one-shot buffers last time. Only grow once the demand
6811 // has proven to be recurring: a one-time burst, which is what loading a
6812 // scene looks like, would otherwise size the area permanently right at
6813 // the point where it is not needed any more.
6814 area.lowDemandFrames = 0;
6816 newCapacity = qMin(qNextPowerOfTwo(needed), STAGING_AREA_MAX);
6817 resize = true;
6818 }
6819 } else if (needed <= area.capacity / 4 && area.capacity > 0) {
6820 area.highDemandFrames = 0;
6822 area.lowDemandFrames = 0;
6823 // Nothing at all for a while, e.g. an app that only uploads during
6824 // startup: give the memory back entirely.
6825 newCapacity = needed ? qMax(area.capacity / 2, STAGING_AREA_MIN) : 0;
6826 resize = true;
6827 }
6828 } else {
6829 area.lowDemandFrames = 0;
6830 area.highDemandFrames = 0;
6831 }
6832
6833 if (resize && newCapacity != area.capacity) {
6834 if (area.buf) {
6837 e.lastActiveFrameSlot = frameSlot;
6838 e.stagingBuffer.buffer = area.buf;
6839 releaseQueue.append(e);
6840 area.buf = nil;
6841 }
6842 area.capacity = 0;
6843 if (newCapacity) {
6844 area.buf = [dev newBufferWithLength: newCapacity options: MTLResourceStorageModeShared];
6845 if (area.buf)
6846 area.capacity = newCapacity;
6847 }
6848 }
6849}
6850
6851id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
6852 bool preferArgumentBuffers,
6853 QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
6854{
6855 QVarLengthArray<int, 8> versions;
6856 versions << 30 << 24 << 23 << 22 << 21 << 20 << 12;
6857
6858 // preferArgumentBuffers overrides whatever variant is set in the QShader.
6859 // This is by design, since we cannot expect the client to start specifying
6860 // the ArgumentBuffer variant that is only relevant for Metal. Also not
6861 // compatible with BatchableVertexShader and other variants since
6862 // ArgumentBufferShader is expected to be the argument-buffers version of
6863 // StandardShader, and it cannot be a combination of multiple variants. That
6864 // limitation should be fine for now.
6865
6866 QVarLengthArray<QShader::Variant, 2> variants;
6867 if (preferArgumentBuffers)
6868 variants << QShader::ArgumentBufferShader;
6869 variants << shaderVariant;
6870
6871 const QList<QShaderKey> shaders = shader.availableShaders();
6872
6873 auto findKey = [&shaders, &versions, &variants](QShader::Source source, QShaderKey *result) {
6874 for (const QShader::Variant &variant : variants) {
6875 for (const int &version : versions) {
6876 const QShaderKey key = { source, version, variant };
6877 if (shaders.contains(key)) {
6878 *result = key;
6879 return true;
6880 }
6881 }
6882 }
6883 return false;
6884 };
6885
6886 QShaderKey key;
6887
6888 if (findKey(QShader::Source::MetalLibShader, &key)) {
6889 QShaderCode mtllib = shader.shader(key);
6890 dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
6891 size_t(mtllib.shader().size()),
6892 dispatch_get_global_queue(0, 0),
6893 DISPATCH_DATA_DESTRUCTOR_DEFAULT);
6894 NSError *err = nil;
6895 id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
6896 dispatch_release(data);
6897 if (!err) {
6898 *entryPoint = mtllib.entryPoint();
6899 *activeKey = key;
6900 return lib;
6901 } else {
6902 const QString msg = QString::fromNSString(err.localizedDescription);
6903 qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
6904 }
6905 }
6906
6907 if (!findKey(QShader::Source::MslShader, &key)) {
6908 qWarning() << "No MSL code found in baked shader" << shader;
6909 return nil;
6910 }
6911
6912 QShaderCode mslSource = shader.shader(key);
6913
6914 NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
6915 MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
6916 opts.languageVersion = toMetalLanguageVersion(key.sourceVersion());
6917 NSError *err = nil;
6918 id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
6919 [opts release];
6920 // src is autoreleased
6921
6922 // if lib is null and err is non-null, we had errors (fail)
6923 // if lib is non-null and err is non-null, we had warnings (success)
6924 // if lib is non-null and err is null, there were no errors or warnings (success)
6925 if (!lib) {
6926 const QString msg = QString::fromNSString(err.localizedDescription);
6927 *error = msg;
6928 return nil;
6929 }
6930
6931 *entryPoint = mslSource.entryPoint();
6932 *activeKey = key;
6933 return lib;
6934}
6935
6936id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
6937{
6938 return [lib newFunctionWithName:[NSString stringWithUTF8String:entryPoint.constData()]];
6939}
6940
6942{
6943 MTLRenderPipelineDescriptor *rpDesc = reinterpret_cast<MTLRenderPipelineDescriptor *>(metalRpDesc);
6944
6945 if (rpD->colorAttachmentCount) {
6946 // defaults when no targetBlends are provided
6947 rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
6948 rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
6949 rpDesc.colorAttachments[0].blendingEnabled = false;
6950
6951 Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
6952 || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
6953
6954 for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
6955 const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
6956 rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
6957 rpDesc.colorAttachments[i].blendingEnabled = b.enable;
6958 rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
6959 rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
6960 rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
6961 rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
6962 rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
6963 rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
6964 rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
6965 }
6966 }
6967
6968 if (rpD->hasDepthStencil) {
6969 // Must only be set when a depth-stencil buffer will actually be bound,
6970 // validation blows up otherwise.
6971 MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
6972 rpDesc.depthAttachmentPixelFormat = fmt;
6973#if defined(Q_OS_MACOS)
6974 if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
6975#else
6976 if (fmt != MTLPixelFormatDepth32Float)
6977#endif
6978 rpDesc.stencilAttachmentPixelFormat = fmt;
6979 }
6980
6981 QRHI_RES_RHI(QRhiMetal);
6982 rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
6983}
6984
6986{
6987 MTLDepthStencilDescriptor *dsDesc = reinterpret_cast<MTLDepthStencilDescriptor *>(metalDsDesc);
6988
6989 dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
6990 dsDesc.depthWriteEnabled = m_depthWrite;
6991 if (m_stencilTest) {
6992 dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
6993 dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
6994 dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
6995 dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
6996 dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
6997 dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
6998 dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
6999
7000 dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
7001 dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
7002 dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
7003 dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
7004 dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
7005 dsDesc.backFaceStencil.readMask = m_stencilReadMask;
7006 dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
7007 }
7008}
7009
7011{
7012 d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
7013 d->cullMode = toMetalCullMode(m_cullMode);
7014 d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
7015 d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
7016 d->depthBias = float(m_depthBias);
7017 d->slopeScaledDepthBias = m_slopeScaledDepthBias;
7018}
7019
7021{
7022 // same binding space for vertex and constant buffers - work it around
7023 // should be in native resource binding not SPIR-V, but this will work anyway
7024 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
7025
7026 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
7027 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
7028 it != itEnd; ++it)
7029 {
7030 const uint loc = uint(it->location());
7031 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
7032 desc.attributes[loc].offset = NSUInteger(it->offset());
7033 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
7034 }
7035 int bindingIndex = 0;
7036 const NSUInteger viewCount = qMax<NSUInteger>(1, q->multiViewCount());
7037 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
7038 it != itEnd; ++it, ++bindingIndex)
7039 {
7040 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
7041 desc.layouts[layoutIdx].stepFunction =
7042 it->classification() == QRhiVertexInputBinding::PerInstance
7043 ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
7044 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
7045 if (desc.layouts[layoutIdx].stepFunction == MTLVertexStepFunctionPerInstance)
7046 desc.layouts[layoutIdx].stepRate *= viewCount;
7047 desc.layouts[layoutIdx].stride = it->stride();
7048 }
7049}
7050
7051void QMetalGraphicsPipelineData::setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
7052{
7053 // same binding space for vertex and constant buffers - work it around
7054 // should be in native resource binding not SPIR-V, but this will work anyway
7055 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
7056
7057 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
7058 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
7059 it != itEnd; ++it)
7060 {
7061 const uint loc = uint(it->location());
7062 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
7063 desc.attributes[loc].offset = NSUInteger(it->offset());
7064 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
7065 }
7066 int bindingIndex = 0;
7067 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
7068 it != itEnd; ++it, ++bindingIndex)
7069 {
7070 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
7071 if (desc.indexBufferIndex) {
7072 desc.layouts[layoutIdx].stepFunction =
7073 it->classification() == QRhiVertexInputBinding::PerInstance
7074 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridXIndexed;
7075 } else {
7076 desc.layouts[layoutIdx].stepFunction =
7077 it->classification() == QRhiVertexInputBinding::PerInstance
7078 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX;
7079 }
7080 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
7081 desc.layouts[layoutIdx].stride = it->stride();
7082 }
7083}
7084
7085void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
7086{
7087 if (binArch) {
7088 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
7089 rpDesc.binaryArchives = binArchArray;
7090 }
7091}
7092
7093void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
7094{
7095 if (binArch) {
7096 NSError *err = nil;
7097 if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) {
7098 const QString msg = QString::fromNSString(err.localizedDescription);
7099 qWarning("Failed to collect render pipeline functions to binary archive: %s", qPrintable(msg));
7100 }
7101 }
7102}
7103
7104static inline bool usesTextures(const QShaderDescription &desc)
7105{
7106 return !desc.combinedImageSamplers().isEmpty()
7107 || !desc.separateImages().isEmpty()
7108 || !desc.storageImages().isEmpty();
7109}
7110
7111static bool hasArgumentBufferVariant(const QShader &shader)
7112{
7113 for (const QShaderKey &k : shader.availableShaders()) {
7114 if (k.sourceVariant() == QShader::ArgumentBufferShader)
7115 return true;
7116 }
7117 return false;
7118}
7119
7121{
7122 const int index = shader->nativeShaderInfo.extraBufferBindings.value(QShaderPrivate::MslArgumentBufferBinding, -1);
7123 if (index >= 0) {
7124 shader->argumentBufferIndex = index;
7125 shader->argumentEncoder = [shader->func newArgumentEncoderWithBufferIndex: NSUInteger(index)];
7126 }
7127}
7128
7130{
7131 QRHI_RES_RHI(QRhiMetal);
7132
7133 // A pipeline that supports indirect command buffers cannot have textures
7134 // bound directly to its functions, so prefer the shader variant that
7135 // reaches them through an argument buffer.
7136 const bool wantArgumentBuffers = m_flags.testFlag(UsesIndirectDraws) && rhiD->caps.indirectCommandBuffers;
7137
7138 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
7139 d->setupVertexInputDescriptor(vertexDesc);
7140
7141 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
7142 rpDesc.vertexDescriptor = vertexDesc;
7143
7144 // Mutability cannot be determined (slotted buffers could be set as
7145 // MTLMutabilityImmutable, but then we potentially need a different
7146 // descriptor for each buffer combination as this depends on the actual
7147 // buffers not just the resource binding layout), so leave
7148 // rpDesc.vertex/fragmentBuffers at the defaults.
7149
7150 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
7151 const QShader shader = shaderStage.shader();
7152 const bool argumentBufferBuild = wantArgumentBuffers && hasArgumentBufferVariant(shader);
7153 auto cacheIt = rhiD->d->shaderCache.constFind({ shaderStage, argumentBufferBuild });
7154 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
7155 switch (shaderStage.type()) {
7156 case QRhiShaderStage::Vertex:
7157 d->vs = *cacheIt;
7158 [d->vs.lib retain];
7159 [d->vs.func retain];
7160 [d->vs.argumentEncoder retain];
7161 rpDesc.vertexFunction = d->vs.func;
7162 break;
7163 case QRhiShaderStage::Fragment:
7164 d->fs = *cacheIt;
7165 [d->fs.lib retain];
7166 [d->fs.func retain];
7167 [d->fs.argumentEncoder retain];
7168 rpDesc.fragmentFunction = d->fs.func;
7169 break;
7170 default:
7171 break;
7172 }
7173 } else {
7174 QString error;
7175 QByteArray entryPoint;
7176 QShaderKey activeKey;
7177 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(),
7178 argumentBufferBuild,
7179 &error, &entryPoint, &activeKey);
7180 if (!lib) {
7181 qWarning("MSL shader compilation failed: %s", qPrintable(error));
7182 return false;
7183 }
7184 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
7185 if (!func) {
7186 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7187 [lib release];
7188 return false;
7189 }
7190 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
7191 // Use the simplest strategy: too many cached shaders -> drop them all.
7192 for (QMetalShader &s : rhiD->d->shaderCache)
7193 s.destroy();
7194 rhiD->d->shaderCache.clear();
7195 }
7196 switch (shaderStage.type()) {
7197 case QRhiShaderStage::Vertex:
7198 d->vs.lib = lib;
7199 d->vs.func = func;
7200 d->vs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
7201 d->vs.desc = shader.description();
7202 d->vs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
7203 setupArgumentBufferEncoder(&d->vs);
7204 rhiD->d->shaderCache.insert({ shaderStage, argumentBufferBuild }, d->vs);
7205 [d->vs.lib retain];
7206 [d->vs.func retain];
7207 [d->vs.argumentEncoder retain];
7208 rpDesc.vertexFunction = func;
7209 break;
7210 case QRhiShaderStage::Fragment:
7211 d->fs.lib = lib;
7212 d->fs.func = func;
7213 d->fs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
7214 d->fs.desc = shader.description();
7215 d->fs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
7216 setupArgumentBufferEncoder(&d->fs);
7217 rhiD->d->shaderCache.insert({ shaderStage, argumentBufferBuild }, d->fs);
7218 [d->fs.lib retain];
7219 [d->fs.func retain];
7220 [d->fs.argumentEncoder retain];
7221 rpDesc.fragmentFunction = func;
7222 break;
7223 default:
7224 [func release];
7225 [lib release];
7226 break;
7227 }
7228 }
7229 }
7230
7231 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
7233
7234 // Safe for an indirect command buffer if the stage has no textures at all,
7235 // or reaches them through an argument buffer.
7236 const auto icbSafe = [](const QMetalShader &s) {
7237 return !usesTextures(s.desc) || s.argumentBufferIndex >= 0;
7238 };
7239 d->icbCapable = wantArgumentBuffers && icbSafe(d->vs) && icbSafe(d->fs);
7240 if (d->icbCapable)
7241 rpDesc.supportIndirectCommandBuffers = YES;
7242
7243 if (m_multiViewCount >= 2)
7244 rpDesc.inputPrimitiveTopology = toMetalPrimitiveTopologyClass(m_topology);
7245
7246 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
7247
7248 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7249 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
7250
7251 NSError *err = nil;
7252 d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
7253 [rpDesc release];
7254 if (!d->ps) {
7255 const QString msg = QString::fromNSString(err.localizedDescription);
7256 qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
7257 return false;
7258 }
7259
7260 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
7262 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
7263 [dsDesc release];
7264
7265 d->primitiveType = toMetalPrimitiveType(m_topology);
7267
7268 return true;
7269}
7270
7271int QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(QShader::Variant vertexCompVariant)
7272{
7273 switch (vertexCompVariant) {
7274 case QShader::NonIndexedVertexAsComputeShader:
7275 return 0;
7276 case QShader::UInt32IndexedVertexAsComputeShader:
7277 return 1;
7278 case QShader::UInt16IndexedVertexAsComputeShader:
7279 return 2;
7280 default:
7281 break;
7282 }
7283 return -1;
7284}
7285
7287{
7288 const int varIndex = vsCompVariantToIndex(vertexCompVariant);
7289 if (varIndex >= 0 && vertexComputeState[varIndex])
7290 return vertexComputeState[varIndex];
7291
7292 id<MTLFunction> func = nil;
7293 if (varIndex >= 0)
7294 func = compVs[varIndex].func;
7295
7296 if (!func) {
7297 qWarning("No compute function found for vertex shader translated for tessellation, this should not happen");
7298 return nil;
7299 }
7300
7301 const QMap<int, int> &ebb(compVs[varIndex].nativeShaderInfo.extraBufferBindings);
7302 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
7303
7304 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
7305 cpDesc.computeFunction = func;
7306 cpDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
7307 cpDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
7308 if (indexBufferBinding >= 0) {
7309 if (vertexCompVariant == QShader::UInt32IndexedVertexAsComputeShader) {
7310 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt32;
7311 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
7312 } else if (vertexCompVariant == QShader::UInt16IndexedVertexAsComputeShader) {
7313 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt16;
7314 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
7315 }
7316 }
7317 q->setupStageInputDescriptor(cpDesc.stageInputDescriptor);
7318
7319 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
7320
7321 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7322 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
7323
7324 NSError *err = nil;
7325 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
7326 options: MTLPipelineOptionNone
7327 reflection: nil
7328 error: &err];
7329 [cpDesc release];
7330 if (!ps) {
7331 const QString msg = QString::fromNSString(err.localizedDescription);
7332 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
7333 } else {
7334 vertexComputeState[varIndex] = ps;
7335 }
7336 // not retained, the only owner is vertexComputeState and so the QRhiGraphicsPipeline
7337 return ps;
7338}
7339
7341{
7342 if (tessControlComputeState)
7343 return tessControlComputeState;
7344
7345 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
7346 cpDesc.computeFunction = compTesc.func;
7347
7348 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
7349
7350 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7351 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
7352
7353 NSError *err = nil;
7354 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
7355 options: MTLPipelineOptionNone
7356 reflection: nil
7357 error: &err];
7358 [cpDesc release];
7359 if (!ps) {
7360 const QString msg = QString::fromNSString(err.localizedDescription);
7361 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
7362 } else {
7363 tessControlComputeState = ps;
7364 }
7365 // not retained, the only owner is tessControlComputeState and so the QRhiGraphicsPipeline
7366 return ps;
7367}
7368
7369static inline bool indexTaken(quint32 index, quint64 indices)
7370{
7371 return (indices >> index) & 0x1;
7372}
7373
7374static inline void takeIndex(quint32 index, quint64 &indices)
7375{
7376 indices |= 1 << index;
7377}
7378
7379static inline int nextAttributeIndex(quint64 indices)
7380{
7381 // Maximum number of vertex attributes per vertex descriptor. There does
7382 // not appear to be a way to query this from the implementation.
7383 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf indicates
7384 // that all GPU families have a value of 31.
7385 static const int maxVertexAttributes = 31;
7386
7387 for (int index = 0; index < maxVertexAttributes; ++index) {
7388 if (!indexTaken(index, indices))
7389 return index;
7390 }
7391
7392 Q_UNREACHABLE_RETURN(-1);
7393}
7394
7395static inline int aligned(quint32 offset, quint32 alignment)
7396{
7397 return ((offset + alignment - 1) / alignment) * alignment;
7398}
7399
7400template<typename T>
7401static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
7402{
7403
7404 int elements = 1;
7405 for (const int dim : variable.arrayDims)
7406 elements *= dim;
7407
7408 if (variable.type == QShaderDescription::VariableType::Struct) {
7409 for (int element = 0; element < elements; ++element) {
7410 for (const auto &member : variable.structMembers) {
7411 addUnusedVertexAttribute(member, rhiD, offset, vertexAlignment);
7412 }
7413 }
7414 } else {
7415 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
7416 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
7417
7418 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
7419 const quint32 alignment = size;
7420 vertexAlignment = std::max(vertexAlignment, alignment);
7421
7422 for (int element = 0; element < elements; ++element) {
7423 // adjust alignment
7424 offset = aligned(offset, alignment);
7425 offset += size;
7426 }
7427 }
7428}
7429
7430template<typename T>
7431static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment)
7432{
7433
7434 int elements = 1;
7435 for (const int dim : variable.arrayDims)
7436 elements *= dim;
7437
7438 if (variable.type == QShaderDescription::VariableType::Struct) {
7439 for (int element = 0; element < elements; ++element) {
7440 for (const auto &member : variable.structMembers) {
7441 addVertexAttribute(member, binding, rhiD, index, offset, attributes, indices, vertexAlignment);
7442 }
7443 }
7444 } else {
7445 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
7446 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
7447
7448 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
7449 const quint32 alignment = size;
7450 vertexAlignment = std::max(vertexAlignment, alignment);
7451
7452 for (int element = 0; element < elements; ++element) {
7453 Q_ASSERT(!indexTaken(index, indices));
7454
7455 // adjust alignment
7456 offset = aligned(offset, alignment);
7457
7458 attributes[index].bufferIndex = binding;
7459 attributes[index].format = toMetalAttributeFormat(format);
7460 attributes[index].offset = offset;
7461
7462 takeIndex(index, indices);
7463 index++;
7464 if (indexTaken(index, indices))
7465 index = nextAttributeIndex(indices);
7466
7467 offset += size;
7468 }
7469 }
7470}
7471
7472static inline bool matches(const QList<QShaderDescription::BlockVariable> &a, const QList<QShaderDescription::BlockVariable> &b)
7473{
7474 if (a.size() == b.size()) {
7475 bool match = true;
7476 for (int i = 0; i < a.size() && match; ++i) {
7477 match &= a[i].type == b[i].type
7478 && a[i].arrayDims == b[i].arrayDims
7479 && matches(a[i].structMembers, b[i].structMembers);
7480 }
7481 return match;
7482 }
7483
7484 return false;
7485}
7486
7487static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b)
7488{
7489 return a.location == b.location
7490 && a.type == b.type
7491 && a.perPatch == b.perPatch
7492 && matches(a.structMembers, b.structMembers);
7493}
7494
7495//
7496// Create the tessellation evaluation render pipeline state
7497//
7498// The tesc runs as a compute shader in a compute pipeline and writes per patch and per patch
7499// control point data into separate storage buffers. The tese runs as a vertex shader in a render
7500// pipeline. Our task is to generate a render pipeline descriptor for the tese that pulls vertices
7501// from these buffers.
7502//
7503// As the buffers we are pulling vertices from are written by a compute pipeline, they follow the
7504// MSL alignment conventions which we must take into account when generating our
7505// MTLVertexDescriptor. We must include the user defined tese input attributes, and any builtins
7506// that were used.
7507//
7508// SPIRV-Cross generates the MSL tese shader code with input attribute indices that reflect the
7509// specified GLSL locations. Interface blocks are flattened with each member having an incremented
7510// attribute index. SPIRV-Cross reports an error on compilation if there are clashes in the index
7511// address space.
7512//
7513// After the user specified attributes are processed, SPIRV-Cross places the in-use builtins at the
7514// next available (lowest value) attribute index. Tese builtins are processed in the following
7515// order:
7516//
7517// in gl_PerVertex
7518// {
7519// vec4 gl_Position;
7520// float gl_PointSize;
7521// float gl_ClipDistance[];
7522// };
7523//
7524// patch in float gl_TessLevelOuter[4];
7525// patch in float gl_TessLevelInner[2];
7526//
7527// Enumerations in QShaderDescription::BuiltinType are defined in this order.
7528//
7529// For quads, SPIRV-Cross places MTLQuadTessellationFactorsHalf per patch in the tessellation
7530// factor buffer. For triangles it uses MTLTriangleTessellationFactorsHalf.
7531//
7532// It should be noted that SPIRV-Cross handles the following builtin inputs internally, with no
7533// host side support required.
7534//
7535// in vec3 gl_TessCoord;
7536// in int gl_PatchVerticesIn;
7537// in int gl_PrimitiveID;
7538//
7540{
7541 if (pipeline->d->ps)
7542 return pipeline->d->ps;
7543
7544 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
7545 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
7546
7547 // tesc output buffers
7548 const QMap<int, int> &ebb(compTesc.nativeShaderInfo.extraBufferBindings);
7549 const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
7550 const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
7551 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
7552 quint32 offsetInTescOutput = 0;
7553 quint32 offsetInTescPatchOutput = 0;
7554 quint32 offsetInTessFactorBuffer = 0;
7555 quint32 tescOutputAlignment = 0;
7556 quint32 tescPatchOutputAlignment = 0;
7557 quint32 tessFactorAlignment = 0;
7558 QSet<int> usedBuffers;
7559
7560 // tesc output variables in ascending location order
7561 QMap<int, QShaderDescription::InOutVariable> tescOutVars;
7562 for (const auto &tescOutVar : compTesc.desc.outputVariables())
7563 tescOutVars[tescOutVar.location] = tescOutVar;
7564
7565 // tese input variables in ascending location order
7566 QMap<int, QShaderDescription::InOutVariable> teseInVars;
7567 for (const auto &teseInVar : vertTese.desc.inputVariables())
7568 teseInVars[teseInVar.location] = teseInVar;
7569
7570 // bit mask tracking usage of vertex attribute indices
7571 quint64 indices = 0;
7572
7573 for (QShaderDescription::InOutVariable &tescOutVar : tescOutVars) {
7574
7575 int index = tescOutVar.location;
7576 int binding = -1;
7577 quint32 *offset = nullptr;
7578 quint32 *alignment = nullptr;
7579
7580 if (tescOutVar.perPatch) {
7581 binding = tescPatchOutputBufferBinding;
7582 offset = &offsetInTescPatchOutput;
7583 alignment = &tescPatchOutputAlignment;
7584 } else {
7585 tescOutVar.arrayDims.removeLast();
7586 binding = tescOutputBufferBinding;
7587 offset = &offsetInTescOutput;
7588 alignment = &tescOutputAlignment;
7589 }
7590
7591 if (teseInVars.contains(index)) {
7592
7593 if (!matches(teseInVars[index], tescOutVar)) {
7594 qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << index;
7595 qWarning() << " tesc out:" << tescOutVar;
7596 qWarning() << " tese in:" << teseInVars[index];
7597 }
7598
7599 if (binding != -1) {
7600 addVertexAttribute(tescOutVar, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
7601 usedBuffers << binding;
7602 } else {
7603 qWarning() << "baked tessellation control shader missing output buffer binding information";
7604 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
7605 }
7606
7607 } else {
7608 qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar;
7609 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
7610 }
7611
7612 teseInVars.remove(tescOutVar.location);
7613 }
7614
7615 for (const QShaderDescription::InOutVariable &teseInVar : teseInVars)
7616 qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar;
7617
7618 // tesc output builtins in ascending location order
7619 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> tescOutBuiltins;
7620 for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables())
7621 tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin;
7622
7623 // tese input builtins in ascending location order
7624 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> teseInBuiltins;
7625 for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables())
7626 teseInBuiltins[teseInBuiltin.type] = teseInBuiltin;
7627
7628 const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode;
7629 bool tessLevelAdded = false;
7630
7631 for (const QShaderDescription::BuiltinVariable &builtin : tescOutBuiltins) {
7632
7633 QShaderDescription::InOutVariable variable;
7634 int binding = -1;
7635 quint32 *offset = nullptr;
7636 quint32 *alignment = nullptr;
7637
7638 switch (builtin.type) {
7639 case QShaderDescription::BuiltinType::PositionBuiltin:
7640 variable.type = QShaderDescription::VariableType::Vec4;
7641 binding = tescOutputBufferBinding;
7642 offset = &offsetInTescOutput;
7643 alignment = &tescOutputAlignment;
7644 break;
7645 case QShaderDescription::BuiltinType::PointSizeBuiltin:
7646 variable.type = QShaderDescription::VariableType::Float;
7647 binding = tescOutputBufferBinding;
7648 offset = &offsetInTescOutput;
7649 alignment = &tescOutputAlignment;
7650 break;
7651 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
7652 variable.type = QShaderDescription::VariableType::Float;
7653 variable.arrayDims = builtin.arrayDims;
7654 binding = tescOutputBufferBinding;
7655 offset = &offsetInTescOutput;
7656 alignment = &tescOutputAlignment;
7657 break;
7658 case QShaderDescription::BuiltinType::TessLevelOuterBuiltin:
7659 variable.type = QShaderDescription::VariableType::Half4;
7660 binding = tessFactorBufferBinding;
7661 offset = &offsetInTessFactorBuffer;
7662 tessLevelAdded = trianglesMode;
7663 alignment = &tessFactorAlignment;
7664 break;
7665 case QShaderDescription::BuiltinType::TessLevelInnerBuiltin:
7666 if (trianglesMode) {
7667 if (!tessLevelAdded) {
7668 variable.type = QShaderDescription::VariableType::Half4;
7669 binding = tessFactorBufferBinding;
7670 offsetInTessFactorBuffer = 0;
7671 offset = &offsetInTessFactorBuffer;
7672 alignment = &tessFactorAlignment;
7673 tessLevelAdded = true;
7674 } else {
7675 teseInBuiltins.remove(builtin.type);
7676 continue;
7677 }
7678 } else {
7679 variable.type = QShaderDescription::VariableType::Half2;
7680 binding = tessFactorBufferBinding;
7681 offsetInTessFactorBuffer = 8;
7682 offset = &offsetInTessFactorBuffer;
7683 alignment = &tessFactorAlignment;
7684 }
7685 break;
7686 default:
7687 Q_UNREACHABLE();
7688 break;
7689 }
7690
7691 if (teseInBuiltins.contains(builtin.type)) {
7692 if (binding != -1) {
7693 int index = nextAttributeIndex(indices);
7694 addVertexAttribute(variable, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
7695 usedBuffers << binding;
7696 } else {
7697 qWarning() << "baked tessellation control shader missing output buffer binding information";
7698 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
7699 }
7700 } else {
7701 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
7702 }
7703
7704 teseInBuiltins.remove(builtin.type);
7705 }
7706
7707 for (const QShaderDescription::BuiltinVariable &builtin : teseInBuiltins) {
7708 switch (builtin.type) {
7709 case QShaderDescription::BuiltinType::PositionBuiltin:
7710 case QShaderDescription::BuiltinType::PointSizeBuiltin:
7711 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
7712 qWarning() << "missing tessellation control output for tessellation evaluation builtin input:" << builtin;
7713 break;
7714 default:
7715 break;
7716 }
7717 }
7718
7719 if (usedBuffers.contains(tescOutputBufferBinding)) {
7720 vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint;
7721 vertexDesc.layouts[tescOutputBufferBinding].stride = aligned(offsetInTescOutput, tescOutputAlignment);
7722 }
7723
7724 if (usedBuffers.contains(tescPatchOutputBufferBinding)) {
7725 vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
7726 vertexDesc.layouts[tescPatchOutputBufferBinding].stride = aligned(offsetInTescPatchOutput, tescPatchOutputAlignment);
7727 }
7728
7729 if (usedBuffers.contains(tessFactorBufferBinding)) {
7730 vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
7731 vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? sizeof(MTLTriangleTessellationFactorsHalf) : sizeof(MTLQuadTessellationFactorsHalf);
7732 }
7733
7734 rpDesc.vertexDescriptor = vertexDesc;
7735 rpDesc.vertexFunction = vertTese.func;
7736 rpDesc.fragmentFunction = pipeline->d->fs.func;
7737
7738 // The portable, cross-API approach is to use CCW, the results are then
7739 // identical (assuming the applied clipSpaceCorrMatrix) for all the 3D
7740 // APIs. The tess.eval. GLSL shader is thus expected to specify ccw. If it
7741 // doesn't, things may not work as expected.
7742 rpDesc.tessellationOutputWindingOrder = toMetalTessellationWindingOrder(vertTese.desc.tessellationWindingOrder());
7743
7744 rpDesc.tessellationPartitionMode = toMetalTessellationPartitionMode(vertTese.desc.tessellationPartitioning());
7745
7746 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, pipeline->renderPassDescriptor());
7748
7749 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
7750
7751 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7752 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
7753
7754 NSError *err = nil;
7755 id<MTLRenderPipelineState> ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
7756 [rpDesc release];
7757 if (!ps) {
7758 const QString msg = QString::fromNSString(err.localizedDescription);
7759 qWarning("Failed to create render pipeline state for tessellation: %s", qPrintable(msg));
7760 } else {
7761 // ps is stored in the QMetalGraphicsPipelineData so the end result in this
7762 // regard is no different from what createVertexFragmentPipeline does
7763 pipeline->d->ps = ps;
7764 }
7765 return ps;
7766}
7767
7769{
7770 QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers;
7771
7772 // Check if something is reusable as-is.
7773 for (QMetalBuffer *workBuf : *workBuffers) {
7774 if (workBuf && workBuf->lastActiveFrameSlot == -1 && workBuf->size() >= size) {
7775 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7776 return workBuf;
7777 }
7778 }
7779
7780 // Once the pool is above a certain threshold, see if there is something
7781 // unused (but too small) and recreate that our size.
7782 if (workBuffers->count() > QMTL_FRAMES_IN_FLIGHT * 8) {
7783 for (QMetalBuffer *workBuf : *workBuffers) {
7784 if (workBuf && workBuf->lastActiveFrameSlot == -1) {
7785 workBuf->setSize(size);
7786 if (workBuf->create()) {
7787 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7788 return workBuf;
7789 }
7790 }
7791 }
7792 }
7793
7794 // Add a new buffer to the pool.
7795 QMetalBuffer *buf;
7796 if (type == WorkBufType::DeviceLocal) {
7797 // for GPU->GPU data (non-slotted, not necessarily host writable)
7798 buf = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
7799 } else {
7800 // for CPU->GPU (non-slotted, host writable/coherent)
7801 buf = new QMetalBuffer(rhiD, QRhiBuffer::Dynamic, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
7802 }
7803 if (buf->create()) {
7804 buf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7805 workBuffers->append(buf);
7806 return buf;
7807 }
7808
7809 qWarning("Failed to acquire work buffer of size %u", size);
7810 return nullptr;
7811}
7812
7813bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag)
7814{
7815 QRHI_RES_RHI(QRhiMetal);
7816 QString error;
7817 QByteArray entryPoint;
7818 QShaderKey activeKey;
7819
7820 const QShaderDescription tescDesc = tesc.description();
7821 const QShaderDescription teseDesc = tese.description();
7822 d->tess.inControlPointCount = uint(m_patchControlPointCount);
7823 d->tess.outControlPointCount = tescDesc.tessellationOutputVertexCount();
7824 if (!d->tess.outControlPointCount)
7825 d->tess.outControlPointCount = teseDesc.tessellationOutputVertexCount();
7826
7827 if (!d->tess.outControlPointCount) {
7828 qWarning("Failed to determine output vertex count from the tessellation control or evaluation shader, cannot tessellate");
7829 d->tess.enabled = false;
7830 d->tess.failed = true;
7831 return false;
7832 }
7833
7834 if (m_multiViewCount >= 2)
7835 qWarning("Multiview is not supported with tessellation");
7836
7837 // Now the vertex shader is a compute shader.
7838 // It should have three dedicated *VertexAsComputeShader variants.
7839 // What the requested variant was (Standard or Batchable) plays no role here.
7840 // (the Qt Quick scenegraph does not use tessellation with its materials)
7841 // Create all three versions.
7842
7843 bool variantsPresent[3] = {};
7844 const QVector<QShaderKey> tessVertKeys = tessVert.availableShaders();
7845 for (const QShaderKey &k : tessVertKeys) {
7846 switch (k.sourceVariant()) {
7847 case QShader::NonIndexedVertexAsComputeShader:
7848 variantsPresent[0] = true;
7849 break;
7850 case QShader::UInt32IndexedVertexAsComputeShader:
7851 variantsPresent[1] = true;
7852 break;
7853 case QShader::UInt16IndexedVertexAsComputeShader:
7854 variantsPresent[2] = true;
7855 break;
7856 default:
7857 break;
7858 }
7859 }
7860 if (!(variantsPresent[0] && variantsPresent[1] && variantsPresent[2])) {
7861 qWarning("Vertex shader is not prepared for Metal tessellation. Cannot tessellate. "
7862 "Perhaps the relevant variants (UInt32IndexedVertexAsComputeShader et al) were not generated? "
7863 "Try passing --msltess to qsb.");
7864 d->tess.enabled = false;
7865 d->tess.failed = true;
7866 return false;
7867 }
7868
7869 int varIndex = 0; // Will map NonIndexed as 0, UInt32 as 1, UInt16 as 2. Do not change this ordering.
7870 for (QShader::Variant variant : {
7871 QShader::NonIndexedVertexAsComputeShader,
7872 QShader::UInt32IndexedVertexAsComputeShader,
7873 QShader::UInt16IndexedVertexAsComputeShader })
7874 {
7875 id<MTLLibrary> lib = rhiD->d->createMetalLib(tessVert, variant, false, &error, &entryPoint, &activeKey);
7876 if (!lib) {
7877 qWarning("MSL shader compilation failed for vertex-as-compute shader %d: %s", int(variant), qPrintable(error));
7878 d->tess.enabled = false;
7879 d->tess.failed = true;
7880 return false;
7881 }
7882 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
7883 if (!func) {
7884 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7885 [lib release];
7886 d->tess.enabled = false;
7887 d->tess.failed = true;
7888 return false;
7889 }
7890 QMetalShader &compVs(d->tess.compVs[varIndex]);
7891 compVs.lib = lib;
7892 compVs.func = func;
7893 compVs.desc = tessVert.description();
7894 compVs.nativeResourceBindingMap = tessVert.nativeResourceBindingMap(activeKey);
7895 compVs.nativeShaderInfo = tessVert.nativeShaderInfo(activeKey);
7896
7897 // pre-create all three MTLComputePipelineStates
7898 if (!d->tess.vsCompPipeline(rhiD, variant)) {
7899 qWarning("Failed to pre-generate compute pipeline for vertex compute shader (tessellation variant %d)", int(variant));
7900 d->tess.enabled = false;
7901 d->tess.failed = true;
7902 return false;
7903 }
7904
7905 ++varIndex;
7906 }
7907
7908 // Pipeline #2 is a compute that runs the tessellation control (compute) shader
7909 id<MTLLibrary> tessControlLib = rhiD->d->createMetalLib(tesc, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7910 if (!tessControlLib) {
7911 qWarning("MSL shader compilation failed for tessellation control compute shader: %s", qPrintable(error));
7912 d->tess.enabled = false;
7913 d->tess.failed = true;
7914 return false;
7915 }
7916 id<MTLFunction> tessControlFunc = rhiD->d->createMSLShaderFunction(tessControlLib, entryPoint);
7917 if (!tessControlFunc) {
7918 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7919 [tessControlLib release];
7920 d->tess.enabled = false;
7921 d->tess.failed = true;
7922 return false;
7923 }
7924 d->tess.compTesc.lib = tessControlLib;
7925 d->tess.compTesc.func = tessControlFunc;
7926 d->tess.compTesc.desc = tesc.description();
7927 d->tess.compTesc.nativeResourceBindingMap = tesc.nativeResourceBindingMap(activeKey);
7928 d->tess.compTesc.nativeShaderInfo = tesc.nativeShaderInfo(activeKey);
7929 if (!d->tess.tescCompPipeline(rhiD)) {
7930 qWarning("Failed to pre-generate compute pipeline for tessellation control shader");
7931 d->tess.enabled = false;
7932 d->tess.failed = true;
7933 return false;
7934 }
7935
7936 // Pipeline #3 is a render pipeline with the tessellation evaluation (vertex) + the fragment shader
7937 id<MTLLibrary> tessEvalLib = rhiD->d->createMetalLib(tese, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7938 if (!tessEvalLib) {
7939 qWarning("MSL shader compilation failed for tessellation evaluation vertex shader: %s", qPrintable(error));
7940 d->tess.enabled = false;
7941 d->tess.failed = true;
7942 return false;
7943 }
7944 id<MTLFunction> tessEvalFunc = rhiD->d->createMSLShaderFunction(tessEvalLib, entryPoint);
7945 if (!tessEvalFunc) {
7946 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7947 [tessEvalLib release];
7948 d->tess.enabled = false;
7949 d->tess.failed = true;
7950 return false;
7951 }
7952 d->tess.vertTese.lib = tessEvalLib;
7953 d->tess.vertTese.func = tessEvalFunc;
7954 d->tess.vertTese.desc = tese.description();
7955 d->tess.vertTese.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey);
7956 d->tess.vertTese.nativeShaderInfo = tese.nativeShaderInfo(activeKey);
7957
7958 id<MTLLibrary> fragLib = rhiD->d->createMetalLib(tessFrag, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7959 if (!fragLib) {
7960 qWarning("MSL shader compilation failed for fragment shader: %s", qPrintable(error));
7961 d->tess.enabled = false;
7962 d->tess.failed = true;
7963 return false;
7964 }
7965 id<MTLFunction> fragFunc = rhiD->d->createMSLShaderFunction(fragLib, entryPoint);
7966 if (!fragFunc) {
7967 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7968 [fragLib release];
7969 d->tess.enabled = false;
7970 d->tess.failed = true;
7971 return false;
7972 }
7973 d->fs.lib = fragLib;
7974 d->fs.func = fragFunc;
7975 d->fs.desc = tessFrag.description();
7976 d->fs.nativeShaderInfo = tessFrag.nativeShaderInfo(activeKey);
7977 d->fs.nativeResourceBindingMap = tessFrag.nativeResourceBindingMap(activeKey);
7978
7979 if (!d->tess.teseFragRenderPipeline(rhiD, this)) {
7980 qWarning("Failed to pre-generate render pipeline for tessellation evaluation + fragment shader");
7981 d->tess.enabled = false;
7982 d->tess.failed = true;
7983 return false;
7984 }
7985
7986 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
7988 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
7989 [dsDesc release];
7990
7991 // no primitiveType
7993
7994 return true;
7995}
7996
7998{
7999 destroy(); // no early test, always invoke and leave it to destroy to decide what to clean up
8000
8001 QRHI_RES_RHI(QRhiMetal);
8002 rhiD->pipelineCreationStart();
8003 if (!rhiD->sanityCheckGraphicsPipeline(this))
8004 return false;
8005
8006 // See if tessellation is involved. Things will be very different, if so.
8007 QShader tessVert;
8008 QShader tesc;
8009 QShader tese;
8010 QShader tessFrag;
8011 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
8012 switch (shaderStage.type()) {
8013 case QRhiShaderStage::Vertex:
8014 tessVert = shaderStage.shader();
8015 break;
8016 case QRhiShaderStage::TessellationControl:
8017 tesc = shaderStage.shader();
8018 break;
8019 case QRhiShaderStage::TessellationEvaluation:
8020 tese = shaderStage.shader();
8021 break;
8022 case QRhiShaderStage::Fragment:
8023 tessFrag = shaderStage.shader();
8024 break;
8025 default:
8026 break;
8027 }
8028 }
8029 d->tess.enabled = tesc.isValid() && tese.isValid() && m_topology == Patches && m_patchControlPointCount > 0;
8030 d->tess.failed = false;
8031
8032 bool ok = d->tess.enabled ? createTessellationPipelines(tessVert, tesc, tese, tessFrag) : createVertexFragmentPipeline();
8033 if (!ok)
8034 return false;
8035
8036 // SPIRV-Cross buffer size buffers
8037 int buffers = 0;
8038 QVarLengthArray<QMetalShader *, 6> shaders;
8039 if (d->tess.enabled) {
8040 shaders.append(&d->tess.compVs[0]);
8041 shaders.append(&d->tess.compVs[1]);
8042 shaders.append(&d->tess.compVs[2]);
8043 shaders.append(&d->tess.compTesc);
8044 shaders.append(&d->tess.vertTese);
8045 } else {
8046 shaders.append(&d->vs);
8047 }
8048 shaders.append(&d->fs);
8049
8050 for (QMetalShader *shader : shaders) {
8051 if (shader->nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8052 const int binding = shader->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
8053 shader->nativeResourceBindingMap[binding] = {binding, -1};
8054 int maxNativeBinding = 0;
8055 for (const QShaderDescription::StorageBlock &block : shader->desc.storageBlocks())
8056 maxNativeBinding = qMax(maxNativeBinding, shader->nativeResourceBindingMap[block.binding].first);
8057
8058 // we use one buffer to hold data for all graphics shader stages, each with a different offset.
8059 // buffer offsets must be 32byte aligned - adjust buffer count accordingly
8060 buffers += ((maxNativeBinding + 1 + 7) / 8) * 8;
8061 }
8062 }
8063
8064 if (buffers) {
8066 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static,
8067 QRhiBuffer::UsageFlags(int(QRhiBuffer::StorageBuffer)
8068 | QMetalBuffer::InternalHostWritable),
8069 buffers * sizeof(int));
8070
8071 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
8073 }
8074
8075 rhiD->pipelineCreationEnd();
8077 generation += 1;
8078 rhiD->registerResource(this);
8079 return true;
8080}
8081
8087
8089{
8090 destroy();
8091 delete d;
8092}
8093
8095{
8096 d->cs.destroy();
8097
8098 if (!d->ps)
8099 return;
8100
8101 delete d->bufferSizeBuffer;
8102 d->bufferSizeBuffer = nullptr;
8103
8107 e.computePipeline.pipelineState = d->ps;
8108 d->ps = nil;
8109
8110 QRHI_RES_RHI(QRhiMetal);
8111 if (rhiD) {
8112 rhiD->d->releaseQueue.append(e);
8113 rhiD->unregisterResource(this);
8114 }
8115}
8116
8117void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
8118{
8119 if (binArch) {
8120 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
8121 cpDesc.binaryArchives = binArchArray;
8122 }
8123}
8124
8125void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
8126{
8127 if (binArch) {
8128 NSError *err = nil;
8129 if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) {
8130 const QString msg = QString::fromNSString(err.localizedDescription);
8131 qWarning("Failed to collect compute pipeline functions to binary archive: %s", qPrintable(msg));
8132 }
8133 }
8134}
8135
8137{
8138 if (d->ps)
8139 destroy();
8140
8141 QRHI_RES_RHI(QRhiMetal);
8142 rhiD->pipelineCreationStart();
8143
8144 auto cacheIt = rhiD->d->shaderCache.constFind({ m_shaderStage, false });
8145 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
8146 d->cs = *cacheIt;
8147 } else {
8148 const QShader shader = m_shaderStage.shader();
8149 QString error;
8150 QByteArray entryPoint;
8151 QShaderKey activeKey;
8152 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), false,
8153 &error, &entryPoint, &activeKey);
8154 if (!lib) {
8155 qWarning("MSL shader compilation failed: %s", qPrintable(error));
8156 return false;
8157 }
8158 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
8159 if (!func) {
8160 qWarning("MSL function for entry point %s not found", entryPoint.constData());
8161 [lib release];
8162 return false;
8163 }
8164 d->cs.lib = lib;
8165 d->cs.func = func;
8166 d->cs.localSize = shader.description().computeShaderLocalSize();
8167 d->cs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
8168 d->cs.desc = shader.description();
8169 d->cs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
8170
8171 // Compute never needs argument buffers on its own (they are there for
8172 // indirect command buffers, which are graphics-only), hence not asking
8173 // for that shader variant above. It can still be requested explicitly
8174 // via the QRhiShaderStage, in which case the textures and samplers have
8175 // to go through an argument buffer here as well.
8176 setupArgumentBufferEncoder(&d->cs);
8177
8178 // SPIRV-Cross buffer size buffers
8179 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8180 const int binding = d->cs.nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
8181 d->cs.nativeResourceBindingMap[binding] = {binding, -1};
8182 }
8183
8184 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
8185 for (QMetalShader &s : rhiD->d->shaderCache)
8186 s.destroy();
8187 rhiD->d->shaderCache.clear();
8188 }
8189 rhiD->d->shaderCache.insert({ m_shaderStage, false }, d->cs);
8190 }
8191
8192 [d->cs.lib retain];
8193 [d->cs.func retain];
8194 [d->cs.argumentEncoder retain];
8195
8196 if (d->cs.argumentBufferIndex >= 0 && !rhiD->caps.indirectCommandBuffers) {
8197 // Sampler states are created with supportArgumentBuffers only when this
8198 // cap is present, and Metal faults the GPU without any warning when one
8199 // that lacks it is used with an argument buffer.
8200 qWarning("The ArgumentBufferShader variant of a compute shader cannot be used on this device");
8201 return false;
8202 }
8203
8204 d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]);
8205
8206 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
8207 cpDesc.computeFunction = d->cs.func;
8208
8209 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
8210
8211 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
8212 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
8213
8214 NSError *err = nil;
8215 d->ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
8216 options: MTLPipelineOptionNone
8217 reflection: nil
8218 error: &err];
8219 [cpDesc release];
8220 if (!d->ps) {
8221 const QString msg = QString::fromNSString(err.localizedDescription);
8222 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
8223 return false;
8224 }
8225
8226 // SPIRV-Cross buffer size buffers
8227 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8228 int buffers = 0;
8229 for (const QShaderDescription::StorageBlock &block : d->cs.desc.storageBlocks())
8230 buffers = qMax(buffers, d->cs.nativeResourceBindingMap[block.binding].first);
8231
8232 buffers += 1;
8233
8234 if (!d->bufferSizeBuffer)
8235 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static,
8236 QRhiBuffer::UsageFlags(int(QRhiBuffer::StorageBuffer)
8237 | QMetalBuffer::InternalHostWritable),
8238 buffers * sizeof(int));
8239
8240 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
8242 }
8243
8244 rhiD->pipelineCreationEnd();
8246 generation += 1;
8247 rhiD->registerResource(this);
8248 return true;
8249}
8250
8254{
8256}
8257
8259{
8260 destroy();
8261 delete d;
8262}
8263
8265{
8266 // nothing to do here, we do not own the MTL cb object
8267}
8268
8270{
8271 nativeHandlesStruct.commandBuffer = (MTLCommandBuffer *) d->cb;
8272 nativeHandlesStruct.encoder = (MTLRenderCommandEncoder *) d->currentRenderPassEncoder;
8273 return &nativeHandlesStruct;
8274}
8275
8276void QMetalCommandBuffer::resetState(double lastGpuTime)
8277{
8278 d->lastGpuTime = lastGpuTime;
8279 d->currentRenderPassEncoder = nil;
8280 d->currentComputePassEncoder = nil;
8281 d->tessellationComputeEncoder = nil;
8282 d->currentPassRpDesc = nil;
8284}
8285
8287{
8289 currentTarget = nullptr;
8290 d->openDebugGroups.clear();
8292}
8293
8295{
8296 currentGraphicsPipeline = nullptr;
8297 currentComputePipeline = nullptr;
8299 pushConstantData.clear();
8301 currentGraphicsSrb = nullptr;
8302 currentComputeSrb = nullptr;
8304 currentResSlot = -1;
8305 currentIndexBuffer = nullptr;
8306 currentIndexOffset = 0;
8307 currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
8308 currentCullMode = -1;
8312 currentDepthBiasValues = { 0.0f, 0.0f };
8313 hasCustomScissorSet = false;
8314 currentScissor = {};
8315 currentViewport = {};
8316 hasBlendConstantsSet = false;
8317 currentBlendConstants = {};
8318 currentStencilRef = 0;
8319 appliedStencilRef.reset();
8320
8321 d->currentShaderResourceBindingState = {};
8322 d->currentDepthStencilState = nil;
8324 d->currentVertexInputsBuffers.clear();
8325 d->currentVertexInputOffsets.clear();
8326}
8327
8328QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
8329 : QRhiSwapChain(rhi),
8330 rtWrapper(rhi, this),
8331 cbWrapper(rhi),
8333{
8334 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8335 d->sem[i] = nullptr;
8336 d->msaaTex[i] = nil;
8337 }
8338}
8339
8341{
8342 destroy();
8343 delete d;
8344}
8345
8347{
8348 if (!d->layer)
8349 return;
8350
8351 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8352 if (d->sem[i]) {
8353 // the semaphores cannot be released if they do not have the initial value
8355
8356 dispatch_release(d->sem[i]);
8357 d->sem[i] = nullptr;
8358 }
8359 }
8360
8361 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8362 [d->msaaTex[i] release];
8363 d->msaaTex[i] = nil;
8364 }
8365
8366 d->layer = nullptr;
8367 m_proxyData = {};
8368
8369 [d->curDrawable release];
8370 d->curDrawable = nil;
8371
8372 QRHI_RES_RHI(QRhiMetal);
8373 if (rhiD) {
8374 rhiD->swapchains.remove(this);
8375 rhiD->unregisterResource(this);
8376 }
8377}
8378
8380{
8381 return &cbWrapper;
8382}
8383
8388
8389// view.layer should ideally be called on the main thread, otherwise the UI
8390// Thread Checker in Xcode drops a warning. Hence trying to proxy it through
8391// QRhiSwapChainProxyData instead of just calling this function directly.
8392static inline CAMetalLayer *layerForWindow(QWindow *window)
8393{
8394 Q_ASSERT(window);
8395 CALayer *layer = nullptr;
8396#ifdef Q_OS_MACOS
8397 if (auto *cocoaWindow = window->nativeInterface<QNativeInterface::Private::QCocoaWindow>())
8398 layer = cocoaWindow->contentLayer();
8399#else
8400 layer = reinterpret_cast<UIView *>(window->winId()).layer;
8401#endif
8402 Q_ASSERT(layer);
8403 return static_cast<CAMetalLayer *>(layer);
8404}
8405
8406// If someone calls this, it is hopefully from the main thread, and they will
8407// then set the returned data on the QRhiSwapChain, so it won't need to query
8408// the layer on its own later on.
8410{
8412 d.reserved[0] = layerForWindow(window);
8413 return d;
8414}
8415
8417{
8418 Q_ASSERT(m_window);
8419 CAMetalLayer *layer = d->layer;
8420 if (!layer)
8421 layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, m_window, QRhi::Metal, 0);
8422
8423 Q_ASSERT(layer);
8424 int height = (int)layer.bounds.size.height;
8425 int width = (int)layer.bounds.size.width;
8426 width *= layer.contentsScale;
8427 height *= layer.contentsScale;
8428 return QSize(width, height);
8429}
8430
8432{
8433 if (f == HDRExtendedSrgbLinear) {
8434 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8435 } else if (f == HDR10) {
8436 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8437 } else if (f == HDRExtendedDisplayP3Linear) {
8438 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8439 }
8440 return f == SDR;
8441}
8442
8444{
8445 QRHI_RES_RHI(QRhiMetal);
8446
8447 chooseFormats(); // ensure colorFormat and similar are filled out
8448
8449 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
8451 rpD->hasDepthStencil = m_depthStencil != nullptr;
8452
8453 rpD->colorFormat[0] = int(d->colorFormat);
8454
8455#ifdef Q_OS_MACOS
8456 // m_depthStencil may not be built yet so cannot rely on computed fields in it
8457 rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
8458 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
8459#else
8460 rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8;
8461#endif
8462
8463 rpD->hasShadingRateMap = m_shadingRateMap != nullptr;
8464
8466
8467 rhiD->registerResource(rpD, false);
8468 return rpD;
8469}
8470
8472{
8473 QRHI_RES_RHI(QRhiMetal);
8474 samples = rhiD->effectiveSampleCount(m_sampleCount);
8475 // pick a format that is allowed for CAMetalLayer.pixelFormat
8476 if (m_format == HDRExtendedSrgbLinear || m_format == HDRExtendedDisplayP3Linear) {
8477 d->colorFormat = MTLPixelFormatRGBA16Float;
8478 d->rhiColorFormat = QRhiTexture::RGBA16F;
8479 return;
8480 }
8481 if (m_format == HDR10) {
8482 d->colorFormat = MTLPixelFormatRGB10A2Unorm;
8483 d->rhiColorFormat = QRhiTexture::RGB10A2;
8484 return;
8485 }
8486 d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
8487 d->rhiColorFormat = QRhiTexture::BGRA8;
8488}
8489
8491{
8492 // wait+signal is the general pattern to ensure the commands for a
8493 // given frame slot have completed (if sem is 1, we go 0 then 1; if
8494 // sem is 0 we go -1, block, completion increments to 0, then us to 1)
8495
8496 dispatch_semaphore_t sem = d->sem[slot];
8497 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
8498 dispatch_semaphore_signal(sem);
8499}
8500
8502{
8503 Q_ASSERT(m_window);
8504
8505 const bool needsRegistration = !window || window != m_window;
8506
8507 if (window && window != m_window)
8508 destroy();
8509 // else no destroy(), this is intentional
8510
8511 QRHI_RES_RHI(QRhiMetal);
8512 if (needsRegistration || !rhiD->swapchains.contains(this))
8513 rhiD->swapchains.insert(this);
8514
8515 window = m_window;
8516
8517 if (window->surfaceType() != QSurface::MetalSurface) {
8518 qWarning("QMetalSwapChain only supports MetalSurface windows");
8519 return false;
8520 }
8521
8522 d->layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, window, QRhi::Metal, 0);
8523 Q_ASSERT(d->layer);
8524
8526 if (d->colorFormat != d->layer.pixelFormat)
8527 d->layer.pixelFormat = d->colorFormat;
8528
8529 if (m_format == HDRExtendedSrgbLinear) {
8530 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
8531 d->layer.wantsExtendedDynamicRangeContent = YES;
8532 } else if (m_format == HDR10) {
8533 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ);
8534 d->layer.wantsExtendedDynamicRangeContent = YES;
8535 } else if (m_format == HDRExtendedDisplayP3Linear) {
8536 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
8537 d->layer.wantsExtendedDynamicRangeContent = YES;
8538 }
8539
8540 if (m_flags.testFlag(UsedAsTransferSource))
8541 d->layer.framebufferOnly = NO;
8542
8543#ifdef Q_OS_MACOS
8544 if (m_flags.testFlag(NoVSync))
8545 d->layer.displaySyncEnabled = NO;
8546#endif
8547
8548 if (m_flags.testFlag(SurfaceHasPreMulAlpha)) {
8549 d->layer.opaque = NO;
8550 } else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
8551 // The CoreAnimation compositor is said to expect premultiplied alpha,
8552 // so this is then wrong when it comes to the blending operations but
8553 // there's nothing we can do. Fortunately Qt Quick always outputs
8554 // premultiplied alpha so it is not a problem there.
8555 d->layer.opaque = NO;
8556 } else {
8557 d->layer.opaque = YES;
8558 }
8559
8560 // Now set the layer's drawableSize which will stay set to the same value
8561 // until the next createOrResize(), thus ensuring atomicity with regards to
8562 // the drawable size in frames.
8563 int width = (int)d->layer.bounds.size.width;
8564 int height = (int)d->layer.bounds.size.height;
8565 CGSize layerSize = CGSizeMake(width, height);
8566 const float scaleFactor = d->layer.contentsScale;
8567 layerSize.width *= scaleFactor;
8568 layerSize.height *= scaleFactor;
8569 d->layer.drawableSize = layerSize;
8570
8571 m_currentPixelSize = QSizeF::fromCGSize(layerSize).toSize();
8572 pixelSize = m_currentPixelSize;
8573
8574 [d->layer setDevice: rhiD->d->dev];
8575
8576 [d->curDrawable release];
8577 d->curDrawable = nil;
8578
8579 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8580 d->lastGpuTime[i] = 0;
8581 if (!d->sem[i])
8582 d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
8583 }
8584
8585 currentFrameSlot = 0;
8586 frameCount = 0;
8587
8588 ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
8589 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
8590 qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
8591 m_depthStencil->sampleCount(), m_sampleCount);
8592 }
8593 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
8594 if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
8595 m_depthStencil->setPixelSize(pixelSize);
8596 if (!m_depthStencil->create())
8597 qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
8598 pixelSize.width(), pixelSize.height());
8599 } else {
8600 qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
8601 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
8602 pixelSize.width(), pixelSize.height());
8603 }
8604 }
8605
8606 rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
8607 rtWrapper.d->pixelSize = pixelSize;
8608 rtWrapper.d->dpr = scaleFactor;
8611 rtWrapper.d->dsAttCount = ds ? 1 : 0;
8612
8613 qCDebug(QRHI_LOG_INFO, "got CAMetalLayer, pixel size %dx%d (scale %.2f)",
8614 pixelSize.width(), pixelSize.height(), scaleFactor);
8615
8616 if (samples > 1) {
8617 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
8618 desc.textureType = MTLTextureType2DMultisample;
8619 desc.pixelFormat = d->colorFormat;
8620 desc.width = NSUInteger(pixelSize.width());
8621 desc.height = NSUInteger(pixelSize.height());
8622 desc.sampleCount = NSUInteger(samples);
8623 desc.resourceOptions = MTLResourceStorageModePrivate;
8624 desc.storageMode = MTLStorageModePrivate;
8625 desc.usage = MTLTextureUsageRenderTarget;
8626 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8627 if (d->msaaTex[i]) {
8630 e.lastActiveFrameSlot = 1; // because currentFrameSlot is reset to 0
8631 e.renderbuffer.texture = d->msaaTex[i];
8632 rhiD->d->releaseQueue.append(e);
8633 }
8634 d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
8635 }
8636 [desc release];
8637 }
8638
8639 rhiD->registerResource(this);
8640
8641 return true;
8642}
8643
8645{
8648 info.limits.colorComponentValue.maxColorComponentValue = 1;
8649 info.limits.colorComponentValue.maxPotentialColorComponentValue = 1;
8651 info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred
8652
8653 if (m_window) {
8654 // Must use m_window, not window, given this may be called before createOrResize().
8655#if defined(Q_OS_MACOS)
8656 NSView *view = reinterpret_cast<NSView *>(m_window->winId());
8657 NSScreen *screen = view.window.screen;
8658 info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue;
8659 info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
8660#elif defined(Q_OS_IOS)
8661 UIView *view = reinterpret_cast<UIView *>(m_window->winId());
8662 UIScreen *screen = view.window.windowScene.screen;
8663 info.limits.colorComponentValue.maxColorComponentValue =
8664 view.window.windowScene.screen.currentEDRHeadroom;
8665 info.limits.colorComponentValue.maxPotentialColorComponentValue =
8666 screen.potentialEDRHeadroom;
8667#endif
8668 }
8669
8670 return info;
8671}
8672
8673QT_END_NAMESPACE
void buildIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb, const QRhiIndirectCommandBufferBuildInfo &info) override
void drawIndirectCount(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, QRhiBuffer *countBuffer, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride) override
static QRhiSwapChainProxyData updateSwapChainProxyData(QWindow *window)
QMetalSwapChain * currentSwapChain
bool isDeviceLost() const override
void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override
QRhiStats statistics() override
void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) override
void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
int ubufAlignment() const override
Definition qrhimetal.mm:868
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override
Definition qrhimetal.mm:899
void endExternal(QRhiCommandBuffer *cb) override
QRhiMetalData * d
void drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, quint32 drawCount, quint32 stride) override
QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice=nullptr)
Definition qrhimetal.mm:577
void beginPass(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
qsizetype subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
void beginExternal(QRhiCommandBuffer *cb) override
void adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb)
void setDefaultScissor(QMetalCommandBuffer *cbD)
void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, bool offsetOnlyChange, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES], const QMetalShader *shaders[SUPPORTED_STAGES])
QRhiSwapChain * createSwapChain() override
Definition qrhimetal.mm:858
QRhiGraphicsPipeline * createGraphicsPipeline() override
const char * icbUnavailableReason(QMetalCommandBuffer *cbD) const
bool create(QRhi::Flags flags) override
Definition qrhimetal.mm:653
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override
void dispatchIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset) override
void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override
void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
QRhiSampler * createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) override
void setPushConstants(QRhiCommandBuffer *cb, quint32 offset, quint32 size, const void *data) override
static const int SUPPORTED_STAGES
void setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) override
bool isYUpInNDC() const override
Definition qrhimetal.mm:878
void commitIndirectCommandBuffer(QRhiResourceUpdateBatch *u, QRhiIndirectCommandBuffer *icb) override
int resourceLimit(QRhi::ResourceLimit limit) const override
QRhiShaderResourceBindings * createShaderResourceBindings() override
void executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot)
QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override
QMatrix4x4 clipSpaceCorrMatrix() const override
Definition qrhimetal.mm:888
void drawIndexedIndirectCount(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, QRhiBuffer *countBuffer, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride) override
const QRhiNativeHandles * nativeHandles() override
void interruptRenderPass(QMetalCommandBuffer *cbD)
void executeDeferredReleases(bool forced=false)
void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
QRhiComputePipeline * createComputePipeline() override
void drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, quint32 drawCount, quint32 stride) override
bool isClipDepthZeroToOne() const override
Definition qrhimetal.mm:883
QList< int > supportedSampleCounts() const override
Definition qrhimetal.mm:847
QRhiTextureRenderTarget * createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override
bool isYUpInFramebuffer() const override
Definition qrhimetal.mm:873
bool prepareIcbKernels()
QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override
const QRhiNativeHandles * nativeHandles(QRhiCommandBuffer *cb) override
void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
void setPipelineCacheData(const QByteArray &data) override
void finalizeDeferredStoreActions(QMetalCommandBuffer *cbD, bool passIsEnding)
QByteArray pipelineCacheData() override
void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override
void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override
void setVertexInput(QRhiCommandBuffer *cb, int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) override
void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override
bool importedDevice
void tessellatedDraw(const TessDrawArgs &args)
void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override
void debugMarkEnd(QRhiCommandBuffer *cb) override
QRhi::FrameOpResult finish() override
QRhiShadingRateMap * createShadingRateMap() override
bool importedCmdQueue
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override
bool makeThreadLocalNativeContextCurrent() override
QRhiTexture * createTexture(QRhiTexture::Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, QRhiTexture::Flags flags) override
void setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override
void releaseCachedResources() override
bool icbDraw(QMetalCommandBuffer *cbD, bool indexed, QMetalBuffer *indirectBufD, quint32 indirectBufferOffset, QMetalBuffer *countBufD, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride)
void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override
void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override
void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
QRhiDriverInfo driverInfo() const override
double lastCompletedGpuTime(QRhiCommandBuffer *cb) override
void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override
void executeIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb, quint32 firstCommand, quint32 commandCount) override
bool prepareIcb(quint32 maxDrawCount)
void finishActiveReadbacks(bool forced=false)
void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override
bool isFeatureSupported(QRhi::Feature feature) const override
Definition qrhimetal.mm:932
void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, qsizetype *curOfs)
void destroy() override
Definition qrhimetal.mm:781
QList< QSize > supportedShadingRates(int sampleCount) const override
Definition qrhimetal.mm:852
void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
static QRhiResourceUpdateBatchPrivate * get(QRhiResourceUpdateBatch *b)
Definition qrhi_p.h:754
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:326
\inmodule QtGui
Definition qshader.h:81
Combined button and popup list for selecting options.
#define __has_feature(x)
@ UnBounded
Definition qrhi_p.h:390
@ Bounded
Definition qrhi_p.h:391
#define QRHI_RES_RHI(t)
Definition qrhi_p.h:33
#define QRHI_RES(t, x)
Definition qrhi_p.h:32
Int aligned(Int v, Int byteAlign)
\variable QRhiVulkanQueueSubmitParams::waitSemaphoreCount
static int mtlPushConstantBufferIndex(const QMetalShader &s)
static bool usesTextures(const QShaderDescription &desc)
static MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
static void qrhimtl_releaseIcbSlots(QRhiMetal *rhiD, QMetalIndirectCommandBuffer *icbD)
static MTLLanguageVersion toMetalLanguageVersion(const QShaderVersion &version)
static MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t)
static CAMetalLayer * layerForWindow(QWindow *window)
static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment)
static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
static void qrhimtl_encodeIcbFromCpu(QMetalIndirectCommandBuffer *icbD, QMetalIndirectCommandBufferData::Slot &slot, MTLPrimitiveType primitiveType, id< MTLBuffer > indexBufMtl, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
static bool hasArgumentBufferVariant(const QShader &shader)
static bool matches(const QList< QShaderDescription::BlockVariable > &a, const QList< QShaderDescription::BlockVariable > &b)
Q_DECLARE_TYPEINFO(QRhiMetalData::TextureReadback, Q_RELOCATABLE_TYPE)
static MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
static QMetalRenderTargetData * currentRenderTargetData(QMetalCommandBuffer *cbD)
static MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
static MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
static MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
static MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
static MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
static constexpr quint32 ICB_DRAW_COUNT_THRESHOLD
BindingType
static MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::PolygonMode mode)
static quint32 mtlPushConstantBlockSize(const QMetalShader &s)
static MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
static MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
static void endTempComputeEncoding(QRhiMetal *rhiD, QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > computeEncoder)
static void takeIndex(quint32 index, quint64 &indices)
static int mapBinding(int binding, int stageIndex, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[], BindingType type)
static id< MTLComputeCommandEncoder > tempComputeEncoder(QRhiMetal *rhiD, QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > maybeComputeEncoder)
static void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceStage, int encoderStage, const QMetalShaderResourceBindingsData *customBindingState=nullptr)
static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
static MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags, const QRhiMetal *d)
static QRhiShaderResourceBinding::StageFlag toRhiSrbStage(int stage)
static void setupArgumentBufferEncoder(QMetalShader *shader)
static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
static uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
static MTLStoreAction interruptionStoreAction(MTLStoreAction finalAction, bool passIsEnding)
#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
Definition qrhimetal.mm:62
static void qrhimtl_replayIcbOnCpu(QMetalCommandBuffer *cbD, QMetalIndirectCommandBuffer *icbD, quint32 firstCommand, quint32 count, int currentFrameSlot)
static bool qrhimtl_ensureIcbSlots(QRhiMetal *rhiD, QMetalIndirectCommandBuffer *icbD, QMetalIndirectCommandBufferData::Fill fill)
static MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
static MTLTessellationPartitionMode toMetalTessellationPartitionMode(QShaderDescription::TessellationPartitioning p)
static MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
static int aligned(quint32 offset, quint32 alignment)
static void declareStageArgumentBufferResources(QMetalCommandBuffer *cbD, int encoderStage, const QMetalShaderResourceBindingsData::Stage &res)
static bool canStoreAttachment(id< MTLTexture > tex)
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_RELOCATABLE_TYPE)
static MTLResourceUsage storageImageUsage(QRhiShaderResourceBinding::Type type)
static bool qrhimtl_icbSlotMatches(const QMetalIndirectCommandBuffer *icbD, const QMetalIndirectCommandBufferData::Slot &slot, MTLPrimitiveType primitiveType, id< MTLBuffer > indexBufMtl, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
static MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
static void bindStageBuffers(QMetalCommandBuffer *cbD, int stage, const QRhiBatchedBindings< id< MTLBuffer > >::Batch &bufferBatch, const QRhiBatchedBindings< NSUInteger >::Batch &offsetBatch)
static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
static void encodeIcbWithCompute(QRhiMetalData *d, id< MTLComputeCommandEncoder > computeEncoder, id< MTLIndirectCommandBuffer > targetIcb, id< MTLBuffer > targetArgBuffer, id< MTLBuffer > targetRangeBuffer, bool indexed, QRhiCommandBuffer::IndexFormat indexFormat, MTLPrimitiveType primitiveType, id< MTLBuffer > indirectBufMtl, quint32 indirectBufferOffset, id< MTLBuffer > indexBufMtl, quint32 indexBufferOffset, id< MTLBuffer > countBufMtl, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride)
static bool indexTaken(quint32 index, quint64 indices)
static void bindStageTextures(QMetalCommandBuffer *cbD, int stage, const QRhiBatchedBindings< id< MTLTexture > >::Batch &textureBatch)
#define QRHI_METAL_DISABLE_BINARY_ARCHIVE
Definition qrhimetal.mm:57
static void bindStageSamplers(QMetalCommandBuffer *cbD, int encoderStage, const QRhiBatchedBindings< id< MTLSamplerState > >::Batch &samplerBatch)
static int nextAttributeIndex(quint64 indices)
static QT_BEGIN_NAMESPACE const int QMTL_FRAMES_IN_FLIGHT
Definition qrhimetal_p.h:26
void f(int c)
[26]
QVarLengthArray< BufferUpdate, 16 > pendingUpdates[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:365
id< MTLBuffer > buf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:360
char * beginFullDynamicBufferUpdateForCurrentFrame() override
QMetalBufferData * d
Definition qrhimetal_p.h:41
QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
int lastActiveFrameSlot
Definition qrhimetal_p.h:43
QRhiBuffer::NativeBuffer nativeBuffer() override
void endFullDynamicBufferUpdateForCurrentFrame() override
To be called when the entire contents of the buffer data has been updated in the memory block returne...
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool create() override
Creates the corresponding native graphics resources.
MTLRenderPassDescriptor * currentPassRpDesc
Definition qrhimetal.mm:441
id< MTLDepthStencilState > currentDepthStencilState
Definition qrhimetal.mm:445
QMetalShaderResourceBindingsData currentShaderResourceBindingState
Definition qrhimetal.mm:446
MTLStoreAction deferredStencilStoreAction
Definition qrhimetal.mm:454
QVarLengthArray< std::pair< uint, MTLStoreAction >, 4 > deferredColorStoreActions
Definition qrhimetal.mm:452
MTLStoreAction deferredDepthStoreAction
Definition qrhimetal.mm:453
id< MTLComputeCommandEncoder > tessellationComputeEncoder
Definition qrhimetal.mm:440
QRhiBatchedBindings< id< MTLBuffer > > currentVertexInputsBuffers
Definition qrhimetal.mm:443
id< MTLRenderCommandEncoder > currentRenderPassEncoder
Definition qrhimetal.mm:438
id< MTLCommandBuffer > cb
Definition qrhimetal.mm:436
QRhiBatchedBindings< NSUInteger > currentVertexInputOffsets
Definition qrhimetal.mm:444
QVarLengthArray< QByteArray, 4 > openDebugGroups
Definition qrhimetal.mm:447
id< MTLComputeCommandEncoder > currentComputePassEncoder
Definition qrhimetal.mm:439
QMetalBuffer * currentIndexBuffer
const QRhiNativeHandles * nativeHandles()
QMetalShaderResourceBindings * currentComputeSrb
QMetalComputePipeline * currentComputePipeline
QMetalShaderResourceBindings * currentGraphicsSrb
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalCommandBuffer(QRhiImplementation *rhi)
void resetPerPassCachedState()
QMetalCommandBufferData * d
QMetalGraphicsPipeline * currentGraphicsPipeline
void resetState(double lastGpuTime=0)
id< MTLComputePipelineState > ps
Definition qrhimetal.mm:557
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:562
QMetalComputePipeline(QRhiImplementation *rhi)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalComputePipelineData * d
bool create() override
QVector< QMetalBuffer * > deviceLocalWorkBuffers
Definition qrhimetal.mm:511
QMetalBuffer * acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type=WorkBufType::DeviceLocal)
QVector< QMetalBuffer * > hostVisibleWorkBuffers
Definition qrhimetal.mm:512
quint32 tescCompOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:530
std::array< id< MTLComputePipelineState >, 3 > vertexComputeState
Definition qrhimetal.mm:521
quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:534
static int vsCompVariantToIndex(QShader::Variant vertexCompVariant)
id< MTLComputePipelineState > tescCompPipeline(QRhiMetal *rhiD)
id< MTLRenderPipelineState > teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline)
QMetalGraphicsPipelineData * q
Definition qrhimetal.mm:515
id< MTLComputePipelineState > vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant)
quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:539
quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:525
id< MTLComputePipelineState > tessControlComputeState
Definition qrhimetal.mm:522
QMetalGraphicsPipeline * q
Definition qrhimetal.mm:492
MTLDepthClipMode depthClipMode
Definition qrhimetal.mm:500
MTLPrimitiveType primitiveType
Definition qrhimetal.mm:496
id< MTLRenderPipelineState > ps
Definition qrhimetal.mm:493
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:552
void setupVertexInputDescriptor(MTLVertexDescriptor *desc)
void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
id< MTLDepthStencilState > ds
Definition qrhimetal.mm:494
MTLTriangleFillMode triangleFillMode
Definition qrhimetal.mm:499
QMetalGraphicsPipelineData * d
bool createVertexFragmentPipeline()
QMetalGraphicsPipeline(QRhiImplementation *rhi)
void setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD)
void makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD)
bool create() override
Creates the corresponding native graphics resources.
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
void setupMetalDepthStencilDescriptor(void *metalDsDesc)
bool createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag)
QRhiCommandBuffer::IndexFormat indexFormat
id< MTLIndirectCommandBuffer > icb
QRhiIndirectCommandBufferBuildInfo buildInfo
Slot frameSlots[QMTL_FRAMES_IN_FLIGHT]
QMetalIndirectCommandBuffer(QRhiImplementation *rhi, Type type, quint32 maxCommandCount)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalIndirectCommandBufferData * d
const QRhiIndexedIndirectDrawCommand * indexedDrawCommands() const
bool create() override
Creates the corresponding native objects.
id< MTLTexture > tex
Definition qrhimetal.mm:371
MTLPixelFormat format
Definition qrhimetal.mm:370
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalRenderBufferData * d
Definition qrhimetal_p.h:69
QRhiTexture::Format backingFormat() const override
bool create() override
Creates the corresponding native graphics resources.
QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint)
QMetalRenderPassDescriptor(QRhiImplementation *rhi)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVector< quint32 > serializedFormat() const override
bool isCompatible(const QRhiRenderPassDescriptor *other) const override
int colorFormat[MAX_COLOR_ATTACHMENTS]
static const int MAX_COLOR_ATTACHMENTS
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() const override
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList
Definition qrhimetal.mm:487
id< MTLSamplerState > samplerState
Definition qrhimetal.mm:398
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, AddressMode u, AddressMode v, AddressMode w)
QMetalSamplerData * d
int lastActiveFrameSlot
bool create() override
QVarLengthArray< Buffer, 8 > buffers
Definition qrhimetal.mm:422
QVarLengthArray< Sampler, 8 > samplers
Definition qrhimetal.mm:424
QRhiBatchedBindings< NSUInteger > bufferOffsetBatches
Definition qrhimetal.mm:426
QVarLengthArray< Texture, 8 > textures
Definition qrhimetal.mm:423
QRhiBatchedBindings< id< MTLSamplerState > > samplerBatches
Definition qrhimetal.mm:428
QRhiBatchedBindings< id< MTLTexture > > textureBatches
Definition qrhimetal.mm:427
QRhiBatchedBindings< id< MTLBuffer > > bufferBatches
Definition qrhimetal.mm:425
bool create() override
Creates the corresponding resource binding set.
QMetalComputePipeline * lastUsedComputePipeline
QMetalShaderResourceBindings(QRhiImplementation *rhi)
QMetalGraphicsPipeline * lastUsedGraphicsPipeline
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
void updateResources(UpdateFlags flags) override
\variable QRhiMetalCommandBufferNativeHandles::commandBuffer
Definition qrhimetal.mm:157
void destroy()
Definition qrhimetal.mm:168
id< MTLLibrary > lib
Definition qrhimetal.mm:158
uint outputVertexCount
Definition qrhimetal.mm:161
std::array< uint, 3 > localSize
Definition qrhimetal.mm:160
QShaderDescription desc
Definition qrhimetal.mm:162
id< MTLFunction > func
Definition qrhimetal.mm:159
int argumentBufferIndex
Definition qrhimetal.mm:166
id< MTLArgumentEncoder > argumentEncoder
Definition qrhimetal.mm:165
id< MTLRasterizationRateMap > rateMap
Definition qrhimetal.mm:403
QMetalShadingRateMapData * d
QMetalShadingRateMap(QRhiImplementation *rhi)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QSize logicalSize() const override
bool createFrom(NativeShadingRateMap src) override
Sets up the shading rate map to use a native 3D API shading rate object src.
id< CAMetalDrawable > curDrawable
Definition qrhimetal.mm:568
dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:569
MTLPixelFormat colorFormat
Definition qrhimetal.mm:574
MTLRenderPassDescriptor * rp
Definition qrhimetal.mm:571
CAMetalLayer * layer
Definition qrhimetal.mm:567
double lastGpuTime[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:570
id< MTLTexture > msaaTex[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:572
QRhiTexture::Format rhiColorFormat
Definition qrhimetal.mm:573
QMetalRenderTargetData * d
QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
QSize pixelSize() const override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
float devicePixelRatio() const override
int sampleCount() const override
QWindow * window
void waitUntilCompleted(int slot)
bool createOrResize() override
Creates the swapchain if not already done and resizes the swapchain buffers to match the current size...
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QRhiCommandBuffer * currentFrameCommandBuffer() override
QMetalSwapChain(QRhiImplementation *rhi)
virtual QRhiSwapChainHdrInfo hdrInfo() override
\variable QRhiSwapChainHdrInfo::limitsType
QMetalRenderBuffer * ds
QMetalSwapChainRenderTarget rtWrapper
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() override
QMetalSwapChainData * d
bool isFormatSupported(Format f) override
QSize surfacePixelSize() override
QRhiRenderTarget * currentFrameRenderTarget() override
MTLPixelFormat viewFormat
Definition qrhimetal.mm:380
id< MTLTexture > tex
Definition qrhimetal.mm:382
MTLPixelFormat viewFormatForSampling
Definition qrhimetal.mm:381
id< MTLTexture > viewForLevel(int level)
QMetalTexture * q
Definition qrhimetal.mm:378
id< MTLTexture > perLevelViews[QRhi::MAX_MIP_LEVELS]
Definition qrhimetal.mm:387
id< MTLBuffer > stagingBuf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:385
id< MTLTexture > writeView
Definition qrhimetal.mm:384
QMetalTextureData(QMetalTexture *t)
Definition qrhimetal.mm:376
id< MTLTexture > samplingView
Definition qrhimetal.mm:383
id< MTLTexture > textureForWrite() const
Definition qrhimetal.mm:390
MTLPixelFormat format
Definition qrhimetal.mm:379
id< MTLTexture > textureForSampling() const
Definition qrhimetal.mm:389
float devicePixelRatio() const override
QMetalRenderTargetData * d
QMetalTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags)
bool create() override
Creates the corresponding native graphics resources.
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() override
int sampleCount() const override
QSize pixelSize() const override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, Flags flags)
bool prepareCreate(QSize *adjustedSize=nullptr)
NativeTexture nativeTexture() override
QMetalTextureData * d
Definition qrhimetal_p.h:90
bool create() override
Creates the corresponding native graphics resources.
int lastActiveFrameSlot
Definition qrhimetal_p.h:94
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool createFrom(NativeTexture src) override
Similar to create(), except that no new native textures are created.
\variable QRhiIndirectDrawCommand::vertexCount
Definition qrhi.h:1768
QRhiReadbackResult * result
Definition qrhimetal.mm:290
QMetalCommandBuffer cbWrapper
Definition qrhimetal.mm:263
OffscreenFrame(QRhiImplementation *rhi)
Definition qrhimetal.mm:260
QRhiReadbackDescription desc
Definition qrhimetal.mm:268
QRhiReadbackResult * result
Definition qrhimetal.mm:269
QRhiTexture::Format format
Definition qrhimetal.mm:273
void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLComputePipelineState > icbEncodePipelineU32
Definition qrhimetal.mm:304
static constexpr quint32 STAGING_AREA_MIN
Definition qrhimetal.mm:342
QRhiMetalData(QRhiMetal *rhi)
Definition qrhimetal.mm:182
QVarLengthArray< BufferReadback, 2 > activeBufferReadbacks
Definition qrhimetal.mm:295
QHash< ShaderCacheKey, QMetalShader > shaderCache
Definition qrhimetal.mm:319
bool setupBinaryArchive(NSURL *sourceFileUrl=nil)
Definition qrhimetal.mm:633
bool icbSetupFailed
Definition qrhimetal.mm:314
id< MTLFunction > icbEncodeFunctionU16
Definition qrhimetal.mm:308
void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLFunction > icbEncodeFunctionU32
Definition qrhimetal.mm:307
MTLCaptureManager * captureMgr
Definition qrhimetal.mm:297
id< MTLBuffer > icbArgumentBuffer
Definition qrhimetal.mm:309
static constexpr quint32 LARGE_STAGING_ALLOC
Definition qrhimetal.mm:341
NSUInteger icbCapacity
Definition qrhimetal.mm:302
void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLIndirectCommandBuffer > icb
Definition qrhimetal.mm:301
QVector< DeferredReleaseEntry > releaseQueue
Definition qrhimetal.mm:257
id< MTLBuffer > allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset)
static constexpr quint32 STAGING_AREA_MAX
Definition qrhimetal.mm:343
id< MTLLibrary > createMetalLib(const QShader &shader, QShader::Variant shaderVariant, bool preferArgumentBuffers, QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
id< MTLFunction > createMSLShaderFunction(id< MTLLibrary > lib, const QByteArray &entryPoint)
id< MTLBuffer > allocBufferStaging(quint32 size, int frameSlot, quint32 *offset)
id< MTLCaptureScope > captureScope
Definition qrhimetal.mm:298
void resetAndResizeBufferStagingArea(int frameSlot)
MTLRenderPassDescriptor * createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, int colorAttCount, QRhiShadingRateMap *shadingRateMap)
id< MTLBuffer > newOneShotStagingBuffer(quint32 size, int frameSlot)
QRhiMetal * q
Definition qrhimetal.mm:184
id< MTLComputePipelineState > icbEncodePipeline
Definition qrhimetal.mm:303
static constexpr int STAGING_AREA_HIGH_DEMAND_FRAMES
Definition qrhimetal.mm:345
id< MTLFunction > icbEncodeFunction
Definition qrhimetal.mm:306
id< MTLComputePipelineState > icbEncodePipelineU16
Definition qrhimetal.mm:305
static const int TEXBUF_ALIGN
Definition qrhimetal.mm:316
id< MTLBuffer > icbRangeBuffer
Definition qrhimetal.mm:311
id< MTLBuffer > allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment, int frameSlot, quint32 minBlockSize, quint32 *offset)
StagingArea bufStagingPool[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:330
id< MTLBinaryArchive > binArch
Definition qrhimetal.mm:187
id< MTLCommandBuffer > newCommandBuffer()
Definition qrhimetal.mm:621
QVarLengthArray< TextureReadback, 2 > activeTextureReadbacks
Definition qrhimetal.mm:285
StagingArea argBufPool[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:329
id< MTLDevice > dev
Definition qrhimetal.mm:185
quint64 globalFrameId
Definition qrhimetal.mm:349
static constexpr int STAGING_AREA_LOW_DEMAND_FRAMES
Definition qrhimetal.mm:344
void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLCommandQueue > cmdQueue
Definition qrhimetal.mm:186
id< MTLBuffer > icbNoCountBuffer
Definition qrhimetal.mm:313
QMetalCommandBuffer * cbD
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:2016
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1616
LimitsType limitsType
Definition qrhi.h:1627
LuminanceBehavior luminanceBehavior
Definition qrhi.h:1638
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1649
void * reserved[2]
Definition qrhi.h:1650