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