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