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