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