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
165 void destroy() {
166 nativeResourceBindingMap.clear();
167 [lib release];
168 lib = nil;
169 [func release];
170 func = nil;
171 }
172};
173
175{
176 QRhiMetalData(QRhiMetal *rhi) : q(rhi), ofr(rhi) { }
177
182
185 const QColor &colorClearValue,
186 const QRhiDepthStencilClearValue &depthStencilClearValue,
187 int colorAttCount,
188 QRhiShadingRateMap *shadingRateMap);
189 id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
190 QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
191 id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
192 bool setupBinaryArchive(NSURL *sourceFileUrl = nil);
193 void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
194 void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
195 void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
196 void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
197
211 int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
212 union {
213 struct {
215 } buffer;
216 struct {
218 } renderbuffer;
219 struct {
220 id<MTLTexture> texture;
222 id<MTLTexture> views[QRhi::MAX_MIP_LEVELS];
223 } texture;
224 struct {
226 } sampler;
227 struct {
229 } stagingBuffer;
230 struct {
235 } graphicsPipeline;
236 struct {
238 } computePipeline;
239 struct {
241 } shadingRateMap;
242 struct {
245 } stagingIcbBuffer;
246 };
247 };
249
251 OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
252 bool active = false;
253 double lastGpuTime = 0;
255 } ofr;
256
267
276
278
281
282 // Indirect Command Buffer (ICB) infrastructure for GPU-driven multi-draw
290
291 static const int TEXBUF_ALIGN = 256; // probably not accurate
292
294};
295
298
310
316
330
335
340
366
381
414
416{
427 QMetalShader vs;
428 QMetalShader fs;
440 bool enabled = false;
441 bool failed = false;
444 QMetalShader compVs[3];
447 QMetalShader compTesc;
448 QMetalShader vertTese;
449 quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
450 {
451 // max vertex output components = resourceLimit(MaxVertexOutputs) * 4 = 60
452 return vertexOrIndexCount * instanceCount * sizeof(float) * 60;
453 }
454 quint32 tescCompOutputBufferSize(quint32 patchCount) const
455 {
456 return outControlPointCount * patchCount * sizeof(float) * 60;
457 }
458 quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
459 {
460 // assume maxTessellationControlPerPatchOutputComponents is 128
461 return patchCount * sizeof(float) * 128;
462 }
463 quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
464 {
465 return ((vertexOrIndexCount + inControlPointCount - 1) / inControlPointCount) * instanceCount;
466 }
471 } tess;
472 void setupVertexInputDescriptor(MTLVertexDescriptor *desc);
473 void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc);
474
475 // SPIRV-Cross buffer size buffers
477};
478
480{
482 QMetalShader cs;
484
485 // SPIRV-Cross buffer size buffers
487};
488
500
501QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
502{
503 Q_UNUSED(params);
504
505 d = new QRhiMetalData(this);
506
507 importedDevice = importDevice != nullptr;
508 if (importedDevice) {
509 if (importDevice->dev) {
510 d->dev = (id<MTLDevice>) importDevice->dev;
511 importedCmdQueue = importDevice->cmdQueue != nullptr;
512 if (importedCmdQueue)
513 d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
514 } else {
515 qWarning("No MTLDevice given, cannot import");
516 importedDevice = false;
517 }
518 }
519}
520
522{
523 delete d;
524}
525
526template <class Int>
527inline Int aligned(Int v, Int byteAlign)
528{
529 return (v + byteAlign - 1) & ~(byteAlign - 1);
530}
531
532bool QRhiMetal::probe(QRhiMetalInitParams *params)
533{
534 QMacAutoReleasePool pool;
535
536 Q_UNUSED(params);
537 id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
538 if (dev) {
539 [dev release];
540 return true;
541 }
542 return false;
543}
544
546{
548 // Do not let the command buffer mess with the refcount of objects. We do
549 // have a proper render loop and will manage lifetimes similarly to other
550 // backends (Vulkan).
551 return [cmdQueue commandBufferWithUnretainedReferences];
552#else
553 return [cmdQueue commandBuffer];
554#endif
555}
556
557bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl)
558{
560 return false;
561#endif
562
563 [binArch release];
564 MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new];
565 binArchDesc.url = sourceFileUrl;
566 NSError *err = nil;
567 binArch = [dev newBinaryArchiveWithDescriptor: binArchDesc error: &err];
568 [binArchDesc release];
569 if (!binArch) {
570 const QString msg = QString::fromNSString(err.localizedDescription);
571 qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg));
572 return false;
573 }
574 return true;
575}
576
577bool QRhiMetal::create(QRhi::Flags flags)
578{
579 rhiFlags = flags;
580
581 if (importedDevice)
582 [d->dev retain];
583 else
584 d->dev = MTLCreateSystemDefaultDevice();
585
586 if (!d->dev) {
587 qWarning("No MTLDevice");
588 return false;
589 }
590
591 const QString deviceName = QString::fromNSString([d->dev name]);
592 qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(deviceName));
593 driverInfoStruct.deviceName = deviceName.toUtf8();
594
595 // deviceId and vendorId stay unset for now. Note that registryID is not
596 // suitable as deviceId because it does not seem stable on macOS and can
597 // apparently change when the system is rebooted.
598
599#ifdef Q_OS_MACOS
600 const MTLDeviceLocation deviceLocation = [d->dev location];
601 switch (deviceLocation) {
602 case MTLDeviceLocationBuiltIn:
603 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
604 break;
605 case MTLDeviceLocationSlot:
606 driverInfoStruct.deviceType = QRhiDriverInfo::DiscreteDevice;
607 break;
608 case MTLDeviceLocationExternal:
609 driverInfoStruct.deviceType = QRhiDriverInfo::ExternalDevice;
610 break;
611 default:
612 break;
613 }
614#else
615 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
616#endif
617
618 const QOperatingSystemVersion ver = QOperatingSystemVersion::current();
619 osMajor = ver.majorVersion();
620 osMinor = ver.minorVersion();
621
622 if (importedCmdQueue)
623 [d->cmdQueue retain];
624 else
625 d->cmdQueue = [d->dev newCommandQueue];
626
627 d->captureMgr = [MTLCaptureManager sharedCaptureManager];
628 // Have a custom capture scope as well which then shows up in XCode as
629 // an option when capturing, and becomes especially useful when having
630 // multiple windows with multiple QRhis.
631 d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
632 const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
633 d->captureScope.label = label.toNSString();
634
635#if defined(Q_OS_MACOS) || defined(Q_OS_VISIONOS)
636 caps.maxTextureSize = 16384;
637 caps.baseVertexAndInstance = true;
638 caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7];
639 caps.maxThreadGroupSize = 1024;
640 caps.multiView = true;
641#elif defined(Q_OS_TVOS)
642 if ([d->dev supportsFamily:MTLGPUFamilyApple3])
643 caps.maxTextureSize = 16384;
644 else
645 caps.maxTextureSize = 8192;
646 caps.baseVertexAndInstance = false;
647 caps.isAppleGPU = true;
648#elif defined(Q_OS_IOS)
649 if ([d->dev supportsFamily:MTLGPUFamilyApple3]) {
650 caps.maxTextureSize = 16384;
651 caps.baseVertexAndInstance = true;
652 } else if ([d->dev supportsFamily:MTLGPUFamilyApple2]) {
653 caps.maxTextureSize = 8192;
654 caps.baseVertexAndInstance = false;
655 } else {
656 caps.maxTextureSize = 4096;
657 caps.baseVertexAndInstance = false;
658 }
659 caps.isAppleGPU = true;
660 if ([d->dev supportsFamily:MTLGPUFamilyApple4])
661 caps.maxThreadGroupSize = 1024;
662 if ([d->dev supportsFamily:MTLGPUFamilyApple5])
663 caps.multiView = true;
664#endif
665
666 caps.supportedSampleCounts = { 1 };
667 for (int sampleCount : { 2, 4, 8 }) {
668 if ([d->dev supportsTextureSampleCount: sampleCount])
669 caps.supportedSampleCounts.append(sampleCount);
670 }
671
672 caps.indirectCommandBuffers = ([d->dev supportsFamily:MTLGPUFamilyApple5]
673 || [d->dev supportsFamily:MTLGPUFamilyMac2])
674 && [d->dev supportsFamily:MTLGPUFamilyMetal3];
675
676 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 1];
677 if (caps.shadingRateMap && caps.multiView)
678 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 2];
679
680 // QTBUG-144444: setDepthClipMode is not available on the Simulator
681 caps.depthClamp = [d->dev supportsFamily:MTLGPUFamilyApple3];
682
683 if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
684 d->setupBinaryArchive();
685
686 nativeHandlesStruct.dev = (MTLDevice *) d->dev;
687 nativeHandlesStruct.cmdQueue = (MTLCommandQueue *) d->cmdQueue;
688
689 return true;
690}
691
693{
696
697 for (QMetalShader &s : d->shaderCache)
698 s.destroy();
699 d->shaderCache.clear();
700
701 [d->captureScope release];
702 d->captureScope = nil;
703
704 [d->icbArgumentBuffer release];
705 d->icbArgumentBuffer = nil;
706
707 [d->icbEncodeFunctionU32 release];
708 d->icbEncodeFunctionU32 = nil;
709
710 [d->icbEncodeFunctionU16 release];
711 d->icbEncodeFunctionU16 = nil;
712
713 [d->icbEncodePipelineU32 release];
714 d->icbEncodePipelineU32 = nil;
715
716 [d->icbEncodePipelineU16 release];
717 d->icbEncodePipelineU16 = nil;
718
719 [d->icb release];
720 d->icb = nil;
721
722 d->icbCapacity = 0;
723
724 [d->binArch release];
725 d->binArch = nil;
726
727 [d->cmdQueue release];
728 if (!importedCmdQueue)
729 d->cmdQueue = nil;
730
731 [d->dev release];
732 if (!importedDevice)
733 d->dev = nil;
734}
735
737{
738 return caps.supportedSampleCounts;
739}
740
742{
743 Q_UNUSED(sampleCount);
744 return { QSize(1, 1) };
745}
746
747QRhiSwapChain *QRhiMetal::createSwapChain()
748{
749 return new QMetalSwapChain(this);
750}
751
752QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
753{
754 return new QMetalBuffer(this, type, usage, size);
755}
756
758{
759 return 256;
760}
761
763{
764 return false;
765}
766
768{
769 return true;
770}
771
773{
774 return true;
775}
776
778{
779 // depth range 0..1
780 static QMatrix4x4 m;
781 if (m.isIdentity()) {
782 // NB the ctor takes row-major
783 m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
784 0.0f, 1.0f, 0.0f, 0.0f,
785 0.0f, 0.0f, 0.5f, 0.5f,
786 0.0f, 0.0f, 0.0f, 1.0f);
787 }
788 return m;
789}
790
791bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
792{
793 Q_UNUSED(flags);
794
795 bool supportsFamilyMac2 = false; // needed for BC* formats
796 bool supportsFamilyApple3 = false;
797
798#ifdef Q_OS_MACOS
799 supportsFamilyMac2 = true;
800 if (caps.isAppleGPU)
801 supportsFamilyApple3 = true;
802#else
803 supportsFamilyApple3 = true;
804#endif
805
806 // BC5 is not available for any Apple hardare
807 if (format == QRhiTexture::BC5)
808 return false;
809
810 if (!supportsFamilyApple3) {
811 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
812 return false;
813 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
814 return false;
815 }
816
817 if (!supportsFamilyMac2)
818 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
819 return false;
820
821 return true;
822}
823
824bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
825{
826 switch (feature) {
827 case QRhi::MultisampleTexture:
828 return true;
829 case QRhi::MultisampleRenderBuffer:
830 return true;
831 case QRhi::DebugMarkers:
832 return true;
833 case QRhi::Timestamps:
834 return true;
835 case QRhi::Instancing:
836 return true;
837 case QRhi::CustomInstanceStepRate:
838 return true;
839 case QRhi::PrimitiveRestart:
840 return true;
841 case QRhi::NonDynamicUniformBuffers:
842 return true;
843 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
844 return false;
845 case QRhi::NPOTTextureRepeat:
846 return true;
847 case QRhi::RedOrAlpha8IsRed:
848 return true;
849 case QRhi::ElementIndexUint:
850 return true;
851 case QRhi::Compute:
852 return true;
853 case QRhi::WideLines:
854 return false;
855 case QRhi::VertexShaderPointSize:
856 return true;
857 case QRhi::BaseVertex:
858 return caps.baseVertexAndInstance;
859 case QRhi::BaseInstance:
860 return caps.baseVertexAndInstance;
861 case QRhi::TriangleFanTopology:
862 return false;
863 case QRhi::ReadBackNonUniformBuffer:
864 return true;
865 case QRhi::ReadBackNonBaseMipLevel:
866 return true;
867 case QRhi::TexelFetch:
868 return true;
869 case QRhi::RenderToNonBaseMipLevel:
870 return true;
871 case QRhi::IntAttributes:
872 return true;
873 case QRhi::ScreenSpaceDerivatives:
874 return true;
875 case QRhi::ReadBackAnyTextureFormat:
876 return true;
877 case QRhi::PipelineCacheDataLoadSave:
878 return true;
879 case QRhi::ImageDataStride:
880 return true;
881 case QRhi::RenderBufferImport:
882 return false;
883 case QRhi::ThreeDimensionalTextures:
884 return true;
885 case QRhi::RenderTo3DTextureSlice:
886 return true;
887 case QRhi::TextureArrays:
888 return true;
889 case QRhi::Tessellation:
890 return true;
891 case QRhi::GeometryShader:
892 return false;
893 case QRhi::TextureArrayRange:
894 return false;
895 case QRhi::NonFillPolygonMode:
896 return true;
897 case QRhi::OneDimensionalTextures:
898 return true;
899 case QRhi::OneDimensionalTextureMipmaps:
900 return false;
901 case QRhi::HalfAttributes:
902 return true;
903 case QRhi::RenderToOneDimensionalTexture:
904 return false;
905 case QRhi::ThreeDimensionalTextureMipmaps:
906 return true;
907 case QRhi::MultiView:
908 return caps.multiView;
909 case QRhi::TextureViewFormat:
910 return false;
911 case QRhi::ResolveDepthStencil:
912 return true;
913 case QRhi::VariableRateShading:
914 return false;
915 case QRhi::VariableRateShadingMap:
916 return caps.shadingRateMap;
917 case QRhi::VariableRateShadingMapWithTexture:
918 return false;
919 case QRhi::PerRenderTargetBlending:
920 case QRhi::SampleVariables:
921 return true;
922 case QRhi::InstanceIndexIncludesBaseInstance:
923 return true;
924 case QRhi::DepthClamp:
925 return caps.depthClamp;
926 case QRhi::DrawIndirect:
927 return true;
928 case QRhi::DrawIndirectMulti:
929 case QRhi::ShaderDrawParameters:
930 return false;
931 default:
932 Q_UNREACHABLE();
933 return false;
934 }
935}
936
937int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
938{
939 switch (limit) {
940 case QRhi::TextureSizeMin:
941 return 1;
942 case QRhi::TextureSizeMax:
943 return caps.maxTextureSize;
944 case QRhi::MaxColorAttachments:
945 return 8;
946 case QRhi::FramesInFlight:
948 case QRhi::MaxAsyncReadbackFrames:
950 case QRhi::MaxThreadGroupsPerDimension:
951 return 65535;
952 case QRhi::MaxThreadsPerThreadGroup:
953 Q_FALLTHROUGH();
954 case QRhi::MaxThreadGroupX:
955 Q_FALLTHROUGH();
956 case QRhi::MaxThreadGroupY:
957 Q_FALLTHROUGH();
958 case QRhi::MaxThreadGroupZ:
959 return caps.maxThreadGroupSize;
960 case QRhi::TextureArraySizeMax:
961 return 2048;
962 case QRhi::MaxUniformBufferRange:
963 return 65536;
964 case QRhi::MaxVertexInputs:
965 return 31;
966 case QRhi::MaxVertexOutputs:
967 return 15; // use the minimum from MTLGPUFamily1/2/3
968 case QRhi::ShadingRateImageTileSize:
969 return 0;
970 default:
971 Q_UNREACHABLE();
972 return 0;
973 }
974}
975
977{
978 return &nativeHandlesStruct;
979}
980
982{
983 return driverInfoStruct;
984}
985
987{
988 QRhiStats result;
989 result.totalPipelineCreationTime = totalPipelineCreationTime();
990 return result;
991}
992
994{
995 // not applicable
996 return false;
997}
998
999void QRhiMetal::setQueueSubmitParams(QRhiNativeHandles *)
1000{
1001 // not applicable
1002}
1003
1005{
1006 for (QMetalShader &s : d->shaderCache)
1007 s.destroy();
1008
1009 d->shaderCache.clear();
1010}
1011
1013{
1014 return false;
1015}
1016
1026
1028{
1029 Q_STATIC_ASSERT(sizeof(QMetalPipelineCacheDataHeader) == 256);
1030 QByteArray data;
1031 if (!d->binArch || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
1032 return data;
1033
1034 QTemporaryFile tmp;
1035 if (!tmp.open()) {
1036 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1037 return data;
1038 }
1039 tmp.close(); // the file exists until the tmp dtor runs
1040
1041 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1042 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1043 NSError *err = nil;
1044 if (![d->binArch serializeToURL: url error: &err]) {
1045 const QString msg = QString::fromNSString(err.localizedDescription);
1046 // Some of these "errors" are not actual errors. (think of "Nothing to serialize")
1047 qCDebug(QRHI_LOG_INFO, "Failed to serialize MTLBinaryArchive: %s", qPrintable(msg));
1048 return data;
1049 }
1050
1051 QFile f(fn);
1052 if (!f.open(QIODevice::ReadOnly)) {
1053 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to reopen temporary file");
1054 return data;
1055 }
1056 const QByteArray blob = f.readAll();
1057 f.close();
1058
1059 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1060 const quint32 dataSize = quint32(blob.size());
1061
1062 data.resize(headerSize + dataSize);
1063
1065 header.rhiId = pipelineCacheRhiId();
1066 header.arch = quint32(sizeof(void*));
1067 header.dataSize = quint32(dataSize);
1068 header.osMajor = osMajor;
1069 header.osMinor = osMinor;
1070 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1071 if (driverStrLen)
1072 memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen);
1073 header.driver[driverStrLen] = '\0';
1074
1075 memcpy(data.data(), &header, headerSize);
1076 memcpy(data.data() + headerSize, blob.constData(), dataSize);
1077 return data;
1078}
1079
1080void QRhiMetal::setPipelineCacheData(const QByteArray &data)
1081{
1082 if (data.isEmpty())
1083 return;
1084
1085 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1086 if (data.size() < qsizetype(headerSize)) {
1087 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)");
1088 return;
1089 }
1090
1091 const size_t dataOffset = headerSize;
1093 memcpy(&header, data.constData(), headerSize);
1094
1095 const quint32 rhiId = pipelineCacheRhiId();
1096 if (header.rhiId != rhiId) {
1097 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
1098 rhiId, header.rhiId);
1099 return;
1100 }
1101
1102 const quint32 arch = quint32(sizeof(void*));
1103 if (header.arch != arch) {
1104 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
1105 arch, header.arch);
1106 return;
1107 }
1108
1109 if (header.osMajor != osMajor || header.osMinor != osMinor) {
1110 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: OS version does not match (%u.%u, %u.%u)",
1111 osMajor, osMinor, header.osMajor, header.osMinor);
1112 return;
1113 }
1114
1115 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1116 if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) {
1117 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Metal device name does not match");
1118 return;
1119 }
1120
1121 if (data.size() < qsizetype(dataOffset + header.dataSize)) {
1122 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)");
1123 return;
1124 }
1125
1126 const char *p = data.constData() + dataOffset;
1127
1128 QTemporaryFile tmp;
1129 if (!tmp.open()) {
1130 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1131 return;
1132 }
1133 tmp.write(p, header.dataSize);
1134 tmp.close(); // the file exists until the tmp dtor runs
1135
1136 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1137 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1138 if (d->setupBinaryArchive(url))
1139 qCDebug(QRHI_LOG_INFO, "Created MTLBinaryArchive with initial data of %u bytes", header.dataSize);
1140}
1141
1142QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
1143 int sampleCount, QRhiRenderBuffer::Flags flags,
1144 QRhiTexture::Format backingFormatHint)
1145{
1146 return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
1147}
1148
1149QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format,
1150 const QSize &pixelSize, int depth, int arraySize,
1151 int sampleCount, QRhiTexture::Flags flags)
1152{
1153 return new QMetalTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
1154}
1155
1156QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
1157 QRhiSampler::Filter mipmapMode,
1158 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
1159{
1160 return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
1161}
1162
1163QRhiShadingRateMap *QRhiMetal::createShadingRateMap()
1164{
1165 return new QMetalShadingRateMap(this);
1166}
1167
1168QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
1169 QRhiTextureRenderTarget::Flags flags)
1170{
1171 return new QMetalTextureRenderTarget(this, desc, flags);
1172}
1173
1175{
1176 return new QMetalGraphicsPipeline(this);
1177}
1178
1180{
1181 return new QMetalComputePipeline(this);
1182}
1183
1185{
1186 return new QMetalShaderResourceBindings(this);
1187}
1188
1194
1195static inline int mapBinding(int binding,
1196 int stageIndex,
1197 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[],
1198 BindingType type)
1199{
1200 const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
1201 if (!map || map->isEmpty())
1202 return binding; // old QShader versions do not have this map, assume 1:1 mapping then
1203
1204 auto it = map->constFind(binding);
1205 if (it != map->cend())
1206 return type == BindingType::Sampler ? it->second : it->first; // may be -1, if the resource is inactive
1207
1208 // Hitting this path is normal too. It is not given that the resource (for
1209 // example, a uniform block) is present in the shaders for all the stages
1210 // specified by the visibility mask in the QRhiShaderResourceBinding.
1211 return -1;
1212}
1213
1215 int stage,
1216 const QRhiBatchedBindings<id<MTLBuffer>>::Batch &bufferBatch,
1217 const QRhiBatchedBindings<NSUInteger>::Batch &offsetBatch)
1218{
1219 switch (stage) {
1220 case QMetalShaderResourceBindingsData::VERTEX:
1221 [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
1222 offsets: offsetBatch.resources.constData()
1223 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1224 break;
1225 case QMetalShaderResourceBindingsData::FRAGMENT:
1226 [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
1227 offsets: offsetBatch.resources.constData()
1228 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1229 break;
1230 case QMetalShaderResourceBindingsData::COMPUTE:
1231 [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
1232 offsets: offsetBatch.resources.constData()
1233 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1234 break;
1237 // do nothing. These are used later for tessellation
1238 break;
1239 default:
1240 Q_UNREACHABLE();
1241 break;
1242 }
1243}
1244
1246 int stage,
1247 const QRhiBatchedBindings<id<MTLTexture>>::Batch &textureBatch)
1248{
1249 switch (stage) {
1250 case QMetalShaderResourceBindingsData::VERTEX:
1251 [cbD->d->currentRenderPassEncoder setVertexTextures: textureBatch.resources.constData()
1252 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1253 break;
1254 case QMetalShaderResourceBindingsData::FRAGMENT:
1255 [cbD->d->currentRenderPassEncoder setFragmentTextures: textureBatch.resources.constData()
1256 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1257 break;
1258 case QMetalShaderResourceBindingsData::COMPUTE:
1259 [cbD->d->currentComputePassEncoder setTextures: textureBatch.resources.constData()
1260 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1261 break;
1264 // do nothing. These are used later for tessellation
1265 break;
1266 default:
1267 Q_UNREACHABLE();
1268 break;
1269 }
1270}
1271
1273 int encoderStage,
1274 const QRhiBatchedBindings<id<MTLSamplerState>>::Batch &samplerBatch)
1275{
1276 switch (encoderStage) {
1277 case QMetalShaderResourceBindingsData::VERTEX:
1278 [cbD->d->currentRenderPassEncoder setVertexSamplerStates: samplerBatch.resources.constData()
1279 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1280 break;
1281 case QMetalShaderResourceBindingsData::FRAGMENT:
1282 [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: samplerBatch.resources.constData()
1283 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1284 break;
1285 case QMetalShaderResourceBindingsData::COMPUTE:
1286 [cbD->d->currentComputePassEncoder setSamplerStates: samplerBatch.resources.constData()
1287 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1288 break;
1291 // do nothing. These are used later for tessellation
1292 break;
1293 default:
1294 Q_UNREACHABLE();
1295 break;
1296 }
1297}
1298
1299// Helper that is not used during the common vertex+fragment and compute
1300// pipelines, but is necessary when tessellation is involved and so the
1301// graphics pipeline is under the hood a combination of multiple compute and
1302// render pipelines. We need to be able to set the buffers, textures, samplers
1303// when a switching between render and compute encoders.
1304static inline void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceStage, int encoderStage,
1305 const QMetalShaderResourceBindingsData *customBindingState = nullptr)
1306{
1307 const QMetalShaderResourceBindingsData *bindingData = customBindingState ? customBindingState : &cbD->d->currentShaderResourceBindingState;
1308
1309 for (int i = 0, ie = bindingData->res[resourceStage].bufferBatches.batches.count(); i != ie; ++i) {
1310 const auto &bufferBatch(bindingData->res[resourceStage].bufferBatches.batches[i]);
1311 const auto &offsetBatch(bindingData->res[resourceStage].bufferOffsetBatches.batches[i]);
1312 bindStageBuffers(cbD, encoderStage, bufferBatch, offsetBatch);
1313 }
1314
1315 for (int i = 0, ie = bindingData->res[resourceStage].textureBatches.batches.count(); i != ie; ++i) {
1316 const auto &batch(bindingData->res[resourceStage].textureBatches.batches[i]);
1317 bindStageTextures(cbD, encoderStage, batch);
1318 }
1319
1320 for (int i = 0, ie = bindingData->res[resourceStage].samplerBatches.batches.count(); i != ie; ++i) {
1321 const auto &batch(bindingData->res[resourceStage].samplerBatches.batches[i]);
1322 bindStageSamplers(cbD, encoderStage, batch);
1323 }
1324}
1325
1327{
1328 switch (stage) {
1329 case QMetalShaderResourceBindingsData::VERTEX:
1330 return QRhiShaderResourceBinding::StageFlag::VertexStage;
1331 case QMetalShaderResourceBindingsData::TESSCTRL:
1332 return QRhiShaderResourceBinding::StageFlag::TessellationControlStage;
1333 case QMetalShaderResourceBindingsData::TESSEVAL:
1334 return QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage;
1335 case QMetalShaderResourceBindingsData::FRAGMENT:
1336 return QRhiShaderResourceBinding::StageFlag::FragmentStage;
1337 case QMetalShaderResourceBindingsData::COMPUTE:
1338 return QRhiShaderResourceBinding::StageFlag::ComputeStage;
1339 }
1340
1341 Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::StageFlag::VertexStage);
1342}
1343
1346 int dynamicOffsetCount,
1347 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
1348 bool offsetOnlyChange,
1349 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES])
1350{
1352
1353 for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) {
1354 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding);
1355 switch (b->type) {
1356 case QRhiShaderResourceBinding::UniformBuffer:
1357 {
1358 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
1359 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1360 quint32 offset = b->u.ubuf.offset;
1361 for (int i = 0; i < dynamicOffsetCount; ++i) {
1362 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
1363 if (dynOfs.first == b->binding) {
1364 offset = dynOfs.second;
1365 break;
1366 }
1367 }
1368
1369 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1370 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1371 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1372 if (nativeBinding >= 0)
1373 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1374 }
1375 }
1376 }
1377 break;
1378 case QRhiShaderResourceBinding::SampledTexture:
1379 case QRhiShaderResourceBinding::Texture:
1380 case QRhiShaderResourceBinding::Sampler:
1381 {
1382 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
1383 for (int elem = 0; elem < data->count; ++elem) {
1384 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.texSamplers[elem].tex);
1385 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.texSamplers[elem].sampler);
1386
1387 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1388 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1389 // Must handle all three cases (combined, separate, separate):
1390 // first = texture binding, second = sampler binding
1391 // first = texture binding
1392 // first = sampler binding (i.e. BindingType::Texture...)
1393 const int textureBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1394 const int samplerBinding = texD && samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Sampler)
1395 : (samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture) : -1);
1396 if (textureBinding >= 0 && texD)
1397 bindingData.res[stage].textures.append({ textureBinding + elem, texD->d->tex });
1398 if (samplerBinding >= 0)
1399 bindingData.res[stage].samplers.append({ samplerBinding + elem, samplerD->d->samplerState });
1400 }
1401 }
1402 }
1403 }
1404 break;
1405 case QRhiShaderResourceBinding::ImageLoad:
1406 case QRhiShaderResourceBinding::ImageStore:
1407 case QRhiShaderResourceBinding::ImageLoadStore:
1408 {
1409 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
1410 id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
1411
1412 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1413 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1414 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1415 if (nativeBinding >= 0)
1416 bindingData.res[stage].textures.append({ nativeBinding, t });
1417 }
1418 }
1419 }
1420 break;
1421 case QRhiShaderResourceBinding::BufferLoad:
1422 case QRhiShaderResourceBinding::BufferStore:
1423 case QRhiShaderResourceBinding::BufferLoadStore:
1424 {
1425 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
1426 id<MTLBuffer> mtlbuf = bufD->d->buf[0];
1427 quint32 offset = b->u.sbuf.offset;
1428 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1429 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1430 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1431 if (nativeBinding >= 0)
1432 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1433 }
1434 }
1435 }
1436 break;
1437 default:
1438 Q_UNREACHABLE();
1439 break;
1440 }
1441 }
1442
1443 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1446 continue;
1448 continue;
1449
1450 // QRhiBatchedBindings works with the native bindings and expects
1451 // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
1452 // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
1453 // have to sort here every time.
1454
1455 std::sort(bindingData.res[stage].buffers.begin(), bindingData.res[stage].buffers.end(), [](const QMetalShaderResourceBindingsData::Stage::Buffer &a, const QMetalShaderResourceBindingsData::Stage::Buffer &b) {
1456 return a.nativeBinding < b.nativeBinding;
1457 });
1458
1459 for (const QMetalShaderResourceBindingsData::Stage::Buffer &buf : std::as_const(bindingData.res[stage].buffers)) {
1460 bindingData.res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf);
1461 bindingData.res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset);
1462 }
1463
1464 bindingData.res[stage].bufferBatches.finish();
1465 bindingData.res[stage].bufferOffsetBatches.finish();
1466
1467 for (int i = 0, ie = bindingData.res[stage].bufferBatches.batches.count(); i != ie; ++i) {
1468 const auto &bufferBatch(bindingData.res[stage].bufferBatches.batches[i]);
1469 const auto &offsetBatch(bindingData.res[stage].bufferOffsetBatches.batches[i]);
1470 // skip setting Buffer binding if the current state is already correct
1471 if (cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches.count() > i
1472 && cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches.count() > i
1473 && bufferBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches[i]
1474 && offsetBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches[i])
1475 {
1476 continue;
1477 }
1478 bindStageBuffers(cbD, stage, bufferBatch, offsetBatch);
1479 }
1480
1481 if (offsetOnlyChange)
1482 continue;
1483
1484 std::sort(bindingData.res[stage].textures.begin(), bindingData.res[stage].textures.end(), [](const QMetalShaderResourceBindingsData::Stage::Texture &a, const QMetalShaderResourceBindingsData::Stage::Texture &b) {
1485 return a.nativeBinding < b.nativeBinding;
1486 });
1487
1488 std::sort(bindingData.res[stage].samplers.begin(), bindingData.res[stage].samplers.end(), [](const QMetalShaderResourceBindingsData::Stage::Sampler &a, const QMetalShaderResourceBindingsData::Stage::Sampler &b) {
1489 return a.nativeBinding < b.nativeBinding;
1490 });
1491
1492 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : std::as_const(bindingData.res[stage].textures))
1493 bindingData.res[stage].textureBatches.feed(t.nativeBinding, t.mtltex);
1494
1495 for (const QMetalShaderResourceBindingsData::Stage::Sampler &s : std::as_const(bindingData.res[stage].samplers))
1496 bindingData.res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler);
1497
1498 bindingData.res[stage].textureBatches.finish();
1499 bindingData.res[stage].samplerBatches.finish();
1500
1501 for (int i = 0, ie = bindingData.res[stage].textureBatches.batches.count(); i != ie; ++i) {
1502 const auto &batch(bindingData.res[stage].textureBatches.batches[i]);
1503 // skip setting Texture binding if the current state is already correct
1504 if (cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches.count() > i
1505 && batch == cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches[i])
1506 {
1507 continue;
1508 }
1509 bindStageTextures(cbD, stage, batch);
1510 }
1511
1512 for (int i = 0, ie = bindingData.res[stage].samplerBatches.batches.count(); i != ie; ++i) {
1513 const auto &batch(bindingData.res[stage].samplerBatches.batches[i]);
1514 // skip setting Sampler State if the current state is already correct
1515 if (cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches.count() > i
1516 && batch == cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches[i])
1517 {
1518 continue;
1519 }
1520 bindStageSamplers(cbD, stage, batch);
1521 }
1522 }
1523
1524 cbD->d->currentShaderResourceBindingState = bindingData;
1525}
1526
1528{
1529 QRHI_RES_RHI(QRhiMetal);
1530
1531 [cbD->d->currentRenderPassEncoder setRenderPipelineState: d->ps];
1532
1533 if (cbD->d->currentDepthStencilState != d->ds) {
1534 [cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
1535 cbD->d->currentDepthStencilState = d->ds;
1536 }
1537 if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
1538 [cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
1539 cbD->currentCullMode = int(d->cullMode);
1540 }
1541 if (cbD->currentTriangleFillMode == -1 || d->triangleFillMode != uint(cbD->currentTriangleFillMode)) {
1542 [cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
1543 cbD->currentTriangleFillMode = int(d->triangleFillMode);
1544 }
1545 if (rhiD->caps.depthClamp) {
1546 if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) {
1547 [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode];
1548 cbD->currentDepthClipMode = int(d->depthClipMode);
1549 }
1550 }
1551 if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
1552 [cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
1553 cbD->currentFrontFaceWinding = int(d->winding);
1554 }
1555 if (!qFuzzyCompare(d->depthBias, cbD->currentDepthBiasValues.first)
1556 || !qFuzzyCompare(d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
1557 {
1558 [cbD->d->currentRenderPassEncoder setDepthBias: d->depthBias
1559 slopeScale: d->slopeScaledDepthBias
1560 clamp: 0.0f];
1561 cbD->currentDepthBiasValues = { d->depthBias, d->slopeScaledDepthBias };
1562 }
1563}
1564
1565void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
1566{
1567 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1570
1571 if (cbD->currentGraphicsPipeline == psD && cbD->currentPipelineGeneration == psD->generation)
1572 return;
1573
1575 cbD->currentComputePipeline = nullptr;
1576 cbD->currentPipelineGeneration = psD->generation;
1577
1578 if (cbD->hasCustomScissorSet && !psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
1580
1581 if (!psD->d->tess.enabled && !psD->d->tess.failed)
1583
1584 // mark work buffers that can now be safely reused as reusable
1585 // NOTE: These are usually empty unless tessellation or mutiview is used.
1586 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.deviceLocalWorkBuffers) {
1587 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1588 workBuf->lastActiveFrameSlot = -1;
1589 }
1590 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.hostVisibleWorkBuffers) {
1591 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1592 workBuf->lastActiveFrameSlot = -1;
1593 }
1594
1595 psD->lastActiveFrameSlot = currentFrameSlot;
1596}
1597
1598void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
1599 int dynamicOffsetCount,
1600 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
1601{
1602 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1606
1607 if (!srb) {
1608 if (gfxPsD)
1609 srb = gfxPsD->m_shaderResourceBindings;
1610 else
1611 srb = compPsD->m_shaderResourceBindings;
1612 }
1613
1615 bool hasSlottedResourceInSrb = false;
1616 bool hasDynamicOffsetInSrb = false;
1617 bool resNeedsRebind = false;
1618
1619 bool pipelineChanged = false;
1620 if (gfxPsD) {
1621 pipelineChanged = srbD->lastUsedGraphicsPipeline != gfxPsD;
1622 srbD->lastUsedGraphicsPipeline = gfxPsD;
1623 } else {
1624 pipelineChanged = srbD->lastUsedComputePipeline != compPsD;
1625 srbD->lastUsedComputePipeline = compPsD;
1626 }
1627
1628 // SPIRV-Cross buffer size buffers
1629 // Need to determine storage buffer sizes here as this is the last opportunity for storage
1630 // buffer bindings (offset, size) to be specified before draw / dispatch call
1631 const bool needsBufferSizeBuffer = (compPsD && compPsD->d->bufferSizeBuffer) || (gfxPsD && gfxPsD->d->bufferSizeBuffer);
1632 QMap<QRhiShaderResourceBinding::StageFlag, QMap<int, quint32>> storageBufferSizes;
1633
1634 // do buffer writes, figure out if we need to rebind, and mark as in-use
1635 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
1636 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
1637 QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
1638 switch (b->type) {
1639 case QRhiShaderResourceBinding::UniformBuffer:
1640 {
1641 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
1642 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
1643 sanityCheckResourceOwnership(bufD);
1645 if (bufD->d->slotted)
1646 hasSlottedResourceInSrb = true;
1647 if (b->u.ubuf.hasDynamicOffset)
1648 hasDynamicOffsetInSrb = true;
1649 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
1650 resNeedsRebind = true;
1651 bd.ubuf.id = bufD->m_id;
1652 bd.ubuf.generation = bufD->generation;
1653 }
1654 bufD->lastActiveFrameSlot = currentFrameSlot;
1655 }
1656 break;
1657 case QRhiShaderResourceBinding::SampledTexture:
1658 case QRhiShaderResourceBinding::Texture:
1659 case QRhiShaderResourceBinding::Sampler:
1660 {
1661 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
1662 if (bd.stex.count != data->count) {
1663 bd.stex.count = data->count;
1664 resNeedsRebind = true;
1665 }
1666 for (int elem = 0; elem < data->count; ++elem) {
1667 QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
1668 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
1669 Q_ASSERT(texD || samplerD);
1670 sanityCheckResourceOwnership(texD);
1671 sanityCheckResourceOwnership(samplerD);
1672 const quint64 texId = texD ? texD->m_id : 0;
1673 const uint texGen = texD ? texD->generation : 0;
1674 const quint64 samplerId = samplerD ? samplerD->m_id : 0;
1675 const uint samplerGen = samplerD ? samplerD->generation : 0;
1676 if (texGen != bd.stex.d[elem].texGeneration
1677 || texId != bd.stex.d[elem].texId
1678 || samplerGen != bd.stex.d[elem].samplerGeneration
1679 || samplerId != bd.stex.d[elem].samplerId)
1680 {
1681 resNeedsRebind = true;
1682 bd.stex.d[elem].texId = texId;
1683 bd.stex.d[elem].texGeneration = texGen;
1684 bd.stex.d[elem].samplerId = samplerId;
1685 bd.stex.d[elem].samplerGeneration = samplerGen;
1686 }
1687 if (texD)
1688 texD->lastActiveFrameSlot = currentFrameSlot;
1689 if (samplerD)
1690 samplerD->lastActiveFrameSlot = currentFrameSlot;
1691 }
1692 }
1693 break;
1694 case QRhiShaderResourceBinding::ImageLoad:
1695 case QRhiShaderResourceBinding::ImageStore:
1696 case QRhiShaderResourceBinding::ImageLoadStore:
1697 {
1698 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
1699 sanityCheckResourceOwnership(texD);
1700 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
1701 resNeedsRebind = true;
1702 bd.simage.id = texD->m_id;
1703 bd.simage.generation = texD->generation;
1704 }
1705 texD->lastActiveFrameSlot = currentFrameSlot;
1706 }
1707 break;
1708 case QRhiShaderResourceBinding::BufferLoad:
1709 case QRhiShaderResourceBinding::BufferStore:
1710 case QRhiShaderResourceBinding::BufferLoadStore:
1711 {
1712 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
1713 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
1714 sanityCheckResourceOwnership(bufD);
1715
1716 if (needsBufferSizeBuffer) {
1717 for (int i = 0; i < 6; ++i) {
1718 const QRhiShaderResourceBinding::StageFlag stage =
1719 QRhiShaderResourceBinding::StageFlag(1 << i);
1720 if (b->stage.testFlag(stage)) {
1721 storageBufferSizes[stage][b->binding] = b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->size();
1722 }
1723 }
1724 }
1725
1727 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
1728 resNeedsRebind = true;
1729 bd.sbuf.id = bufD->m_id;
1730 bd.sbuf.generation = bufD->generation;
1731 }
1732 bufD->lastActiveFrameSlot = currentFrameSlot;
1733 }
1734 break;
1735 default:
1736 Q_UNREACHABLE();
1737 break;
1738 }
1739 }
1740
1741 if (needsBufferSizeBuffer) {
1742 QMetalBuffer *bufD = nullptr;
1743 QVarLengthArray<std::pair<QMetalShader *, QRhiShaderResourceBinding::StageFlag>, 4> shaders;
1744
1745 if (compPsD) {
1746 bufD = compPsD->d->bufferSizeBuffer;
1747 Q_ASSERT(compPsD->d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
1748 shaders.append({&compPsD->d->cs, QRhiShaderResourceBinding::StageFlag::ComputeStage});
1749 } else {
1750 bufD = gfxPsD->d->bufferSizeBuffer;
1751 if (gfxPsD->d->tess.enabled) {
1752
1753 // Assumptions
1754 // * We only use one of the compute vertex shader variants in a pipeline at any one time
1755 // * The vertex shader variants all have the same storage block bindings
1756 // * The vertex shader variants all have the same native resource binding map
1757 // * The vertex shader variants all have the same MslBufferSizeBufferBinding requirement
1758 // * The vertex shader variants all have the same MslBufferSizeBufferBinding binding
1759 // => We only need to use one vertex shader variant to generate the identical shader
1760 // resource bindings
1761 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[1].desc.storageBlocks());
1762 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[2].desc.storageBlocks());
1763 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
1764 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
1765 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
1766 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
1767 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
1768 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
1769 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
1770 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
1771 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
1772 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
1773
1774 if (gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
1775 shaders.append({&gfxPsD->d->tess.compVs[0], QRhiShaderResourceBinding::StageFlag::VertexStage});
1776
1777 if (gfxPsD->d->tess.compTesc.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
1778 shaders.append({&gfxPsD->d->tess.compTesc, QRhiShaderResourceBinding::StageFlag::TessellationControlStage});
1779
1780 if (gfxPsD->d->tess.vertTese.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
1781 shaders.append({&gfxPsD->d->tess.vertTese, QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage});
1782
1783 } else {
1784 if (gfxPsD->d->vs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
1785 shaders.append({&gfxPsD->d->vs, QRhiShaderResourceBinding::StageFlag::VertexStage});
1786 }
1787 if (gfxPsD->d->fs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
1788 shaders.append({&gfxPsD->d->fs, QRhiShaderResourceBinding::StageFlag::FragmentStage});
1789 }
1790
1791 quint32 offset = 0;
1792 for (const auto &shader : shaders) {
1793
1794 const int binding = shader.first->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
1795
1796 // if we don't have a srb entry for the buffer size buffer
1797 if (!(storageBufferSizes.contains(shader.second) && storageBufferSizes[shader.second].contains(binding))) {
1798
1799 int maxNativeBinding = 0;
1800 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks())
1801 maxNativeBinding = qMax(maxNativeBinding, shader.first->nativeResourceBindingMap[block.binding].first);
1802
1803 const int size = (maxNativeBinding + 1) * sizeof(int);
1804
1805 Q_ASSERT(offset + size <= bufD->size());
1806 srbD->sortedBindings.append(QRhiShaderResourceBinding::bufferLoad(binding, shader.second, bufD, offset, size));
1807
1808 QMetalShaderResourceBindings::BoundResourceData bd;
1809 bd.sbuf.id = bufD->m_id;
1810 bd.sbuf.generation = bufD->generation;
1811 srbD->boundResourceData.append(bd);
1812 }
1813
1814 // create the buffer size buffer data
1815 QVarLengthArray<int, 8> bufferSizeBufferData;
1816 Q_ASSERT(storageBufferSizes.contains(shader.second));
1817 const QMap<int, quint32> &sizes(storageBufferSizes[shader.second]);
1818 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks()) {
1819 const int index = shader.first->nativeResourceBindingMap[block.binding].first;
1820
1821 // if the native binding is -1, the buffer is present but not accessed in the shader
1822 if (index < 0)
1823 continue;
1824
1825 if (bufferSizeBufferData.size() <= index)
1826 bufferSizeBufferData.resize(index + 1);
1827
1828 Q_ASSERT(sizes.contains(block.binding));
1829 bufferSizeBufferData[index] = sizes[block.binding];
1830 }
1831
1832 QRhiBufferData data;
1833 const quint32 size = bufferSizeBufferData.size() * sizeof(int);
1834 data.assign(reinterpret_cast<const char *>(bufferSizeBufferData.constData()), size);
1835 Q_ASSERT(offset + size <= bufD->size());
1836 bufD->d->pendingUpdates[bufD->d->slotted ? currentFrameSlot : 0].append({ offset, data });
1837
1838 // buffer offsets must be 32byte aligned
1839 offset += ((size + 31) / 32) * 32;
1840 }
1841
1843 bufD->lastActiveFrameSlot = currentFrameSlot;
1844 }
1845
1846 // make sure the resources for the correct slot get bound
1847 const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
1848 if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
1849 resNeedsRebind = true;
1850
1851 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srbD) : (cbD->currentComputeSrb != srbD);
1852 const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
1853
1854 // dynamic uniform buffer offsets always trigger a rebind
1855 if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt || pipelineChanged) {
1856 const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr };
1857 if (gfxPsD) {
1858 cbD->currentGraphicsSrb = srbD;
1859 cbD->currentComputeSrb = nullptr;
1860 if (gfxPsD->d->tess.enabled) {
1861 // If tessellating, we don't know which compVs shader to use until the draw call is
1862 // made. They should all have the same native resource binding map, so pick one.
1863 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
1864 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
1865 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->tess.compVs[0].nativeResourceBindingMap;
1866 resBindMaps[QMetalShaderResourceBindingsData::TESSCTRL] = &gfxPsD->d->tess.compTesc.nativeResourceBindingMap;
1867 resBindMaps[QMetalShaderResourceBindingsData::TESSEVAL] = &gfxPsD->d->tess.vertTese.nativeResourceBindingMap;
1868 } else {
1869 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs.nativeResourceBindingMap;
1870 }
1871 resBindMaps[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs.nativeResourceBindingMap;
1872 } else {
1873 cbD->currentGraphicsSrb = nullptr;
1874 cbD->currentComputeSrb = srbD;
1875 resBindMaps[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs.nativeResourceBindingMap;
1876 }
1877 cbD->currentSrbGeneration = srbD->generation;
1878 cbD->currentResSlot = resSlot;
1879
1880 const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
1881 enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange, resBindMaps);
1882 }
1883}
1884
1885void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
1886 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
1887 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
1888{
1889 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1891
1892 QRhiBatchedBindings<id<MTLBuffer> > buffers;
1893 QRhiBatchedBindings<NSUInteger> offsets;
1894 for (int i = 0; i < bindingCount; ++i) {
1895 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
1897 bufD->lastActiveFrameSlot = currentFrameSlot;
1898 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1899 buffers.feed(startBinding + i, mtlbuf);
1900 offsets.feed(startBinding + i, bindings[i].second);
1901 }
1902 buffers.finish();
1903 offsets.finish();
1904
1905 // same binding space for vertex and constant buffers - work it around
1907 // There's nothing guaranteeing setShaderResources() was called before
1908 // setVertexInput()... but whatever srb will get bound will have to be
1909 // layout-compatible anyways so maxBinding is the same.
1910 if (!srbD)
1911 srbD = QRHI_RES(QMetalShaderResourceBindings, cbD->currentGraphicsPipeline->shaderResourceBindings());
1912 const int firstVertexBinding = srbD->maxBinding + 1;
1913
1914 if (firstVertexBinding != cbD->d->currentFirstVertexBinding
1915 || buffers != cbD->d->currentVertexInputsBuffers
1916 || offsets != cbD->d->currentVertexInputOffsets)
1917 {
1918 cbD->d->currentFirstVertexBinding = firstVertexBinding;
1919 cbD->d->currentVertexInputsBuffers = buffers;
1920 cbD->d->currentVertexInputOffsets = offsets;
1921
1922 for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
1923 const auto &bufferBatch(buffers.batches[i]);
1924 const auto &offsetBatch(offsets.batches[i]);
1925 [cbD->d->currentRenderPassEncoder setVertexBuffers:
1926 bufferBatch.resources.constData()
1927 offsets: offsetBatch.resources.constData()
1928 withRange: NSMakeRange(uint(firstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1929 }
1930 }
1931
1932 if (indexBuf) {
1933 QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
1935 ibufD->lastActiveFrameSlot = currentFrameSlot;
1936 cbD->currentIndexBuffer = ibufD;
1937 cbD->currentIndexOffset = indexOffset;
1938 cbD->currentIndexFormat = indexFormat;
1939 } else {
1940 cbD->currentIndexBuffer = nullptr;
1941 }
1942}
1943
1945{
1946 cbD->hasCustomScissorSet = false;
1947
1948 const QSize outputSize = cbD->currentTarget->pixelSize();
1949 std::array<float, 4> vp = cbD->currentViewport.viewport();
1950 float x = 0, y = 0, w = 0, h = 0;
1951
1952 if (qFuzzyIsNull(vp[2]) && qFuzzyIsNull(vp[3])) {
1953 x = 0;
1954 y = 0;
1955 w = outputSize.width();
1956 h = outputSize.height();
1957 } else {
1958 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
1959 qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, vp, &x, &y, &w, &h);
1960 }
1961
1962 MTLScissorRect s;
1963 s.x = NSUInteger(x);
1964 s.y = NSUInteger(y);
1965 s.width = NSUInteger(w);
1966 s.height = NSUInteger(h);
1967 [cbD->d->currentRenderPassEncoder setScissorRect: s];
1968}
1969
1970void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
1971{
1972 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1974 QSize outputSize = cbD->currentTarget->pixelSize();
1975
1976 // If we have a shading rate map check and use the output size as given by the "screenSize"
1977 // call. This is important for the viewport to be correct when using a shading rate map, as
1978 // the pixel size of the target will likely be smaller then what will be rendered to the output.
1979 // This is specifically needed for visionOS.
1980 if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
1981 QRhiTextureRenderTarget *rt = static_cast<QRhiTextureRenderTarget *>(cbD->currentTarget);
1982 if (QRhiShadingRateMap *srm = rt->description().shadingRateMap()) {
1983 if (id<MTLRasterizationRateMap> rateMap = QRHI_RES(QMetalShadingRateMap, srm)->d->rateMap) {
1984 auto screenSize = [rateMap screenSize];
1985 outputSize = QSize(screenSize.width, screenSize.height);
1986 }
1987 }
1988 }
1989
1990 // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
1991 float x, y, w, h;
1992 if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
1993 return;
1994
1995 MTLViewport vp;
1996 vp.originX = double(x);
1997 vp.originY = double(y);
1998 vp.width = double(w);
1999 vp.height = double(h);
2000 vp.znear = double(viewport.minDepth());
2001 vp.zfar = double(viewport.maxDepth());
2002
2003 [cbD->d->currentRenderPassEncoder setViewport: vp];
2004
2005 cbD->currentViewport = viewport;
2007 && !cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
2008 {
2010 }
2011}
2012
2013void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
2014{
2015 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2017 Q_ASSERT(!cbD->currentGraphicsPipeline
2018 || cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
2019 const QSize outputSize = cbD->currentTarget->pixelSize();
2020
2021 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
2022 int x, y, w, h;
2023 if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
2024 return;
2025
2026 MTLScissorRect s;
2027 s.x = NSUInteger(x);
2028 s.y = NSUInteger(y);
2029 s.width = NSUInteger(w);
2030 s.height = NSUInteger(h);
2031
2032 [cbD->d->currentRenderPassEncoder setScissorRect: s];
2033
2034 cbD->hasCustomScissorSet = true;
2035}
2036
2037void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
2038{
2039 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2041
2042 [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF()
2043 green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
2044}
2045
2046void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
2047{
2048 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2050
2051 [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
2052}
2053
2054void QRhiMetal::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize)
2055{
2056 Q_UNUSED(cb);
2057 Q_UNUSED(coarsePixelSize);
2058}
2059
2061tempComputeEncoder(QMetalCommandBuffer *cbD, id<MTLComputeCommandEncoder> maybeComputeEncoder)
2062{
2063 if (cbD->d->currentRenderPassEncoder) {
2064 [cbD->d->currentRenderPassEncoder endEncoding];
2065 cbD->d->currentRenderPassEncoder = nil;
2066 }
2067
2068 if (!maybeComputeEncoder)
2069 maybeComputeEncoder = [cbD->d->cb computeCommandEncoder];
2070
2071 return maybeComputeEncoder;
2072}
2073
2075 id<MTLComputeCommandEncoder> computeEncoder)
2076{
2077 if (computeEncoder) {
2078 [computeEncoder endEncoding];
2079 computeEncoder = nil;
2080 }
2081
2082 QMetalRenderTargetData * rtD = nullptr;
2083
2084 switch (cbD->currentTarget->resourceType()) {
2085 case QRhiResource::SwapChainRenderTarget:
2086 rtD = QRHI_RES(QMetalSwapChainRenderTarget, cbD->currentTarget)->d;
2087 break;
2088 case QRhiResource::TextureRenderTarget:
2089 rtD = QRHI_RES(QMetalTextureRenderTarget, cbD->currentTarget)->d;
2090 break;
2091 default:
2092 break;
2093 }
2094
2095 Q_ASSERT(rtD);
2096
2097 QVarLengthArray<MTLLoadAction, 4> oldColorLoad;
2098 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2099 oldColorLoad.append(cbD->d->currentPassRpDesc.colorAttachments[i].loadAction);
2100 if (cbD->d->currentPassRpDesc.colorAttachments[i].storeAction != MTLStoreActionDontCare)
2101 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
2102 }
2103
2104 MTLLoadAction oldDepthLoad;
2105 MTLLoadAction oldStencilLoad;
2106 if (rtD->dsAttCount) {
2107 oldDepthLoad = cbD->d->currentPassRpDesc.depthAttachment.loadAction;
2108 if (cbD->d->currentPassRpDesc.depthAttachment.storeAction != MTLStoreActionDontCare)
2109 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
2110
2111 oldStencilLoad = cbD->d->currentPassRpDesc.stencilAttachment.loadAction;
2112 if (cbD->d->currentPassRpDesc.stencilAttachment.storeAction != MTLStoreActionDontCare)
2113 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
2114 }
2115
2116 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
2118
2119 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2120 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = oldColorLoad[i];
2121 }
2122
2123 if (rtD->dsAttCount) {
2124 cbD->d->currentPassRpDesc.depthAttachment.loadAction = oldDepthLoad;
2125 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = oldStencilLoad;
2126 }
2127
2128}
2129
2131{
2132 QMetalCommandBuffer *cbD = args.cbD;
2134 if (graphicsPipeline->d->tess.failed)
2135 return;
2136
2137 const bool indexed = args.type != TessDrawArgs::NonIndexed;
2138 const quint32 instanceCount = indexed ? args.drawIndexed.instanceCount : args.draw.instanceCount;
2139 const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount;
2140
2141 QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess);
2142 QMetalGraphicsPipelineData::ExtraBufferManager &extraBufMgr(graphicsPipeline->d->extraBufMgr);
2143 const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount);
2144 QMetalBuffer *vertOutBuf = nullptr;
2145 QMetalBuffer *tescOutBuf = nullptr;
2146 QMetalBuffer *tescPatchOutBuf = nullptr;
2147 QMetalBuffer *tescFactorBuf = nullptr;
2148 QMetalBuffer *tescParamsBuf = nullptr;
2149 id<MTLComputeCommandEncoder> vertTescComputeEncoder
2150 = tempComputeEncoder(cbD, cbD->d->tessellationComputeEncoder);
2151 cbD->d->tessellationComputeEncoder = vertTescComputeEncoder;
2152
2153 // Step 1: vertex shader (as compute)
2154 {
2155 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2156 QShader::Variant shaderVariant = QShader::NonIndexedVertexAsComputeShader;
2157 if (args.type == TessDrawArgs::U16Indexed)
2158 shaderVariant = QShader::UInt16IndexedVertexAsComputeShader;
2159 else if (args.type == TessDrawArgs::U32Indexed)
2160 shaderVariant = QShader::UInt32IndexedVertexAsComputeShader;
2161 const int varIndex = QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(shaderVariant);
2162 id<MTLComputePipelineState> computePipelineState = tess.vsCompPipeline(this, shaderVariant);
2163 [computeEncoder setComputePipelineState: computePipelineState];
2164
2165 // Make uniform buffers, textures, and samplers (meant for the
2166 // vertex stage from the client's point of view) visible in the
2167 // "vertex as compute" shader
2168 cbD->d->currentComputePassEncoder = computeEncoder;
2170 cbD->d->currentComputePassEncoder = nil;
2171
2172 const QMap<int, int> &ebb(tess.compVs[varIndex].nativeShaderInfo.extraBufferBindings);
2173 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2174 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
2175
2176 if (outputBufferBinding >= 0) {
2177 const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount);
2178 vertOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2179 if (!vertOutBuf)
2180 return;
2181 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2182 }
2183
2184 if (indexBufferBinding >= 0)
2185 [computeEncoder setBuffer: (id<MTLBuffer>) args.drawIndexed.indexBuffer offset: 0 atIndex: indexBufferBinding];
2186
2187 for (int i = 0, ie = cbD->d->currentVertexInputsBuffers.batches.count(); i != ie; ++i) {
2188 const auto &bufferBatch(cbD->d->currentVertexInputsBuffers.batches[i]);
2189 const auto &offsetBatch(cbD->d->currentVertexInputOffsets.batches[i]);
2190 [computeEncoder setBuffers: bufferBatch.resources.constData()
2191 offsets: offsetBatch.resources.constData()
2192 withRange: NSMakeRange(uint(cbD->d->currentFirstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
2193 }
2194
2195 if (indexed) {
2196 [computeEncoder setStageInRegion: MTLRegionMake2D(args.drawIndexed.vertexOffset, args.drawIndexed.firstInstance,
2197 args.drawIndexed.indexCount, args.drawIndexed.instanceCount)];
2198 } else {
2199 [computeEncoder setStageInRegion: MTLRegionMake2D(args.draw.firstVertex, args.draw.firstInstance,
2200 args.draw.vertexCount, args.draw.instanceCount)];
2201 }
2202
2203 [computeEncoder dispatchThreads: MTLSizeMake(vertexOrIndexCount, instanceCount, 1)
2204 threadsPerThreadgroup: MTLSizeMake(computePipelineState.threadExecutionWidth, 1, 1)];
2205 }
2206
2207 // Step 2: tessellation control shader (as compute)
2208 {
2209 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2210 id<MTLComputePipelineState> computePipelineState = tess.tescCompPipeline(this);
2211 [computeEncoder setComputePipelineState: computePipelineState];
2212
2213 cbD->d->currentComputePassEncoder = computeEncoder;
2215 cbD->d->currentComputePassEncoder = nil;
2216
2217 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2218 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2219 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2220 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2221 const int paramsBufferBinding = ebb.value(QShaderPrivate::MslTessTescParamsBufferBinding, -1);
2222 const int inputBufferBinding = ebb.value(QShaderPrivate::MslTessTescInputBufferBinding, -1);
2223
2224 if (outputBufferBinding >= 0) {
2225 const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount);
2226 tescOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2227 if (!tescOutBuf)
2228 return;
2229 [computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2230 }
2231
2232 if (patchOutputBufferBinding >= 0) {
2233 const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount);
2234 tescPatchOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2235 if (!tescPatchOutBuf)
2236 return;
2237 [computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2238 }
2239
2240 if (tessFactorBufferBinding >= 0) {
2241 tescFactorBuf = extraBufMgr.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
2242 [computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2243 }
2244
2245 if (paramsBufferBinding >= 0) {
2246 struct {
2247 quint32 inControlPointCount;
2248 quint32 patchCount;
2249 } params;
2250 tescParamsBuf = extraBufMgr.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible);
2251 if (!tescParamsBuf)
2252 return;
2253 params.inControlPointCount = tess.inControlPointCount;
2254 params.patchCount = patchCount;
2255 id<MTLBuffer> paramsBuf = tescParamsBuf->d->buf[0];
2256 char *p = reinterpret_cast<char *>([paramsBuf contents]);
2257 memcpy(p, &params, sizeof(params));
2258 [computeEncoder setBuffer: paramsBuf offset: 0 atIndex: paramsBufferBinding];
2259 }
2260
2261 if (vertOutBuf && inputBufferBinding >= 0)
2262 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: inputBufferBinding];
2263
2264 int sgSize = int(computePipelineState.threadExecutionWidth);
2265 int wgSize = std::lcm(tess.outControlPointCount, sgSize);
2266 while (wgSize > caps.maxThreadGroupSize) {
2267 sgSize /= 2;
2268 wgSize = std::lcm(tess.outControlPointCount, sgSize);
2269 }
2270 [computeEncoder dispatchThreads: MTLSizeMake(patchCount * tess.outControlPointCount, 1, 1)
2271 threadsPerThreadgroup: MTLSizeMake(wgSize, 1, 1)];
2272 }
2273
2274 // Much of the state in the QMetalCommandBuffer is going to be reset
2275 // when we get a new render encoder. Save what we need. (cheaper than
2276 // starting to walk over the srb again)
2277 const QMetalShaderResourceBindingsData resourceBindings = cbD->d->currentShaderResourceBindingState;
2278
2279 endTempComputeEncoding(cbD, cbD->d->tessellationComputeEncoder);
2280 cbD->d->tessellationComputeEncoder = nil;
2281
2282 // Step 3: tessellation evaluation (as vertex) + fragment shader
2283 {
2284 // No need to call tess.teseFragRenderPipeline because it was done
2285 // once and we know the result is stored in the standard place
2286 // (graphicsPipeline->d->ps).
2287
2289 id<MTLRenderCommandEncoder> renderEncoder = cbD->d->currentRenderPassEncoder;
2290
2293
2294 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2295 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2296 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2297 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2298
2299 if (outputBufferBinding >= 0 && tescOutBuf)
2300 [renderEncoder setVertexBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2301
2302 if (patchOutputBufferBinding >= 0 && tescPatchOutBuf)
2303 [renderEncoder setVertexBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2304
2305 if (tessFactorBufferBinding >= 0 && tescFactorBuf) {
2306 [renderEncoder setTessellationFactorBuffer: tescFactorBuf->d->buf[0] offset: 0 instanceStride: 0];
2307 [renderEncoder setVertexBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2308 }
2309
2310 [cbD->d->currentRenderPassEncoder drawPatches: tess.outControlPointCount
2311 patchStart: 0
2312 patchCount: patchCount
2313 patchIndexBuffer: nil
2314 patchIndexBufferOffset: 0
2315 instanceCount: 1
2316 baseInstance: 0];
2317 }
2318}
2319
2320void QRhiMetal::adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb)
2321{
2322 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2323 const int multiViewCount = cbD->currentGraphicsPipeline->m_multiViewCount;
2324 if (multiViewCount <= 1)
2325 return;
2326
2327 const QMap<int, int> &ebb(cbD->currentGraphicsPipeline->d->vs.nativeShaderInfo.extraBufferBindings);
2328 const int viewMaskBufBinding = ebb.value(QShaderPrivate::MslMultiViewMaskBufferBinding, -1);
2329 if (viewMaskBufBinding == -1) {
2330 qWarning("No extra buffer for multiview in the vertex shader; was it built with --view-count specified?");
2331 return;
2332 }
2333 struct {
2334 quint32 viewOffset;
2335 quint32 viewCount;
2336 } multiViewInfo;
2337 multiViewInfo.viewOffset = 0;
2338 multiViewInfo.viewCount = quint32(multiViewCount);
2339 QMetalBuffer *buf = cbD->currentGraphicsPipeline->d->extraBufMgr.acquireWorkBuffer(this, sizeof(multiViewInfo),
2341 if (buf) {
2342 id<MTLBuffer> mtlbuf = buf->d->buf[0];
2343 char *p = reinterpret_cast<char *>([mtlbuf contents]);
2344 memcpy(p, &multiViewInfo, sizeof(multiViewInfo));
2345 [cbD->d->currentRenderPassEncoder setVertexBuffer: mtlbuf offset: 0 atIndex: viewMaskBufBinding];
2346 // The instance count is adjusted for layered rendering. The vertex shader is expected to contain something like:
2347 // uint gl_ViewIndex = spvViewMask[0] + (gl_InstanceIndex - gl_BaseInstance) % spvViewMask[1];
2348 // where spvViewMask is the buffer with multiViewInfo passed in above.
2349 *instanceCount *= multiViewCount;
2350 }
2351}
2352
2353void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
2354 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
2355{
2356 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2358
2359 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2360 TessDrawArgs a;
2361 a.cbD = cbD;
2362 a.type = TessDrawArgs::NonIndexed;
2363 a.draw.vertexCount = vertexCount;
2364 a.draw.instanceCount = instanceCount;
2365 a.draw.firstVertex = firstVertex;
2366 a.draw.firstInstance = firstInstance;
2368 return;
2369 }
2370
2371 adjustForMultiViewDraw(&instanceCount, cb);
2372
2373 if (caps.baseVertexAndInstance) {
2374 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2375 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
2376 } else {
2377 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2378 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount];
2379 }
2380}
2381
2382void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
2383 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
2384{
2385 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2387
2388 if (!cbD->currentIndexBuffer)
2389 return;
2390
2391 const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
2392 Q_ASSERT(indexOffset == aligned(indexOffset, 4u));
2393
2395 id<MTLBuffer> mtlibuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
2396
2397 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2398 TessDrawArgs a;
2399 a.cbD = cbD;
2400 a.type = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? TessDrawArgs::U16Indexed : TessDrawArgs::U32Indexed;
2401 a.drawIndexed.indexCount = indexCount;
2402 a.drawIndexed.instanceCount = instanceCount;
2403 a.drawIndexed.firstIndex = firstIndex;
2404 a.drawIndexed.vertexOffset = vertexOffset;
2405 a.drawIndexed.firstInstance = firstInstance;
2406 a.drawIndexed.indexBuffer = mtlibuf;
2408 return;
2409 }
2410
2411 adjustForMultiViewDraw(&instanceCount, cb);
2412
2413 if (caps.baseVertexAndInstance) {
2414 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2415 indexCount: indexCount
2416 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2417 indexBuffer: mtlibuf
2418 indexBufferOffset: indexOffset
2419 instanceCount: instanceCount
2420 baseVertex: vertexOffset
2421 baseInstance: firstInstance];
2422 } else {
2423 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2424 indexCount: indexCount
2425 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2426 indexBuffer: mtlibuf
2427 indexBufferOffset: indexOffset
2428 instanceCount: instanceCount];
2429 }
2430}
2431
2432void QRhiMetal::drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
2433 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
2434{
2435 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2437
2438 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
2440 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
2441 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
2442
2443 NSUInteger offset = indirectBufferOffset;
2444 for (quint32 i = 0; i < drawCount; ++i) {
2445 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2446 indirectBuffer: indirectBufMtl
2447 indirectBufferOffset: offset];
2448 offset += stride;
2449 }
2450}
2451
2452void QRhiMetal::drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
2453 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
2454{
2455 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2457
2458 if (!cbD->currentIndexBuffer)
2459 return;
2460
2461 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
2462 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
2463
2464 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
2466 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
2467 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
2468
2469 // ICB (Indirect Command Buffer) path: uses a GPU compute kernel to encode
2470 // draw commands into an MTLIndirectCommandBuffer, then executes them in a
2471 // single executeCommandsInBuffer call. This eliminates per-draw CPU overhead
2472 // for large batch counts. Requires the pipeline to declare UsesIndirectDraws
2473 // (which enables supportIndirectCommandBuffers on the Metal pipeline descriptor).
2474
2475 // The ICB encoding overhead (compute pass + render pass restart)
2476 // can be around 100-150 microseconds, whereas an individual drawIndexedPrimitives call
2477 // typically takes 1-2 microseconds. A default threshold of 128 is intended to strike a
2478 // reasonable crossover balance, aiming to utilize the GPU-driven approach
2479 // when it is most likely to outweigh the fixed setup cost.
2480 static const quint32 ICB_DRAW_COUNT_THRESHOLD = 128;
2481 const bool useIcb = cbD->currentGraphicsPipeline
2482 && caps.indirectCommandBuffers
2483 && cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesIndirectDraws)
2484 && drawCount > ICB_DRAW_COUNT_THRESHOLD;
2485
2486 if (useIcb) {
2487 bool icbOk = true;
2488
2489 // Lazy-compile MSL compute kernels for ICB encoding (once per QRhi lifetime)
2490 if (!d->icbEncodePipelineU32) {
2491 NSError *err = nil;
2492 NSString *src = [NSString stringWithUTF8String:s_icbEncodeMsl];
2493 MTLCompileOptions *opts = [MTLCompileOptions new];
2494 opts.languageVersion = MTLLanguageVersion2_1;
2495 id<MTLLibrary> lib = [d->dev newLibraryWithSource:src options:opts error:&err];
2496 [opts release];
2497 if (!lib) {
2498 qWarning("Failed to compile ICB encode kernel: %s",
2499 qPrintable(QString::fromNSString(err.localizedDescription)));
2500 icbOk = false;
2501 }
2502 if (icbOk) {
2503 d->icbEncodeFunctionU32 = [lib newFunctionWithName:@"encode_icb_indexed_u32"];
2504 d->icbEncodeFunctionU16 = [lib newFunctionWithName:@"encode_icb_indexed_u16"];
2505 [lib release];
2506 if (!d->icbEncodeFunctionU32 || !d->icbEncodeFunctionU16) {
2507 qWarning("ICB encode kernel functions not found");
2508 icbOk = false;
2509 }
2510 }
2511 if (icbOk) {
2512 d->icbEncodePipelineU32 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU32 error:&err];
2513 if (!d->icbEncodePipelineU32) {
2514 qWarning("Failed to create ICB encode compute pipeline (u32): %s",
2515 qPrintable(QString::fromNSString(err.localizedDescription)));
2516 icbOk = false;
2517 }
2518 }
2519 if (icbOk) {
2520 d->icbEncodePipelineU16 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU16 error:&err];
2521 if (!d->icbEncodePipelineU16) {
2522 qWarning("Failed to create ICB encode compute pipeline (u16): %s",
2523 qPrintable(QString::fromNSString(err.localizedDescription)));
2524 icbOk = false;
2525 }
2526 }
2527 }
2528
2529 // Ensure ICB has enough capacity (grows on demand, never shrinks).
2530 // Old ICB resources are deferred-released to avoid use-after-free when
2531 // a previous frame's compute pass is still in flight (the command buffer
2532 // uses commandBufferWithUnretainedReferences).
2533 if (icbOk && (!d->icb || d->icbCapacity < drawCount)) {
2534 if (d->icb) {
2537 e.lastActiveFrameSlot = currentFrameSlot;
2538 e.stagingIcbBuffer.icb = d->icb;
2539 e.stagingIcbBuffer.argBuffer = d->icbArgumentBuffer;
2540 d->releaseQueue.append(e);
2541 }
2542 d->icb = nil;
2543 d->icbArgumentBuffer = nil;
2544
2545 MTLIndirectCommandBufferDescriptor *icbDesc = [MTLIndirectCommandBufferDescriptor new];
2546 icbDesc.commandTypes = MTLIndirectCommandTypeDrawIndexed;
2547 icbDesc.inheritPipelineState = YES;
2548 icbDesc.inheritBuffers = YES;
2549 icbDesc.maxVertexBufferBindCount = 0;
2550 icbDesc.maxFragmentBufferBindCount = 0;
2551 d->icb = [d->dev newIndirectCommandBufferWithDescriptor:icbDesc
2552 maxCommandCount:drawCount
2553 options:MTLResourceStorageModePrivate];
2554 [icbDesc release];
2555 if (!d->icb) {
2556 qWarning("Failed to create MTLIndirectCommandBuffer");
2557 d->icbCapacity = 0;
2558 icbOk = false;
2559 } else {
2560 d->icbCapacity = drawCount;
2561
2562 id<MTLArgumentEncoder> argEnc = [d->icbEncodeFunctionU32 newArgumentEncoderWithBufferIndex:1];
2563 d->icbArgumentBuffer = [d->dev newBufferWithLength:argEnc.encodedLength
2564 options:MTLResourceStorageModeShared];
2565 [argEnc setArgumentBuffer:d->icbArgumentBuffer offset:0];
2566 [argEnc setIndirectCommandBuffer:d->icb atIndex:0];
2567 [argEnc release];
2568 }
2569 }
2570
2571 if (icbOk) {
2572 // Save state before render pass interruption (following tessellation pattern).
2574 const QMetalShaderResourceBindingsData savedResourceBindings = cbD->d->currentShaderResourceBindingState;
2575 const int savedFirstVertexBinding = cbD->d->currentFirstVertexBinding;
2576 const auto savedVertexBuffers = cbD->d->currentVertexInputsBuffers;
2577 const auto savedVertexOffsets = cbD->d->currentVertexInputOffsets;
2578 const quint32 savedIndexOffset = cbD->currentIndexOffset;
2579 const QRhiCommandBuffer::IndexFormat savedIndexFormat = cbD->currentIndexFormat;
2580
2581 // End the current render encoder to make room for the compute pass.
2582 [cbD->d->currentRenderPassEncoder endEncoding];
2583 cbD->d->currentRenderPassEncoder = nil;
2584
2585 // Dispatch compute kernel to encode draw commands into the ICB.
2586 id<MTLComputeCommandEncoder> computeEncoder;
2587 {
2588 const bool useU16 = (savedIndexFormat == QRhiCommandBuffer::IndexUInt16);
2589 id<MTLComputePipelineState> computePipeline = useU16 ? d->icbEncodePipelineU16 : d->icbEncodePipelineU32;
2590
2591 computeEncoder = [cbD->d->cb computeCommandEncoder];
2592 uint32_t drawCountVal = drawCount;
2593 uint32_t metalPrimType = uint32_t(savedPipeline->d->primitiveType);
2594 uint32_t strideVal = stride;
2595
2596 [computeEncoder setComputePipelineState:computePipeline];
2597 [computeEncoder setBuffer:indirectBufMtl offset:indirectBufferOffset atIndex:0];
2598 [computeEncoder setBuffer:d->icbArgumentBuffer offset:0 atIndex:1];
2599 [computeEncoder setBytes:&drawCountVal length:sizeof(uint32_t) atIndex:2];
2600 [computeEncoder setBuffer:indexBufMtl offset:savedIndexOffset atIndex:3];
2601 [computeEncoder setBytes:&metalPrimType length:sizeof(uint32_t) atIndex:4];
2602 [computeEncoder setBytes:&strideVal length:sizeof(uint32_t) atIndex:5];
2603 [computeEncoder useResource:d->icb usage:MTLResourceUsageWrite];
2604 [computeEncoder useResource:indirectBufMtl usage:MTLResourceUsageRead];
2605 [computeEncoder useResource:indexBufMtl usage:MTLResourceUsageRead];
2606
2607 NSUInteger tw = computePipeline.threadExecutionWidth;
2608 [computeEncoder dispatchThreads:MTLSizeMake(drawCount, 1, 1)
2609 threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
2610 }
2611
2612 // Restart the render pass with Load actions to preserve existing content.
2613 endTempComputeEncoding(cbD, computeEncoder);
2614
2615 // Restore pipeline, shader resources, and vertex bindings on the new encoder.
2618 QMetalShaderResourceBindingsData::VERTEX, &savedResourceBindings);
2620 QMetalShaderResourceBindingsData::FRAGMENT, &savedResourceBindings);
2621
2622 if (savedFirstVertexBinding >= 0) {
2623 cbD->d->currentFirstVertexBinding = savedFirstVertexBinding;
2624 cbD->d->currentVertexInputsBuffers = savedVertexBuffers;
2625 cbD->d->currentVertexInputOffsets = savedVertexOffsets;
2626 for (int i = 0, ie = savedVertexBuffers.batches.count(); i != ie; ++i) {
2627 const auto &bufferBatch(savedVertexBuffers.batches[i]);
2628 const auto &offsetBatch(savedVertexOffsets.batches[i]);
2629 [cbD->d->currentRenderPassEncoder setVertexBuffers:
2630 bufferBatch.resources.constData()
2631 offsets: offsetBatch.resources.constData()
2632 withRange: NSMakeRange(uint(savedFirstVertexBinding) + bufferBatch.startBinding,
2633 NSUInteger(bufferBatch.resources.count()))];
2634 }
2635 }
2636
2637 cbD->currentIndexBuffer = indexBufD;
2638 cbD->currentIndexOffset = savedIndexOffset;
2639 cbD->currentIndexFormat = savedIndexFormat;
2640
2641 // Declare buffer dependencies and execute the GPU-encoded ICB.
2642 [cbD->d->currentRenderPassEncoder useResource:indirectBufMtl
2643 usage:MTLResourceUsageRead
2644 stages:MTLRenderStageVertex | MTLRenderStageFragment];
2645 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
2646 usage:MTLResourceUsageRead
2647 stages:MTLRenderStageVertex | MTLRenderStageFragment];
2648 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:d->icb
2649 withRange:NSMakeRange(0, drawCount)];
2650 return;
2651 }
2652 }
2653
2654 // CPU-side for-loop fallback: used when ICB is not applicable or setup failed.
2655 NSUInteger offset = indirectBufferOffset;
2656 for (quint32 i = 0; i < drawCount; ++i) {
2657 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2658 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2659 indexBuffer: indexBufMtl
2660 indexBufferOffset: cbD->currentIndexOffset
2661 indirectBuffer: indirectBufMtl
2662 indirectBufferOffset: offset];
2663 offset += stride;
2664 }
2665}
2666
2667void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
2668{
2669 if (!debugMarkers)
2670 return;
2671
2672 NSString *str = [NSString stringWithUTF8String: name.constData()];
2673 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2674 if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
2675 [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
2676 else
2677 [cbD->d->cb pushDebugGroup: str];
2678}
2679
2680void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
2681{
2682 if (!debugMarkers)
2683 return;
2684
2685 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2686 if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
2687 [cbD->d->currentRenderPassEncoder popDebugGroup];
2688 else
2689 [cbD->d->cb popDebugGroup];
2690}
2691
2692void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
2693{
2694 if (!debugMarkers)
2695 return;
2696
2697 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2698 if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
2699 [cbD->d->currentRenderPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
2700}
2701
2702const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
2703{
2704 return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
2705}
2706
2707void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
2708{
2709 Q_UNUSED(cb);
2710}
2711
2712void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
2713{
2714 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2716}
2717
2718double QRhiMetal::lastCompletedGpuTime(QRhiCommandBuffer *cb)
2719{
2720 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2721 return cbD->d->lastGpuTime;
2722}
2723
2724QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
2725{
2726 Q_UNUSED(flags);
2727
2728 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
2729 currentSwapChain = swapChainD;
2730 currentFrameSlot = swapChainD->currentFrameSlot;
2731
2732 // If we are too far ahead, block. This is also what ensures that any
2733 // resource used in the previous frame for this slot is now not in use
2734 // anymore by the GPU.
2735 dispatch_semaphore_wait(swapChainD->d->sem[currentFrameSlot], DISPATCH_TIME_FOREVER);
2736
2737 // Do this also for any other swapchain's commands with the same frame slot
2738 // While this reduces concurrency, it keeps resource usage safe: swapchain
2739 // A starting its frame 0, followed by swapchain B starting its own frame 0
2740 // will make B wait for A's frame 0 commands, so if a resource is written
2741 // in B's frame or when B checks for pending resource releases, that won't
2742 // mess up A's in-flight commands (as they are not in flight anymore).
2743 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
2744 if (sc != swapChainD)
2745 sc->waitUntilCompleted(currentFrameSlot); // wait+signal
2746 }
2747
2748 [d->captureScope beginScope];
2749
2750 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
2751
2753 if (swapChainD->samples > 1) {
2754 colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
2755 colorAtt.needsDrawableForResolveTex = true;
2756 } else {
2757 colorAtt.needsDrawableForTex = true;
2758 }
2759
2760 swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
2761 swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
2762 swapChainD->rtWrapper.d->fb.dsResolveTex = nil;
2763 swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
2764 swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
2765
2766 if (swapChainD->ds)
2767 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
2768
2770 swapChainD->cbWrapper.resetState(swapChainD->d->lastGpuTime[currentFrameSlot]);
2771 swapChainD->d->lastGpuTime[currentFrameSlot] = 0;
2773
2774 return QRhi::FrameOpSuccess;
2775}
2776
2777QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
2778{
2779 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
2780 Q_ASSERT(currentSwapChain == swapChainD);
2781
2782 // Keep strong reference to command buffer
2783 id<MTLCommandBuffer> commandBuffer = swapChainD->cbWrapper.d->cb;
2784
2785 __block int thisFrameSlot = currentFrameSlot;
2786 [commandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> cb) {
2787 swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
2788 dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
2789 }];
2790
2792 // When Metal API validation diagnostics is enabled in Xcode the texture is
2793 // released before the command buffer is done with it. Manually keep it alive
2794 // to work around this.
2795 id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain];
2796 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
2797 [drawableTexture release];
2798 }];
2799#endif
2800
2801 if (flags.testFlag(QRhi::SkipPresent)) {
2802 // Just need to commit, that's it
2803 [commandBuffer commit];
2804 } else {
2805 if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) {
2806 // Got something to present
2807 if (swapChainD->d->layer.presentsWithTransaction) {
2808 [commandBuffer commit];
2809 // Keep strong reference to Metal layer
2810 auto *metalLayer = swapChainD->d->layer;
2811 auto presentWithTransaction = ^{
2812 [commandBuffer waitUntilScheduled];
2813 // If the layer has been resized while we waited to be scheduled we bail out,
2814 // as the drawable is no longer valid for the layer, and we'll get a follow-up
2815 // display with the right size. We know we are on the main thread here, which
2816 // means we can access the layer directly. We also know that the layer is valid,
2817 // since the block keeps a strong reference to it, compared to the QRhiSwapChain
2818 // that can go away under our feet by the time we're scheduled.
2819 const auto surfaceSize = QSizeF::fromCGSize(metalLayer.bounds.size) * metalLayer.contentsScale;
2820 const auto textureSize = QSizeF(drawable.texture.width, drawable.texture.height);
2821 if (textureSize == surfaceSize) {
2822 [drawable present];
2823 } else {
2824 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable << "due to texture size"
2825 << textureSize << "not matching surface size" << surfaceSize;
2826 }
2827 };
2828
2829 if (NSThread.currentThread == NSThread.mainThread) {
2830 presentWithTransaction();
2831 } else {
2832 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
2833 Q_ASSERT(qtMetalLayer);
2834 // Let the main thread present the drawable from displayLayer
2835 qtMetalLayer.mainThreadPresentation = presentWithTransaction;
2836 }
2837 } else {
2838 // Keep strong reference to Metal layer so it's valid in the block
2839 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
2840 [commandBuffer addScheduledHandler:^(id<MTLCommandBuffer>) {
2841 if (qtMetalLayer) {
2842 // The schedule handler comes in on the com.Metal.CompletionQueueDispatch
2843 // thread, which means we might be racing against a display cycle on the
2844 // main thread. If the displayLayer is already in progress, we don't want
2845 // to step on its toes.
2846 if (qtMetalLayer.displayLock.tryLockForRead()) {
2847 [drawable present];
2848 qtMetalLayer.displayLock.unlock();
2849 } else {
2850 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable
2851 << "due to" << qtMetalLayer << "needing display";
2852 }
2853 } else {
2854 [drawable present];
2855 }
2856 }];
2857 [commandBuffer commit];
2858 }
2859 } else {
2860 // Still need to commit, even if we don't have a drawable
2861 [commandBuffer commit];
2862 }
2863
2864 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
2865 }
2866
2867 // Must not hold on to the drawable, regardless of needsPresent
2868 [swapChainD->d->curDrawable release];
2869 swapChainD->d->curDrawable = nil;
2870
2871 [d->captureScope endScope];
2872
2873 swapChainD->frameCount += 1;
2874 currentSwapChain = nullptr;
2875 return QRhi::FrameOpSuccess;
2876}
2877
2878QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
2879{
2880 Q_UNUSED(flags);
2881
2882 currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
2883
2884 for (QMetalSwapChain *sc : std::as_const(swapchains))
2885 sc->waitUntilCompleted(currentFrameSlot);
2886
2887 d->ofr.active = true;
2888 *cb = &d->ofr.cbWrapper;
2889 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
2890
2892 d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime);
2893 d->ofr.lastGpuTime = 0;
2895
2896 return QRhi::FrameOpSuccess;
2897}
2898
2899QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags)
2900{
2901 Q_UNUSED(flags);
2902 Q_ASSERT(d->ofr.active);
2903 d->ofr.active = false;
2904
2905 id<MTLCommandBuffer> cb = d->ofr.cbWrapper.d->cb;
2906 [cb commit];
2907
2908 // offscreen frames wait for completion, unlike swapchain ones
2909 [cb waitUntilCompleted];
2910
2911 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
2912
2914
2915 return QRhi::FrameOpSuccess;
2916}
2917
2919{
2920 id<MTLCommandBuffer> cb = nil;
2921 QMetalSwapChain *swapChainD = nullptr;
2922 if (inFrame) {
2923 if (d->ofr.active) {
2924 Q_ASSERT(!currentSwapChain);
2925 Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
2926 cb = d->ofr.cbWrapper.d->cb;
2927 } else {
2928 Q_ASSERT(currentSwapChain);
2929 swapChainD = currentSwapChain;
2930 Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
2931 cb = swapChainD->cbWrapper.d->cb;
2932 }
2933 }
2934
2935 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
2936 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
2937 if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
2938 // no wait as this is the thing we're going to be commit below and
2939 // beginFrame decremented sem already and going to be signaled by endFrame
2940 continue;
2941 }
2942 sc->waitUntilCompleted(i);
2943 }
2944 }
2945
2946 if (cb) {
2947 [cb commit];
2948 [cb waitUntilCompleted];
2949 }
2950
2951 if (inFrame) {
2952 if (d->ofr.active) {
2953 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
2954 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
2955 } else {
2956 swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
2957 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
2958 }
2959 }
2960
2962
2964
2965 return QRhi::FrameOpSuccess;
2966}
2967
2969 const QColor &colorClearValue,
2970 const QRhiDepthStencilClearValue &depthStencilClearValue,
2971 int colorAttCount,
2972 QRhiShadingRateMap *shadingRateMap)
2973{
2974 MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
2975 MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
2976 colorClearValue.alphaF());
2977
2978 for (uint i = 0; i < uint(colorAttCount); ++i) {
2979 rp.colorAttachments[i].loadAction = MTLLoadActionClear;
2980 rp.colorAttachments[i].storeAction = MTLStoreActionStore;
2981 rp.colorAttachments[i].clearColor = c;
2982 }
2983
2984 if (hasDepthStencil) {
2985 rp.depthAttachment.loadAction = MTLLoadActionClear;
2986 rp.depthAttachment.storeAction = MTLStoreActionDontCare;
2987 rp.stencilAttachment.loadAction = MTLLoadActionClear;
2988 rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
2989 rp.depthAttachment.clearDepth = double(depthStencilClearValue.depthClearValue());
2990 rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
2991 }
2992
2993 if (shadingRateMap)
2994 rp.rasterizationRateMap = QRHI_RES(QMetalShadingRateMap, shadingRateMap)->d->rateMap;
2995
2996 return rp;
2997}
2998
2999qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
3000{
3001 qsizetype size = 0;
3002 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
3003 subresDesc.data().size() : subresDesc.image().sizeInBytes();
3004 if (imageSizeBytes > 0)
3005 size += aligned<qsizetype>(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3006 return size;
3007}
3008
3009void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
3010 int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
3011 qsizetype *curOfs)
3012{
3013 const QPoint dp = subresDesc.destinationTopLeft();
3014 const QByteArray rawData = subresDesc.data();
3015 QImage img = subresDesc.image();
3016 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3017 id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
3018
3019 if (!img.isNull()) {
3020 const qsizetype fullImageSizeBytes = img.sizeInBytes();
3021 QSize size = img.size();
3022 int bpl = img.bytesPerLine();
3023
3024 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
3025 const int sx = subresDesc.sourceTopLeft().x();
3026 const int sy = subresDesc.sourceTopLeft().y();
3027 if (!subresDesc.sourceSize().isEmpty())
3028 size = subresDesc.sourceSize();
3029 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3030 if (size.width() == img.width()) {
3031 const int bpc = qMax(1, img.depth() / 8);
3032 Q_ASSERT(size.height() * img.bytesPerLine() <= fullImageSizeBytes);
3033 memcpy(reinterpret_cast<char *>(mp) + *curOfs,
3034 img.constBits() + sy * img.bytesPerLine() + sx * bpc,
3035 size.height() * img.bytesPerLine());
3036 } else {
3037 img = img.copy(sx, sy, size.width(), size.height());
3038 bpl = img.bytesPerLine();
3039 Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
3040 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(img.sizeInBytes()));
3041 }
3042 } else {
3043 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3044 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
3045 }
3046
3047 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3048 sourceOffset: NSUInteger(*curOfs)
3049 sourceBytesPerRow: NSUInteger(bpl)
3050 sourceBytesPerImage: 0
3051 sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1)
3052 toTexture: texD->d->tex
3053 destinationSlice: NSUInteger(is3D ? 0 : layer)
3054 destinationLevel: NSUInteger(level)
3055 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3056 options: MTLBlitOptionNone];
3057
3058 *curOfs += aligned<qsizetype>(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3059 } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
3060 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3061 const int subresw = subresSize.width();
3062 const int subresh = subresSize.height();
3063 int w, h;
3064 if (subresDesc.sourceSize().isEmpty()) {
3065 w = subresw;
3066 h = subresh;
3067 } else {
3068 w = subresDesc.sourceSize().width();
3069 h = subresDesc.sourceSize().height();
3070 }
3071
3072 quint32 bpl = 0;
3073 QSize blockDim;
3074 compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
3075
3076 const int dx = aligned(dp.x(), blockDim.width());
3077 const int dy = aligned(dp.y(), blockDim.height());
3078 if (dx + w != subresw)
3079 w = aligned(w, blockDim.width());
3080 if (dy + h != subresh)
3081 h = aligned(h, blockDim.height());
3082
3083 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3084
3085 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3086 sourceOffset: NSUInteger(*curOfs)
3087 sourceBytesPerRow: bpl
3088 sourceBytesPerImage: 0
3089 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3090 toTexture: texD->d->tex
3091 destinationSlice: NSUInteger(is3D ? 0 : layer)
3092 destinationLevel: NSUInteger(level)
3093 destinationOrigin: MTLOriginMake(NSUInteger(dx), NSUInteger(dy), NSUInteger(is3D ? layer : 0))
3094 options: MTLBlitOptionNone];
3095
3096 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3097 } else if (!rawData.isEmpty()) {
3098 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3099 const int subresw = subresSize.width();
3100 const int subresh = subresSize.height();
3101 int w, h;
3102 if (subresDesc.sourceSize().isEmpty()) {
3103 w = subresw;
3104 h = subresh;
3105 } else {
3106 w = subresDesc.sourceSize().width();
3107 h = subresDesc.sourceSize().height();
3108 }
3109
3110 quint32 bpl = 0;
3111 if (subresDesc.dataStride())
3112 bpl = subresDesc.dataStride();
3113 else
3114 textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, nullptr);
3115
3116 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3117
3118 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3119 sourceOffset: NSUInteger(*curOfs)
3120 sourceBytesPerRow: bpl
3121 sourceBytesPerImage: 0
3122 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3123 toTexture: texD->d->tex
3124 destinationSlice: NSUInteger(is3D ? 0 : layer)
3125 destinationLevel: NSUInteger(level)
3126 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3127 options: MTLBlitOptionNone];
3128
3129 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3130 } else {
3131 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
3132 }
3133}
3134
3135void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3136{
3137 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3139
3140 id<MTLBlitCommandEncoder> blitEnc = nil;
3141 auto ensureBlit = [&blitEnc, cbD, this]() {
3142 if (!blitEnc) {
3143 blitEnc = [cbD->d->cb blitCommandEncoder];
3144 if (debugMarkers)
3145 [blitEnc pushDebugGroup: @"Texture upload/copy"];
3146 }
3147 };
3148
3149 for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
3150 const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
3152 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3153 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
3154 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3155 if (u.offset == 0 && u.data.size() == bufD->m_size)
3156 bufD->d->pendingUpdates[i].clear();
3157 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3158 }
3160 // Due to the Metal API the handling of static and dynamic buffers is
3161 // basically the same. So go through the same pendingUpdates machinery.
3162 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3163 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
3164 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
3165 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
3166 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3168 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3170 const int idx = bufD->d->slotted ? currentFrameSlot : 0;
3171 if (bufD->m_type == QRhiBuffer::Dynamic) {
3172 char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]);
3173 if (p) {
3174 u.result->data.resize(u.readSize);
3175 memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize));
3176 }
3177 if (u.result->completed)
3178 u.result->completed();
3179 } else {
3180 QRhiMetalData::BufferReadback readback;
3181 readback.activeFrameSlot = idx;
3182 readback.buf = bufD->d->buf[idx];
3183 readback.offset = u.offset;
3184 readback.readSize = u.readSize;
3185 readback.result = u.result;
3186 d->activeBufferReadbacks.append(readback);
3187#ifdef Q_OS_MACOS
3188 if (bufD->d->managed) {
3189 // On non-Apple Silicon, manually synchronize memory from GPU to CPU
3190 ensureBlit();
3191 [blitEnc synchronizeResource:readback.buf];
3192 }
3193#endif
3194 }
3195 }
3196 }
3197
3198 for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
3199 const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
3201 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
3202 qsizetype stagingSize = 0;
3203 for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) {
3204 for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
3205 for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level]))
3206 stagingSize += subresUploadByteSize(subresDesc);
3207 }
3208 }
3209
3210 ensureBlit();
3211 Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
3212 utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: NSUInteger(stagingSize)
3213 options: MTLResourceStorageModeShared];
3214
3215 void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
3216 qsizetype curOfs = 0;
3217 for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) {
3218 for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
3219 for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level]))
3220 enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
3221 }
3222 }
3223
3224 utexD->lastActiveFrameSlot = currentFrameSlot;
3225
3228 e.lastActiveFrameSlot = currentFrameSlot;
3229 e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
3230 utexD->d->stagingBuf[currentFrameSlot] = nil;
3231 d->releaseQueue.append(e);
3233 Q_ASSERT(u.src && u.dst);
3234 QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
3235 QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
3236 const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3237 const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3238 const QPoint dp = u.desc.destinationTopLeft();
3239 const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
3240 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
3241 const QPoint sp = u.desc.sourceTopLeft();
3242
3243 ensureBlit();
3244 [blitEnc copyFromTexture: srcD->d->tex
3245 sourceSlice: NSUInteger(srcIs3D ? 0 : u.desc.sourceLayer())
3246 sourceLevel: NSUInteger(u.desc.sourceLevel())
3247 sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), NSUInteger(srcIs3D ? u.desc.sourceLayer() : 0))
3248 sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1)
3249 toTexture: dstD->d->tex
3250 destinationSlice: NSUInteger(dstIs3D ? 0 : u.desc.destinationLayer())
3251 destinationLevel: NSUInteger(u.desc.destinationLevel())
3252 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(dstIs3D ? u.desc.destinationLayer() : 0))];
3253
3254 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3257 readback.activeFrameSlot = currentFrameSlot;
3258 readback.desc = u.rb;
3259 readback.result = u.result;
3260
3261 QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
3262 QMetalSwapChain *swapChainD = nullptr;
3263 id<MTLTexture> src;
3264 QRect rect;
3265 bool is3D = false;
3266 if (texD) {
3267 if (texD->samples > 1) {
3268 qWarning("Multisample texture cannot be read back");
3269 continue;
3270 }
3271 is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3272 if (u.rb.rect().isValid())
3273 rect = u.rb.rect();
3274 else
3275 rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
3276 readback.format = texD->m_format;
3277 src = texD->d->tex;
3278 texD->lastActiveFrameSlot = currentFrameSlot;
3279 } else {
3280 Q_ASSERT(currentSwapChain);
3282 if (u.rb.rect().isValid())
3283 rect = u.rb.rect();
3284 else
3285 rect = QRect({0, 0}, swapChainD->pixelSize);
3286 readback.format = swapChainD->d->rhiColorFormat;
3287 // Multisample swapchains need nothing special since resolving
3288 // happens when ending a renderpass.
3289 const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
3290 src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
3291 }
3292 readback.pixelSize = rect.size();
3293
3294 quint32 bpl = 0;
3295 textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize, nullptr);
3296 readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared];
3297
3298 ensureBlit();
3299 [blitEnc copyFromTexture: src
3300 sourceSlice: NSUInteger(is3D ? 0 : u.rb.layer())
3301 sourceLevel: NSUInteger(u.rb.level())
3302 sourceOrigin: MTLOriginMake(NSUInteger(rect.x()), NSUInteger(rect.y()), NSUInteger(is3D ? u.rb.layer() : 0))
3303 sourceSize: MTLSizeMake(NSUInteger(rect.width()), NSUInteger(rect.height()), 1)
3304 toBuffer: readback.buf
3305 destinationOffset: 0
3306 destinationBytesPerRow: bpl
3307 destinationBytesPerImage: 0
3308 options: MTLBlitOptionNone];
3309
3310 d->activeTextureReadbacks.append(readback);
3312 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
3313 ensureBlit();
3314 [blitEnc generateMipmapsForTexture: utexD->d->tex];
3315 utexD->lastActiveFrameSlot = currentFrameSlot;
3316 }
3317 }
3318
3319 if (blitEnc) {
3320 if (debugMarkers)
3321 [blitEnc popDebugGroup];
3322 [blitEnc endEncoding];
3323 }
3324
3325 ud->free();
3326}
3327
3328// this handles all types of buffers, not just Dynamic
3330{
3331 if (bufD->d->pendingUpdates[slot].isEmpty())
3332 return;
3333
3334 void *p = [bufD->d->buf[slot] contents];
3335 quint32 changeBegin = UINT32_MAX;
3336 quint32 changeEnd = 0;
3337 for (const QMetalBufferData::BufferUpdate &u : std::as_const(bufD->d->pendingUpdates[slot])) {
3338 memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
3339 if (u.offset < changeBegin)
3340 changeBegin = u.offset;
3341 if (u.offset + u.data.size() > changeEnd)
3342 changeEnd = u.offset + u.data.size();
3343 }
3344#ifdef Q_OS_MACOS
3345 if (changeBegin < UINT32_MAX && changeBegin < changeEnd && bufD->d->managed)
3346 [bufD->d->buf[slot] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))];
3347#endif
3348
3349 bufD->d->pendingUpdates[slot].clear();
3350}
3351
3353{
3354 executeBufferHostWritesForSlot(bufD, bufD->d->slotted ? currentFrameSlot : 0);
3355}
3356
3357void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3358{
3359 Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
3360
3361 enqueueResourceUpdates(cb, resourceUpdates);
3362}
3363
3364void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
3365 QRhiRenderTarget *rt,
3366 const QColor &colorClearValue,
3367 const QRhiDepthStencilClearValue &depthStencilClearValue,
3368 QRhiResourceUpdateBatch *resourceUpdates,
3370{
3371 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3373
3374 if (resourceUpdates)
3375 enqueueResourceUpdates(cb, resourceUpdates);
3376
3377 QMetalRenderTargetData *rtD = nullptr;
3378 switch (rt->resourceType()) {
3379 case QRhiResource::SwapChainRenderTarget:
3380 {
3382 rtD = rtSc->d;
3383 QRhiShadingRateMap *shadingRateMap = rtSc->swapChain()->shadingRateMap();
3384 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
3385 colorClearValue,
3386 depthStencilClearValue,
3387 rtD->colorAttCount,
3388 shadingRateMap);
3389 if (rtD->colorAttCount) {
3390 QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
3392 Q_ASSERT(currentSwapChain);
3394 if (!swapChainD->d->curDrawable) {
3395 QMacAutoReleasePool pool;
3396 swapChainD->d->curDrawable = [[swapChainD->d->layer nextDrawable] retain];
3397 }
3398 if (!swapChainD->d->curDrawable) {
3399 qWarning("No drawable");
3400 return;
3401 }
3402 id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
3403 if (color0.needsDrawableForTex) {
3404 color0.tex = scTex;
3405 color0.needsDrawableForTex = false;
3406 } else {
3407 color0.resolveTex = scTex;
3408 color0.needsDrawableForResolveTex = false;
3409 }
3410 }
3411 }
3412 if (shadingRateMap)
3413 QRHI_RES(QMetalShadingRateMap, shadingRateMap)->lastActiveFrameSlot = currentFrameSlot;
3414 }
3415 break;
3416 case QRhiResource::TextureRenderTarget:
3417 {
3419 rtD = rtTex->d;
3420 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList))
3421 rtTex->create();
3422 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
3423 colorClearValue,
3424 depthStencilClearValue,
3425 rtD->colorAttCount,
3426 rtTex->m_desc.shadingRateMap());
3427 if (rtD->fb.preserveColor) {
3428 for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
3429 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
3430 }
3431 if (rtD->dsAttCount && rtD->fb.preserveDs) {
3432 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
3433 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
3434 }
3435 int colorAttCount = 0;
3436 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
3437 it != itEnd; ++it)
3438 {
3439 colorAttCount += 1;
3440 if (it->texture()) {
3441 QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
3442 if (it->multiViewCount() >= 2)
3443 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(it->multiViewCount());
3444 } else if (it->renderBuffer()) {
3445 QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
3446 }
3447 if (it->resolveTexture())
3448 QRHI_RES(QMetalTexture, it->resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
3449 }
3450 if (rtTex->m_desc.depthStencilBuffer())
3451 QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
3452 if (rtTex->m_desc.depthTexture()) {
3453 QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture());
3454 depthTexture->lastActiveFrameSlot = currentFrameSlot;
3455 if (depthTexture->arraySize() >= 2) {
3456 const int depthLayer = rtTex->m_desc.depthLayer();
3457 if (depthLayer >= 0) {
3458 cbD->d->currentPassRpDesc.depthAttachment.slice = NSUInteger(depthLayer);
3459 cbD->d->currentPassRpDesc.stencilAttachment.slice = NSUInteger(depthLayer);
3460 if (colorAttCount == 0)
3461 cbD->d->currentPassRpDesc.renderTargetArrayLength = 1;
3462 } else if (colorAttCount == 0) {
3463 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize());
3464 }
3465 }
3466 }
3467 if (rtTex->m_desc.depthResolveTexture())
3468 QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
3469 if (rtTex->m_desc.shadingRateMap())
3470 QRHI_RES(QMetalShadingRateMap, rtTex->m_desc.shadingRateMap())->lastActiveFrameSlot = currentFrameSlot;
3471 }
3472 break;
3473 default:
3474 Q_UNREACHABLE();
3475 break;
3476 }
3477
3478 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
3479 cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
3480 cbD->d->currentPassRpDesc.colorAttachments[i].slice = NSUInteger(rtD->fb.colorAtt[i].arrayLayer);
3481 cbD->d->currentPassRpDesc.colorAttachments[i].depthPlane = NSUInteger(rtD->fb.colorAtt[i].slice);
3482 cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level);
3483 if (rtD->fb.colorAtt[i].resolveTex) {
3484 cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = rtD->fb.preserveColor ? MTLStoreActionStoreAndMultisampleResolve
3485 : MTLStoreActionMultisampleResolve;
3486 cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
3487 cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer);
3488 cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel);
3489 }
3490 }
3491
3492 if (rtD->dsAttCount) {
3493 Q_ASSERT(rtD->fb.dsTex);
3494 cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
3495 cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
3496 if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
3497 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
3498 if (rtD->fb.dsResolveTex) {
3499 cbD->d->currentPassRpDesc.depthAttachment.storeAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve
3500 : MTLStoreActionMultisampleResolve;
3501 cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex;
3502 if (rtD->fb.hasStencil) {
3503 cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex;
3504 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = cbD->d->currentPassRpDesc.depthAttachment.storeAction;
3505 }
3506 }
3507 }
3508
3509 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
3510
3512
3514 cbD->currentTarget = rt;
3515}
3516
3517void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3518{
3519 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3521
3522 [cbD->d->currentRenderPassEncoder endEncoding];
3523
3525 cbD->currentTarget = nullptr;
3526
3527 if (resourceUpdates)
3528 enqueueResourceUpdates(cb, resourceUpdates);
3529}
3530
3531void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb,
3532 QRhiResourceUpdateBatch *resourceUpdates,
3534{
3535 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3537
3538 if (resourceUpdates)
3539 enqueueResourceUpdates(cb, resourceUpdates);
3540
3541 cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
3544}
3545
3546void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3547{
3548 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3550
3551 [cbD->d->currentComputePassEncoder endEncoding];
3553
3554 if (resourceUpdates)
3555 enqueueResourceUpdates(cb, resourceUpdates);
3556}
3557
3558void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
3559{
3560 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3563
3564 if (cbD->currentComputePipeline != psD || cbD->currentPipelineGeneration != psD->generation) {
3565 cbD->currentGraphicsPipeline = nullptr;
3566 cbD->currentComputePipeline = psD;
3567 cbD->currentPipelineGeneration = psD->generation;
3568
3569 [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
3570 }
3571
3572 psD->lastActiveFrameSlot = currentFrameSlot;
3573}
3574
3575void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
3576{
3577 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3580
3581 [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(NSUInteger(x), NSUInteger(y), NSUInteger(z))
3582 threadsPerThreadgroup: psD->d->localSize];
3583}
3584
3586{
3587 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
3588 [e.buffer.buffers[i] release];
3589}
3590
3592{
3593 [e.renderbuffer.texture release];
3594}
3595
3597{
3598 [e.texture.texture release];
3599 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
3600 [e.texture.stagingBuffers[i] release];
3601 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
3602 [e.texture.views[i] release];
3603}
3604
3606{
3607 [e.sampler.samplerState release];
3608}
3609
3611{
3612 for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
3613 const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
3614 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
3615 switch (e.type) {
3618 break;
3621 break;
3624 break;
3627 break;
3628 case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
3629 [e.stagingBuffer.buffer release];
3630 break;
3631 case QRhiMetalData::DeferredReleaseEntry::GraphicsPipeline:
3632 [e.graphicsPipeline.pipelineState release];
3633 [e.graphicsPipeline.depthStencilState release];
3634 [e.graphicsPipeline.tessVertexComputeState[0] release];
3635 [e.graphicsPipeline.tessVertexComputeState[1] release];
3636 [e.graphicsPipeline.tessVertexComputeState[2] release];
3637 [e.graphicsPipeline.tessTessControlComputeState release];
3638 break;
3639 case QRhiMetalData::DeferredReleaseEntry::ComputePipeline:
3640 [e.computePipeline.pipelineState release];
3641 break;
3642 case QRhiMetalData::DeferredReleaseEntry::ShadingRateMap:
3643 [e.shadingRateMap.rateMap release];
3644 break;
3645 case QRhiMetalData::DeferredReleaseEntry::StagingIcbBuffer:
3646 [e.stagingIcbBuffer.icb release];
3647 [e.stagingIcbBuffer.argBuffer release];
3648 break;
3649 default:
3650 break;
3651 }
3652 d->releaseQueue.removeAt(i);
3653 }
3654 }
3655}
3656
3658{
3659 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
3660
3661 for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) {
3662 const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]);
3663 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
3664 readback.result->format = readback.format;
3665 readback.result->pixelSize = readback.pixelSize;
3666 readback.result->data.resize(int(readback.bufSize));
3667 void *p = [readback.buf contents];
3668 memcpy(readback.result->data.data(), p, readback.bufSize);
3669 [readback.buf release];
3670
3671 if (readback.result->completed)
3672 completedCallbacks.append(readback.result->completed);
3673
3674 d->activeTextureReadbacks.remove(i);
3675 }
3676 }
3677
3678 for (int i = d->activeBufferReadbacks.count() - 1; i >= 0; --i) {
3679 const QRhiMetalData::BufferReadback &readback(d->activeBufferReadbacks[i]);
3680 if (forced || currentFrameSlot == readback.activeFrameSlot
3681 || readback.activeFrameSlot < 0) {
3682 readback.result->data.resize(readback.readSize);
3683 char *p = reinterpret_cast<char *>([readback.buf contents]);
3684 Q_ASSERT(p);
3685 memcpy(readback.result->data.data(), p + readback.offset, size_t(readback.readSize));
3686
3687 if (readback.result->completed)
3688 completedCallbacks.append(readback.result->completed);
3689
3690 d->activeBufferReadbacks.remove(i);
3691 }
3692 }
3693
3694 for (auto f : completedCallbacks)
3695 f();
3696}
3697
3698QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
3700 d(new QMetalBufferData)
3701{
3702 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
3703 d->buf[i] = nil;
3704}
3705
3707{
3708 destroy();
3709 delete d;
3710}
3711
3713{
3714 if (!d->buf[0])
3715 return;
3716
3720
3721 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3722 e.buffer.buffers[i] = d->buf[i];
3723 d->buf[i] = nil;
3724 d->pendingUpdates[i].clear();
3725 }
3726
3727 QRHI_RES_RHI(QRhiMetal);
3728 if (rhiD) {
3729 rhiD->d->releaseQueue.append(e);
3730 rhiD->unregisterResource(this);
3731 }
3732}
3733
3735{
3736 if (d->buf[0])
3737 destroy();
3738
3739 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
3740 qWarning("StorageBuffer cannot be combined with Dynamic");
3741 return false;
3742 }
3743
3744 const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
3745 const quint32 roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256u) : nonZeroSize;
3746
3747 d->managed = false;
3748 MTLResourceOptions opts = MTLResourceStorageModeShared;
3749
3750 QRHI_RES_RHI(QRhiMetal);
3751#ifdef Q_OS_MACOS
3752 if (!rhiD->caps.isAppleGPU && m_type != Dynamic) {
3753 opts = MTLResourceStorageModeManaged;
3754 d->managed = true;
3755 }
3756#endif
3757
3758 // Have QMTL_FRAMES_IN_FLIGHT versions regardless of the type, for now.
3759 // This is because writing to a Managed buffer (which is what Immutable and
3760 // Static maps to on macOS) is not safe when another frame reading from the
3761 // same buffer is still in flight.
3762 d->slotted = !m_usage.testFlag(QRhiBuffer::StorageBuffer); // except for SSBOs written in the shader
3763 // and a special case for internal work buffers
3764 if (int(m_usage) == WorkBufPoolUsage)
3765 d->slotted = false;
3766
3767 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3768 if (i == 0 || d->slotted) {
3769 d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
3770 if (!m_objectName.isEmpty()) {
3771 if (!d->slotted) {
3772 d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
3773 } else {
3774 const QByteArray name = m_objectName + '/' + QByteArray::number(i);
3775 d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
3776 }
3777 }
3778 }
3779 }
3780
3782 generation += 1;
3783 rhiD->registerResource(this);
3784 return true;
3785}
3786
3788{
3789 if (d->slotted) {
3790 NativeBuffer b;
3791 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT));
3792 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3793 QRHI_RES_RHI(QRhiMetal);
3795 b.objects[i] = &d->buf[i];
3796 }
3797 b.slotCount = QMTL_FRAMES_IN_FLIGHT;
3798 return b;
3799 }
3800 return { { &d->buf[0] }, 1 };
3801}
3802
3804{
3805 // Shortcut the entire buffer update mechanism and allow the client to do
3806 // the host writes directly to the buffer. This will lead to unexpected
3807 // results when combined with QRhiResourceUpdateBatch-based updates for the
3808 // buffer, but provides a fast path for dynamic buffers that have all their
3809 // content changed in every frame.
3810 Q_ASSERT(m_type == Dynamic);
3811 QRHI_RES_RHI(QRhiMetal);
3812 Q_ASSERT(rhiD->inFrame);
3813 const int slot = rhiD->currentFrameSlot;
3814 void *p = [d->buf[slot] contents];
3815 return static_cast<char *>(p);
3816}
3817
3819{
3820#ifdef Q_OS_MACOS
3821 if (d->managed) {
3822 QRHI_RES_RHI(QRhiMetal);
3823 const int slot = rhiD->currentFrameSlot;
3824 [d->buf[slot] didModifyRange: NSMakeRange(0, NSUInteger(m_size))];
3825 }
3826#endif
3827}
3828
3829static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags, const QRhiMetal *d)
3830{
3831#ifndef Q_OS_MACOS
3832 Q_UNUSED(d);
3833#endif
3834
3835 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
3836 switch (format) {
3837 case QRhiTexture::RGBA8:
3838 return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
3839 case QRhiTexture::BGRA8:
3840 return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
3841 case QRhiTexture::R8:
3842#ifdef Q_OS_MACOS
3843 return MTLPixelFormatR8Unorm;
3844#else
3845 return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
3846#endif
3847 case QRhiTexture::R8SI:
3848 return MTLPixelFormatR8Sint;
3849 case QRhiTexture::R8UI:
3850 return MTLPixelFormatR8Uint;
3851 case QRhiTexture::RG8:
3852#ifdef Q_OS_MACOS
3853 return MTLPixelFormatRG8Unorm;
3854#else
3855 return srgb ? MTLPixelFormatRG8Unorm_sRGB : MTLPixelFormatRG8Unorm;
3856#endif
3857 case QRhiTexture::R16:
3858 return MTLPixelFormatR16Unorm;
3859 case QRhiTexture::RG16:
3860 return MTLPixelFormatRG16Unorm;
3861 case QRhiTexture::RED_OR_ALPHA8:
3862 return MTLPixelFormatR8Unorm;
3863
3864 case QRhiTexture::RGBA16F:
3865 return MTLPixelFormatRGBA16Float;
3866 case QRhiTexture::RGBA32F:
3867 return MTLPixelFormatRGBA32Float;
3868 case QRhiTexture::R16F:
3869 return MTLPixelFormatR16Float;
3870 case QRhiTexture::R32F:
3871 return MTLPixelFormatR32Float;
3872
3873 case QRhiTexture::RGB10A2:
3874 return MTLPixelFormatRGB10A2Unorm;
3875
3876 case QRhiTexture::R32SI:
3877 return MTLPixelFormatR32Sint;
3878 case QRhiTexture::R32UI:
3879 return MTLPixelFormatR32Uint;
3880 case QRhiTexture::RG32SI:
3881 return MTLPixelFormatRG32Sint;
3882 case QRhiTexture::RG32UI:
3883 return MTLPixelFormatRG32Uint;
3884 case QRhiTexture::RGBA32SI:
3885 return MTLPixelFormatRGBA32Sint;
3886 case QRhiTexture::RGBA32UI:
3887 return MTLPixelFormatRGBA32Uint;
3888
3889#ifdef Q_OS_MACOS
3890 case QRhiTexture::D16:
3891 return MTLPixelFormatDepth16Unorm;
3892 case QRhiTexture::D24:
3893 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
3894 case QRhiTexture::D24S8:
3895 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
3896#else
3897 case QRhiTexture::D16:
3898 return MTLPixelFormatDepth32Float;
3899 case QRhiTexture::D24:
3900 return MTLPixelFormatDepth32Float;
3901 case QRhiTexture::D24S8:
3902 return MTLPixelFormatDepth32Float_Stencil8;
3903#endif
3904 case QRhiTexture::D32F:
3905 return MTLPixelFormatDepth32Float;
3906 case QRhiTexture::D32FS8:
3907 return MTLPixelFormatDepth32Float_Stencil8;
3908
3909#ifdef Q_OS_MACOS
3910 case QRhiTexture::BC1:
3911 return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
3912 case QRhiTexture::BC2:
3913 return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
3914 case QRhiTexture::BC3:
3915 return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
3916 case QRhiTexture::BC4:
3917 return MTLPixelFormatBC4_RUnorm;
3918 case QRhiTexture::BC5:
3919 qWarning("QRhiMetal does not support BC5");
3920 return MTLPixelFormatInvalid;
3921 case QRhiTexture::BC6H:
3922 return MTLPixelFormatBC6H_RGBUfloat;
3923 case QRhiTexture::BC7:
3924 return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
3925#else
3926 case QRhiTexture::BC1:
3927 case QRhiTexture::BC2:
3928 case QRhiTexture::BC3:
3929 case QRhiTexture::BC4:
3930 case QRhiTexture::BC5:
3931 case QRhiTexture::BC6H:
3932 case QRhiTexture::BC7:
3933 qWarning("QRhiMetal: BCx compression not supported on this platform");
3934 return MTLPixelFormatInvalid;
3935#endif
3936
3937#ifndef Q_OS_MACOS
3938 case QRhiTexture::ETC2_RGB8:
3939 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
3940 case QRhiTexture::ETC2_RGB8A1:
3941 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
3942 case QRhiTexture::ETC2_RGBA8:
3943 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
3944
3945 case QRhiTexture::ASTC_4x4:
3946 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
3947 case QRhiTexture::ASTC_5x4:
3948 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
3949 case QRhiTexture::ASTC_5x5:
3950 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
3951 case QRhiTexture::ASTC_6x5:
3952 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
3953 case QRhiTexture::ASTC_6x6:
3954 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
3955 case QRhiTexture::ASTC_8x5:
3956 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
3957 case QRhiTexture::ASTC_8x6:
3958 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
3959 case QRhiTexture::ASTC_8x8:
3960 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
3961 case QRhiTexture::ASTC_10x5:
3962 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
3963 case QRhiTexture::ASTC_10x6:
3964 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
3965 case QRhiTexture::ASTC_10x8:
3966 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
3967 case QRhiTexture::ASTC_10x10:
3968 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
3969 case QRhiTexture::ASTC_12x10:
3970 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
3971 case QRhiTexture::ASTC_12x12:
3972 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
3973#else
3974 case QRhiTexture::ETC2_RGB8:
3975 if (d->caps.isAppleGPU)
3976 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
3977 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
3978 return MTLPixelFormatInvalid;
3979 case QRhiTexture::ETC2_RGB8A1:
3980 if (d->caps.isAppleGPU)
3981 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
3982 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
3983 return MTLPixelFormatInvalid;
3984 case QRhiTexture::ETC2_RGBA8:
3985 if (d->caps.isAppleGPU)
3986 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
3987 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
3988 return MTLPixelFormatInvalid;
3989 case QRhiTexture::ASTC_4x4:
3990 if (d->caps.isAppleGPU)
3991 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
3992 qWarning("QRhiMetal: ASTC compression not supported on this platform");
3993 return MTLPixelFormatInvalid;
3994 case QRhiTexture::ASTC_5x4:
3995 if (d->caps.isAppleGPU)
3996 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
3997 qWarning("QRhiMetal: ASTC compression not supported on this platform");
3998 return MTLPixelFormatInvalid;
3999 case QRhiTexture::ASTC_5x5:
4000 if (d->caps.isAppleGPU)
4001 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
4002 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4003 return MTLPixelFormatInvalid;
4004 case QRhiTexture::ASTC_6x5:
4005 if (d->caps.isAppleGPU)
4006 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
4007 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4008 return MTLPixelFormatInvalid;
4009 case QRhiTexture::ASTC_6x6:
4010 if (d->caps.isAppleGPU)
4011 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
4012 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4013 return MTLPixelFormatInvalid;
4014 case QRhiTexture::ASTC_8x5:
4015 if (d->caps.isAppleGPU)
4016 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
4017 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4018 return MTLPixelFormatInvalid;
4019 case QRhiTexture::ASTC_8x6:
4020 if (d->caps.isAppleGPU)
4021 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
4022 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4023 return MTLPixelFormatInvalid;
4024 case QRhiTexture::ASTC_8x8:
4025 if (d->caps.isAppleGPU)
4026 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
4027 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4028 return MTLPixelFormatInvalid;
4029 case QRhiTexture::ASTC_10x5:
4030 if (d->caps.isAppleGPU)
4031 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
4032 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4033 return MTLPixelFormatInvalid;
4034 case QRhiTexture::ASTC_10x6:
4035 if (d->caps.isAppleGPU)
4036 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
4037 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4038 return MTLPixelFormatInvalid;
4039 case QRhiTexture::ASTC_10x8:
4040 if (d->caps.isAppleGPU)
4041 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
4042 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4043 return MTLPixelFormatInvalid;
4044 case QRhiTexture::ASTC_10x10:
4045 if (d->caps.isAppleGPU)
4046 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
4047 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4048 return MTLPixelFormatInvalid;
4049 case QRhiTexture::ASTC_12x10:
4050 if (d->caps.isAppleGPU)
4051 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
4052 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4053 return MTLPixelFormatInvalid;
4054 case QRhiTexture::ASTC_12x12:
4055 if (d->caps.isAppleGPU)
4056 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
4057 qWarning("QRhiMetal: ASTC compression not supported on this platform");
4058 return MTLPixelFormatInvalid;
4059#endif
4060
4061 default:
4062 Q_UNREACHABLE();
4063 return MTLPixelFormatInvalid;
4064 }
4065}
4066
4067QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
4068 int sampleCount, QRhiRenderBuffer::Flags flags,
4069 QRhiTexture::Format backingFormatHint)
4072{
4073}
4074
4076{
4077 destroy();
4078 delete d;
4079}
4080
4082{
4083 if (!d->tex)
4084 return;
4085
4089
4090 e.renderbuffer.texture = d->tex;
4091 d->tex = nil;
4092
4093 QRHI_RES_RHI(QRhiMetal);
4094 if (rhiD) {
4095 rhiD->d->releaseQueue.append(e);
4096 rhiD->unregisterResource(this);
4097 }
4098}
4099
4101{
4102 if (d->tex)
4103 destroy();
4104
4105 if (m_pixelSize.isEmpty())
4106 return false;
4107
4108 QRHI_RES_RHI(QRhiMetal);
4109 samples = rhiD->effectiveSampleCount(m_sampleCount);
4110
4111 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
4112 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
4113 desc.width = NSUInteger(m_pixelSize.width());
4114 desc.height = NSUInteger(m_pixelSize.height());
4115 if (samples > 1)
4116 desc.sampleCount = NSUInteger(samples);
4117 desc.resourceOptions = MTLResourceStorageModePrivate;
4118 desc.usage = MTLTextureUsageRenderTarget;
4119
4120 switch (m_type) {
4121 case DepthStencil:
4122#ifdef Q_OS_MACOS
4123 if (rhiD->caps.isAppleGPU) {
4124 desc.storageMode = MTLStorageModeMemoryless;
4125 d->format = MTLPixelFormatDepth32Float_Stencil8;
4126 } else {
4127 desc.storageMode = MTLStorageModePrivate;
4128 d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
4129 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
4130 }
4131#else
4132 desc.storageMode = MTLStorageModeMemoryless;
4133 d->format = MTLPixelFormatDepth32Float_Stencil8;
4134#endif
4135 desc.pixelFormat = d->format;
4136 break;
4137 case Color:
4138 desc.storageMode = MTLStorageModePrivate;
4139 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
4140 d->format = toMetalTextureFormat(m_backingFormatHint, {}, rhiD);
4141 else
4142 d->format = MTLPixelFormatRGBA8Unorm;
4143 desc.pixelFormat = d->format;
4144 break;
4145 default:
4146 Q_UNREACHABLE();
4147 break;
4148 }
4149
4150 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
4151 [desc release];
4152
4153 if (!m_objectName.isEmpty())
4154 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
4155
4157 generation += 1;
4158 rhiD->registerResource(this);
4159 return true;
4160}
4161
4163{
4164 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
4165 return m_backingFormatHint;
4166 else
4167 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
4168}
4169
4170QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
4171 int arraySize, int sampleCount, Flags flags)
4173 d(new QMetalTextureData(this))
4174{
4175 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
4176 d->stagingBuf[i] = nil;
4177
4178 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
4179 d->perLevelViews[i] = nil;
4180}
4181
4183{
4184 destroy();
4185 delete d;
4186}
4187
4189{
4190 if (!d->tex)
4191 return;
4192
4196
4197 e.texture.texture = d->owns ? d->tex : nil;
4198 d->tex = nil;
4199
4200 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
4201 e.texture.stagingBuffers[i] = d->stagingBuf[i];
4202 d->stagingBuf[i] = nil;
4203 }
4204
4205 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
4206 e.texture.views[i] = d->perLevelViews[i];
4207 d->perLevelViews[i] = nil;
4208 }
4209
4210 QRHI_RES_RHI(QRhiMetal);
4211 if (rhiD) {
4212 rhiD->d->releaseQueue.append(e);
4213 rhiD->unregisterResource(this);
4214 }
4215}
4216
4217bool QMetalTexture::prepareCreate(QSize *adjustedSize)
4218{
4219 if (d->tex)
4220 destroy();
4221
4222 const bool isCube = m_flags.testFlag(CubeMap);
4223 const bool is3D = m_flags.testFlag(ThreeDimensional);
4224 const bool isArray = m_flags.testFlag(TextureArray);
4225 const bool hasMipMaps = m_flags.testFlag(MipMapped);
4226 const bool is1D = m_flags.testFlag(OneDimensional);
4227
4228 const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
4229 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
4230
4231 QRHI_RES_RHI(QRhiMetal);
4232 d->format = toMetalTextureFormat(m_format, m_flags, rhiD);
4233 mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
4234 samples = rhiD->effectiveSampleCount(m_sampleCount);
4235 if (samples > 1) {
4236 if (isCube) {
4237 qWarning("Cubemap texture cannot be multisample");
4238 return false;
4239 }
4240 if (is3D) {
4241 qWarning("3D texture cannot be multisample");
4242 return false;
4243 }
4244 if (hasMipMaps) {
4245 qWarning("Multisample texture cannot have mipmaps");
4246 return false;
4247 }
4248 }
4249 if (isCube && is3D) {
4250 qWarning("Texture cannot be both cube and 3D");
4251 return false;
4252 }
4253 if (isArray && is3D) {
4254 qWarning("Texture cannot be both array and 3D");
4255 return false;
4256 }
4257 if (is1D && is3D) {
4258 qWarning("Texture cannot be both 1D and 3D");
4259 return false;
4260 }
4261 if (is1D && isCube) {
4262 qWarning("Texture cannot be both 1D and cube");
4263 return false;
4264 }
4265 if (m_depth > 1 && !is3D) {
4266 qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
4267 return false;
4268 }
4269 if (m_arraySize > 0 && !isArray) {
4270 qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
4271 return false;
4272 }
4273 if (m_arraySize < 1 && isArray) {
4274 qWarning("Texture is an array but array size is %d", m_arraySize);
4275 return false;
4276 }
4277
4278 if (adjustedSize)
4279 *adjustedSize = size;
4280
4281 return true;
4282}
4283
4285{
4286 QSize size;
4287 if (!prepareCreate(&size))
4288 return false;
4289
4290 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
4291
4292 const bool isCube = m_flags.testFlag(CubeMap);
4293 const bool is3D = m_flags.testFlag(ThreeDimensional);
4294 const bool isArray = m_flags.testFlag(TextureArray);
4295 const bool is1D = m_flags.testFlag(OneDimensional);
4296 if (isCube) {
4297 desc.textureType = MTLTextureTypeCube;
4298 } else if (is3D) {
4299 desc.textureType = MTLTextureType3D;
4300 } else if (is1D) {
4301 desc.textureType = isArray ? MTLTextureType1DArray : MTLTextureType1D;
4302 } else if (isArray) {
4303 desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
4304 } else {
4305 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
4306 }
4307 desc.pixelFormat = d->format;
4308 desc.width = NSUInteger(size.width());
4309 desc.height = NSUInteger(size.height());
4310 desc.depth = is3D ? qMax(1, m_depth) : 1;
4311 desc.mipmapLevelCount = NSUInteger(mipLevelCount);
4312 if (samples > 1)
4313 desc.sampleCount = NSUInteger(samples);
4314 if (isArray)
4315 desc.arrayLength = NSUInteger(qMax(0, m_arraySize));
4316 desc.resourceOptions = MTLResourceStorageModePrivate;
4317 desc.storageMode = MTLStorageModePrivate;
4318 desc.usage = MTLTextureUsageShaderRead;
4319 if (m_flags.testFlag(RenderTarget))
4320 desc.usage |= MTLTextureUsageRenderTarget;
4321 if (m_flags.testFlag(UsedWithLoadStore))
4322 desc.usage |= MTLTextureUsageShaderWrite;
4323
4324 QRHI_RES_RHI(QRhiMetal);
4325 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
4326 [desc release];
4327
4328 if (!m_objectName.isEmpty())
4329 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
4330
4331 d->owns = true;
4332
4334 generation += 1;
4335 rhiD->registerResource(this);
4336 return true;
4337}
4338
4339bool QMetalTexture::createFrom(QRhiTexture::NativeTexture src)
4340{
4341 id<MTLTexture> tex = id<MTLTexture>(src.object);
4342 if (tex == 0)
4343 return false;
4344
4345 if (!prepareCreate())
4346 return false;
4347
4348 d->tex = tex;
4349
4350 d->owns = false;
4351
4353 generation += 1;
4354 QRHI_RES_RHI(QRhiMetal);
4355 rhiD->registerResource(this);
4356 return true;
4357}
4358
4360{
4361 return {quint64(d->tex), 0};
4362}
4363
4365{
4366 Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
4367 if (perLevelViews[level])
4368 return perLevelViews[level];
4369
4370 const MTLTextureType type = [tex textureType];
4371 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
4372 const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
4373 id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
4374 levels: NSMakeRange(NSUInteger(level), 1)
4375 slices: NSMakeRange(0, isCube ? 6 : (isArray ? qMax(0, q->m_arraySize) : 1))];
4376
4377 perLevelViews[level] = view;
4378 return view;
4379}
4380
4381QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
4382 AddressMode u, AddressMode v, AddressMode w)
4384 d(new QMetalSamplerData)
4385{
4386}
4387
4389{
4390 destroy();
4391 delete d;
4392}
4393
4395{
4396 if (!d->samplerState)
4397 return;
4398
4402
4403 e.sampler.samplerState = d->samplerState;
4404 d->samplerState = nil;
4405
4406 QRHI_RES_RHI(QRhiMetal);
4407 if (rhiD) {
4408 rhiD->d->releaseQueue.append(e);
4409 rhiD->unregisterResource(this);
4410 }
4411}
4412
4413static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
4414{
4415 switch (f) {
4416 case QRhiSampler::Nearest:
4417 return MTLSamplerMinMagFilterNearest;
4418 case QRhiSampler::Linear:
4419 return MTLSamplerMinMagFilterLinear;
4420 default:
4421 Q_UNREACHABLE();
4422 return MTLSamplerMinMagFilterNearest;
4423 }
4424}
4425
4426static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
4427{
4428 switch (f) {
4429 case QRhiSampler::None:
4430 return MTLSamplerMipFilterNotMipmapped;
4431 case QRhiSampler::Nearest:
4432 return MTLSamplerMipFilterNearest;
4433 case QRhiSampler::Linear:
4434 return MTLSamplerMipFilterLinear;
4435 default:
4436 Q_UNREACHABLE();
4437 return MTLSamplerMipFilterNotMipmapped;
4438 }
4439}
4440
4441static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
4442{
4443 switch (m) {
4444 case QRhiSampler::Repeat:
4445 return MTLSamplerAddressModeRepeat;
4446 case QRhiSampler::ClampToEdge:
4447 return MTLSamplerAddressModeClampToEdge;
4448 case QRhiSampler::Mirror:
4449 return MTLSamplerAddressModeMirrorRepeat;
4450 default:
4451 Q_UNREACHABLE();
4452 return MTLSamplerAddressModeClampToEdge;
4453 }
4454}
4455
4456static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
4457{
4458 switch (op) {
4459 case QRhiSampler::Never:
4460 return MTLCompareFunctionNever;
4461 case QRhiSampler::Less:
4462 return MTLCompareFunctionLess;
4463 case QRhiSampler::Equal:
4464 return MTLCompareFunctionEqual;
4465 case QRhiSampler::LessOrEqual:
4466 return MTLCompareFunctionLessEqual;
4467 case QRhiSampler::Greater:
4468 return MTLCompareFunctionGreater;
4469 case QRhiSampler::NotEqual:
4470 return MTLCompareFunctionNotEqual;
4471 case QRhiSampler::GreaterOrEqual:
4472 return MTLCompareFunctionGreaterEqual;
4473 case QRhiSampler::Always:
4474 return MTLCompareFunctionAlways;
4475 default:
4476 Q_UNREACHABLE();
4477 return MTLCompareFunctionNever;
4478 }
4479}
4480
4482{
4483 if (d->samplerState)
4484 destroy();
4485
4486 MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
4487 desc.minFilter = toMetalFilter(m_minFilter);
4488 desc.magFilter = toMetalFilter(m_magFilter);
4489 desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
4490 desc.sAddressMode = toMetalAddressMode(m_addressU);
4491 desc.tAddressMode = toMetalAddressMode(m_addressV);
4492 desc.rAddressMode = toMetalAddressMode(m_addressW);
4493 desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
4494
4495 QRHI_RES_RHI(QRhiMetal);
4496 d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
4497 [desc release];
4498
4500 generation += 1;
4501 rhiD->registerResource(this);
4502 return true;
4503}
4504
4508{
4509}
4510
4512{
4513 destroy();
4514 delete d;
4515}
4516
4518{
4519 if (!d->rateMap)
4520 return;
4521
4525
4526 e.shadingRateMap.rateMap = d->rateMap;
4527 d->rateMap = nil;
4528
4529 QRHI_RES_RHI(QRhiMetal);
4530 if (rhiD) {
4531 rhiD->d->releaseQueue.append(e);
4532 rhiD->unregisterResource(this);
4533 }
4534}
4535
4536bool QMetalShadingRateMap::createFrom(NativeShadingRateMap src)
4537{
4538 if (d->rateMap)
4539 destroy();
4540
4541 d->rateMap = (id<MTLRasterizationRateMap>) (quintptr(src.object));
4542 if (!d->rateMap)
4543 return false;
4544
4545 [d->rateMap retain];
4546
4548 generation += 1;
4549 QRHI_RES_RHI(QRhiMetal);
4550 rhiD->registerResource(this);
4551 return true;
4552}
4553
4554// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
4555// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
4558{
4559 serializedFormatData.reserve(16);
4560}
4561
4566
4568{
4569 QRHI_RES_RHI(QRhiMetal);
4570 if (rhiD)
4571 rhiD->unregisterResource(this);
4572}
4573
4574bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
4575{
4576 if (!other)
4577 return false;
4578
4580
4582 return false;
4583
4585 return false;
4586
4587 for (int i = 0; i < colorAttachmentCount; ++i) {
4588 if (colorFormat[i] != o->colorFormat[i])
4589 return false;
4590 }
4591
4592 if (hasDepthStencil) {
4593 if (dsFormat != o->dsFormat)
4594 return false;
4595 }
4596
4598 return false;
4599
4600 return true;
4601}
4602
4604{
4605 serializedFormatData.clear();
4606 auto p = std::back_inserter(serializedFormatData);
4607
4608 *p++ = colorAttachmentCount;
4609 *p++ = hasDepthStencil;
4610 for (int i = 0; i < colorAttachmentCount; ++i)
4611 *p++ = colorFormat[i];
4612 *p++ = hasDepthStencil ? dsFormat : 0;
4613 *p++ = hasShadingRateMap;
4614}
4615
4617{
4618 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
4621 memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
4622 rpD->dsFormat = dsFormat;
4624
4626
4627 QRHI_RES_RHI(QRhiMetal);
4628 rhiD->registerResource(rpD, false);
4629 return rpD;
4630}
4631
4633{
4634 return serializedFormatData;
4635}
4636
4637QMetalSwapChainRenderTarget::QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
4640{
4641}
4642
4648
4650{
4651 // nothing to do here
4652}
4653
4655{
4656 return d->pixelSize;
4657}
4658
4660{
4661 return d->dpr;
4662}
4663
4665{
4666 return d->sampleCount;
4667}
4668
4670 const QRhiTextureRenderTargetDescription &desc,
4671 Flags flags)
4674{
4675}
4676
4682
4684{
4685 QRHI_RES_RHI(QRhiMetal);
4686 if (rhiD)
4687 rhiD->unregisterResource(this);
4688}
4689
4691{
4692 const int colorAttachmentCount = int(m_desc.colorAttachmentCount());
4693 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
4694 rpD->colorAttachmentCount = colorAttachmentCount;
4695 rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
4696
4697 for (int i = 0; i < colorAttachmentCount; ++i) {
4698 const QRhiColorAttachment *colorAtt = m_desc.colorAttachmentAt(i);
4699 QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAtt->texture());
4700 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAtt->renderBuffer());
4701 rpD->colorFormat[i] = int(texD ? texD->d->format : rbD->d->format);
4702 }
4703
4704 if (m_desc.depthTexture())
4705 rpD->dsFormat = int(QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format);
4706 else if (m_desc.depthStencilBuffer())
4707 rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format);
4708
4709 rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr;
4710
4712
4713 QRHI_RES_RHI(QRhiMetal);
4714 rhiD->registerResource(rpD, false);
4715 return rpD;
4716}
4717
4719{
4720 QRHI_RES_RHI(QRhiMetal);
4721 Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
4722 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
4723 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
4724
4725 d->colorAttCount = 0;
4726 int attIndex = 0;
4727 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
4728 d->colorAttCount += 1;
4729 QMetalTexture *texD = QRHI_RES(QMetalTexture, it->texture());
4730 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, it->renderBuffer());
4731 Q_ASSERT(texD || rbD);
4732 id<MTLTexture> dst = nil;
4733 bool is3D = false;
4734 if (texD) {
4735 dst = texD->d->tex;
4736 if (attIndex == 0) {
4737 d->pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
4739 }
4740 is3D = texD->flags().testFlag(QRhiTexture::ThreeDimensional);
4741 } else if (rbD) {
4742 dst = rbD->d->tex;
4743 if (attIndex == 0) {
4744 d->pixelSize = rbD->pixelSize();
4746 }
4747 }
4749 colorAtt.tex = dst;
4750 colorAtt.arrayLayer = is3D ? 0 : it->layer();
4751 colorAtt.slice = is3D ? it->layer() : 0;
4752 colorAtt.level = it->level();
4753 QMetalTexture *resTexD = QRHI_RES(QMetalTexture, it->resolveTexture());
4754 colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
4755 colorAtt.resolveLayer = it->resolveLayer();
4756 colorAtt.resolveLevel = it->resolveLevel();
4757 d->fb.colorAtt[attIndex] = colorAtt;
4758 }
4759 d->dpr = 1;
4760
4761 if (hasDepthStencil) {
4762 if (m_desc.depthTexture()) {
4763 QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
4764 d->fb.dsTex = depthTexD->d->tex;
4765 d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format());
4766 d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture();
4767 d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
4768 if (d->colorAttCount == 0) {
4769 d->pixelSize = depthTexD->pixelSize();
4770 d->sampleCount = depthTexD->samples;
4771 }
4772 } else {
4773 QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
4774 d->fb.dsTex = depthRbD->d->tex;
4775 d->fb.hasStencil = true;
4776 d->fb.depthNeedsStore = false;
4777 d->fb.preserveDs = false;
4778 if (d->colorAttCount == 0) {
4779 d->pixelSize = depthRbD->pixelSize();
4780 d->sampleCount = depthRbD->samples;
4781 }
4782 }
4783 if (m_desc.depthResolveTexture()) {
4784 QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture());
4785 d->fb.dsResolveTex = depthResolveTexD->d->tex;
4786 }
4787 d->dsAttCount = 1;
4788 } else {
4789 d->dsAttCount = 0;
4790 }
4791
4792 if (d->colorAttCount > 0)
4793 d->fb.preserveColor = m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
4794
4795 QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList);
4796
4797 rhiD->registerResource(this, false);
4798 return true;
4799}
4800
4802{
4803 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(m_desc, d->currentResIdList))
4804 const_cast<QMetalTextureRenderTarget *>(this)->create();
4805
4806 return d->pixelSize;
4807}
4808
4810{
4811 return d->dpr;
4812}
4813
4815{
4816 return d->sampleCount;
4817}
4818
4823
4828
4830{
4831 sortedBindings.clear();
4832 maxBinding = -1;
4833
4834 QRHI_RES_RHI(QRhiMetal);
4835 if (rhiD)
4836 rhiD->unregisterResource(this);
4837}
4838
4840{
4841 if (!sortedBindings.isEmpty())
4842 destroy();
4843
4844 QRHI_RES_RHI(QRhiMetal);
4845 if (!rhiD->sanityCheckShaderResourceBindings(this))
4846 return false;
4847
4848 rhiD->updateLayoutDesc(this);
4849
4850 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
4851 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
4852 if (!sortedBindings.isEmpty())
4853 maxBinding = QRhiImplementation::shaderResourceBindingData(sortedBindings.last())->binding;
4854 else
4855 maxBinding = -1;
4856
4857 boundResourceData.resize(sortedBindings.count());
4858
4859 for (BoundResourceData &bd : boundResourceData)
4860 memset(&bd, 0, sizeof(BoundResourceData));
4861
4862 generation += 1;
4863 rhiD->registerResource(this, false);
4864 return true;
4865}
4866
4868{
4869 sortedBindings.clear();
4870 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
4871 if (!flags.testFlag(BindingsAreSorted))
4872 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
4873
4874 for (BoundResourceData &bd : boundResourceData)
4875 memset(&bd, 0, sizeof(BoundResourceData));
4876
4877 generation += 1;
4878}
4879
4883{
4884 d->q = this;
4885 d->tess.q = d;
4886}
4887
4893
4895{
4896 d->vs.destroy();
4897 d->fs.destroy();
4898
4899 d->tess.compVs[0].destroy();
4900 d->tess.compVs[1].destroy();
4901 d->tess.compVs[2].destroy();
4902
4903 d->tess.compTesc.destroy();
4904 d->tess.vertTese.destroy();
4905
4906 qDeleteAll(d->extraBufMgr.deviceLocalWorkBuffers);
4907 d->extraBufMgr.deviceLocalWorkBuffers.clear();
4908 qDeleteAll(d->extraBufMgr.hostVisibleWorkBuffers);
4909 d->extraBufMgr.hostVisibleWorkBuffers.clear();
4910
4911 delete d->bufferSizeBuffer;
4912 d->bufferSizeBuffer = nullptr;
4913
4914 if (!d->ps && !d->ds
4915 && !d->tess.vertexComputeState[0] && !d->tess.vertexComputeState[1] && !d->tess.vertexComputeState[2]
4916 && !d->tess.tessControlComputeState)
4917 {
4918 return;
4919 }
4920
4924 e.graphicsPipeline.pipelineState = d->ps;
4925 e.graphicsPipeline.depthStencilState = d->ds;
4926 e.graphicsPipeline.tessVertexComputeState = d->tess.vertexComputeState;
4927 e.graphicsPipeline.tessTessControlComputeState = d->tess.tessControlComputeState;
4928 d->ps = nil;
4929 d->ds = nil;
4930 d->tess.vertexComputeState = {};
4931 d->tess.tessControlComputeState = nil;
4932
4933 QRHI_RES_RHI(QRhiMetal);
4934 if (rhiD) {
4935 rhiD->d->releaseQueue.append(e);
4936 rhiD->unregisterResource(this);
4937 }
4938}
4939
4940static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
4941{
4942 switch (format) {
4943 case QRhiVertexInputAttribute::Float4:
4944 return MTLVertexFormatFloat4;
4945 case QRhiVertexInputAttribute::Float3:
4946 return MTLVertexFormatFloat3;
4947 case QRhiVertexInputAttribute::Float2:
4948 return MTLVertexFormatFloat2;
4949 case QRhiVertexInputAttribute::Float:
4950 return MTLVertexFormatFloat;
4951 case QRhiVertexInputAttribute::UNormByte4:
4952 return MTLVertexFormatUChar4Normalized;
4953 case QRhiVertexInputAttribute::UNormByte2:
4954 return MTLVertexFormatUChar2Normalized;
4955 case QRhiVertexInputAttribute::UNormByte:
4956 return MTLVertexFormatUCharNormalized;
4957 case QRhiVertexInputAttribute::UInt4:
4958 return MTLVertexFormatUInt4;
4959 case QRhiVertexInputAttribute::UInt3:
4960 return MTLVertexFormatUInt3;
4961 case QRhiVertexInputAttribute::UInt2:
4962 return MTLVertexFormatUInt2;
4963 case QRhiVertexInputAttribute::UInt:
4964 return MTLVertexFormatUInt;
4965 case QRhiVertexInputAttribute::SInt4:
4966 return MTLVertexFormatInt4;
4967 case QRhiVertexInputAttribute::SInt3:
4968 return MTLVertexFormatInt3;
4969 case QRhiVertexInputAttribute::SInt2:
4970 return MTLVertexFormatInt2;
4971 case QRhiVertexInputAttribute::SInt:
4972 return MTLVertexFormatInt;
4973 case QRhiVertexInputAttribute::Half4:
4974 return MTLVertexFormatHalf4;
4975 case QRhiVertexInputAttribute::Half3:
4976 return MTLVertexFormatHalf3;
4977 case QRhiVertexInputAttribute::Half2:
4978 return MTLVertexFormatHalf2;
4979 case QRhiVertexInputAttribute::Half:
4980 return MTLVertexFormatHalf;
4981 case QRhiVertexInputAttribute::UShort4:
4982 return MTLVertexFormatUShort4;
4983 case QRhiVertexInputAttribute::UShort3:
4984 return MTLVertexFormatUShort3;
4985 case QRhiVertexInputAttribute::UShort2:
4986 return MTLVertexFormatUShort2;
4987 case QRhiVertexInputAttribute::UShort:
4988 return MTLVertexFormatUShort;
4989 case QRhiVertexInputAttribute::SShort4:
4990 return MTLVertexFormatShort4;
4991 case QRhiVertexInputAttribute::SShort3:
4992 return MTLVertexFormatShort3;
4993 case QRhiVertexInputAttribute::SShort2:
4994 return MTLVertexFormatShort2;
4995 case QRhiVertexInputAttribute::SShort:
4996 return MTLVertexFormatShort;
4997 default:
4998 Q_UNREACHABLE();
4999 return MTLVertexFormatFloat4;
5000 }
5001}
5002
5003static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
5004{
5005 switch (f) {
5006 case QRhiGraphicsPipeline::Zero:
5007 return MTLBlendFactorZero;
5008 case QRhiGraphicsPipeline::One:
5009 return MTLBlendFactorOne;
5010 case QRhiGraphicsPipeline::SrcColor:
5011 return MTLBlendFactorSourceColor;
5012 case QRhiGraphicsPipeline::OneMinusSrcColor:
5013 return MTLBlendFactorOneMinusSourceColor;
5014 case QRhiGraphicsPipeline::DstColor:
5015 return MTLBlendFactorDestinationColor;
5016 case QRhiGraphicsPipeline::OneMinusDstColor:
5017 return MTLBlendFactorOneMinusDestinationColor;
5018 case QRhiGraphicsPipeline::SrcAlpha:
5019 return MTLBlendFactorSourceAlpha;
5020 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
5021 return MTLBlendFactorOneMinusSourceAlpha;
5022 case QRhiGraphicsPipeline::DstAlpha:
5023 return MTLBlendFactorDestinationAlpha;
5024 case QRhiGraphicsPipeline::OneMinusDstAlpha:
5025 return MTLBlendFactorOneMinusDestinationAlpha;
5026 case QRhiGraphicsPipeline::ConstantColor:
5027 return MTLBlendFactorBlendColor;
5028 case QRhiGraphicsPipeline::ConstantAlpha:
5029 return MTLBlendFactorBlendAlpha;
5030 case QRhiGraphicsPipeline::OneMinusConstantColor:
5031 return MTLBlendFactorOneMinusBlendColor;
5032 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
5033 return MTLBlendFactorOneMinusBlendAlpha;
5034 case QRhiGraphicsPipeline::SrcAlphaSaturate:
5035 return MTLBlendFactorSourceAlphaSaturated;
5036 case QRhiGraphicsPipeline::Src1Color:
5037 return MTLBlendFactorSource1Color;
5038 case QRhiGraphicsPipeline::OneMinusSrc1Color:
5039 return MTLBlendFactorOneMinusSource1Color;
5040 case QRhiGraphicsPipeline::Src1Alpha:
5041 return MTLBlendFactorSource1Alpha;
5042 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
5043 return MTLBlendFactorOneMinusSource1Alpha;
5044 default:
5045 Q_UNREACHABLE();
5046 return MTLBlendFactorZero;
5047 }
5048}
5049
5050static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
5051{
5052 switch (op) {
5053 case QRhiGraphicsPipeline::Add:
5054 return MTLBlendOperationAdd;
5055 case QRhiGraphicsPipeline::Subtract:
5056 return MTLBlendOperationSubtract;
5057 case QRhiGraphicsPipeline::ReverseSubtract:
5058 return MTLBlendOperationReverseSubtract;
5059 case QRhiGraphicsPipeline::Min:
5060 return MTLBlendOperationMin;
5061 case QRhiGraphicsPipeline::Max:
5062 return MTLBlendOperationMax;
5063 default:
5064 Q_UNREACHABLE();
5065 return MTLBlendOperationAdd;
5066 }
5067}
5068
5069static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
5070{
5071 uint f = 0;
5072 if (c.testFlag(QRhiGraphicsPipeline::R))
5073 f |= MTLColorWriteMaskRed;
5074 if (c.testFlag(QRhiGraphicsPipeline::G))
5075 f |= MTLColorWriteMaskGreen;
5076 if (c.testFlag(QRhiGraphicsPipeline::B))
5077 f |= MTLColorWriteMaskBlue;
5078 if (c.testFlag(QRhiGraphicsPipeline::A))
5079 f |= MTLColorWriteMaskAlpha;
5080 return f;
5081}
5082
5083static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
5084{
5085 switch (op) {
5086 case QRhiGraphicsPipeline::Never:
5087 return MTLCompareFunctionNever;
5088 case QRhiGraphicsPipeline::Less:
5089 return MTLCompareFunctionLess;
5090 case QRhiGraphicsPipeline::Equal:
5091 return MTLCompareFunctionEqual;
5092 case QRhiGraphicsPipeline::LessOrEqual:
5093 return MTLCompareFunctionLessEqual;
5094 case QRhiGraphicsPipeline::Greater:
5095 return MTLCompareFunctionGreater;
5096 case QRhiGraphicsPipeline::NotEqual:
5097 return MTLCompareFunctionNotEqual;
5098 case QRhiGraphicsPipeline::GreaterOrEqual:
5099 return MTLCompareFunctionGreaterEqual;
5100 case QRhiGraphicsPipeline::Always:
5101 return MTLCompareFunctionAlways;
5102 default:
5103 Q_UNREACHABLE();
5104 return MTLCompareFunctionAlways;
5105 }
5106}
5107
5108static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
5109{
5110 switch (op) {
5111 case QRhiGraphicsPipeline::StencilZero:
5112 return MTLStencilOperationZero;
5113 case QRhiGraphicsPipeline::Keep:
5114 return MTLStencilOperationKeep;
5115 case QRhiGraphicsPipeline::Replace:
5116 return MTLStencilOperationReplace;
5117 case QRhiGraphicsPipeline::IncrementAndClamp:
5118 return MTLStencilOperationIncrementClamp;
5119 case QRhiGraphicsPipeline::DecrementAndClamp:
5120 return MTLStencilOperationDecrementClamp;
5121 case QRhiGraphicsPipeline::Invert:
5122 return MTLStencilOperationInvert;
5123 case QRhiGraphicsPipeline::IncrementAndWrap:
5124 return MTLStencilOperationIncrementWrap;
5125 case QRhiGraphicsPipeline::DecrementAndWrap:
5126 return MTLStencilOperationDecrementWrap;
5127 default:
5128 Q_UNREACHABLE();
5129 return MTLStencilOperationKeep;
5130 }
5131}
5132
5133static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
5134{
5135 switch (t) {
5136 case QRhiGraphicsPipeline::Triangles:
5137 return MTLPrimitiveTypeTriangle;
5138 case QRhiGraphicsPipeline::TriangleStrip:
5139 return MTLPrimitiveTypeTriangleStrip;
5140 case QRhiGraphicsPipeline::Lines:
5141 return MTLPrimitiveTypeLine;
5142 case QRhiGraphicsPipeline::LineStrip:
5143 return MTLPrimitiveTypeLineStrip;
5144 case QRhiGraphicsPipeline::Points:
5145 return MTLPrimitiveTypePoint;
5146 default:
5147 Q_UNREACHABLE();
5148 return MTLPrimitiveTypeTriangle;
5149 }
5150}
5151
5152static inline MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t)
5153{
5154 switch (t) {
5155 case QRhiGraphicsPipeline::Triangles:
5156 case QRhiGraphicsPipeline::TriangleStrip:
5157 case QRhiGraphicsPipeline::TriangleFan:
5158 return MTLPrimitiveTopologyClassTriangle;
5159 case QRhiGraphicsPipeline::Lines:
5160 case QRhiGraphicsPipeline::LineStrip:
5161 return MTLPrimitiveTopologyClassLine;
5162 case QRhiGraphicsPipeline::Points:
5163 return MTLPrimitiveTopologyClassPoint;
5164 default:
5165 Q_UNREACHABLE();
5166 return MTLPrimitiveTopologyClassTriangle;
5167 }
5168}
5169
5170static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
5171{
5172 switch (c) {
5173 case QRhiGraphicsPipeline::None:
5174 return MTLCullModeNone;
5175 case QRhiGraphicsPipeline::Front:
5176 return MTLCullModeFront;
5177 case QRhiGraphicsPipeline::Back:
5178 return MTLCullModeBack;
5179 default:
5180 Q_UNREACHABLE();
5181 return MTLCullModeNone;
5182 }
5183}
5184
5185static inline MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::PolygonMode mode)
5186{
5187 switch (mode) {
5188 case QRhiGraphicsPipeline::Fill:
5189 return MTLTriangleFillModeFill;
5190 case QRhiGraphicsPipeline::Line:
5191 return MTLTriangleFillModeLines;
5192 default:
5193 Q_UNREACHABLE();
5194 return MTLTriangleFillModeFill;
5195 }
5196}
5197
5198static inline MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
5199{
5200 switch (w) {
5201 case QShaderDescription::CwTessellationWindingOrder:
5202 return MTLWindingClockwise;
5203 case QShaderDescription::CcwTessellationWindingOrder:
5204 return MTLWindingCounterClockwise;
5205 default:
5206 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
5207 return MTLWindingCounterClockwise;
5208 }
5209}
5210
5211static inline MTLTessellationPartitionMode toMetalTessellationPartitionMode(QShaderDescription::TessellationPartitioning p)
5212{
5213 switch (p) {
5214 case QShaderDescription::EqualTessellationPartitioning:
5215 return MTLTessellationPartitionModePow2;
5216 case QShaderDescription::FractionalEvenTessellationPartitioning:
5217 return MTLTessellationPartitionModeFractionalEven;
5218 case QShaderDescription::FractionalOddTessellationPartitioning:
5219 return MTLTessellationPartitionModeFractionalOdd;
5220 default:
5221 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
5222 return MTLTessellationPartitionModePow2;
5223 }
5224}
5225
5226static inline MTLLanguageVersion toMetalLanguageVersion(const QShaderVersion &version)
5227{
5228 int v = version.version();
5229 return MTLLanguageVersion(((v / 10) << 16) + (v % 10));
5230}
5231
5232id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
5233 QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
5234{
5235 QVarLengthArray<int, 8> versions;
5236 versions << 30 << 24 << 23 << 22 << 21 << 20 << 12;
5237
5238 const QList<QShaderKey> shaders = shader.availableShaders();
5239
5240 QShaderKey key;
5241
5242 for (const int &version : versions) {
5243 key = { QShader::Source::MetalLibShader, version, shaderVariant };
5244 if (shaders.contains(key))
5245 break;
5246 }
5247
5248 QShaderCode mtllib = shader.shader(key);
5249 if (!mtllib.shader().isEmpty()) {
5250 dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
5251 size_t(mtllib.shader().size()),
5252 dispatch_get_global_queue(0, 0),
5253 DISPATCH_DATA_DESTRUCTOR_DEFAULT);
5254 NSError *err = nil;
5255 id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
5256 dispatch_release(data);
5257 if (!err) {
5258 *entryPoint = mtllib.entryPoint();
5259 *activeKey = key;
5260 return lib;
5261 } else {
5262 const QString msg = QString::fromNSString(err.localizedDescription);
5263 qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
5264 }
5265 }
5266
5267 for (const int &version : versions) {
5268 key = { QShader::Source::MslShader, version, shaderVariant };
5269 if (shaders.contains(key))
5270 break;
5271 }
5272
5273 QShaderCode mslSource = shader.shader(key);
5274 if (mslSource.shader().isEmpty()) {
5275 qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader;
5276 return nil;
5277 }
5278
5279 NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
5280 MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
5281 opts.languageVersion = toMetalLanguageVersion(key.sourceVersion());
5282 NSError *err = nil;
5283 id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
5284 [opts release];
5285 // src is autoreleased
5286
5287 // if lib is null and err is non-null, we had errors (fail)
5288 // if lib is non-null and err is non-null, we had warnings (success)
5289 // if lib is non-null and err is null, there were no errors or warnings (success)
5290 if (!lib) {
5291 const QString msg = QString::fromNSString(err.localizedDescription);
5292 *error = msg;
5293 return nil;
5294 }
5295
5296 *entryPoint = mslSource.entryPoint();
5297 *activeKey = key;
5298 return lib;
5299}
5300
5301id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
5302{
5303 return [lib newFunctionWithName:[NSString stringWithUTF8String:entryPoint.constData()]];
5304}
5305
5307{
5308 MTLRenderPipelineDescriptor *rpDesc = reinterpret_cast<MTLRenderPipelineDescriptor *>(metalRpDesc);
5309
5310 if (rpD->colorAttachmentCount) {
5311 // defaults when no targetBlends are provided
5312 rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
5313 rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
5314 rpDesc.colorAttachments[0].blendingEnabled = false;
5315
5316 Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
5317 || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
5318
5319 for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
5320 const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
5321 rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
5322 rpDesc.colorAttachments[i].blendingEnabled = b.enable;
5323 rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
5324 rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
5325 rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
5326 rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
5327 rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
5328 rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
5329 rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
5330 }
5331 }
5332
5333 if (rpD->hasDepthStencil) {
5334 // Must only be set when a depth-stencil buffer will actually be bound,
5335 // validation blows up otherwise.
5336 MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
5337 rpDesc.depthAttachmentPixelFormat = fmt;
5338#if defined(Q_OS_MACOS)
5339 if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
5340#else
5341 if (fmt != MTLPixelFormatDepth32Float)
5342#endif
5343 rpDesc.stencilAttachmentPixelFormat = fmt;
5344 }
5345
5346 QRHI_RES_RHI(QRhiMetal);
5347 rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
5348}
5349
5351{
5352 MTLDepthStencilDescriptor *dsDesc = reinterpret_cast<MTLDepthStencilDescriptor *>(metalDsDesc);
5353
5354 dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
5355 dsDesc.depthWriteEnabled = m_depthWrite;
5356 if (m_stencilTest) {
5357 dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
5358 dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
5359 dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
5360 dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
5361 dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
5362 dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
5363 dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
5364
5365 dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
5366 dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
5367 dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
5368 dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
5369 dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
5370 dsDesc.backFaceStencil.readMask = m_stencilReadMask;
5371 dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
5372 }
5373}
5374
5376{
5377 d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
5378 d->cullMode = toMetalCullMode(m_cullMode);
5379 d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
5380 d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
5381 d->depthBias = float(m_depthBias);
5382 d->slopeScaledDepthBias = m_slopeScaledDepthBias;
5383}
5384
5386{
5387 // same binding space for vertex and constant buffers - work it around
5388 // should be in native resource binding not SPIR-V, but this will work anyway
5389 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
5390
5391 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
5392 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
5393 it != itEnd; ++it)
5394 {
5395 const uint loc = uint(it->location());
5396 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
5397 desc.attributes[loc].offset = NSUInteger(it->offset());
5398 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
5399 }
5400 int bindingIndex = 0;
5401 const NSUInteger viewCount = qMax<NSUInteger>(1, q->multiViewCount());
5402 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
5403 it != itEnd; ++it, ++bindingIndex)
5404 {
5405 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
5406 desc.layouts[layoutIdx].stepFunction =
5407 it->classification() == QRhiVertexInputBinding::PerInstance
5408 ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
5409 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
5410 if (desc.layouts[layoutIdx].stepFunction == MTLVertexStepFunctionPerInstance)
5411 desc.layouts[layoutIdx].stepRate *= viewCount;
5412 desc.layouts[layoutIdx].stride = it->stride();
5413 }
5414}
5415
5416void QMetalGraphicsPipelineData::setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
5417{
5418 // same binding space for vertex and constant buffers - work it around
5419 // should be in native resource binding not SPIR-V, but this will work anyway
5420 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
5421
5422 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
5423 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
5424 it != itEnd; ++it)
5425 {
5426 const uint loc = uint(it->location());
5427 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
5428 desc.attributes[loc].offset = NSUInteger(it->offset());
5429 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
5430 }
5431 int bindingIndex = 0;
5432 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
5433 it != itEnd; ++it, ++bindingIndex)
5434 {
5435 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
5436 if (desc.indexBufferIndex) {
5437 desc.layouts[layoutIdx].stepFunction =
5438 it->classification() == QRhiVertexInputBinding::PerInstance
5439 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridXIndexed;
5440 } else {
5441 desc.layouts[layoutIdx].stepFunction =
5442 it->classification() == QRhiVertexInputBinding::PerInstance
5443 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX;
5444 }
5445 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
5446 desc.layouts[layoutIdx].stride = it->stride();
5447 }
5448}
5449
5450void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
5451{
5452 if (binArch) {
5453 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
5454 rpDesc.binaryArchives = binArchArray;
5455 }
5456}
5457
5458void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
5459{
5460 if (binArch) {
5461 NSError *err = nil;
5462 if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) {
5463 const QString msg = QString::fromNSString(err.localizedDescription);
5464 qWarning("Failed to collect render pipeline functions to binary archive: %s", qPrintable(msg));
5465 }
5466 }
5467}
5468
5470{
5471 QRHI_RES_RHI(QRhiMetal);
5472
5473 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
5474 d->setupVertexInputDescriptor(vertexDesc);
5475
5476 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
5477 rpDesc.vertexDescriptor = vertexDesc;
5478
5479 // Mutability cannot be determined (slotted buffers could be set as
5480 // MTLMutabilityImmutable, but then we potentially need a different
5481 // descriptor for each buffer combination as this depends on the actual
5482 // buffers not just the resource binding layout), so leave
5483 // rpDesc.vertex/fragmentBuffers at the defaults.
5484
5485 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
5486 auto cacheIt = rhiD->d->shaderCache.constFind(shaderStage);
5487 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
5488 switch (shaderStage.type()) {
5489 case QRhiShaderStage::Vertex:
5490 d->vs = *cacheIt;
5491 [d->vs.lib retain];
5492 [d->vs.func retain];
5493 rpDesc.vertexFunction = d->vs.func;
5494 break;
5495 case QRhiShaderStage::Fragment:
5496 d->fs = *cacheIt;
5497 [d->fs.lib retain];
5498 [d->fs.func retain];
5499 rpDesc.fragmentFunction = d->fs.func;
5500 break;
5501 default:
5502 break;
5503 }
5504 } else {
5505 const QShader shader = shaderStage.shader();
5506 QString error;
5507 QByteArray entryPoint;
5508 QShaderKey activeKey;
5509 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(),
5510 &error, &entryPoint, &activeKey);
5511 if (!lib) {
5512 qWarning("MSL shader compilation failed: %s", qPrintable(error));
5513 return false;
5514 }
5515 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
5516 if (!func) {
5517 qWarning("MSL function for entry point %s not found", entryPoint.constData());
5518 [lib release];
5519 return false;
5520 }
5521 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
5522 // Use the simplest strategy: too many cached shaders -> drop them all.
5523 for (QMetalShader &s : rhiD->d->shaderCache)
5524 s.destroy();
5525 rhiD->d->shaderCache.clear();
5526 }
5527 switch (shaderStage.type()) {
5528 case QRhiShaderStage::Vertex:
5529 d->vs.lib = lib;
5530 d->vs.func = func;
5531 d->vs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
5532 d->vs.desc = shader.description();
5533 d->vs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
5534 rhiD->d->shaderCache.insert(shaderStage, d->vs);
5535 [d->vs.lib retain];
5536 [d->vs.func retain];
5537 rpDesc.vertexFunction = func;
5538 break;
5539 case QRhiShaderStage::Fragment:
5540 d->fs.lib = lib;
5541 d->fs.func = func;
5542 d->fs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
5543 d->fs.desc = shader.description();
5544 d->fs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
5545 rhiD->d->shaderCache.insert(shaderStage, d->fs);
5546 [d->fs.lib retain];
5547 [d->fs.func retain];
5548 rpDesc.fragmentFunction = func;
5549 break;
5550 default:
5551 [func release];
5552 [lib release];
5553 break;
5554 }
5555 }
5556 }
5557
5558 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
5560
5561 if (m_flags.testFlag(UsesIndirectDraws) && rhiD->caps.indirectCommandBuffers)
5562 rpDesc.supportIndirectCommandBuffers = YES;
5563
5564 if (m_multiViewCount >= 2)
5565 rpDesc.inputPrimitiveTopology = toMetalPrimitiveTopologyClass(m_topology);
5566
5567 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
5568
5569 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
5570 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
5571
5572 NSError *err = nil;
5573 d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
5574 [rpDesc release];
5575 if (!d->ps) {
5576 const QString msg = QString::fromNSString(err.localizedDescription);
5577 qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
5578 return false;
5579 }
5580
5581 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
5583 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
5584 [dsDesc release];
5585
5586 d->primitiveType = toMetalPrimitiveType(m_topology);
5588
5589 return true;
5590}
5591
5592int QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(QShader::Variant vertexCompVariant)
5593{
5594 switch (vertexCompVariant) {
5595 case QShader::NonIndexedVertexAsComputeShader:
5596 return 0;
5597 case QShader::UInt32IndexedVertexAsComputeShader:
5598 return 1;
5599 case QShader::UInt16IndexedVertexAsComputeShader:
5600 return 2;
5601 default:
5602 break;
5603 }
5604 return -1;
5605}
5606
5608{
5609 const int varIndex = vsCompVariantToIndex(vertexCompVariant);
5610 if (varIndex >= 0 && vertexComputeState[varIndex])
5611 return vertexComputeState[varIndex];
5612
5613 id<MTLFunction> func = nil;
5614 if (varIndex >= 0)
5615 func = compVs[varIndex].func;
5616
5617 if (!func) {
5618 qWarning("No compute function found for vertex shader translated for tessellation, this should not happen");
5619 return nil;
5620 }
5621
5622 const QMap<int, int> &ebb(compVs[varIndex].nativeShaderInfo.extraBufferBindings);
5623 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
5624
5625 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
5626 cpDesc.computeFunction = func;
5627 cpDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
5628 cpDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
5629 if (indexBufferBinding >= 0) {
5630 if (vertexCompVariant == QShader::UInt32IndexedVertexAsComputeShader) {
5631 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt32;
5632 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
5633 } else if (vertexCompVariant == QShader::UInt16IndexedVertexAsComputeShader) {
5634 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt16;
5635 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
5636 }
5637 }
5638 q->setupStageInputDescriptor(cpDesc.stageInputDescriptor);
5639
5640 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
5641
5642 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
5643 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
5644
5645 NSError *err = nil;
5646 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
5647 options: MTLPipelineOptionNone
5648 reflection: nil
5649 error: &err];
5650 [cpDesc release];
5651 if (!ps) {
5652 const QString msg = QString::fromNSString(err.localizedDescription);
5653 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
5654 } else {
5655 vertexComputeState[varIndex] = ps;
5656 }
5657 // not retained, the only owner is vertexComputeState and so the QRhiGraphicsPipeline
5658 return ps;
5659}
5660
5662{
5663 if (tessControlComputeState)
5664 return tessControlComputeState;
5665
5666 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
5667 cpDesc.computeFunction = compTesc.func;
5668
5669 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
5670
5671 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
5672 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
5673
5674 NSError *err = nil;
5675 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
5676 options: MTLPipelineOptionNone
5677 reflection: nil
5678 error: &err];
5679 [cpDesc release];
5680 if (!ps) {
5681 const QString msg = QString::fromNSString(err.localizedDescription);
5682 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
5683 } else {
5684 tessControlComputeState = ps;
5685 }
5686 // not retained, the only owner is tessControlComputeState and so the QRhiGraphicsPipeline
5687 return ps;
5688}
5689
5690static inline bool indexTaken(quint32 index, quint64 indices)
5691{
5692 return (indices >> index) & 0x1;
5693}
5694
5695static inline void takeIndex(quint32 index, quint64 &indices)
5696{
5697 indices |= 1 << index;
5698}
5699
5700static inline int nextAttributeIndex(quint64 indices)
5701{
5702 // Maximum number of vertex attributes per vertex descriptor. There does
5703 // not appear to be a way to query this from the implementation.
5704 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf indicates
5705 // that all GPU families have a value of 31.
5706 static const int maxVertexAttributes = 31;
5707
5708 for (int index = 0; index < maxVertexAttributes; ++index) {
5709 if (!indexTaken(index, indices))
5710 return index;
5711 }
5712
5713 Q_UNREACHABLE_RETURN(-1);
5714}
5715
5716static inline int aligned(quint32 offset, quint32 alignment)
5717{
5718 return ((offset + alignment - 1) / alignment) * alignment;
5719}
5720
5721template<typename T>
5722static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
5723{
5724
5725 int elements = 1;
5726 for (const int dim : variable.arrayDims)
5727 elements *= dim;
5728
5729 if (variable.type == QShaderDescription::VariableType::Struct) {
5730 for (int element = 0; element < elements; ++element) {
5731 for (const auto &member : variable.structMembers) {
5732 addUnusedVertexAttribute(member, rhiD, offset, vertexAlignment);
5733 }
5734 }
5735 } else {
5736 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
5737 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
5738
5739 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
5740 const quint32 alignment = size;
5741 vertexAlignment = std::max(vertexAlignment, alignment);
5742
5743 for (int element = 0; element < elements; ++element) {
5744 // adjust alignment
5745 offset = aligned(offset, alignment);
5746 offset += size;
5747 }
5748 }
5749}
5750
5751template<typename T>
5752static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment)
5753{
5754
5755 int elements = 1;
5756 for (const int dim : variable.arrayDims)
5757 elements *= dim;
5758
5759 if (variable.type == QShaderDescription::VariableType::Struct) {
5760 for (int element = 0; element < elements; ++element) {
5761 for (const auto &member : variable.structMembers) {
5762 addVertexAttribute(member, binding, rhiD, index, offset, attributes, indices, vertexAlignment);
5763 }
5764 }
5765 } else {
5766 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
5767 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
5768
5769 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
5770 const quint32 alignment = size;
5771 vertexAlignment = std::max(vertexAlignment, alignment);
5772
5773 for (int element = 0; element < elements; ++element) {
5774 Q_ASSERT(!indexTaken(index, indices));
5775
5776 // adjust alignment
5777 offset = aligned(offset, alignment);
5778
5779 attributes[index].bufferIndex = binding;
5780 attributes[index].format = toMetalAttributeFormat(format);
5781 attributes[index].offset = offset;
5782
5783 takeIndex(index, indices);
5784 index++;
5785 if (indexTaken(index, indices))
5786 index = nextAttributeIndex(indices);
5787
5788 offset += size;
5789 }
5790 }
5791}
5792
5793static inline bool matches(const QList<QShaderDescription::BlockVariable> &a, const QList<QShaderDescription::BlockVariable> &b)
5794{
5795 if (a.size() == b.size()) {
5796 bool match = true;
5797 for (int i = 0; i < a.size() && match; ++i) {
5798 match &= a[i].type == b[i].type
5799 && a[i].arrayDims == b[i].arrayDims
5800 && matches(a[i].structMembers, b[i].structMembers);
5801 }
5802 return match;
5803 }
5804
5805 return false;
5806}
5807
5808static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b)
5809{
5810 return a.location == b.location
5811 && a.type == b.type
5812 && a.perPatch == b.perPatch
5813 && matches(a.structMembers, b.structMembers);
5814}
5815
5816//
5817// Create the tessellation evaluation render pipeline state
5818//
5819// The tesc runs as a compute shader in a compute pipeline and writes per patch and per patch
5820// control point data into separate storage buffers. The tese runs as a vertex shader in a render
5821// pipeline. Our task is to generate a render pipeline descriptor for the tese that pulls vertices
5822// from these buffers.
5823//
5824// As the buffers we are pulling vertices from are written by a compute pipeline, they follow the
5825// MSL alignment conventions which we must take into account when generating our
5826// MTLVertexDescriptor. We must include the user defined tese input attributes, and any builtins
5827// that were used.
5828//
5829// SPIRV-Cross generates the MSL tese shader code with input attribute indices that reflect the
5830// specified GLSL locations. Interface blocks are flattened with each member having an incremented
5831// attribute index. SPIRV-Cross reports an error on compilation if there are clashes in the index
5832// address space.
5833//
5834// After the user specified attributes are processed, SPIRV-Cross places the in-use builtins at the
5835// next available (lowest value) attribute index. Tese builtins are processed in the following
5836// order:
5837//
5838// in gl_PerVertex
5839// {
5840// vec4 gl_Position;
5841// float gl_PointSize;
5842// float gl_ClipDistance[];
5843// };
5844//
5845// patch in float gl_TessLevelOuter[4];
5846// patch in float gl_TessLevelInner[2];
5847//
5848// Enumerations in QShaderDescription::BuiltinType are defined in this order.
5849//
5850// For quads, SPIRV-Cross places MTLQuadTessellationFactorsHalf per patch in the tessellation
5851// factor buffer. For triangles it uses MTLTriangleTessellationFactorsHalf.
5852//
5853// It should be noted that SPIRV-Cross handles the following builtin inputs internally, with no
5854// host side support required.
5855//
5856// in vec3 gl_TessCoord;
5857// in int gl_PatchVerticesIn;
5858// in int gl_PrimitiveID;
5859//
5861{
5862 if (pipeline->d->ps)
5863 return pipeline->d->ps;
5864
5865 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
5866 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
5867
5868 // tesc output buffers
5869 const QMap<int, int> &ebb(compTesc.nativeShaderInfo.extraBufferBindings);
5870 const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
5871 const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
5872 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
5873 quint32 offsetInTescOutput = 0;
5874 quint32 offsetInTescPatchOutput = 0;
5875 quint32 offsetInTessFactorBuffer = 0;
5876 quint32 tescOutputAlignment = 0;
5877 quint32 tescPatchOutputAlignment = 0;
5878 quint32 tessFactorAlignment = 0;
5879 QSet<int> usedBuffers;
5880
5881 // tesc output variables in ascending location order
5882 QMap<int, QShaderDescription::InOutVariable> tescOutVars;
5883 for (const auto &tescOutVar : compTesc.desc.outputVariables())
5884 tescOutVars[tescOutVar.location] = tescOutVar;
5885
5886 // tese input variables in ascending location order
5887 QMap<int, QShaderDescription::InOutVariable> teseInVars;
5888 for (const auto &teseInVar : vertTese.desc.inputVariables())
5889 teseInVars[teseInVar.location] = teseInVar;
5890
5891 // bit mask tracking usage of vertex attribute indices
5892 quint64 indices = 0;
5893
5894 for (QShaderDescription::InOutVariable &tescOutVar : tescOutVars) {
5895
5896 int index = tescOutVar.location;
5897 int binding = -1;
5898 quint32 *offset = nullptr;
5899 quint32 *alignment = nullptr;
5900
5901 if (tescOutVar.perPatch) {
5902 binding = tescPatchOutputBufferBinding;
5903 offset = &offsetInTescPatchOutput;
5904 alignment = &tescPatchOutputAlignment;
5905 } else {
5906 tescOutVar.arrayDims.removeLast();
5907 binding = tescOutputBufferBinding;
5908 offset = &offsetInTescOutput;
5909 alignment = &tescOutputAlignment;
5910 }
5911
5912 if (teseInVars.contains(index)) {
5913
5914 if (!matches(teseInVars[index], tescOutVar)) {
5915 qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << index;
5916 qWarning() << " tesc out:" << tescOutVar;
5917 qWarning() << " tese in:" << teseInVars[index];
5918 }
5919
5920 if (binding != -1) {
5921 addVertexAttribute(tescOutVar, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
5922 usedBuffers << binding;
5923 } else {
5924 qWarning() << "baked tessellation control shader missing output buffer binding information";
5925 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
5926 }
5927
5928 } else {
5929 qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar;
5930 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
5931 }
5932
5933 teseInVars.remove(tescOutVar.location);
5934 }
5935
5936 for (const QShaderDescription::InOutVariable &teseInVar : teseInVars)
5937 qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar;
5938
5939 // tesc output builtins in ascending location order
5940 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> tescOutBuiltins;
5941 for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables())
5942 tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin;
5943
5944 // tese input builtins in ascending location order
5945 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> teseInBuiltins;
5946 for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables())
5947 teseInBuiltins[teseInBuiltin.type] = teseInBuiltin;
5948
5949 const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode;
5950 bool tessLevelAdded = false;
5951
5952 for (const QShaderDescription::BuiltinVariable &builtin : tescOutBuiltins) {
5953
5954 QShaderDescription::InOutVariable variable;
5955 int binding = -1;
5956 quint32 *offset = nullptr;
5957 quint32 *alignment = nullptr;
5958
5959 switch (builtin.type) {
5960 case QShaderDescription::BuiltinType::PositionBuiltin:
5961 variable.type = QShaderDescription::VariableType::Vec4;
5962 binding = tescOutputBufferBinding;
5963 offset = &offsetInTescOutput;
5964 alignment = &tescOutputAlignment;
5965 break;
5966 case QShaderDescription::BuiltinType::PointSizeBuiltin:
5967 variable.type = QShaderDescription::VariableType::Float;
5968 binding = tescOutputBufferBinding;
5969 offset = &offsetInTescOutput;
5970 alignment = &tescOutputAlignment;
5971 break;
5972 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
5973 variable.type = QShaderDescription::VariableType::Float;
5974 variable.arrayDims = builtin.arrayDims;
5975 binding = tescOutputBufferBinding;
5976 offset = &offsetInTescOutput;
5977 alignment = &tescOutputAlignment;
5978 break;
5979 case QShaderDescription::BuiltinType::TessLevelOuterBuiltin:
5980 variable.type = QShaderDescription::VariableType::Half4;
5981 binding = tessFactorBufferBinding;
5982 offset = &offsetInTessFactorBuffer;
5983 tessLevelAdded = trianglesMode;
5984 alignment = &tessFactorAlignment;
5985 break;
5986 case QShaderDescription::BuiltinType::TessLevelInnerBuiltin:
5987 if (trianglesMode) {
5988 if (!tessLevelAdded) {
5989 variable.type = QShaderDescription::VariableType::Half4;
5990 binding = tessFactorBufferBinding;
5991 offsetInTessFactorBuffer = 0;
5992 offset = &offsetInTessFactorBuffer;
5993 alignment = &tessFactorAlignment;
5994 tessLevelAdded = true;
5995 } else {
5996 teseInBuiltins.remove(builtin.type);
5997 continue;
5998 }
5999 } else {
6000 variable.type = QShaderDescription::VariableType::Half2;
6001 binding = tessFactorBufferBinding;
6002 offsetInTessFactorBuffer = 8;
6003 offset = &offsetInTessFactorBuffer;
6004 alignment = &tessFactorAlignment;
6005 }
6006 break;
6007 default:
6008 Q_UNREACHABLE();
6009 break;
6010 }
6011
6012 if (teseInBuiltins.contains(builtin.type)) {
6013 if (binding != -1) {
6014 int index = nextAttributeIndex(indices);
6015 addVertexAttribute(variable, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
6016 usedBuffers << binding;
6017 } else {
6018 qWarning() << "baked tessellation control shader missing output buffer binding information";
6019 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
6020 }
6021 } else {
6022 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
6023 }
6024
6025 teseInBuiltins.remove(builtin.type);
6026 }
6027
6028 for (const QShaderDescription::BuiltinVariable &builtin : teseInBuiltins) {
6029 switch (builtin.type) {
6030 case QShaderDescription::BuiltinType::PositionBuiltin:
6031 case QShaderDescription::BuiltinType::PointSizeBuiltin:
6032 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
6033 qWarning() << "missing tessellation control output for tessellation evaluation builtin input:" << builtin;
6034 break;
6035 default:
6036 break;
6037 }
6038 }
6039
6040 if (usedBuffers.contains(tescOutputBufferBinding)) {
6041 vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint;
6042 vertexDesc.layouts[tescOutputBufferBinding].stride = aligned(offsetInTescOutput, tescOutputAlignment);
6043 }
6044
6045 if (usedBuffers.contains(tescPatchOutputBufferBinding)) {
6046 vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
6047 vertexDesc.layouts[tescPatchOutputBufferBinding].stride = aligned(offsetInTescPatchOutput, tescPatchOutputAlignment);
6048 }
6049
6050 if (usedBuffers.contains(tessFactorBufferBinding)) {
6051 vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
6052 vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? sizeof(MTLTriangleTessellationFactorsHalf) : sizeof(MTLQuadTessellationFactorsHalf);
6053 }
6054
6055 rpDesc.vertexDescriptor = vertexDesc;
6056 rpDesc.vertexFunction = vertTese.func;
6057 rpDesc.fragmentFunction = pipeline->d->fs.func;
6058
6059 // The portable, cross-API approach is to use CCW, the results are then
6060 // identical (assuming the applied clipSpaceCorrMatrix) for all the 3D
6061 // APIs. The tess.eval. GLSL shader is thus expected to specify ccw. If it
6062 // doesn't, things may not work as expected.
6063 rpDesc.tessellationOutputWindingOrder = toMetalTessellationWindingOrder(vertTese.desc.tessellationWindingOrder());
6064
6065 rpDesc.tessellationPartitionMode = toMetalTessellationPartitionMode(vertTese.desc.tessellationPartitioning());
6066
6067 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, pipeline->renderPassDescriptor());
6069
6070 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
6071
6072 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
6073 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
6074
6075 NSError *err = nil;
6076 id<MTLRenderPipelineState> ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
6077 [rpDesc release];
6078 if (!ps) {
6079 const QString msg = QString::fromNSString(err.localizedDescription);
6080 qWarning("Failed to create render pipeline state for tessellation: %s", qPrintable(msg));
6081 } else {
6082 // ps is stored in the QMetalGraphicsPipelineData so the end result in this
6083 // regard is no different from what createVertexFragmentPipeline does
6084 pipeline->d->ps = ps;
6085 }
6086 return ps;
6087}
6088
6090{
6091 QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers;
6092
6093 // Check if something is reusable as-is.
6094 for (QMetalBuffer *workBuf : *workBuffers) {
6095 if (workBuf && workBuf->lastActiveFrameSlot == -1 && workBuf->size() >= size) {
6096 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
6097 return workBuf;
6098 }
6099 }
6100
6101 // Once the pool is above a certain threshold, see if there is something
6102 // unused (but too small) and recreate that our size.
6103 if (workBuffers->count() > QMTL_FRAMES_IN_FLIGHT * 8) {
6104 for (QMetalBuffer *workBuf : *workBuffers) {
6105 if (workBuf && workBuf->lastActiveFrameSlot == -1) {
6106 workBuf->setSize(size);
6107 if (workBuf->create()) {
6108 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
6109 return workBuf;
6110 }
6111 }
6112 }
6113 }
6114
6115 // Add a new buffer to the pool.
6116 QMetalBuffer *buf;
6117 if (type == WorkBufType::DeviceLocal) {
6118 // for GPU->GPU data (non-slotted, not necessarily host writable)
6119 buf = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
6120 } else {
6121 // for CPU->GPU (non-slotted, host writable/coherent)
6122 buf = new QMetalBuffer(rhiD, QRhiBuffer::Dynamic, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
6123 }
6124 if (buf->create()) {
6125 buf->lastActiveFrameSlot = rhiD->currentFrameSlot;
6126 workBuffers->append(buf);
6127 return buf;
6128 }
6129
6130 qWarning("Failed to acquire work buffer of size %u", size);
6131 return nullptr;
6132}
6133
6134bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag)
6135{
6136 QRHI_RES_RHI(QRhiMetal);
6137 QString error;
6138 QByteArray entryPoint;
6139 QShaderKey activeKey;
6140
6141 const QShaderDescription tescDesc = tesc.description();
6142 const QShaderDescription teseDesc = tese.description();
6143 d->tess.inControlPointCount = uint(m_patchControlPointCount);
6144 d->tess.outControlPointCount = tescDesc.tessellationOutputVertexCount();
6145 if (!d->tess.outControlPointCount)
6146 d->tess.outControlPointCount = teseDesc.tessellationOutputVertexCount();
6147
6148 if (!d->tess.outControlPointCount) {
6149 qWarning("Failed to determine output vertex count from the tessellation control or evaluation shader, cannot tessellate");
6150 d->tess.enabled = false;
6151 d->tess.failed = true;
6152 return false;
6153 }
6154
6155 if (m_multiViewCount >= 2)
6156 qWarning("Multiview is not supported with tessellation");
6157
6158 // Now the vertex shader is a compute shader.
6159 // It should have three dedicated *VertexAsComputeShader variants.
6160 // What the requested variant was (Standard or Batchable) plays no role here.
6161 // (the Qt Quick scenegraph does not use tessellation with its materials)
6162 // Create all three versions.
6163
6164 bool variantsPresent[3] = {};
6165 const QVector<QShaderKey> tessVertKeys = tessVert.availableShaders();
6166 for (const QShaderKey &k : tessVertKeys) {
6167 switch (k.sourceVariant()) {
6168 case QShader::NonIndexedVertexAsComputeShader:
6169 variantsPresent[0] = true;
6170 break;
6171 case QShader::UInt32IndexedVertexAsComputeShader:
6172 variantsPresent[1] = true;
6173 break;
6174 case QShader::UInt16IndexedVertexAsComputeShader:
6175 variantsPresent[2] = true;
6176 break;
6177 default:
6178 break;
6179 }
6180 }
6181 if (!(variantsPresent[0] && variantsPresent[1] && variantsPresent[2])) {
6182 qWarning("Vertex shader is not prepared for Metal tessellation. Cannot tessellate. "
6183 "Perhaps the relevant variants (UInt32IndexedVertexAsComputeShader et al) were not generated? "
6184 "Try passing --msltess to qsb.");
6185 d->tess.enabled = false;
6186 d->tess.failed = true;
6187 return false;
6188 }
6189
6190 int varIndex = 0; // Will map NonIndexed as 0, UInt32 as 1, UInt16 as 2. Do not change this ordering.
6191 for (QShader::Variant variant : {
6192 QShader::NonIndexedVertexAsComputeShader,
6193 QShader::UInt32IndexedVertexAsComputeShader,
6194 QShader::UInt16IndexedVertexAsComputeShader })
6195 {
6196 id<MTLLibrary> lib = rhiD->d->createMetalLib(tessVert, variant, &error, &entryPoint, &activeKey);
6197 if (!lib) {
6198 qWarning("MSL shader compilation failed for vertex-as-compute shader %d: %s", int(variant), qPrintable(error));
6199 d->tess.enabled = false;
6200 d->tess.failed = true;
6201 return false;
6202 }
6203 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
6204 if (!func) {
6205 qWarning("MSL function for entry point %s not found", entryPoint.constData());
6206 [lib release];
6207 d->tess.enabled = false;
6208 d->tess.failed = true;
6209 return false;
6210 }
6211 QMetalShader &compVs(d->tess.compVs[varIndex]);
6212 compVs.lib = lib;
6213 compVs.func = func;
6214 compVs.desc = tessVert.description();
6215 compVs.nativeResourceBindingMap = tessVert.nativeResourceBindingMap(activeKey);
6216 compVs.nativeShaderInfo = tessVert.nativeShaderInfo(activeKey);
6217
6218 // pre-create all three MTLComputePipelineStates
6219 if (!d->tess.vsCompPipeline(rhiD, variant)) {
6220 qWarning("Failed to pre-generate compute pipeline for vertex compute shader (tessellation variant %d)", int(variant));
6221 d->tess.enabled = false;
6222 d->tess.failed = true;
6223 return false;
6224 }
6225
6226 ++varIndex;
6227 }
6228
6229 // Pipeline #2 is a compute that runs the tessellation control (compute) shader
6230 id<MTLLibrary> tessControlLib = rhiD->d->createMetalLib(tesc, QShader::StandardShader, &error, &entryPoint, &activeKey);
6231 if (!tessControlLib) {
6232 qWarning("MSL shader compilation failed for tessellation control compute shader: %s", qPrintable(error));
6233 d->tess.enabled = false;
6234 d->tess.failed = true;
6235 return false;
6236 }
6237 id<MTLFunction> tessControlFunc = rhiD->d->createMSLShaderFunction(tessControlLib, entryPoint);
6238 if (!tessControlFunc) {
6239 qWarning("MSL function for entry point %s not found", entryPoint.constData());
6240 [tessControlLib release];
6241 d->tess.enabled = false;
6242 d->tess.failed = true;
6243 return false;
6244 }
6245 d->tess.compTesc.lib = tessControlLib;
6246 d->tess.compTesc.func = tessControlFunc;
6247 d->tess.compTesc.desc = tesc.description();
6248 d->tess.compTesc.nativeResourceBindingMap = tesc.nativeResourceBindingMap(activeKey);
6249 d->tess.compTesc.nativeShaderInfo = tesc.nativeShaderInfo(activeKey);
6250 if (!d->tess.tescCompPipeline(rhiD)) {
6251 qWarning("Failed to pre-generate compute pipeline for tessellation control shader");
6252 d->tess.enabled = false;
6253 d->tess.failed = true;
6254 return false;
6255 }
6256
6257 // Pipeline #3 is a render pipeline with the tessellation evaluation (vertex) + the fragment shader
6258 id<MTLLibrary> tessEvalLib = rhiD->d->createMetalLib(tese, QShader::StandardShader, &error, &entryPoint, &activeKey);
6259 if (!tessEvalLib) {
6260 qWarning("MSL shader compilation failed for tessellation evaluation vertex shader: %s", qPrintable(error));
6261 d->tess.enabled = false;
6262 d->tess.failed = true;
6263 return false;
6264 }
6265 id<MTLFunction> tessEvalFunc = rhiD->d->createMSLShaderFunction(tessEvalLib, entryPoint);
6266 if (!tessEvalFunc) {
6267 qWarning("MSL function for entry point %s not found", entryPoint.constData());
6268 [tessEvalLib release];
6269 d->tess.enabled = false;
6270 d->tess.failed = true;
6271 return false;
6272 }
6273 d->tess.vertTese.lib = tessEvalLib;
6274 d->tess.vertTese.func = tessEvalFunc;
6275 d->tess.vertTese.desc = tese.description();
6276 d->tess.vertTese.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey);
6277 d->tess.vertTese.nativeShaderInfo = tese.nativeShaderInfo(activeKey);
6278
6279 id<MTLLibrary> fragLib = rhiD->d->createMetalLib(tessFrag, QShader::StandardShader, &error, &entryPoint, &activeKey);
6280 if (!fragLib) {
6281 qWarning("MSL shader compilation failed for fragment shader: %s", qPrintable(error));
6282 d->tess.enabled = false;
6283 d->tess.failed = true;
6284 return false;
6285 }
6286 id<MTLFunction> fragFunc = rhiD->d->createMSLShaderFunction(fragLib, entryPoint);
6287 if (!fragFunc) {
6288 qWarning("MSL function for entry point %s not found", entryPoint.constData());
6289 [fragLib release];
6290 d->tess.enabled = false;
6291 d->tess.failed = true;
6292 return false;
6293 }
6294 d->fs.lib = fragLib;
6295 d->fs.func = fragFunc;
6296 d->fs.desc = tessFrag.description();
6297 d->fs.nativeShaderInfo = tessFrag.nativeShaderInfo(activeKey);
6298 d->fs.nativeResourceBindingMap = tessFrag.nativeResourceBindingMap(activeKey);
6299
6300 if (!d->tess.teseFragRenderPipeline(rhiD, this)) {
6301 qWarning("Failed to pre-generate render pipeline for tessellation evaluation + fragment shader");
6302 d->tess.enabled = false;
6303 d->tess.failed = true;
6304 return false;
6305 }
6306
6307 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
6309 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
6310 [dsDesc release];
6311
6312 // no primitiveType
6314
6315 return true;
6316}
6317
6319{
6320 destroy(); // no early test, always invoke and leave it to destroy to decide what to clean up
6321
6322 QRHI_RES_RHI(QRhiMetal);
6323 rhiD->pipelineCreationStart();
6324 if (!rhiD->sanityCheckGraphicsPipeline(this))
6325 return false;
6326
6327 // See if tessellation is involved. Things will be very different, if so.
6328 QShader tessVert;
6329 QShader tesc;
6330 QShader tese;
6331 QShader tessFrag;
6332 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
6333 switch (shaderStage.type()) {
6334 case QRhiShaderStage::Vertex:
6335 tessVert = shaderStage.shader();
6336 break;
6337 case QRhiShaderStage::TessellationControl:
6338 tesc = shaderStage.shader();
6339 break;
6340 case QRhiShaderStage::TessellationEvaluation:
6341 tese = shaderStage.shader();
6342 break;
6343 case QRhiShaderStage::Fragment:
6344 tessFrag = shaderStage.shader();
6345 break;
6346 default:
6347 break;
6348 }
6349 }
6350 d->tess.enabled = tesc.isValid() && tese.isValid() && m_topology == Patches && m_patchControlPointCount > 0;
6351 d->tess.failed = false;
6352
6353 bool ok = d->tess.enabled ? createTessellationPipelines(tessVert, tesc, tese, tessFrag) : createVertexFragmentPipeline();
6354 if (!ok)
6355 return false;
6356
6357 // SPIRV-Cross buffer size buffers
6358 int buffers = 0;
6359 QVarLengthArray<QMetalShader *, 6> shaders;
6360 if (d->tess.enabled) {
6361 shaders.append(&d->tess.compVs[0]);
6362 shaders.append(&d->tess.compVs[1]);
6363 shaders.append(&d->tess.compVs[2]);
6364 shaders.append(&d->tess.compTesc);
6365 shaders.append(&d->tess.vertTese);
6366 } else {
6367 shaders.append(&d->vs);
6368 }
6369 shaders.append(&d->fs);
6370
6371 for (QMetalShader *shader : shaders) {
6372 if (shader->nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
6373 const int binding = shader->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
6374 shader->nativeResourceBindingMap[binding] = {binding, -1};
6375 int maxNativeBinding = 0;
6376 for (const QShaderDescription::StorageBlock &block : shader->desc.storageBlocks())
6377 maxNativeBinding = qMax(maxNativeBinding, shader->nativeResourceBindingMap[block.binding].first);
6378
6379 // we use one buffer to hold data for all graphics shader stages, each with a different offset.
6380 // buffer offsets must be 32byte aligned - adjust buffer count accordingly
6381 buffers += ((maxNativeBinding + 1 + 7) / 8) * 8;
6382 }
6383 }
6384
6385 if (buffers) {
6386 if (!d->bufferSizeBuffer)
6387 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::StorageBuffer, buffers * sizeof(int));
6388
6389 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
6391 }
6392
6393 rhiD->pipelineCreationEnd();
6395 generation += 1;
6396 rhiD->registerResource(this);
6397 return true;
6398}
6399
6405
6407{
6408 destroy();
6409 delete d;
6410}
6411
6413{
6414 d->cs.destroy();
6415
6416 if (!d->ps)
6417 return;
6418
6419 delete d->bufferSizeBuffer;
6420 d->bufferSizeBuffer = nullptr;
6421
6425 e.computePipeline.pipelineState = d->ps;
6426 d->ps = nil;
6427
6428 QRHI_RES_RHI(QRhiMetal);
6429 if (rhiD) {
6430 rhiD->d->releaseQueue.append(e);
6431 rhiD->unregisterResource(this);
6432 }
6433}
6434
6435void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
6436{
6437 if (binArch) {
6438 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
6439 cpDesc.binaryArchives = binArchArray;
6440 }
6441}
6442
6443void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
6444{
6445 if (binArch) {
6446 NSError *err = nil;
6447 if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) {
6448 const QString msg = QString::fromNSString(err.localizedDescription);
6449 qWarning("Failed to collect compute pipeline functions to binary archive: %s", qPrintable(msg));
6450 }
6451 }
6452}
6453
6455{
6456 if (d->ps)
6457 destroy();
6458
6459 QRHI_RES_RHI(QRhiMetal);
6460 rhiD->pipelineCreationStart();
6461
6462 auto cacheIt = rhiD->d->shaderCache.constFind(m_shaderStage);
6463 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
6464 d->cs = *cacheIt;
6465 } else {
6466 const QShader shader = m_shaderStage.shader();
6467 QString error;
6468 QByteArray entryPoint;
6469 QShaderKey activeKey;
6470 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
6471 &error, &entryPoint, &activeKey);
6472 if (!lib) {
6473 qWarning("MSL shader compilation failed: %s", qPrintable(error));
6474 return false;
6475 }
6476 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
6477 if (!func) {
6478 qWarning("MSL function for entry point %s not found", entryPoint.constData());
6479 [lib release];
6480 return false;
6481 }
6482 d->cs.lib = lib;
6483 d->cs.func = func;
6484 d->cs.localSize = shader.description().computeShaderLocalSize();
6485 d->cs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
6486 d->cs.desc = shader.description();
6487 d->cs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
6488
6489 // SPIRV-Cross buffer size buffers
6490 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
6491 const int binding = d->cs.nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
6492 d->cs.nativeResourceBindingMap[binding] = {binding, -1};
6493 }
6494
6495 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
6496 for (QMetalShader &s : rhiD->d->shaderCache)
6497 s.destroy();
6498 rhiD->d->shaderCache.clear();
6499 }
6500 rhiD->d->shaderCache.insert(m_shaderStage, d->cs);
6501 }
6502
6503 [d->cs.lib retain];
6504 [d->cs.func retain];
6505
6506 d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]);
6507
6508 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
6509 cpDesc.computeFunction = d->cs.func;
6510
6511 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
6512
6513 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
6514 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
6515
6516 NSError *err = nil;
6517 d->ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
6518 options: MTLPipelineOptionNone
6519 reflection: nil
6520 error: &err];
6521 [cpDesc release];
6522 if (!d->ps) {
6523 const QString msg = QString::fromNSString(err.localizedDescription);
6524 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
6525 return false;
6526 }
6527
6528 // SPIRV-Cross buffer size buffers
6529 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
6530 int buffers = 0;
6531 for (const QShaderDescription::StorageBlock &block : d->cs.desc.storageBlocks())
6532 buffers = qMax(buffers, d->cs.nativeResourceBindingMap[block.binding].first);
6533
6534 buffers += 1;
6535
6536 if (!d->bufferSizeBuffer)
6537 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::StorageBuffer, buffers * sizeof(int));
6538
6539 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
6541 }
6542
6543 rhiD->pipelineCreationEnd();
6545 generation += 1;
6546 rhiD->registerResource(this);
6547 return true;
6548}
6549
6553{
6555}
6556
6558{
6559 destroy();
6560 delete d;
6561}
6562
6564{
6565 // nothing to do here, we do not own the MTL cb object
6566}
6567
6569{
6570 nativeHandlesStruct.commandBuffer = (MTLCommandBuffer *) d->cb;
6571 nativeHandlesStruct.encoder = (MTLRenderCommandEncoder *) d->currentRenderPassEncoder;
6572 return &nativeHandlesStruct;
6573}
6574
6575void QMetalCommandBuffer::resetState(double lastGpuTime)
6576{
6577 d->lastGpuTime = lastGpuTime;
6578 d->currentRenderPassEncoder = nil;
6579 d->currentComputePassEncoder = nil;
6580 d->tessellationComputeEncoder = nil;
6581 d->currentPassRpDesc = nil;
6583}
6584
6586{
6588 currentTarget = nullptr;
6590}
6591
6593{
6594 currentGraphicsPipeline = nullptr;
6595 currentComputePipeline = nullptr;
6596 currentPipelineGeneration = 0;
6597 currentGraphicsSrb = nullptr;
6598 currentComputeSrb = nullptr;
6599 currentSrbGeneration = 0;
6600 currentResSlot = -1;
6601 currentIndexBuffer = nullptr;
6602 currentIndexOffset = 0;
6603 currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
6604 currentCullMode = -1;
6608 currentDepthBiasValues = { 0.0f, 0.0f };
6609 hasCustomScissorSet = false;
6610 currentViewport = {};
6611
6612 d->currentShaderResourceBindingState = {};
6613 d->currentDepthStencilState = nil;
6615 d->currentVertexInputsBuffers.clear();
6616 d->currentVertexInputOffsets.clear();
6617}
6618
6619QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
6620 : QRhiSwapChain(rhi),
6621 rtWrapper(rhi, this),
6622 cbWrapper(rhi),
6624{
6625 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
6626 d->sem[i] = nullptr;
6627 d->msaaTex[i] = nil;
6628 }
6629}
6630
6632{
6633 destroy();
6634 delete d;
6635}
6636
6638{
6639 if (!d->layer)
6640 return;
6641
6642 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
6643 if (d->sem[i]) {
6644 // the semaphores cannot be released if they do not have the initial value
6646
6647 dispatch_release(d->sem[i]);
6648 d->sem[i] = nullptr;
6649 }
6650 }
6651
6652 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
6653 [d->msaaTex[i] release];
6654 d->msaaTex[i] = nil;
6655 }
6656
6657 d->layer = nullptr;
6658 m_proxyData = {};
6659
6660 [d->curDrawable release];
6661 d->curDrawable = nil;
6662
6663 QRHI_RES_RHI(QRhiMetal);
6664 if (rhiD) {
6665 rhiD->swapchains.remove(this);
6666 rhiD->unregisterResource(this);
6667 }
6668}
6669
6671{
6672 return &cbWrapper;
6673}
6674
6679
6680// view.layer should ideally be called on the main thread, otherwise the UI
6681// Thread Checker in Xcode drops a warning. Hence trying to proxy it through
6682// QRhiSwapChainProxyData instead of just calling this function directly.
6683static inline CAMetalLayer *layerForWindow(QWindow *window)
6684{
6685 Q_ASSERT(window);
6686 CALayer *layer = nullptr;
6687#ifdef Q_OS_MACOS
6688 if (auto *cocoaWindow = window->nativeInterface<QNativeInterface::Private::QCocoaWindow>())
6689 layer = cocoaWindow->contentLayer();
6690#else
6691 layer = reinterpret_cast<UIView *>(window->winId()).layer;
6692#endif
6693 Q_ASSERT(layer);
6694 return static_cast<CAMetalLayer *>(layer);
6695}
6696
6697// If someone calls this, it is hopefully from the main thread, and they will
6698// then set the returned data on the QRhiSwapChain, so it won't need to query
6699// the layer on its own later on.
6701{
6703 d.reserved[0] = layerForWindow(window);
6704 return d;
6705}
6706
6708{
6709 Q_ASSERT(m_window);
6710 CAMetalLayer *layer = d->layer;
6711 if (!layer)
6712 layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, m_window, QRhi::Metal, 0);
6713
6714 Q_ASSERT(layer);
6715 int height = (int)layer.bounds.size.height;
6716 int width = (int)layer.bounds.size.width;
6717 width *= layer.contentsScale;
6718 height *= layer.contentsScale;
6719 return QSize(width, height);
6720}
6721
6723{
6724 if (f == HDRExtendedSrgbLinear) {
6725 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
6726 } else if (f == HDR10) {
6727 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
6728 } else if (f == HDRExtendedDisplayP3Linear) {
6729 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
6730 }
6731 return f == SDR;
6732}
6733
6735{
6736 QRHI_RES_RHI(QRhiMetal);
6737
6738 chooseFormats(); // ensure colorFormat and similar are filled out
6739
6740 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
6742 rpD->hasDepthStencil = m_depthStencil != nullptr;
6743
6744 rpD->colorFormat[0] = int(d->colorFormat);
6745
6746#ifdef Q_OS_MACOS
6747 // m_depthStencil may not be built yet so cannot rely on computed fields in it
6748 rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
6749 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
6750#else
6751 rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8;
6752#endif
6753
6754 rpD->hasShadingRateMap = m_shadingRateMap != nullptr;
6755
6757
6758 rhiD->registerResource(rpD, false);
6759 return rpD;
6760}
6761
6763{
6764 QRHI_RES_RHI(QRhiMetal);
6765 samples = rhiD->effectiveSampleCount(m_sampleCount);
6766 // pick a format that is allowed for CAMetalLayer.pixelFormat
6767 if (m_format == HDRExtendedSrgbLinear || m_format == HDRExtendedDisplayP3Linear) {
6768 d->colorFormat = MTLPixelFormatRGBA16Float;
6769 d->rhiColorFormat = QRhiTexture::RGBA16F;
6770 return;
6771 }
6772 if (m_format == HDR10) {
6773 d->colorFormat = MTLPixelFormatRGB10A2Unorm;
6774 d->rhiColorFormat = QRhiTexture::RGB10A2;
6775 return;
6776 }
6777 d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
6778 d->rhiColorFormat = QRhiTexture::BGRA8;
6779}
6780
6782{
6783 // wait+signal is the general pattern to ensure the commands for a
6784 // given frame slot have completed (if sem is 1, we go 0 then 1; if
6785 // sem is 0 we go -1, block, completion increments to 0, then us to 1)
6786
6787 dispatch_semaphore_t sem = d->sem[slot];
6788 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
6789 dispatch_semaphore_signal(sem);
6790}
6791
6793{
6794 Q_ASSERT(m_window);
6795
6796 const bool needsRegistration = !window || window != m_window;
6797
6798 if (window && window != m_window)
6799 destroy();
6800 // else no destroy(), this is intentional
6801
6802 QRHI_RES_RHI(QRhiMetal);
6803 if (needsRegistration || !rhiD->swapchains.contains(this))
6804 rhiD->swapchains.insert(this);
6805
6806 window = m_window;
6807
6808 if (window->surfaceType() != QSurface::MetalSurface) {
6809 qWarning("QMetalSwapChain only supports MetalSurface windows");
6810 return false;
6811 }
6812
6813 d->layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, window, QRhi::Metal, 0);
6814 Q_ASSERT(d->layer);
6815
6817 if (d->colorFormat != d->layer.pixelFormat)
6818 d->layer.pixelFormat = d->colorFormat;
6819
6820 if (m_format == HDRExtendedSrgbLinear) {
6821 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
6822 d->layer.wantsExtendedDynamicRangeContent = YES;
6823 } else if (m_format == HDR10) {
6824 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ);
6825 d->layer.wantsExtendedDynamicRangeContent = YES;
6826 } else if (m_format == HDRExtendedDisplayP3Linear) {
6827 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
6828 d->layer.wantsExtendedDynamicRangeContent = YES;
6829 }
6830
6831 if (m_flags.testFlag(UsedAsTransferSource))
6832 d->layer.framebufferOnly = NO;
6833
6834#ifdef Q_OS_MACOS
6835 if (m_flags.testFlag(NoVSync))
6836 d->layer.displaySyncEnabled = NO;
6837#endif
6838
6839 if (m_flags.testFlag(SurfaceHasPreMulAlpha)) {
6840 d->layer.opaque = NO;
6841 } else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
6842 // The CoreAnimation compositor is said to expect premultiplied alpha,
6843 // so this is then wrong when it comes to the blending operations but
6844 // there's nothing we can do. Fortunately Qt Quick always outputs
6845 // premultiplied alpha so it is not a problem there.
6846 d->layer.opaque = NO;
6847 } else {
6848 d->layer.opaque = YES;
6849 }
6850
6851 // Now set the layer's drawableSize which will stay set to the same value
6852 // until the next createOrResize(), thus ensuring atomicity with regards to
6853 // the drawable size in frames.
6854 int width = (int)d->layer.bounds.size.width;
6855 int height = (int)d->layer.bounds.size.height;
6856 CGSize layerSize = CGSizeMake(width, height);
6857 const float scaleFactor = d->layer.contentsScale;
6858 layerSize.width *= scaleFactor;
6859 layerSize.height *= scaleFactor;
6860 d->layer.drawableSize = layerSize;
6861
6862 m_currentPixelSize = QSizeF::fromCGSize(layerSize).toSize();
6863 pixelSize = m_currentPixelSize;
6864
6865 [d->layer setDevice: rhiD->d->dev];
6866
6867 [d->curDrawable release];
6868 d->curDrawable = nil;
6869
6870 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
6871 d->lastGpuTime[i] = 0;
6872 if (!d->sem[i])
6873 d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
6874 }
6875
6876 currentFrameSlot = 0;
6877 frameCount = 0;
6878
6879 ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
6880 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
6881 qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
6882 m_depthStencil->sampleCount(), m_sampleCount);
6883 }
6884 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
6885 if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
6886 m_depthStencil->setPixelSize(pixelSize);
6887 if (!m_depthStencil->create())
6888 qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
6889 pixelSize.width(), pixelSize.height());
6890 } else {
6891 qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
6892 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
6893 pixelSize.width(), pixelSize.height());
6894 }
6895 }
6896
6897 rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
6898 rtWrapper.d->pixelSize = pixelSize;
6899 rtWrapper.d->dpr = scaleFactor;
6902 rtWrapper.d->dsAttCount = ds ? 1 : 0;
6903
6904 qCDebug(QRHI_LOG_INFO, "got CAMetalLayer, pixel size %dx%d (scale %.2f)",
6905 pixelSize.width(), pixelSize.height(), scaleFactor);
6906
6907 if (samples > 1) {
6908 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
6909 desc.textureType = MTLTextureType2DMultisample;
6910 desc.pixelFormat = d->colorFormat;
6911 desc.width = NSUInteger(pixelSize.width());
6912 desc.height = NSUInteger(pixelSize.height());
6913 desc.sampleCount = NSUInteger(samples);
6914 desc.resourceOptions = MTLResourceStorageModePrivate;
6915 desc.storageMode = MTLStorageModePrivate;
6916 desc.usage = MTLTextureUsageRenderTarget;
6917 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
6918 if (d->msaaTex[i]) {
6921 e.lastActiveFrameSlot = 1; // because currentFrameSlot is reset to 0
6922 e.renderbuffer.texture = d->msaaTex[i];
6923 rhiD->d->releaseQueue.append(e);
6924 }
6925 d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
6926 }
6927 [desc release];
6928 }
6929
6930 rhiD->registerResource(this);
6931
6932 return true;
6933}
6934
6936{
6939 info.limits.colorComponentValue.maxColorComponentValue = 1;
6940 info.limits.colorComponentValue.maxPotentialColorComponentValue = 1;
6942 info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred
6943
6944 if (m_window) {
6945 // Must use m_window, not window, given this may be called before createOrResize().
6946#if defined(Q_OS_MACOS)
6947 NSView *view = reinterpret_cast<NSView *>(m_window->winId());
6948 NSScreen *screen = view.window.screen;
6949 info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue;
6950 info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
6951#elif defined(Q_OS_IOS)
6952 UIView *view = reinterpret_cast<UIView *>(m_window->winId());
6953 UIScreen *screen = view.window.windowScene.screen;
6954 info.limits.colorComponentValue.maxColorComponentValue =
6955 view.window.windowScene.screen.currentEDRHeadroom;
6956 info.limits.colorComponentValue.maxPotentialColorComponentValue =
6957 screen.potentialEDRHeadroom;
6958#endif
6959 }
6960
6961 return info;
6962}
6963
6964QT_END_NAMESPACE
static QRhiSwapChainProxyData updateSwapChainProxyData(QWindow *window)
QMetalSwapChain * currentSwapChain
bool isDeviceLost() const override
void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override
QRhiStats statistics() override
Definition qrhimetal.mm:986
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:757
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override
Definition qrhimetal.mm:791
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:501
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)
QRhiSwapChain * createSwapChain() override
Definition qrhimetal.mm:747
QRhiGraphicsPipeline * createGraphicsPipeline() override
bool create(QRhi::Flags flags) override
Definition qrhimetal.mm:577
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) 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 enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, bool offsetOnlyChange, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES])
void setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) override
bool isYUpInNDC() const override
Definition qrhimetal.mm:767
int resourceLimit(QRhi::ResourceLimit limit) const override
Definition qrhimetal.mm:937
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:777
const QRhiNativeHandles * nativeHandles() override
Definition qrhimetal.mm:976
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:772
QRhiTextureRenderTarget * createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override
bool isYUpInFramebuffer() const override
Definition qrhimetal.mm:762
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
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
Definition qrhimetal.mm:993
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
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
Definition qrhimetal.mm:981
double lastCompletedGpuTime(QRhiCommandBuffer *cb) override
QList< int > supportedSampleCounts() const override
void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override
void finishActiveReadbacks(bool forced=false)
void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override
bool isFeatureSupported(QRhi::Feature feature) const override
Definition qrhimetal.mm:824
void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, qsizetype *curOfs)
void destroy() override
Definition qrhimetal.mm:692
QList< QSize > supportedShadingRates(int sampleCount) const override
Definition qrhimetal.mm:741
void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
static QRhiResourceUpdateBatchPrivate * get(QRhiResourceUpdateBatch *b)
Definition qrhi_p.h:597
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:323
\inmodule QtGui
Definition qshader.h:81
#define __has_feature(x)
@ UnBounded
Definition qrhi_p.h:285
@ Bounded
Definition qrhi_p.h:286
#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 MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
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 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 MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
static MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
static MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
static MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
static id< MTLComputeCommandEncoder > tempComputeEncoder(QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > maybeComputeEncoder)
static MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
static void endTempComputeEncoding(QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > computeEncoder)
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 takeIndex(quint32 index, quint64 &indices)
static int mapBinding(int binding, int stageIndex, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[], BindingType type)
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 addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
static uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
Definition qrhimetal.mm:61
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)
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_RELOCATABLE_TYPE)
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 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:308
id< MTLBuffer > buf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:303
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:374
id< MTLDepthStencilState > currentDepthStencilState
Definition qrhimetal.mm:378
QMetalShaderResourceBindingsData currentShaderResourceBindingState
Definition qrhimetal.mm:379
id< MTLComputeCommandEncoder > tessellationComputeEncoder
Definition qrhimetal.mm:373
QRhiBatchedBindings< id< MTLBuffer > > currentVertexInputsBuffers
Definition qrhimetal.mm:376
id< MTLRenderCommandEncoder > currentRenderPassEncoder
Definition qrhimetal.mm:371
id< MTLCommandBuffer > cb
Definition qrhimetal.mm:369
QRhiBatchedBindings< NSUInteger > currentVertexInputOffsets
Definition qrhimetal.mm:377
id< MTLComputeCommandEncoder > currentComputePassEncoder
Definition qrhimetal.mm:372
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:481
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:486
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:435
QMetalBuffer * acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type=WorkBufType::DeviceLocal)
QVector< QMetalBuffer * > hostVisibleWorkBuffers
Definition qrhimetal.mm:436
quint32 tescCompOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:454
std::array< id< MTLComputePipelineState >, 3 > vertexComputeState
Definition qrhimetal.mm:445
quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:458
static int vsCompVariantToIndex(QShader::Variant vertexCompVariant)
id< MTLComputePipelineState > tescCompPipeline(QRhiMetal *rhiD)
id< MTLRenderPipelineState > teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline)
QMetalGraphicsPipelineData * q
Definition qrhimetal.mm:439
id< MTLComputePipelineState > vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant)
quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:463
quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:449
id< MTLComputePipelineState > tessControlComputeState
Definition qrhimetal.mm:446
QMetalGraphicsPipeline * q
Definition qrhimetal.mm:417
MTLDepthClipMode depthClipMode
Definition qrhimetal.mm:424
MTLPrimitiveType primitiveType
Definition qrhimetal.mm:420
id< MTLRenderPipelineState > ps
Definition qrhimetal.mm:418
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:476
void setupVertexInputDescriptor(MTLVertexDescriptor *desc)
void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
id< MTLDepthStencilState > ds
Definition qrhimetal.mm:419
MTLTriangleFillMode triangleFillMode
Definition qrhimetal.mm:423
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)
id< MTLTexture > tex
Definition qrhimetal.mm:314
MTLPixelFormat format
Definition qrhimetal.mm:313
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:403
id< MTLTexture > dsResolveTex
Definition qrhimetal.mm:405
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList
Definition qrhimetal.mm:412
id< MTLTexture > dsTex
Definition qrhimetal.mm:404
id< MTLSamplerState > samplerState
Definition qrhimetal.mm:333
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:356
QVarLengthArray< Sampler, 8 > samplers
Definition qrhimetal.mm:358
QRhiBatchedBindings< NSUInteger > bufferOffsetBatches
Definition qrhimetal.mm:360
QVarLengthArray< Texture, 8 > textures
Definition qrhimetal.mm:357
QRhiBatchedBindings< id< MTLSamplerState > > samplerBatches
Definition qrhimetal.mm:362
QRhiBatchedBindings< id< MTLTexture > > textureBatches
Definition qrhimetal.mm:361
QRhiBatchedBindings< id< MTLBuffer > > bufferBatches
Definition qrhimetal.mm:359
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:338
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:492
dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:493
MTLPixelFormat colorFormat
Definition qrhimetal.mm:498
MTLRenderPassDescriptor * rp
Definition qrhimetal.mm:495
CAMetalLayer * layer
Definition qrhimetal.mm:491
double lastGpuTime[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:494
id< MTLTexture > msaaTex[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:496
QRhiTexture::Format rhiColorFormat
Definition qrhimetal.mm:497
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:323
id< MTLTexture > viewForLevel(int level)
QMetalTexture * q
Definition qrhimetal.mm:321
id< MTLTexture > perLevelViews[QRhi::MAX_MIP_LEVELS]
Definition qrhimetal.mm:326
id< MTLBuffer > stagingBuf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:324
QMetalTextureData(QMetalTexture *t)
Definition qrhimetal.mm:319
MTLPixelFormat format
Definition qrhimetal.mm:322
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.
QRhiReadbackResult * result
Definition qrhimetal.mm:271
id< MTLComputePipelineState > pipelineState
Definition qrhimetal.mm:237
id< MTLDepthStencilState > depthStencilState
Definition qrhimetal.mm:232
std::array< id< MTLComputePipelineState >, 3 > tessVertexComputeState
Definition qrhimetal.mm:233
id< MTLRasterizationRateMap > rateMap
Definition qrhimetal.mm:240
id< MTLSamplerState > samplerState
Definition qrhimetal.mm:225
id< MTLBuffer > stagingBuffers[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:221
id< MTLComputePipelineState > tessTessControlComputeState
Definition qrhimetal.mm:234
id< MTLIndirectCommandBuffer > icb
Definition qrhimetal.mm:243
id< MTLRenderPipelineState > pipelineState
Definition qrhimetal.mm:231
id< MTLBuffer > buffers[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:214
id< MTLTexture > views[QRhi::MAX_MIP_LEVELS]
Definition qrhimetal.mm:222
QMetalCommandBuffer cbWrapper
Definition qrhimetal.mm:254
OffscreenFrame(QRhiImplementation *rhi)
Definition qrhimetal.mm:251
QRhiReadbackDescription desc
Definition qrhimetal.mm:259
QRhiReadbackResult * result
Definition qrhimetal.mm:260
QRhiTexture::Format format
Definition qrhimetal.mm:264
void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLComputePipelineState > icbEncodePipelineU32
Definition qrhimetal.mm:285
QRhiMetalData(QRhiMetal *rhi)
Definition qrhimetal.mm:176
QVarLengthArray< BufferReadback, 2 > activeBufferReadbacks
Definition qrhimetal.mm:277
QHash< QRhiShaderStage, QMetalShader > shaderCache
Definition qrhimetal.mm:293
bool setupBinaryArchive(NSURL *sourceFileUrl=nil)
Definition qrhimetal.mm:557
id< MTLFunction > icbEncodeFunctionU16
Definition qrhimetal.mm:288
void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLFunction > icbEncodeFunctionU32
Definition qrhimetal.mm:287
MTLCaptureManager * captureMgr
Definition qrhimetal.mm:279
id< MTLBuffer > icbArgumentBuffer
Definition qrhimetal.mm:289
NSUInteger icbCapacity
Definition qrhimetal.mm:284
void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLLibrary > createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
id< MTLIndirectCommandBuffer > icb
Definition qrhimetal.mm:283
QVector< DeferredReleaseEntry > releaseQueue
Definition qrhimetal.mm:248
id< MTLFunction > createMSLShaderFunction(id< MTLLibrary > lib, const QByteArray &entryPoint)
id< MTLCaptureScope > captureScope
Definition qrhimetal.mm:280
MTLRenderPassDescriptor * createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, int colorAttCount, QRhiShadingRateMap *shadingRateMap)
QRhiMetal * q
Definition qrhimetal.mm:178
id< MTLComputePipelineState > icbEncodePipelineU16
Definition qrhimetal.mm:286
static const int TEXBUF_ALIGN
Definition qrhimetal.mm:291
id< MTLBinaryArchive > binArch
Definition qrhimetal.mm:181
id< MTLCommandBuffer > newCommandBuffer()
Definition qrhimetal.mm:545
QVarLengthArray< TextureReadback, 2 > activeTextureReadbacks
Definition qrhimetal.mm:266
id< MTLDevice > dev
Definition qrhimetal.mm:179
void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLCommandQueue > cmdQueue
Definition qrhimetal.mm:180
QMetalCommandBuffer * cbD
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1872
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1562
LimitsType limitsType
Definition qrhi.h:1573
float maxPotentialColorComponentValue
Definition qrhi.h:1581
LuminanceBehavior luminanceBehavior
Definition qrhi.h:1584
float maxColorComponentValue
Definition qrhi.h:1580
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1595