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