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