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