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