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