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"
7#include "qshader_p.h"
8#include <QGuiApplication>
9#include <QWindow>
10#include <QUrl>
11#include <QFile>
12#include <QTemporaryFile>
13#include <QFileInfo>
14#include <qmath.h>
15#include <QtCore/qendian.h>
16#include <QOperatingSystemVersion>
17
18#include <QtCore/private/qcore_mac_p.h>
19#include <QtGui/private/qmetallayer_p.h>
20#include <QtGui/qpa/qplatformwindow_p.h>
21
22#ifdef Q_OS_MACOS
23#include <AppKit/AppKit.h>
24#else
25#include <UIKit/UIKit.h>
26#endif
27
28#include <QuartzCore/CATransaction.h>
29
30#include <Metal/Metal.h>
31
32#include <utility> // for std::pair
33
35
36/*
37 Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
38 Shared (host visible) and duplicated (to help having 2 frames in flight),
39 "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
40 Textures are Private (device local) and a host visible staging buffer is
41 used to upload data to them. Does not rely on strong objects refs from
42 command buffers but does rely on the automatic resource tracking of the
43 command encoders. Assumes that an autorelease pool (ideally per frame) is
44 available on the thread on which QRhi is used.
45*/
46
47#if __has_feature(objc_arc)
48#error ARC not supported
49#endif
50
51// Even though the macOS 13 MTLBinaryArchive problem (QTBUG-106703) seems
52// to be solved in later 13.x releases, we have reports from old Intel hardware
53// and older macOS versions where this causes problems (QTBUG-114338).
54// Thus we no longer do OS version based differentiation, but rather have a
55// single toggle that is currently on, and so QRhi::(set)pipelineCache()
56// does nothing with Metal.
57#define QRHI_METAL_DISABLE_BINARY_ARCHIVE
58
59// We should be able to operate with command buffers that do not automatically
60// retain/release the resources used by them. (since we have logic that mirrors
61// other backends such as the Vulkan one anyway)
62#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
63
64/*!
65 \class QRhiMetalInitParams
66 \inmodule QtGuiPrivate
67 \inheaderfile rhi/qrhi.h
68 \since 6.6
69 \brief Metal specific initialization parameters.
70
71 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
72 for details.
73
74 A Metal-based QRhi needs no special parameters for initialization.
75
76 \badcode
77 QRhiMetalInitParams params;
78 rhi = QRhi::create(QRhi::Metal, &params);
79 \endcode
80
81 \note Metal API validation cannot be enabled programmatically by the QRhi.
82 Instead, either run the debug build of the application in XCode, by
83 generating a \c{.xcodeproj} file via \c{cmake -G Xcode}, or set the
84 environment variable \c{METAL_DEVICE_WRAPPER_TYPE=1}. The variable needs to
85 be set early on in the environment, preferably before starting the process;
86 attempting to set it at QRhi creation time is not functional in practice.
87 (too late probably)
88
89 \note QRhiSwapChain can only target QWindow instances that have their
90 surface type set to QSurface::MetalSurface.
91
92 \section2 Working with existing Metal devices
93
94 When interoperating with another graphics engine, it may be necessary to
95 get a QRhi instance that uses the same Metal device. This can be achieved
96 by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
97 device must be set to a non-null value then. Optionally, a command queue
98 object can be specified as well.
99
100 The QRhi does not take ownership of any of the external objects.
101 */
102
103/*!
104 \class QRhiMetalNativeHandles
105 \inmodule QtGuiPrivate
106 \inheaderfile rhi/qrhi.h
107 \since 6.6
108 \brief Holds the Metal device used by the QRhi.
109
110 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
111 for details.
112 */
113
114/*!
115 \variable QRhiMetalNativeHandles::dev
116
117 Set to a valid MTLDevice to import an existing device.
118*/
119
120/*!
121 \variable QRhiMetalNativeHandles::cmdQueue
122
123 Set to a valid MTLCommandQueue when importing an existing command queue.
124 When \nullptr, QRhi will create a new command queue.
125*/
126
127/*!
128 \class QRhiMetalCommandBufferNativeHandles
129 \inmodule QtGuiPrivate
130 \inheaderfile rhi/qrhi.h
131 \since 6.6
132 \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
133
134 \note The command buffer object is only guaranteed to be valid while
135 recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
136 - \l{QRhi::endFrame()}{endFrame()} or
137 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
138 \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
139
140 \note The command encoder is only valid while recording a pass, that is,
141 between \l{QRhiCommandBuffer::beginPass()} -
142 \l{QRhiCommandBuffer::endPass()}.
143
144 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
145 for details.
146 */
147
148/*!
149 \variable QRhiMetalCommandBufferNativeHandles::commandBuffer
150*/
151
152/*!
153 \variable QRhiMetalCommandBufferNativeHandles::encoder
154*/
155
157{
160 std::array<uint, 3> localSize = {};
167
168 void destroy() {
169 nativeResourceBindingMap.clear();
170 [lib release];
171 lib = nil;
172 [func release];
173 func = nil;
174 [argumentEncoder release];
175 argumentEncoder = nil;
177 }
178};
179
181{
182 QRhiMetalData(QRhiMetal *rhi) : q(rhi), ofr(rhi) { }
183
188
191 const QColor &colorClearValue,
192 const QRhiDepthStencilClearValue &depthStencilClearValue,
193 int colorAttCount,
194 QRhiShadingRateMap *shadingRateMap);
195 id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
196 bool preferArgumentBuffers,
197 QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
198 id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
199 bool setupBinaryArchive(NSURL *sourceFileUrl = nil);
200 void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
201 void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc);
202 void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
203 void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc);
204
258
260 OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
261 bool active = false;
262 double lastGpuTime = 0;
264 } ofr;
265
274 // Only used for depth(-stencil) textures whose Metal pixel format does
275 // not match the layout of the readback result, where the staging
276 // buffer holds a densely packed depth plane, followed by a densely
277 // packed stencil plane when the format has stencil. See
278 // enqueueResourceUpdates() and finishActiveReadbacks().
281 // Whether the depth plane holds 32-bit floats (as opposed to a
282 // 24-bit unorm value in the low bits of each 32-bit word).
283 bool depthPlaneIsFloat = false;
284 };
286
294
296
299
300 // Indirect Command Buffer (ICB) infrastructure for GPU-driven multi-draw
310 // Written by the encode kernel, consumed by executeCommandsInBuffer.
312 // Holds 0xFFFFFFFF, stands in for the count buffer when there is none.
314 bool icbSetupFailed = false;
315
316 static const int TEXBUF_ALIGN = 256; // probably not accurate
317
318 using ShaderCacheKey = std::pair<QRhiShaderStage, bool>; // stage, argument_buffers
320
331 id<MTLBuffer> allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment,
332 int frameSlot, quint32 minBlockSize, quint32 *offset);
333 id<MTLBuffer> allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset);
334 id<MTLBuffer> allocBufferStaging(quint32 size, int frameSlot, quint32 *offset);
335 id<MTLBuffer> newOneShotStagingBuffer(quint32 size, int frameSlot);
337
338 // Uploads larger than this always get their own one-shot MTLBuffer and do
339 // not count towards the area's size, so that a one-off large upload cannot
340 // inflate it.
341 static constexpr quint32 LARGE_STAGING_ALLOC = 512 * 1024;
342 static constexpr quint32 STAGING_AREA_MIN = 64 * 1024;
343 static constexpr quint32 STAGING_AREA_MAX = 16 * 1024 * 1024;
344 static constexpr int STAGING_AREA_LOW_DEMAND_FRAMES = 60;
345 static constexpr int STAGING_AREA_HIGH_DEMAND_FRAMES = 3;
346
347 // Counts both swapchain and offscreen frames. Never 0 once a frame started,
348 // so that a default-initialized "used in frame" cannot match.
350};
351
354
356{
357 bool managed = false;
358 bool slotted = false;
359 bool isDeviceLocal = false; // true = MTLResourceStorageModePrivate buf[0], no slotting, non-host visible
366};
367
373
395
400
405
433
456
489
491{
495 bool icbCapable = false;
516 bool enabled = false;
517 bool failed = false;
525 quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
526 {
527 // max vertex output components = resourceLimit(MaxVertexOutputs) * 4 = 60
528 return vertexOrIndexCount * instanceCount * sizeof(float) * 60;
529 }
530 quint32 tescCompOutputBufferSize(quint32 patchCount) const
531 {
532 return outControlPointCount * patchCount * sizeof(float) * 60;
533 }
534 quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
535 {
536 // assume maxTessellationControlPerPatchOutputComponents is 128
537 return patchCount * sizeof(float) * 128;
538 }
539 quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
540 {
541 return ((vertexOrIndexCount + inControlPointCount - 1) / inControlPointCount) * instanceCount;
542 }
547 } tess;
548 void setupVertexInputDescriptor(MTLVertexDescriptor *desc);
549 void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc);
550
551 // SPIRV-Cross buffer size buffers
553};
554
556{
560
561 // SPIRV-Cross buffer size buffers
563};
564
576
577QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
578{
579 Q_UNUSED(params);
580
581 d = new QRhiMetalData(this);
582
583 importedDevice = importDevice != nullptr;
584 if (importedDevice) {
585 if (importDevice->dev) {
586 d->dev = (id<MTLDevice>) importDevice->dev;
587 importedCmdQueue = importDevice->cmdQueue != nullptr;
588 if (importedCmdQueue)
589 d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
590 } else {
591 qWarning("No MTLDevice given, cannot import");
592 importedDevice = false;
593 }
594 }
595}
596
598{
599 delete d;
600}
601
602template <class Int>
603inline Int aligned(Int v, Int byteAlign)
604{
605 return (v + byteAlign - 1) & ~(byteAlign - 1);
606}
607
608bool QRhiMetal::probe(QRhiMetalInitParams *params)
609{
610 QMacAutoReleasePool pool;
611
612 Q_UNUSED(params);
613 id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
614 if (dev) {
615 [dev release];
616 return true;
617 }
618 return false;
619}
620
622{
624 // Do not let the command buffer mess with the refcount of objects. We do
625 // have a proper render loop and will manage lifetimes similarly to other
626 // backends (Vulkan).
627 return [cmdQueue commandBufferWithUnretainedReferences];
628#else
629 return [cmdQueue commandBuffer];
630#endif
631}
632
633bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl)
634{
636 return false;
637#endif
638
639 [binArch release];
640 MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new];
641 binArchDesc.url = sourceFileUrl;
642 NSError *err = nil;
643 binArch = [dev newBinaryArchiveWithDescriptor: binArchDesc error: &err];
644 [binArchDesc release];
645 if (!binArch) {
646 const QString msg = QString::fromNSString(err.localizedDescription);
647 qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg));
648 return false;
649 }
650 return true;
651}
652
653bool QRhiMetal::create(QRhi::Flags flags)
654{
655 rhiFlags = flags;
656
657 if (importedDevice)
658 [d->dev retain];
659 else
660 d->dev = MTLCreateSystemDefaultDevice();
661
662 if (!d->dev) {
663 qWarning("No MTLDevice");
664 return false;
665 }
666
667 const QString deviceName = QString::fromNSString([d->dev name]);
668 qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(deviceName));
669 driverInfoStruct.deviceName = deviceName.toUtf8();
670
671 // deviceId and vendorId stay unset for now. Note that registryID is not
672 // suitable as deviceId because it does not seem stable on macOS and can
673 // apparently change when the system is rebooted.
674
675#ifdef Q_OS_MACOS
676 const MTLDeviceLocation deviceLocation = [d->dev location];
677 switch (deviceLocation) {
678 case MTLDeviceLocationBuiltIn:
679 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
680 break;
681 case MTLDeviceLocationSlot:
682 driverInfoStruct.deviceType = QRhiDriverInfo::DiscreteDevice;
683 break;
684 case MTLDeviceLocationExternal:
685 driverInfoStruct.deviceType = QRhiDriverInfo::ExternalDevice;
686 break;
687 default:
688 break;
689 }
690#else
691 driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
692#endif
693
694 const QOperatingSystemVersion ver = QOperatingSystemVersion::current();
695 osMajor = ver.majorVersion();
696 osMinor = ver.minorVersion();
697
698 if (importedCmdQueue)
699 [d->cmdQueue retain];
700 else
701 d->cmdQueue = [d->dev newCommandQueue];
702
703 d->captureMgr = [MTLCaptureManager sharedCaptureManager];
704 // Have a custom capture scope as well which then shows up in XCode as
705 // an option when capturing, and becomes especially useful when having
706 // multiple windows with multiple QRhis.
707 d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
708 const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
709 d->captureScope.label = label.toNSString();
710
711#if defined(Q_OS_MACOS) || defined(Q_OS_VISIONOS)
712 caps.maxTextureSize = 16384;
713 caps.baseVertexAndInstance = true;
714 caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7];
715 caps.maxThreadGroupSize = 1024;
716 caps.multiView = true;
717#elif defined(Q_OS_TVOS)
718 if ([d->dev supportsFamily:MTLGPUFamilyApple3])
719 caps.maxTextureSize = 16384;
720 else
721 caps.maxTextureSize = 8192;
722 caps.baseVertexAndInstance = false;
723 caps.isAppleGPU = true;
724#elif defined(Q_OS_IOS)
725 if ([d->dev supportsFamily:MTLGPUFamilyApple3]) {
726 caps.maxTextureSize = 16384;
727 caps.baseVertexAndInstance = true;
728 } else if ([d->dev supportsFamily:MTLGPUFamilyApple2]) {
729 caps.maxTextureSize = 8192;
730 caps.baseVertexAndInstance = false;
731 } else {
732 caps.maxTextureSize = 4096;
733 caps.baseVertexAndInstance = false;
734 }
735 caps.isAppleGPU = true;
736 if ([d->dev supportsFamily:MTLGPUFamilyApple4])
737 caps.maxThreadGroupSize = 1024;
738 if ([d->dev supportsFamily:MTLGPUFamilyApple5])
739 caps.multiView = true;
740#endif
741
742 // MTLBlitCommandEncoder's buffer-to-buffer copy requires 4 byte aligned
743 // offsets and size on non-Apple GPUs, whereas uploadStaticBuffer() takes
744 // any offset and size. Keep the legacy host-write path there.
745 caps.usePrivateStaticBuffers = caps.isAppleGPU;
746 if (qEnvironmentVariableIntValue("QT_METAL_NO_PRIVATE_STATIC_BUFFERS"))
747 caps.usePrivateStaticBuffers = false;
748
749 caps.supportedSampleCounts = { 1 };
750 for (int sampleCount : { 2, 4, 8 }) {
751 if ([d->dev supportsTextureSampleCount: sampleCount])
752 caps.supportedSampleCounts.append(sampleCount);
753 }
754
755 caps.indirectCommandBuffers = ([d->dev supportsFamily:MTLGPUFamilyApple5]
756 || [d->dev supportsFamily:MTLGPUFamilyMac2])
757 && [d->dev supportsFamily:MTLGPUFamilyMetal3];
758
759 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 1];
760 if (caps.shadingRateMap && caps.multiView)
761 caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 2];
762
763 // QTBUG-144444: setDepthClipMode is not available on the Simulator
764 caps.depthClamp = [d->dev supportsFamily:MTLGPUFamilyApple3];
765
766 if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
767 d->setupBinaryArchive();
768
769 nativeHandlesStruct.dev = (MTLDevice *) d->dev;
770 nativeHandlesStruct.cmdQueue = (MTLCommandQueue *) d->cmdQueue;
771
772 return true;
773}
774
776{
779
780 for (QMetalShader &s : d->shaderCache)
781 s.destroy();
782 d->shaderCache.clear();
783
784 [d->captureScope release];
785 d->captureScope = nil;
786
787 for (auto *pools : { d->argBufPool, d->bufStagingPool }) {
788 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
789 [pools[i].buf release];
790 pools[i].buf = nil;
791 pools[i].capacity = 0;
792 pools[i].offset = 0;
793 }
794 }
795
796 [d->icbArgumentBuffer release];
797 d->icbArgumentBuffer = nil;
798
799 [d->icbRangeBuffer release];
800 d->icbRangeBuffer = nil;
801
802 [d->icbNoCountBuffer release];
803 d->icbNoCountBuffer = nil;
804
805 [d->icbEncodeFunction release];
806 d->icbEncodeFunction = nil;
807
808 [d->icbEncodeFunctionU32 release];
809 d->icbEncodeFunctionU32 = nil;
810
811 [d->icbEncodeFunctionU16 release];
812 d->icbEncodeFunctionU16 = nil;
813
814 [d->icbEncodePipeline release];
815 d->icbEncodePipeline = nil;
816
817 [d->icbEncodePipelineU32 release];
818 d->icbEncodePipelineU32 = nil;
819
820 [d->icbEncodePipelineU16 release];
821 d->icbEncodePipelineU16 = nil;
822
823 [d->icb release];
824 d->icb = nil;
825
826 d->icbCapacity = 0;
827 d->icbSetupFailed = false;
828
829 [d->binArch release];
830 d->binArch = nil;
831
832 [d->cmdQueue release];
833 if (!importedCmdQueue)
834 d->cmdQueue = nil;
835
836 [d->dev release];
837 if (!importedDevice)
838 d->dev = nil;
839}
840
842{
843 return caps.supportedSampleCounts;
844}
845
847{
848 Q_UNUSED(sampleCount);
849 return { QSize(1, 1) };
850}
851
852QRhiSwapChain *QRhiMetal::createSwapChain()
853{
854 return new QMetalSwapChain(this);
855}
856
857QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
858{
859 return new QMetalBuffer(this, type, usage, size);
860}
861
863{
864 return 256;
865}
866
868{
869 return false;
870}
871
873{
874 return true;
875}
876
878{
879 return true;
880}
881
883{
884 // depth range 0..1
885 // NB the ctor takes row-major
886 static constexpr QMatrix4x4 m(1.0f, 0.0f, 0.0f, 0.0f,
887 0.0f, 1.0f, 0.0f, 0.0f,
888 0.0f, 0.0f, 0.5f, 0.5f,
889 0.0f, 0.0f, 0.0f, 1.0f);
890 return m;
891}
892
893bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
894{
895 Q_UNUSED(flags);
896
897 bool supportsFamilyMac2 = false; // needed for BC* formats
898 bool supportsFamilyApple3 = false;
899
900#ifdef Q_OS_MACOS
901 supportsFamilyMac2 = true;
902 if (caps.isAppleGPU)
903 supportsFamilyApple3 = true;
904#else
905 supportsFamilyApple3 = true;
906#endif
907
908 // BC5 is not available for any Apple hardare
909 if (format == QRhiTexture::BC5)
910 return false;
911
912 if (!supportsFamilyApple3) {
913 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
914 return false;
915 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
916 return false;
917 }
918
919 if (!supportsFamilyMac2)
920 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
921 return false;
922
923 return true;
924}
925
926bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
927{
928 switch (feature) {
929 case QRhi::MultisampleTexture:
930 return true;
931 case QRhi::MultisampleRenderBuffer:
932 return true;
933 case QRhi::DebugMarkers:
934 return true;
935 case QRhi::Timestamps:
936 return true;
937 case QRhi::Instancing:
938 return true;
939 case QRhi::CustomInstanceStepRate:
940 return true;
941 case QRhi::PrimitiveRestart:
942 return true;
943 case QRhi::NonDynamicUniformBuffers:
944 return true;
945 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
946 return false;
947 case QRhi::NPOTTextureRepeat:
948 return true;
949 case QRhi::RedOrAlpha8IsRed:
950 return true;
951 case QRhi::ElementIndexUint:
952 return true;
953 case QRhi::Compute:
954 return true;
955 case QRhi::WideLines:
956 return false;
957 case QRhi::VertexShaderPointSize:
958 return true;
959 case QRhi::BaseVertex:
960 return caps.baseVertexAndInstance;
961 case QRhi::BaseInstance:
962 return caps.baseVertexAndInstance;
963 case QRhi::TriangleFanTopology:
964 return false;
965 case QRhi::ReadBackNonUniformBuffer:
966 return true;
967 case QRhi::ReadBackNonBaseMipLevel:
968 return true;
969 case QRhi::TexelFetch:
970 return true;
971 case QRhi::RenderToNonBaseMipLevel:
972 return true;
973 case QRhi::IntAttributes:
974 return true;
975 case QRhi::ScreenSpaceDerivatives:
976 return true;
977 case QRhi::ReadBackAnyTextureFormat:
978 return true;
979 case QRhi::PipelineCacheDataLoadSave:
981 return false;
982#else
983 return true;
984#endif
985 case QRhi::ImageDataStride:
986 return true;
987 case QRhi::RenderBufferImport:
988 return false;
989 case QRhi::ThreeDimensionalTextures:
990 return true;
991 case QRhi::RenderTo3DTextureSlice:
992 return true;
993 case QRhi::TextureArrays:
994 return true;
995 case QRhi::Tessellation:
996 return true;
997 case QRhi::GeometryShader:
998 return false;
999 case QRhi::TextureArrayRange:
1000 return true;
1001 case QRhi::NonFillPolygonMode:
1002 return true;
1003 case QRhi::OneDimensionalTextures:
1004 return true;
1005 case QRhi::OneDimensionalTextureMipmaps:
1006 return false;
1007 case QRhi::HalfAttributes:
1008 return true;
1009 case QRhi::RenderToOneDimensionalTexture:
1010 return false;
1011 case QRhi::ThreeDimensionalTextureMipmaps:
1012 return true;
1013 case QRhi::MultiView:
1014 return caps.multiView;
1015 case QRhi::TextureViewFormat:
1016 return true;
1017 case QRhi::ResolveDepthStencil:
1018 return true;
1019 case QRhi::VariableRateShading:
1020 return false;
1021 case QRhi::VariableRateShadingMap:
1022 return caps.shadingRateMap;
1023 case QRhi::VariableRateShadingMapWithTexture:
1024 return false;
1025 case QRhi::PerRenderTargetBlending:
1026 case QRhi::SampleVariables:
1027 return true;
1028 case QRhi::InstanceIndexIncludesBaseInstance:
1029 return true;
1030 case QRhi::DepthClamp:
1031 return caps.depthClamp;
1032 case QRhi::DrawIndirect:
1033 return true;
1034 case QRhi::DrawIndirectMulti:
1035 return caps.indirectCommandBuffers;
1036 case QRhi::ShaderDrawParameters:
1037 return false;
1038 case QRhi::PushConstants:
1039 return true;
1040 case QRhi::DrawIndirectCount:
1041 return caps.indirectCommandBuffers;
1042 case QRhi::DispatchIndirect:
1043 return true;
1044 case QRhi::BufferToBufferCopy:
1045 return true;
1046 case QRhi::StaticBuffersOnGpuTimeline:
1047 return caps.usePrivateStaticBuffers;
1048 default:
1049 Q_UNREACHABLE();
1050 return false;
1051 }
1052}
1053
1054int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
1055{
1056 switch (limit) {
1057 case QRhi::TextureSizeMin:
1058 return 1;
1059 case QRhi::TextureSizeMax:
1060 return caps.maxTextureSize;
1061 case QRhi::MaxColorAttachments:
1062 return 8;
1063 case QRhi::FramesInFlight:
1064 return QMTL_FRAMES_IN_FLIGHT;
1065 case QRhi::MaxAsyncReadbackFrames:
1066 return QMTL_FRAMES_IN_FLIGHT;
1067 case QRhi::MaxThreadGroupsPerDimension:
1068 return 65535;
1069 case QRhi::MaxThreadsPerThreadGroup:
1070 Q_FALLTHROUGH();
1071 case QRhi::MaxThreadGroupX:
1072 Q_FALLTHROUGH();
1073 case QRhi::MaxThreadGroupY:
1074 Q_FALLTHROUGH();
1075 case QRhi::MaxThreadGroupZ:
1076 return caps.maxThreadGroupSize;
1077 case QRhi::TextureArraySizeMax:
1078 return 2048;
1079 case QRhi::MaxUniformBufferRange:
1080 return 65536;
1081 case QRhi::MaxVertexInputs:
1082 return 31;
1083 case QRhi::MaxVertexOutputs:
1084 return 15; // use the minimum from MTLGPUFamily1/2/3
1085 case QRhi::MaxPushConstantsSize:
1086 return 4096; // the limit for setVertexBytes() and friends
1087 case QRhi::MaxVertexStorageBuffers:
1088 case QRhi::MaxFragmentStorageBuffers:
1089 return 31;
1090 case QRhi::ShadingRateImageTileSize:
1091 return 0;
1092 default:
1093 Q_UNREACHABLE();
1094 return 0;
1095 }
1096}
1097
1099{
1100 return &nativeHandlesStruct;
1101}
1102
1104{
1105 return driverInfoStruct;
1106}
1107
1109{
1110 QRhiStats result;
1111 result.totalPipelineCreationTime = totalPipelineCreationTime();
1112 return result;
1113}
1114
1116{
1117 // not applicable
1118 return false;
1119}
1120
1121void QRhiMetal::setQueueSubmitParams(QRhiNativeHandles *)
1122{
1123 // not applicable
1124}
1125
1127{
1128 for (QMetalShader &s : d->shaderCache)
1129 s.destroy();
1130
1131 d->shaderCache.clear();
1132}
1133
1135{
1136 return false;
1137}
1138
1148
1150{
1151 Q_STATIC_ASSERT(sizeof(QMetalPipelineCacheDataHeader) == 256);
1152 QByteArray data;
1153 if (!d->binArch || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
1154 return data;
1155
1156 QTemporaryFile tmp;
1157 if (!tmp.open()) {
1158 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1159 return data;
1160 }
1161 tmp.close(); // the file exists until the tmp dtor runs
1162
1163 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1164 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1165 NSError *err = nil;
1166 if (![d->binArch serializeToURL: url error: &err]) {
1167 const QString msg = QString::fromNSString(err.localizedDescription);
1168 // Some of these "errors" are not actual errors. (think of "Nothing to serialize")
1169 qCDebug(QRHI_LOG_INFO, "Failed to serialize MTLBinaryArchive: %s", qPrintable(msg));
1170 return data;
1171 }
1172
1173 QFile f(fn);
1174 if (!f.open(QIODevice::ReadOnly)) {
1175 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to reopen temporary file");
1176 return data;
1177 }
1178 const QByteArray blob = f.readAll();
1179 f.close();
1180
1181 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1182 const quint32 dataSize = quint32(blob.size());
1183
1184 data.resize(headerSize + dataSize);
1185
1187 header.rhiId = pipelineCacheRhiId();
1188 header.arch = quint32(sizeof(void*));
1189 header.dataSize = quint32(dataSize);
1190 header.osMajor = osMajor;
1191 header.osMinor = osMinor;
1192 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1193 if (driverStrLen)
1194 memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen);
1195 header.driver[driverStrLen] = '\0';
1196
1197 memcpy(data.data(), &header, headerSize);
1198 memcpy(data.data() + headerSize, blob.constData(), dataSize);
1199 return data;
1200}
1201
1202void QRhiMetal::setPipelineCacheData(const QByteArray &data)
1203{
1204 if (data.isEmpty())
1205 return;
1206
1207 const size_t headerSize = sizeof(QMetalPipelineCacheDataHeader);
1208 if (data.size() < qsizetype(headerSize)) {
1209 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)");
1210 return;
1211 }
1212
1213 const size_t dataOffset = headerSize;
1215 memcpy(&header, data.constData(), headerSize);
1216
1217 const quint32 rhiId = pipelineCacheRhiId();
1218 if (header.rhiId != rhiId) {
1219 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
1220 rhiId, header.rhiId);
1221 return;
1222 }
1223
1224 const quint32 arch = quint32(sizeof(void*));
1225 if (header.arch != arch) {
1226 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
1227 arch, header.arch);
1228 return;
1229 }
1230
1231 if (header.osMajor != osMajor || header.osMinor != osMinor) {
1232 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: OS version does not match (%u.%u, %u.%u)",
1233 osMajor, osMinor, header.osMajor, header.osMinor);
1234 return;
1235 }
1236
1237 const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.length()));
1238 if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) {
1239 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Metal device name does not match");
1240 return;
1241 }
1242
1243 if (quint64(data.size()) < quint64(dataOffset) + header.dataSize) {
1244 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)");
1245 return;
1246 }
1247
1248 const char *p = data.constData() + dataOffset;
1249
1250 QTemporaryFile tmp;
1251 if (!tmp.open()) {
1252 qCDebug(QRHI_LOG_INFO, "pipelineCacheData: Failed to create temporary file for Metal");
1253 return;
1254 }
1255 tmp.write(p, header.dataSize);
1256 tmp.close(); // the file exists until the tmp dtor runs
1257
1258 const QString fn = QFileInfo(tmp.fileName()).absoluteFilePath();
1259 NSURL *url = QUrl::fromLocalFile(fn).toNSURL();
1260 if (d->setupBinaryArchive(url))
1261 qCDebug(QRHI_LOG_INFO, "Created MTLBinaryArchive with initial data of %u bytes", header.dataSize);
1262}
1263
1264QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
1265 int sampleCount, QRhiRenderBuffer::Flags flags,
1266 QRhiTexture::Format backingFormatHint)
1267{
1268 return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
1269}
1270
1271QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format,
1272 const QSize &pixelSize, int depth, int arraySize,
1273 int sampleCount, QRhiTexture::Flags flags)
1274{
1275 return new QMetalTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
1276}
1277
1278QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
1279 QRhiSampler::Filter mipmapMode,
1280 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
1281{
1282 return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
1283}
1284
1285QRhiShadingRateMap *QRhiMetal::createShadingRateMap()
1286{
1287 return new QMetalShadingRateMap(this);
1288}
1289
1290QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
1291 QRhiTextureRenderTarget::Flags flags)
1292{
1293 return new QMetalTextureRenderTarget(this, desc, flags);
1294}
1295
1297{
1298 return new QMetalGraphicsPipeline(this);
1299}
1300
1302{
1303 return new QMetalComputePipeline(this);
1304}
1305
1307{
1308 return new QMetalShaderResourceBindings(this);
1309}
1310
1316
1317static inline int mapBinding(int binding,
1318 int stageIndex,
1319 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[],
1320 BindingType type)
1321{
1322 const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
1323 if (!map || map->isEmpty())
1324 return binding; // old QShader versions do not have this map, assume 1:1 mapping then
1325
1326 auto it = map->constFind(binding);
1327 if (it != map->cend())
1328 return type == BindingType::Sampler ? it->second : it->first; // may be -1, if the resource is inactive
1329
1330 // Hitting this path is normal too. It is not given that the resource (for
1331 // example, a uniform block) is present in the shaders for all the stages
1332 // specified by the visibility mask in the QRhiShaderResourceBinding.
1333 return -1;
1334}
1335
1336static inline MTLResourceUsage storageImageUsage(QRhiShaderResourceBinding::Type type)
1337{
1338 switch (type) {
1339 case QRhiShaderResourceBinding::ImageLoad:
1340 return MTLResourceUsageRead;
1341 case QRhiShaderResourceBinding::ImageStore:
1342 return MTLResourceUsageWrite;
1343 default:
1344 return MTLResourceUsageRead | MTLResourceUsageWrite;
1345 }
1346}
1347
1349 int encoderStage,
1351{
1352 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : res.textures) {
1353 switch (encoderStage) {
1354 case QMetalShaderResourceBindingsData::VERTEX:
1355 [cbD->d->currentRenderPassEncoder useResource: t.mtltex usage: t.usage stages: MTLRenderStageVertex];
1356 break;
1357 case QMetalShaderResourceBindingsData::FRAGMENT:
1358 [cbD->d->currentRenderPassEncoder useResource: t.mtltex usage: t.usage stages: MTLRenderStageFragment];
1359 break;
1360 case QMetalShaderResourceBindingsData::COMPUTE:
1361 [cbD->d->currentComputePassEncoder useResource: t.mtltex usage: t.usage];
1362 break;
1363 default:
1364 break;
1365 }
1366 }
1367}
1368
1370 int stage,
1371 const QRhiBatchedBindings<id<MTLBuffer>>::Batch &bufferBatch,
1372 const QRhiBatchedBindings<NSUInteger>::Batch &offsetBatch)
1373{
1374 switch (stage) {
1375 case QMetalShaderResourceBindingsData::VERTEX:
1376 [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
1377 offsets: offsetBatch.resources.constData()
1378 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1379 break;
1380 case QMetalShaderResourceBindingsData::FRAGMENT:
1381 [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
1382 offsets: offsetBatch.resources.constData()
1383 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1384 break;
1385 case QMetalShaderResourceBindingsData::COMPUTE:
1386 [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
1387 offsets: offsetBatch.resources.constData()
1388 withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
1389 break;
1392 // do nothing. These are used later for tessellation
1393 break;
1394 default:
1395 Q_UNREACHABLE();
1396 break;
1397 }
1398}
1399
1401 int stage,
1402 const QRhiBatchedBindings<id<MTLTexture>>::Batch &textureBatch)
1403{
1404 switch (stage) {
1405 case QMetalShaderResourceBindingsData::VERTEX:
1406 [cbD->d->currentRenderPassEncoder setVertexTextures: textureBatch.resources.constData()
1407 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1408 break;
1409 case QMetalShaderResourceBindingsData::FRAGMENT:
1410 [cbD->d->currentRenderPassEncoder setFragmentTextures: textureBatch.resources.constData()
1411 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1412 break;
1413 case QMetalShaderResourceBindingsData::COMPUTE:
1414 [cbD->d->currentComputePassEncoder setTextures: textureBatch.resources.constData()
1415 withRange: NSMakeRange(textureBatch.startBinding, NSUInteger(textureBatch.resources.count()))];
1416 break;
1419 // do nothing. These are used later for tessellation
1420 break;
1421 default:
1422 Q_UNREACHABLE();
1423 break;
1424 }
1425}
1426
1428 int encoderStage,
1429 const QRhiBatchedBindings<id<MTLSamplerState>>::Batch &samplerBatch)
1430{
1431 switch (encoderStage) {
1432 case QMetalShaderResourceBindingsData::VERTEX:
1433 [cbD->d->currentRenderPassEncoder setVertexSamplerStates: samplerBatch.resources.constData()
1434 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1435 break;
1436 case QMetalShaderResourceBindingsData::FRAGMENT:
1437 [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: samplerBatch.resources.constData()
1438 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1439 break;
1440 case QMetalShaderResourceBindingsData::COMPUTE:
1441 [cbD->d->currentComputePassEncoder setSamplerStates: samplerBatch.resources.constData()
1442 withRange: NSMakeRange(samplerBatch.startBinding, NSUInteger(samplerBatch.resources.count()))];
1443 break;
1446 // do nothing. These are used later for tessellation
1447 break;
1448 default:
1449 Q_UNREACHABLE();
1450 break;
1451 }
1452}
1453
1454// Helper that is not used during the common vertex+fragment and compute
1455// pipelines, but is necessary when tessellation is involved and so the
1456// graphics pipeline is under the hood a combination of multiple compute and
1457// render pipelines. We need to be able to set the buffers, textures, samplers
1458// when a switching between render and compute encoders.
1459static inline void rebindShaderResources(QMetalCommandBuffer *cbD, int resourceStage, int encoderStage,
1460 const QMetalShaderResourceBindingsData *customBindingState = nullptr)
1461{
1462 const QMetalShaderResourceBindingsData *bindingData = customBindingState ? customBindingState : &cbD->d->currentShaderResourceBindingState;
1463
1464 for (int i = 0, ie = bindingData->res[resourceStage].bufferBatches.batches.count(); i != ie; ++i) {
1465 const auto &bufferBatch(bindingData->res[resourceStage].bufferBatches.batches[i]);
1466 const auto &offsetBatch(bindingData->res[resourceStage].bufferOffsetBatches.batches[i]);
1467 bindStageBuffers(cbD, encoderStage, bufferBatch, offsetBatch);
1468 }
1469
1470 for (int i = 0, ie = bindingData->res[resourceStage].textureBatches.batches.count(); i != ie; ++i) {
1471 const auto &batch(bindingData->res[resourceStage].textureBatches.batches[i]);
1472 bindStageTextures(cbD, encoderStage, batch);
1473 }
1474
1475 for (int i = 0, ie = bindingData->res[resourceStage].samplerBatches.batches.count(); i != ie; ++i) {
1476 const auto &batch(bindingData->res[resourceStage].samplerBatches.batches[i]);
1477 bindStageSamplers(cbD, encoderStage, batch);
1478 }
1479
1480 if (bindingData->res[resourceStage].usesArgumentBuffer)
1481 declareStageArgumentBufferResources(cbD, encoderStage, bindingData->res[resourceStage]);
1482}
1483
1485{
1486 switch (stage) {
1487 case QMetalShaderResourceBindingsData::VERTEX:
1488 return QRhiShaderResourceBinding::StageFlag::VertexStage;
1489 case QMetalShaderResourceBindingsData::TESSCTRL:
1490 return QRhiShaderResourceBinding::StageFlag::TessellationControlStage;
1491 case QMetalShaderResourceBindingsData::TESSEVAL:
1492 return QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage;
1493 case QMetalShaderResourceBindingsData::FRAGMENT:
1494 return QRhiShaderResourceBinding::StageFlag::FragmentStage;
1495 case QMetalShaderResourceBindingsData::COMPUTE:
1496 return QRhiShaderResourceBinding::StageFlag::ComputeStage;
1497 }
1498
1499 Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::StageFlag::VertexStage);
1500}
1501
1504 int dynamicOffsetCount,
1505 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
1506 bool offsetOnlyChange,
1507 const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES],
1508 const QMetalShader *shaders[SUPPORTED_STAGES])
1509{
1511
1512 for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) {
1513 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding);
1514 switch (b->type) {
1515 case QRhiShaderResourceBinding::UniformBuffer:
1516 {
1517 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
1518 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1519 quint32 offset = b->u.ubuf.offset;
1520 for (int i = 0; i < dynamicOffsetCount; ++i) {
1521 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
1522 if (dynOfs.first == b->binding) {
1523 offset = dynOfs.second;
1524 break;
1525 }
1526 }
1527
1528 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1529 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1530 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1531 if (nativeBinding >= 0)
1532 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1533 }
1534 }
1535 }
1536 break;
1537 case QRhiShaderResourceBinding::SampledTexture:
1538 case QRhiShaderResourceBinding::Texture:
1539 case QRhiShaderResourceBinding::Sampler:
1540 {
1541 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->stex;
1542 for (int elem = 0; elem < data->count(); ++elem) {
1543 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->stex.texSamplers[elem].tex);
1544 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->stex.texSamplers[elem].sampler);
1545
1546 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1547 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1548 // Must handle all three cases (combined, separate, separate):
1549 // first = texture binding, second = sampler binding
1550 // first = texture binding
1551 // first = sampler binding (i.e. BindingType::Texture...)
1552 const int textureBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1553 const int samplerBinding = texD && samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Sampler)
1554 : (samplerD ? mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture) : -1);
1555 if (textureBinding >= 0 && texD)
1556 bindingData.res[stage].textures.append({ textureBinding + elem, texD->d->textureForSampling(), MTLResourceUsageRead });
1557 if (samplerBinding >= 0)
1558 bindingData.res[stage].samplers.append({ samplerBinding + elem, samplerD->d->samplerState });
1559 }
1560 }
1561 }
1562 }
1563 break;
1564 case QRhiShaderResourceBinding::ImageLoad:
1565 case QRhiShaderResourceBinding::ImageStore:
1566 case QRhiShaderResourceBinding::ImageLoadStore:
1567 {
1568 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
1569 id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
1570
1571 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1572 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1573 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Texture);
1574 if (nativeBinding >= 0)
1575 bindingData.res[stage].textures.append({ nativeBinding, t, storageImageUsage(b->type) });
1576 }
1577 }
1578 }
1579 break;
1580 case QRhiShaderResourceBinding::BufferLoad:
1581 case QRhiShaderResourceBinding::BufferStore:
1582 case QRhiShaderResourceBinding::BufferLoadStore:
1583 {
1584 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
1585 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
1586 quint32 offset = b->u.sbuf.offset;
1587 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1588 if (b->stage.testFlag(toRhiSrbStage(stage))) {
1589 const int nativeBinding = mapBinding(b->binding, stage, nativeResourceBindingMaps, BindingType::Buffer);
1590 if (nativeBinding >= 0)
1591 bindingData.res[stage].buffers.append({ nativeBinding, mtlbuf, offset });
1592 }
1593 }
1594 }
1595 break;
1596 default:
1597 Q_UNREACHABLE();
1598 break;
1599 }
1600 }
1601
1602 // With the argument buffer shader variant the texture and sampler values
1603 // collected above are ids within the argument buffer, so encode them into
1604 // one and bind that instead of binding them individually.
1605 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1606 const QMetalShader *shader = shaders[stage];
1607 if (!shader || !shader->argumentEncoder)
1608 continue;
1609 QMetalShaderResourceBindingsData::Stage &res(bindingData.res[stage]);
1610
1611 // offsetOnlyChange means same srb, same pipeline, and no resource
1612 // changed, so what is in the argument buffer cannot have changed
1613 // either. Carry the encoded one over instead of building it again.
1614 if (offsetOnlyChange) {
1616 cbD->d->currentShaderResourceBindingState.res[stage]);
1617 if (prev.usesArgumentBuffer) {
1618 for (const QMetalShaderResourceBindingsData::Stage::Buffer &b : prev.buffers) {
1619 if (b.nativeBinding == shader->argumentBufferIndex) {
1620 res.samplers.clear();
1621 res.buffers.append(b);
1622 res.usesArgumentBuffer = true;
1623 break;
1624 }
1625 }
1626 if (res.usesArgumentBuffer)
1627 continue;
1628 }
1629 }
1630
1631 // The argument buffer is in the constant address space, and it is bound
1632 // like any other buffer, so the offset must satisfy the same 256 byte
1633 // requirement on macOS that makes ubufAlignment() 256. The encoder's
1634 // own alignment is typically well below that.
1635 const quint32 argBufAlignment = qMax(quint32(shader->argumentEncoder.alignment),
1636 quint32(ubufAlignment()));
1637 quint32 argBufOffset = 0;
1638 id<MTLBuffer> argBuf = d->allocArgumentBuffer(quint32(shader->argumentEncoder.encodedLength),
1639 argBufAlignment, currentFrameSlot, &argBufOffset);
1640 if (!argBuf) {
1641 // Nothing gets bound at the argument buffer's index then, so the
1642 // shader will dereference garbage. Acceptable: failing to
1643 // sub-allocate from a small shared memory buffer means the process
1644 // is out of memory anyway.
1645 qWarning("Failed to allocate Metal argument buffer");
1646 res.textures.clear();
1647 res.samplers.clear();
1648 continue;
1649 }
1650 [shader->argumentEncoder setArgumentBuffer: argBuf offset: argBufOffset];
1651 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : std::as_const(res.textures))
1652 [shader->argumentEncoder setTexture: t.mtltex atIndex: NSUInteger(t.nativeBinding)];
1653 for (const QMetalShaderResourceBindingsData::Stage::Sampler &sm : std::as_const(res.samplers))
1654 [shader->argumentEncoder setSamplerState: sm.mtlsampler atIndex: NSUInteger(sm.nativeBinding)];
1655 res.samplers.clear();
1656 res.buffers.append({ shader->argumentBufferIndex, argBuf, argBufOffset });
1657 res.usesArgumentBuffer = true;
1658 }
1659
1660 for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) {
1663 continue;
1665 continue;
1666
1667 // QRhiBatchedBindings works with the native bindings and expects
1668 // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
1669 // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
1670 // have to sort here every time.
1671
1672 std::sort(bindingData.res[stage].buffers.begin(), bindingData.res[stage].buffers.end(), [](const QMetalShaderResourceBindingsData::Stage::Buffer &a, const QMetalShaderResourceBindingsData::Stage::Buffer &b) {
1673 return a.nativeBinding < b.nativeBinding;
1674 });
1675
1676 for (const QMetalShaderResourceBindingsData::Stage::Buffer &buf : std::as_const(bindingData.res[stage].buffers)) {
1677 bindingData.res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf);
1678 bindingData.res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset);
1679 }
1680
1681 bindingData.res[stage].bufferBatches.finish();
1682 bindingData.res[stage].bufferOffsetBatches.finish();
1683
1684 for (int i = 0, ie = bindingData.res[stage].bufferBatches.batches.count(); i != ie; ++i) {
1685 const auto &bufferBatch(bindingData.res[stage].bufferBatches.batches[i]);
1686 const auto &offsetBatch(bindingData.res[stage].bufferOffsetBatches.batches[i]);
1687 // skip setting Buffer binding if the current state is already correct
1688 if (cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches.count() > i
1689 && cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches.count() > i
1690 && bufferBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferBatches.batches[i]
1691 && offsetBatch == cbD->d->currentShaderResourceBindingState.res[stage].bufferOffsetBatches.batches[i])
1692 {
1693 continue;
1694 }
1695 bindStageBuffers(cbD, stage, bufferBatch, offsetBatch);
1696 }
1697
1698 if (offsetOnlyChange)
1699 continue;
1700
1701 if (bindingData.res[stage].usesArgumentBuffer) {
1702 declareStageArgumentBufferResources(cbD, stage, bindingData.res[stage]);
1703 continue;
1704 }
1705
1706 std::sort(bindingData.res[stage].textures.begin(), bindingData.res[stage].textures.end(), [](const QMetalShaderResourceBindingsData::Stage::Texture &a, const QMetalShaderResourceBindingsData::Stage::Texture &b) {
1707 return a.nativeBinding < b.nativeBinding;
1708 });
1709
1710 std::sort(bindingData.res[stage].samplers.begin(), bindingData.res[stage].samplers.end(), [](const QMetalShaderResourceBindingsData::Stage::Sampler &a, const QMetalShaderResourceBindingsData::Stage::Sampler &b) {
1711 return a.nativeBinding < b.nativeBinding;
1712 });
1713
1714 for (const QMetalShaderResourceBindingsData::Stage::Texture &t : std::as_const(bindingData.res[stage].textures))
1715 bindingData.res[stage].textureBatches.feed(t.nativeBinding, t.mtltex);
1716
1717 for (const QMetalShaderResourceBindingsData::Stage::Sampler &s : std::as_const(bindingData.res[stage].samplers))
1718 bindingData.res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler);
1719
1720 bindingData.res[stage].textureBatches.finish();
1721 bindingData.res[stage].samplerBatches.finish();
1722
1723 for (int i = 0, ie = bindingData.res[stage].textureBatches.batches.count(); i != ie; ++i) {
1724 const auto &batch(bindingData.res[stage].textureBatches.batches[i]);
1725 // skip setting Texture binding if the current state is already correct
1726 if (cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches.count() > i
1727 && batch == cbD->d->currentShaderResourceBindingState.res[stage].textureBatches.batches[i])
1728 {
1729 continue;
1730 }
1731 bindStageTextures(cbD, stage, batch);
1732 }
1733
1734 for (int i = 0, ie = bindingData.res[stage].samplerBatches.batches.count(); i != ie; ++i) {
1735 const auto &batch(bindingData.res[stage].samplerBatches.batches[i]);
1736 // skip setting Sampler State if the current state is already correct
1737 if (cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches.count() > i
1738 && batch == cbD->d->currentShaderResourceBindingState.res[stage].samplerBatches.batches[i])
1739 {
1740 continue;
1741 }
1742 bindStageSamplers(cbD, stage, batch);
1743 }
1744 }
1745
1746 cbD->d->currentShaderResourceBindingState = bindingData;
1747}
1748
1750{
1751 const QList<QShaderDescription::PushConstantBlock> blocks = s.desc.pushConstantBlocks();
1752 return blocks.isEmpty() ? 0 : quint32(blocks.first().size);
1753}
1754
1755static inline int mtlPushConstantBufferIndex(const QMetalShader &s)
1756{
1757 return s.nativeShaderInfo.extraBufferBindings.value(QShaderPrivate::MslPushConstantBufferBinding, -1);
1758}
1759
1761{
1762 QRHI_RES_RHI(QRhiMetal);
1763
1764 // Also update the tracked state, so that the callers that reactivate a
1765 // pipeline on a new encoder after interrupting the pass do not have to.
1766 cbD->currentGraphicsPipeline = this;
1767 cbD->currentComputePipeline = nullptr;
1769
1770 [cbD->d->currentRenderPassEncoder setRenderPipelineState: d->ps];
1771
1772 if (cbD->d->currentDepthStencilState != d->ds) {
1773 [cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
1774 cbD->d->currentDepthStencilState = d->ds;
1775 }
1776 if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
1777 [cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
1778 cbD->currentCullMode = int(d->cullMode);
1779 }
1780 if (cbD->currentTriangleFillMode == -1 || d->triangleFillMode != uint(cbD->currentTriangleFillMode)) {
1781 [cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
1782 cbD->currentTriangleFillMode = int(d->triangleFillMode);
1783 }
1784 if (rhiD->caps.depthClamp) {
1785 if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) {
1786 [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode];
1787 cbD->currentDepthClipMode = int(d->depthClipMode);
1788 }
1789 }
1790 if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
1791 [cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
1792 cbD->currentFrontFaceWinding = int(d->winding);
1793 }
1794 if (!qFuzzyCompare(d->depthBias, cbD->currentDepthBiasValues.first)
1795 || !qFuzzyCompare(d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second))
1796 {
1797 [cbD->d->currentRenderPassEncoder setDepthBias: d->depthBias
1798 slopeScale: d->slopeScaledDepthBias
1799 clamp: 0.0f];
1800 cbD->currentDepthBiasValues = { d->depthBias, d->slopeScaledDepthBias };
1801 }
1802
1803 if (cbD->pushConstantsNeedRebind && !d->tess.enabled) {
1804 const int vsIdx = mtlPushConstantBufferIndex(d->vs);
1805 const int fsIdx = mtlPushConstantBufferIndex(d->fs);
1806 if (vsIdx >= 0 || fsIdx >= 0) {
1807 const quint32 blockSize = qMax(mtlPushConstantBlockSize(d->vs), mtlPushConstantBlockSize(d->fs));
1808 if (quint32(cbD->pushConstantData.size()) < blockSize)
1809 cbD->pushConstantData.resize(int(blockSize), 0);
1810 const NSUInteger total = NSUInteger(cbD->pushConstantData.size());
1811 if (vsIdx >= 0)
1812 [cbD->d->currentRenderPassEncoder setVertexBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(vsIdx)];
1813 if (fsIdx >= 0)
1814 [cbD->d->currentRenderPassEncoder setFragmentBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(fsIdx)];
1815 cbD->pushConstantsNeedRebind = false;
1816 }
1817 }
1818}
1819
1820void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
1821{
1822 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1825
1827 return;
1828
1830 cbD->currentComputePipeline = nullptr;
1832
1833 if (cbD->hasCustomScissorSet && !psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
1835
1836 if (!psD->d->tess.enabled && !psD->d->tess.failed)
1838
1839 // mark work buffers that can now be safely reused as reusable
1840 // NOTE: These are usually empty unless tessellation or mutiview is used.
1841 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.deviceLocalWorkBuffers) {
1842 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1843 workBuf->lastActiveFrameSlot = -1;
1844 }
1845 for (QMetalBuffer *workBuf : psD->d->extraBufMgr.hostVisibleWorkBuffers) {
1846 if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
1847 workBuf->lastActiveFrameSlot = -1;
1848 }
1849
1850 psD->lastActiveFrameSlot = currentFrameSlot;
1851}
1852
1853void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
1854 int dynamicOffsetCount,
1855 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
1856{
1857 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
1861
1862 if (!srb) {
1863 if (gfxPsD)
1864 srb = gfxPsD->m_shaderResourceBindings;
1865 else
1866 srb = compPsD->m_shaderResourceBindings;
1867 }
1868
1870 bool hasSlottedResourceInSrb = false;
1871 bool hasDynamicOffsetInSrb = false;
1872 bool resNeedsRebind = false;
1873
1874 bool pipelineChanged = false;
1875 if (gfxPsD) {
1876 pipelineChanged = srbD->lastUsedGraphicsPipeline != gfxPsD;
1877 srbD->lastUsedGraphicsPipeline = gfxPsD;
1878 } else {
1879 pipelineChanged = srbD->lastUsedComputePipeline != compPsD;
1880 srbD->lastUsedComputePipeline = compPsD;
1881 }
1882
1883 // SPIRV-Cross buffer size buffers
1884 // Need to determine storage buffer sizes here as this is the last opportunity for storage
1885 // buffer bindings (offset, size) to be specified before draw / dispatch call
1886 const bool needsBufferSizeBuffer = (compPsD && compPsD->d->bufferSizeBuffer) || (gfxPsD && gfxPsD->d->bufferSizeBuffer);
1887 QMap<QRhiShaderResourceBinding::StageFlag, QMap<int, quint32>> storageBufferSizes;
1888
1889 // do buffer writes, figure out if we need to rebind, and mark as in-use
1890 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
1891 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
1892 QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
1893 switch (b->type) {
1894 case QRhiShaderResourceBinding::UniformBuffer:
1895 {
1896 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
1897 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
1898 sanityCheckResourceOwnership(bufD);
1900 if (bufD->d->slotted)
1901 hasSlottedResourceInSrb = true;
1902 if (b->u.ubuf.hasDynamicOffset)
1903 hasDynamicOffsetInSrb = true;
1904 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
1905 resNeedsRebind = true;
1906 bd.ubuf.id = bufD->m_id;
1907 bd.ubuf.generation = bufD->generation;
1908 }
1909 bufD->lastActiveFrameSlot = currentFrameSlot;
1910 }
1911 break;
1912 case QRhiShaderResourceBinding::SampledTexture:
1913 case QRhiShaderResourceBinding::Texture:
1914 case QRhiShaderResourceBinding::Sampler:
1915 {
1916 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->stex;
1917 if (bd.stex.d.size() != data->count()) {
1918 bd.stex.d.resize(data->count());
1919 resNeedsRebind = true;
1920 }
1921 for (int elem = 0; elem < data->count(); ++elem) {
1922 QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
1923 QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
1924 Q_ASSERT(texD || samplerD);
1925 sanityCheckResourceOwnership(texD);
1926 sanityCheckResourceOwnership(samplerD);
1927 const quint64 texId = texD ? texD->m_id : 0;
1928 const uint texGen = texD ? texD->generation : 0;
1929 const quint64 samplerId = samplerD ? samplerD->m_id : 0;
1930 const uint samplerGen = samplerD ? samplerD->generation : 0;
1931 if (texGen != bd.stex.d[elem].texGeneration
1932 || texId != bd.stex.d[elem].texId
1933 || samplerGen != bd.stex.d[elem].samplerGeneration
1934 || samplerId != bd.stex.d[elem].samplerId)
1935 {
1936 resNeedsRebind = true;
1937 bd.stex.d[elem].texId = texId;
1938 bd.stex.d[elem].texGeneration = texGen;
1939 bd.stex.d[elem].samplerId = samplerId;
1940 bd.stex.d[elem].samplerGeneration = samplerGen;
1941 }
1942 if (texD)
1943 texD->lastActiveFrameSlot = currentFrameSlot;
1944 if (samplerD)
1945 samplerD->lastActiveFrameSlot = currentFrameSlot;
1946 }
1947 }
1948 break;
1949 case QRhiShaderResourceBinding::ImageLoad:
1950 case QRhiShaderResourceBinding::ImageStore:
1951 case QRhiShaderResourceBinding::ImageLoadStore:
1952 {
1953 QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
1954 sanityCheckResourceOwnership(texD);
1955 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
1956 resNeedsRebind = true;
1957 bd.simage.id = texD->m_id;
1958 bd.simage.generation = texD->generation;
1959 }
1960 texD->lastActiveFrameSlot = currentFrameSlot;
1961 }
1962 break;
1963 case QRhiShaderResourceBinding::BufferLoad:
1964 case QRhiShaderResourceBinding::BufferStore:
1965 case QRhiShaderResourceBinding::BufferLoadStore:
1966 {
1967 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
1968 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
1969 sanityCheckResourceOwnership(bufD);
1970
1971 if (needsBufferSizeBuffer) {
1972 for (int i = 0; i < 6; ++i) {
1973 const QRhiShaderResourceBinding::StageFlag stage =
1974 QRhiShaderResourceBinding::StageFlag(1 << i);
1975 if (b->stage.testFlag(stage)) {
1976 storageBufferSizes[stage][b->binding] = b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->size();
1977 }
1978 }
1979 }
1980
1982 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
1983 resNeedsRebind = true;
1984 bd.sbuf.id = bufD->m_id;
1985 bd.sbuf.generation = bufD->generation;
1986 }
1987 bufD->lastActiveFrameSlot = currentFrameSlot;
1988 }
1989 break;
1990 default:
1991 Q_UNREACHABLE();
1992 break;
1993 }
1994 }
1995
1996 if (needsBufferSizeBuffer) {
1997 QMetalBuffer *bufD = nullptr;
1998 QVarLengthArray<std::pair<QMetalShader *, QRhiShaderResourceBinding::StageFlag>, 4> shaders;
1999
2000 if (compPsD) {
2001 bufD = compPsD->d->bufferSizeBuffer;
2002 Q_ASSERT(compPsD->d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2003 shaders.append({&compPsD->d->cs, QRhiShaderResourceBinding::StageFlag::ComputeStage});
2004 } else {
2005 bufD = gfxPsD->d->bufferSizeBuffer;
2006 if (gfxPsD->d->tess.enabled) {
2007
2008 // Assumptions
2009 // * We only use one of the compute vertex shader variants in a pipeline at any one time
2010 // * The vertex shader variants all have the same storage block bindings
2011 // * The vertex shader variants all have the same native resource binding map
2012 // * The vertex shader variants all have the same MslBufferSizeBufferBinding requirement
2013 // * The vertex shader variants all have the same MslBufferSizeBufferBinding binding
2014 // => We only need to use one vertex shader variant to generate the identical shader
2015 // resource bindings
2016 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[1].desc.storageBlocks());
2017 Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[2].desc.storageBlocks());
2018 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
2019 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
2020 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
2021 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2022 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)
2023 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding));
2024 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
2025 == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
2026 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]
2027 == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]);
2028
2029 if (gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2030 shaders.append({&gfxPsD->d->tess.compVs[0], QRhiShaderResourceBinding::StageFlag::VertexStage});
2031
2032 if (gfxPsD->d->tess.compTesc.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2033 shaders.append({&gfxPsD->d->tess.compTesc, QRhiShaderResourceBinding::StageFlag::TessellationControlStage});
2034
2035 if (gfxPsD->d->tess.vertTese.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2036 shaders.append({&gfxPsD->d->tess.vertTese, QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage});
2037
2038 } else {
2039 if (gfxPsD->d->vs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2040 shaders.append({&gfxPsD->d->vs, QRhiShaderResourceBinding::StageFlag::VertexStage});
2041 }
2042 if (gfxPsD->d->fs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding))
2043 shaders.append({&gfxPsD->d->fs, QRhiShaderResourceBinding::StageFlag::FragmentStage});
2044 }
2045
2046 quint32 offset = 0;
2047 for (const auto &shader : shaders) {
2048
2049 const int binding = shader.first->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
2050
2051 // if we don't have a srb entry for the buffer size buffer
2052 if (!(storageBufferSizes.contains(shader.second) && storageBufferSizes[shader.second].contains(binding))) {
2053
2054 int maxNativeBinding = 0;
2055 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks())
2056 maxNativeBinding = qMax(maxNativeBinding, shader.first->nativeResourceBindingMap[block.binding].first);
2057
2058 const int size = (maxNativeBinding + 1) * sizeof(int);
2059
2060 Q_ASSERT(offset + size <= bufD->size());
2061 srbD->sortedBindings.append(QRhiShaderResourceBinding::bufferLoad(binding, shader.second, bufD, offset, size));
2062
2063 QMetalShaderResourceBindings::BoundResourceData bd;
2064 bd.sbuf.id = bufD->m_id;
2065 bd.sbuf.generation = bufD->generation;
2066 srbD->boundResourceData.append(bd);
2067 }
2068
2069 // create the buffer size buffer data
2070 QVarLengthArray<int, 8> bufferSizeBufferData;
2071 Q_ASSERT(storageBufferSizes.contains(shader.second));
2072 const QMap<int, quint32> &sizes(storageBufferSizes[shader.second]);
2073 for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks()) {
2074 const int index = shader.first->nativeResourceBindingMap[block.binding].first;
2075
2076 // if the native binding is -1, the buffer is present but not accessed in the shader
2077 if (index < 0)
2078 continue;
2079
2080 if (bufferSizeBufferData.size() <= index)
2081 bufferSizeBufferData.resize(index + 1);
2082
2083 Q_ASSERT(sizes.contains(block.binding));
2084 bufferSizeBufferData[index] = sizes[block.binding];
2085 }
2086
2087 QRhiBufferData data;
2088 const quint32 size = bufferSizeBufferData.size() * sizeof(int);
2089 data.assign(reinterpret_cast<const char *>(bufferSizeBufferData.constData()), size);
2090 Q_ASSERT(offset + size <= bufD->size());
2091 bufD->d->pendingUpdates[bufD->d->slotted ? currentFrameSlot : 0].append({ offset, data });
2092
2093 // buffer offsets must be 32byte aligned
2094 offset += ((size + 31) / 32) * 32;
2095 }
2096
2098 bufD->lastActiveFrameSlot = currentFrameSlot;
2099 }
2100
2101 // make sure the resources for the correct slot get bound
2102 const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
2103 if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
2104 resNeedsRebind = true;
2105
2106 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srbD) : (cbD->currentComputeSrb != srbD);
2107 const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
2108
2109 // dynamic uniform buffer offsets always trigger a rebind
2110 if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt || pipelineChanged) {
2111 const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr };
2112 const QMetalShader *shaders[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr, nullptr, nullptr };
2113 if (gfxPsD) {
2114 cbD->currentGraphicsSrb = srbD;
2115 cbD->currentComputeSrb = nullptr;
2116 if (gfxPsD->d->tess.enabled) {
2117 // If tessellating, we don't know which compVs shader to use until the draw call is
2118 // made. They should all have the same native resource binding map, so pick one.
2119 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap);
2120 Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap);
2121 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->tess.compVs[0].nativeResourceBindingMap;
2122 resBindMaps[QMetalShaderResourceBindingsData::TESSCTRL] = &gfxPsD->d->tess.compTesc.nativeResourceBindingMap;
2123 resBindMaps[QMetalShaderResourceBindingsData::TESSEVAL] = &gfxPsD->d->tess.vertTese.nativeResourceBindingMap;
2124 } else {
2125 resBindMaps[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs.nativeResourceBindingMap;
2126 shaders[QMetalShaderResourceBindingsData::VERTEX] = &gfxPsD->d->vs;
2127 }
2128 resBindMaps[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs.nativeResourceBindingMap;
2129 shaders[QMetalShaderResourceBindingsData::FRAGMENT] = &gfxPsD->d->fs;
2130 } else {
2131 cbD->currentGraphicsSrb = nullptr;
2132 cbD->currentComputeSrb = srbD;
2133 resBindMaps[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs.nativeResourceBindingMap;
2134 shaders[QMetalShaderResourceBindingsData::COMPUTE] = &compPsD->d->cs;
2135 }
2137 cbD->currentResSlot = resSlot;
2138
2139 const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind
2140 && !srbChanged && !srbRebuilt && !pipelineChanged;
2141 enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange,
2142 resBindMaps, shaders);
2143 }
2144}
2145
2146void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
2147 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
2148 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
2149{
2150 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2152
2153 QRhiBatchedBindings<id<MTLBuffer> > buffers;
2154 QRhiBatchedBindings<NSUInteger> offsets;
2155 for (int i = 0; i < bindingCount; ++i) {
2156 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
2158 bufD->lastActiveFrameSlot = currentFrameSlot;
2159 id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
2160 buffers.feed(startBinding + i, mtlbuf);
2161 offsets.feed(startBinding + i, bindings[i].second);
2162 }
2163 buffers.finish();
2164 offsets.finish();
2165
2166 // same binding space for vertex and constant buffers - work it around
2168 // There's nothing guaranteeing setShaderResources() was called before
2169 // setVertexInput()... but whatever srb will get bound will have to be
2170 // layout-compatible anyways so maxBinding is the same.
2171 if (!srbD)
2172 srbD = QRHI_RES(QMetalShaderResourceBindings, cbD->currentGraphicsPipeline->shaderResourceBindings());
2173 const int firstVertexBinding = srbD->maxBinding + 1;
2174
2175 if (firstVertexBinding != cbD->d->currentFirstVertexBinding
2176 || buffers != cbD->d->currentVertexInputsBuffers
2177 || offsets != cbD->d->currentVertexInputOffsets)
2178 {
2179 cbD->d->currentFirstVertexBinding = firstVertexBinding;
2180 cbD->d->currentVertexInputsBuffers = buffers;
2181 cbD->d->currentVertexInputOffsets = offsets;
2182
2183 for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
2184 const auto &bufferBatch(buffers.batches[i]);
2185 const auto &offsetBatch(offsets.batches[i]);
2186 [cbD->d->currentRenderPassEncoder setVertexBuffers:
2187 bufferBatch.resources.constData()
2188 offsets: offsetBatch.resources.constData()
2189 withRange: NSMakeRange(uint(firstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
2190 }
2191 }
2192
2193 if (indexBuf) {
2194 QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
2196 ibufD->lastActiveFrameSlot = currentFrameSlot;
2197 cbD->currentIndexBuffer = ibufD;
2198 cbD->currentIndexOffset = indexOffset;
2199 cbD->currentIndexFormat = indexFormat;
2200 } else {
2201 cbD->currentIndexBuffer = nullptr;
2202 }
2203}
2204
2205// The size of the coordinate space viewports and scissors are specified in.
2206// Normally this is the pixel size of the render target. When a rasterization
2207// rate map is attached, Metal interprets viewports and scissors in the logical
2208// (screen space) coordinate system of the map, which is typically larger than
2209// the physical texture size. This is what foveated rendering on visionOS uses.
2210QSize QRhiMetal::outputSizeForTarget(QRhiRenderTarget *target)
2211{
2212 QRhiShadingRateMap *srm = nullptr;
2213 switch (target->resourceType()) {
2214 case QRhiResource::TextureRenderTarget:
2215 srm = QRHI_RES(QMetalTextureRenderTarget, target)->m_desc.shadingRateMap();
2216 break;
2217 case QRhiResource::SwapChainRenderTarget:
2218 srm = QRHI_RES(QMetalSwapChainRenderTarget, target)->swapChain()->shadingRateMap();
2219 break;
2220 default:
2221 break;
2222 }
2223
2224 if (srm) {
2225 const QSize logicalSize = srm->logicalSize();
2226 if (logicalSize.isValid())
2227 return logicalSize;
2228 }
2229
2230 return target->pixelSize();
2231}
2232
2234{
2235 cbD->hasCustomScissorSet = false;
2236
2237 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2238 std::array<float, 4> vp = cbD->currentViewport.viewport();
2239 float x = 0, y = 0, w = 0, h = 0;
2240
2241 if (qFuzzyIsNull(vp[2]) && qFuzzyIsNull(vp[3])) {
2242 x = 0;
2243 y = 0;
2244 w = outputSize.width();
2245 h = outputSize.height();
2246 } else {
2247 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
2248 qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, vp, &x, &y, &w, &h);
2249 }
2250
2251 MTLScissorRect s;
2252 s.x = NSUInteger(x);
2253 s.y = NSUInteger(y);
2254 s.width = NSUInteger(w);
2255 s.height = NSUInteger(h);
2256 [cbD->d->currentRenderPassEncoder setScissorRect: s];
2257}
2258
2259void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
2260{
2261 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2263 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2264
2265 // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
2266 float x, y, w, h;
2267 if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
2268 return;
2269
2270 MTLViewport vp;
2271 vp.originX = double(x);
2272 vp.originY = double(y);
2273 vp.width = double(w);
2274 vp.height = double(h);
2275 vp.znear = double(viewport.minDepth());
2276 vp.zfar = double(viewport.maxDepth());
2277
2278 [cbD->d->currentRenderPassEncoder setViewport: vp];
2279
2280 cbD->currentViewport = viewport;
2282 && !cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
2283 {
2285 }
2286}
2287
2288void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
2289{
2290 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2292 Q_ASSERT(!cbD->currentGraphicsPipeline
2293 || cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
2294 const QSize outputSize = outputSizeForTarget(cbD->currentTarget);
2295
2296 // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
2297 int x, y, w, h;
2298 if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
2299 return;
2300
2301 MTLScissorRect s;
2302 s.x = NSUInteger(x);
2303 s.y = NSUInteger(y);
2304 s.width = NSUInteger(w);
2305 s.height = NSUInteger(h);
2306
2307 [cbD->d->currentRenderPassEncoder setScissorRect: s];
2308
2309 cbD->hasCustomScissorSet = true;
2310 cbD->currentScissor = scissor;
2311}
2312
2313void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
2314{
2315 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2317
2318 [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF()
2319 green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
2320
2321 cbD->hasBlendConstantsSet = true;
2322 cbD->currentBlendConstants = c;
2323}
2324
2325void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
2326{
2327 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2329
2330 [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
2331
2332 cbD->hasStencilRefSet = true;
2333 cbD->currentStencilRef = refValue;
2334}
2335
2336void QRhiMetal::setPushConstants(QRhiCommandBuffer *cb, quint32 offset, quint32 size, const void *data)
2337{
2338 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2340
2341 // setVertexBytes() and friends take the whole block every time, so a shadow
2342 // copy is needed to honour an update of part of it.
2343 auto patch = [cbD, offset, size, data](quint32 blockSize) {
2344 const quint32 total = qMax(blockSize, offset + size);
2345 if (quint32(cbD->pushConstantData.size()) < total)
2346 cbD->pushConstantData.resize(int(total), 0);
2347 memcpy(cbD->pushConstantData.data() + offset, data, size);
2348 return total;
2349 };
2350
2353 if (!psD)
2354 return;
2355 const int idx = mtlPushConstantBufferIndex(psD->d->cs);
2356 if (idx < 0) {
2357 qWarning("No pipeline with a push constant block is active; setPushConstants ignored");
2358 return;
2359 }
2360 const quint32 total = patch(mtlPushConstantBlockSize(psD->d->cs));
2361 [cbD->d->currentComputePassEncoder setBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(idx)];
2362 } else {
2364 if (!psD)
2365 return;
2366 if (psD->d->tess.enabled) {
2367 qWarning("Push constants are not supported with tessellation on Metal");
2368 return;
2369 }
2370 const int vsIdx = mtlPushConstantBufferIndex(psD->d->vs);
2371 const int fsIdx = mtlPushConstantBufferIndex(psD->d->fs);
2372 if (vsIdx < 0 && fsIdx < 0) {
2373 qWarning("No pipeline with a push constant block is active; setPushConstants ignored");
2374 return;
2375 }
2376 const quint32 total = patch(qMax(mtlPushConstantBlockSize(psD->d->vs), mtlPushConstantBlockSize(psD->d->fs)));
2377 if (vsIdx >= 0)
2378 [cbD->d->currentRenderPassEncoder setVertexBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(vsIdx)];
2379 if (fsIdx >= 0)
2380 [cbD->d->currentRenderPassEncoder setFragmentBytes: cbD->pushConstantData.constData() length: total atIndex: NSUInteger(fsIdx)];
2381 }
2382}
2383
2384void QRhiMetal::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize)
2385{
2386 Q_UNUSED(cb);
2387 Q_UNUSED(coarsePixelSize);
2388}
2389
2391{
2392 switch (cbD->currentTarget->resourceType()) {
2393 case QRhiResource::SwapChainRenderTarget:
2394 return QRHI_RES(QMetalSwapChainRenderTarget, cbD->currentTarget)->d;
2395 case QRhiResource::TextureRenderTarget:
2396 return QRHI_RES(QMetalTextureRenderTarget, cbD->currentTarget)->d;
2397 default:
2398 return nullptr;
2399 }
2400}
2401
2402// Memoryless attachments only exist for the duration of a single encoder, there
2403// is nowhere to store them to.
2404static inline bool canStoreAttachment(id<MTLTexture> tex)
2405{
2406 return tex && tex.storageMode != MTLStorageModeMemoryless;
2407}
2408
2409// When the pass continues on a new encoder afterwards, the contents have to be
2410// stored, not just resolved or discarded. An attachment with a resolve texture
2411// can only use the resolving store actions.
2412static inline MTLStoreAction interruptionStoreAction(MTLStoreAction finalAction, bool passIsEnding)
2413{
2414 if (passIsEnding)
2415 return finalAction;
2416 return finalAction == MTLStoreActionDontCare ? MTLStoreActionStore
2417 : MTLStoreActionStoreAndMultisampleResolve;
2418}
2419
2420// Store actions are deferred (MTLStoreActionUnknown) for attachments whose
2421// contents may need to outlive the encoder, so they have to be finalized before
2422// the encoder ends.
2423// Store actions cannot be mutated otherwise, and for a resolve attachment the
2424// only legal choices are the two resolving ones.
2426{
2427 for (const auto &[index, finalAction] : std::as_const(cbD->d->deferredColorStoreActions))
2428 [cbD->d->currentRenderPassEncoder setColorStoreAction:
2429 interruptionStoreAction(finalAction, passIsEnding) atIndex: index];
2430
2431 if (cbD->d->deferredDepthStoreAction != MTLStoreActionUnknown) {
2432 [cbD->d->currentRenderPassEncoder setDepthStoreAction:
2433 interruptionStoreAction(cbD->d->deferredDepthStoreAction, passIsEnding)];
2434 }
2435 if (cbD->d->deferredStencilStoreAction != MTLStoreActionUnknown) {
2436 [cbD->d->currentRenderPassEncoder setStencilStoreAction:
2437 interruptionStoreAction(cbD->d->deferredStencilStoreAction, passIsEnding)];
2438 }
2439}
2440
2441// Ends the render command encoder such that the pass can be continued on a new
2442// encoder afterwards.
2444{
2446 for (qsizetype i = 0; i < cbD->d->openDebugGroups.size(); ++i)
2447 [cbD->d->currentRenderPassEncoder popDebugGroup];
2448 [cbD->d->currentRenderPassEncoder endEncoding];
2449 cbD->d->currentRenderPassEncoder = nil;
2450}
2451
2454 id<MTLComputeCommandEncoder> maybeComputeEncoder)
2455{
2456 if (cbD->d->currentRenderPassEncoder)
2458
2459 if (!maybeComputeEncoder)
2460 maybeComputeEncoder = [cbD->d->cb computeCommandEncoder];
2461
2462 return maybeComputeEncoder;
2463}
2464
2466 id<MTLComputeCommandEncoder> computeEncoder)
2467{
2468 if (computeEncoder) {
2469 [computeEncoder endEncoding];
2470 computeEncoder = nil;
2471 }
2472
2474 Q_ASSERT(rtD);
2475
2476 // A memoryless attachment cannot be loaded, its contents are lost (see
2477 // NoTransientBacking), even when the store action is not DontCare, which is
2478 // the case with a resolve.
2479 const auto canLoad = [](MTLRenderPassAttachmentDescriptor *att) {
2480 return att.storeAction != MTLStoreActionDontCare && canStoreAttachment(att.texture);
2481 };
2482
2483 QVarLengthArray<MTLLoadAction, 4> oldColorLoad;
2484 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2485 oldColorLoad.append(cbD->d->currentPassRpDesc.colorAttachments[i].loadAction);
2486 if (canLoad(cbD->d->currentPassRpDesc.colorAttachments[i]))
2487 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
2488 }
2489
2490 MTLLoadAction oldDepthLoad;
2491 MTLLoadAction oldStencilLoad;
2492 if (rtD->dsAttCount) {
2493 oldDepthLoad = cbD->d->currentPassRpDesc.depthAttachment.loadAction;
2494 if (canLoad(cbD->d->currentPassRpDesc.depthAttachment))
2495 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
2496
2497 oldStencilLoad = cbD->d->currentPassRpDesc.stencilAttachment.loadAction;
2498 if (canLoad(cbD->d->currentPassRpDesc.stencilAttachment))
2499 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
2500 }
2501
2502 // The state below is not tied to the pipeline, so it is not restored by the
2503 // callers when they reactivate it on the new encoder. Preserve it here,
2504 // otherwise the pass would silently continue with a full-target viewport,
2505 // no scissor, and default blend constants and stencil reference.
2506 const QRhiViewport prevViewport = cbD->currentViewport;
2507 const bool prevHasScissor = cbD->hasCustomScissorSet;
2508 const QRhiScissor prevScissor = cbD->currentScissor;
2509 const bool prevHasBlendConstants = cbD->hasBlendConstantsSet;
2510 const QColor prevBlendConstants = cbD->currentBlendConstants;
2511 const bool prevHasStencilRef = cbD->hasStencilRefSet;
2512 const quint32 prevStencilRef = cbD->currentStencilRef;
2513 // Whether the pipeline was relying on the viewport-derived default scissor,
2514 // which setViewport() below cannot reapply on its own: it only does so when
2515 // a pipeline is already current, and there is none at that point.
2516 const bool prevHasDefaultScissor = cbD->currentGraphicsPipeline
2517 && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor);
2518 // Rebound when the callers reactivate the pipeline, or when the next
2519 // pipeline with a push constant block is set.
2520 const QVarLengthArray<char, 128> prevPushConstantData = cbD->pushConstantData;
2521
2522 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
2524
2525 for (const QByteArray &name : std::as_const(cbD->d->openDebugGroups))
2526 [cbD->d->currentRenderPassEncoder pushDebugGroup: [NSString stringWithUTF8String: name.constData()]];
2527
2528 cbD->pushConstantData = prevPushConstantData;
2529 cbD->pushConstantsNeedRebind = !prevPushConstantData.isEmpty();
2530
2531 // Must come before the callers reactivate the pipeline: setScissor()
2532 // expects no pipeline to be current yet, and setDefaultScissor() consults
2533 // the viewport.
2534 if (!qFuzzyIsNull(prevViewport.viewport()[2]) || !qFuzzyIsNull(prevViewport.viewport()[3]))
2535 rhiD->setViewport(cbD, prevViewport);
2536 if (prevHasScissor)
2537 rhiD->setScissor(cbD, prevScissor);
2538 else if (prevHasDefaultScissor)
2539 rhiD->setDefaultScissor(cbD);
2540 if (prevHasBlendConstants)
2541 rhiD->setBlendConstants(cbD, prevBlendConstants);
2542 if (prevHasStencilRef)
2543 rhiD->setStencilRef(cbD, prevStencilRef);
2544
2545 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
2546 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = oldColorLoad[i];
2547 }
2548
2549 if (rtD->dsAttCount) {
2550 cbD->d->currentPassRpDesc.depthAttachment.loadAction = oldDepthLoad;
2551 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = oldStencilLoad;
2552 }
2553
2554}
2555
2557{
2558 QMetalCommandBuffer *cbD = args.cbD;
2560 if (graphicsPipeline->d->tess.failed)
2561 return;
2562
2563 const bool indexed = args.type != TessDrawArgs::NonIndexed;
2564 const quint32 instanceCount = indexed ? args.drawIndexed.instanceCount : args.draw.instanceCount;
2565 const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount;
2566
2567 QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess);
2568 QMetalGraphicsPipelineData::ExtraBufferManager &extraBufMgr(graphicsPipeline->d->extraBufMgr);
2569 const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount);
2570 QMetalBuffer *vertOutBuf = nullptr;
2571 QMetalBuffer *tescOutBuf = nullptr;
2572 QMetalBuffer *tescPatchOutBuf = nullptr;
2573 QMetalBuffer *tescFactorBuf = nullptr;
2574 QMetalBuffer *tescParamsBuf = nullptr;
2575 id<MTLComputeCommandEncoder> vertTescComputeEncoder
2576 = tempComputeEncoder(this, cbD, cbD->d->tessellationComputeEncoder);
2577 cbD->d->tessellationComputeEncoder = vertTescComputeEncoder;
2578
2579 // Step 1: vertex shader (as compute)
2580 {
2581 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2582 QShader::Variant shaderVariant = QShader::NonIndexedVertexAsComputeShader;
2583 if (args.type == TessDrawArgs::U16Indexed)
2584 shaderVariant = QShader::UInt16IndexedVertexAsComputeShader;
2585 else if (args.type == TessDrawArgs::U32Indexed)
2586 shaderVariant = QShader::UInt32IndexedVertexAsComputeShader;
2587 const int varIndex = QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(shaderVariant);
2588 id<MTLComputePipelineState> computePipelineState = tess.vsCompPipeline(this, shaderVariant);
2589 [computeEncoder setComputePipelineState: computePipelineState];
2590
2591 // Make uniform buffers, textures, and samplers (meant for the
2592 // vertex stage from the client's point of view) visible in the
2593 // "vertex as compute" shader
2594 cbD->d->currentComputePassEncoder = computeEncoder;
2596 cbD->d->currentComputePassEncoder = nil;
2597
2598 const QMap<int, int> &ebb(tess.compVs[varIndex].nativeShaderInfo.extraBufferBindings);
2599 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2600 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
2601
2602 if (outputBufferBinding >= 0) {
2603 const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount);
2604 vertOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2605 if (!vertOutBuf)
2606 return;
2607 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2608 }
2609
2610 if (indexBufferBinding >= 0)
2611 [computeEncoder setBuffer: (id<MTLBuffer>) args.drawIndexed.indexBuffer offset: 0 atIndex: indexBufferBinding];
2612
2613 for (int i = 0, ie = cbD->d->currentVertexInputsBuffers.batches.count(); i != ie; ++i) {
2614 const auto &bufferBatch(cbD->d->currentVertexInputsBuffers.batches[i]);
2615 const auto &offsetBatch(cbD->d->currentVertexInputOffsets.batches[i]);
2616 [computeEncoder setBuffers: bufferBatch.resources.constData()
2617 offsets: offsetBatch.resources.constData()
2618 withRange: NSMakeRange(uint(cbD->d->currentFirstVertexBinding) + bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))];
2619 }
2620
2621 if (indexed) {
2622 [computeEncoder setStageInRegion: MTLRegionMake2D(args.drawIndexed.vertexOffset, args.drawIndexed.firstInstance,
2623 args.drawIndexed.indexCount, args.drawIndexed.instanceCount)];
2624 } else {
2625 [computeEncoder setStageInRegion: MTLRegionMake2D(args.draw.firstVertex, args.draw.firstInstance,
2626 args.draw.vertexCount, args.draw.instanceCount)];
2627 }
2628
2629 [computeEncoder dispatchThreads: MTLSizeMake(vertexOrIndexCount, instanceCount, 1)
2630 threadsPerThreadgroup: MTLSizeMake(computePipelineState.threadExecutionWidth, 1, 1)];
2631 }
2632
2633 // Step 2: tessellation control shader (as compute)
2634 {
2635 id<MTLComputeCommandEncoder> computeEncoder = vertTescComputeEncoder;
2636 id<MTLComputePipelineState> computePipelineState = tess.tescCompPipeline(this);
2637 [computeEncoder setComputePipelineState: computePipelineState];
2638
2639 cbD->d->currentComputePassEncoder = computeEncoder;
2641 cbD->d->currentComputePassEncoder = nil;
2642
2643 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2644 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2645 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2646 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2647 const int paramsBufferBinding = ebb.value(QShaderPrivate::MslTessTescParamsBufferBinding, -1);
2648 const int inputBufferBinding = ebb.value(QShaderPrivate::MslTessTescInputBufferBinding, -1);
2649
2650 if (outputBufferBinding >= 0) {
2651 const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount);
2652 tescOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2653 if (!tescOutBuf)
2654 return;
2655 [computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2656 }
2657
2658 if (patchOutputBufferBinding >= 0) {
2659 const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount);
2660 tescPatchOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
2661 if (!tescPatchOutBuf)
2662 return;
2663 [computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2664 }
2665
2666 if (tessFactorBufferBinding >= 0) {
2667 tescFactorBuf = extraBufMgr.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
2668 [computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2669 }
2670
2671 if (paramsBufferBinding >= 0) {
2672 struct {
2673 quint32 inControlPointCount;
2674 quint32 patchCount;
2675 } params;
2676 tescParamsBuf = extraBufMgr.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible);
2677 if (!tescParamsBuf)
2678 return;
2679 params.inControlPointCount = tess.inControlPointCount;
2680 params.patchCount = patchCount;
2681 id<MTLBuffer> paramsBuf = tescParamsBuf->d->buf[0];
2682 char *p = reinterpret_cast<char *>([paramsBuf contents]);
2683 memcpy(p, &params, sizeof(params));
2684 [computeEncoder setBuffer: paramsBuf offset: 0 atIndex: paramsBufferBinding];
2685 }
2686
2687 if (vertOutBuf && inputBufferBinding >= 0)
2688 [computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: inputBufferBinding];
2689
2690 int sgSize = int(computePipelineState.threadExecutionWidth);
2691 int wgSize = std::lcm(tess.outControlPointCount, sgSize);
2692 while (wgSize > caps.maxThreadGroupSize) {
2693 sgSize /= 2;
2694 wgSize = std::lcm(tess.outControlPointCount, sgSize);
2695 }
2696 [computeEncoder dispatchThreads: MTLSizeMake(patchCount * tess.outControlPointCount, 1, 1)
2697 threadsPerThreadgroup: MTLSizeMake(wgSize, 1, 1)];
2698 }
2699
2700 // Much of the state in the QMetalCommandBuffer is going to be reset
2701 // when we get a new render encoder. Save what we need. (cheaper than
2702 // starting to walk over the srb again)
2703 const QMetalShaderResourceBindingsData resourceBindings = cbD->d->currentShaderResourceBindingState;
2704
2705 endTempComputeEncoding(this, cbD, cbD->d->tessellationComputeEncoder);
2706 cbD->d->tessellationComputeEncoder = nil;
2707
2708 // Step 3: tessellation evaluation (as vertex) + fragment shader
2709 {
2710 // No need to call tess.teseFragRenderPipeline because it was done
2711 // once and we know the result is stored in the standard place
2712 // (graphicsPipeline->d->ps).
2713
2715 id<MTLRenderCommandEncoder> renderEncoder = cbD->d->currentRenderPassEncoder;
2716
2719
2720 const QMap<int, int> &ebb(tess.compTesc.nativeShaderInfo.extraBufferBindings);
2721 const int outputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
2722 const int patchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
2723 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
2724
2725 if (outputBufferBinding >= 0 && tescOutBuf)
2726 [renderEncoder setVertexBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
2727
2728 if (patchOutputBufferBinding >= 0 && tescPatchOutBuf)
2729 [renderEncoder setVertexBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
2730
2731 if (tessFactorBufferBinding >= 0 && tescFactorBuf) {
2732 [renderEncoder setTessellationFactorBuffer: tescFactorBuf->d->buf[0] offset: 0 instanceStride: 0];
2733 [renderEncoder setVertexBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
2734 }
2735
2736 [cbD->d->currentRenderPassEncoder drawPatches: tess.outControlPointCount
2737 patchStart: 0
2738 patchCount: patchCount
2739 patchIndexBuffer: nil
2740 patchIndexBufferOffset: 0
2741 instanceCount: 1
2742 baseInstance: 0];
2743 }
2744}
2745
2746void QRhiMetal::adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb)
2747{
2748 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2749 const int multiViewCount = cbD->currentGraphicsPipeline->m_multiViewCount;
2750 if (multiViewCount <= 1)
2751 return;
2752
2753 const QMap<int, int> &ebb(cbD->currentGraphicsPipeline->d->vs.nativeShaderInfo.extraBufferBindings);
2754 const int viewMaskBufBinding = ebb.value(QShaderPrivate::MslMultiViewMaskBufferBinding, -1);
2755 if (viewMaskBufBinding == -1) {
2756 qWarning("No extra buffer for multiview in the vertex shader; was it built with --view-count specified?");
2757 return;
2758 }
2759 struct {
2760 quint32 viewOffset;
2761 quint32 viewCount;
2762 } multiViewInfo;
2763 multiViewInfo.viewOffset = 0;
2764 multiViewInfo.viewCount = quint32(multiViewCount);
2765 QMetalBuffer *buf = cbD->currentGraphicsPipeline->d->extraBufMgr.acquireWorkBuffer(this, sizeof(multiViewInfo),
2767 if (buf) {
2768 id<MTLBuffer> mtlbuf = buf->d->buf[0];
2769 char *p = reinterpret_cast<char *>([mtlbuf contents]);
2770 memcpy(p, &multiViewInfo, sizeof(multiViewInfo));
2771 [cbD->d->currentRenderPassEncoder setVertexBuffer: mtlbuf offset: 0 atIndex: viewMaskBufBinding];
2772 // The instance count is adjusted for layered rendering. The vertex shader is expected to contain something like:
2773 // uint gl_ViewIndex = spvViewMask[0] + (gl_InstanceIndex - gl_BaseInstance) % spvViewMask[1];
2774 // where spvViewMask is the buffer with multiViewInfo passed in above.
2775 *instanceCount *= multiViewCount;
2776 }
2777}
2778
2779void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
2780 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
2781{
2782 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2784
2785 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2786 TessDrawArgs a;
2787 a.cbD = cbD;
2788 a.type = TessDrawArgs::NonIndexed;
2789 a.draw.vertexCount = vertexCount;
2790 a.draw.instanceCount = instanceCount;
2791 a.draw.firstVertex = firstVertex;
2792 a.draw.firstInstance = firstInstance;
2794 return;
2795 }
2796
2797 adjustForMultiViewDraw(&instanceCount, cb);
2798
2799 if (caps.baseVertexAndInstance) {
2800 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2801 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
2802 } else {
2803 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2804 vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount];
2805 }
2806}
2807
2808void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
2809 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
2810{
2811 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
2813
2814 if (!cbD->currentIndexBuffer)
2815 return;
2816
2817 const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
2818 Q_ASSERT(indexOffset == aligned(indexOffset, 4u));
2819
2821 id<MTLBuffer> mtlibuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
2822
2823 if (cbD->currentGraphicsPipeline->d->tess.enabled) {
2824 TessDrawArgs a;
2825 a.cbD = cbD;
2826 a.type = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? TessDrawArgs::U16Indexed : TessDrawArgs::U32Indexed;
2827 a.drawIndexed.indexCount = indexCount;
2828 a.drawIndexed.instanceCount = instanceCount;
2829 a.drawIndexed.firstIndex = firstIndex;
2830 a.drawIndexed.vertexOffset = vertexOffset;
2831 a.drawIndexed.firstInstance = firstInstance;
2832 a.drawIndexed.indexBuffer = mtlibuf;
2834 return;
2835 }
2836
2837 adjustForMultiViewDraw(&instanceCount, cb);
2838
2839 if (caps.baseVertexAndInstance) {
2840 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2841 indexCount: indexCount
2842 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2843 indexBuffer: mtlibuf
2844 indexBufferOffset: indexOffset
2845 instanceCount: instanceCount
2846 baseVertex: vertexOffset
2847 baseInstance: firstInstance];
2848 } else {
2849 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
2850 indexCount: indexCount
2851 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
2852 indexBuffer: mtlibuf
2853 indexBufferOffset: indexOffset
2854 instanceCount: instanceCount];
2855 }
2856}
2857
2858// Returns null when the ICB path is usable, otherwise the reason why not.
2860{
2861 if (!caps.indirectCommandBuffers)
2862 return "indirect command buffers are not supported on this device";
2864 || !cbD->currentGraphicsPipeline->m_flags.testFlag(QRhiGraphicsPipeline::UsesIndirectDraws))
2865 {
2866 return "the current graphics pipeline was not created with UsesIndirectDraws";
2867 }
2868 if (cbD->currentGraphicsPipeline->d->tess.enabled)
2869 return "the current graphics pipeline uses tessellation";
2871 return "the shaders of the current graphics pipeline sample textures but have no "
2872 "argument buffer variant, which Metal requires for a pipeline that supports "
2873 "indirect command buffers; rebuild them with qsb --msl-argument-buffers "
2874 "(or use MSLARGUMENTBUFFERS with qt_add_shaders)";
2875 }
2876 return nullptr;
2877}
2878
2880{
2882 return false;
2883
2884 if (!d->icbEncodePipeline) {
2885 NSError *err = nil;
2886 NSString *src = [NSString stringWithUTF8String:s_icbEncodeMsl];
2887 MTLCompileOptions *opts = [MTLCompileOptions new];
2888 opts.languageVersion = MTLLanguageVersion2_1;
2889 id<MTLLibrary> lib = [d->dev newLibraryWithSource:src options:opts error:&err];
2890 [opts release];
2891 if (!lib) {
2892 qWarning("Failed to compile ICB encode kernel: %s",
2893 qPrintable(QString::fromNSString(err.localizedDescription)));
2894 d->icbSetupFailed = true;
2895 return false;
2896 }
2897 d->icbEncodeFunction = [lib newFunctionWithName:@"encode_icb"];
2898 d->icbEncodeFunctionU32 = [lib newFunctionWithName:@"encode_icb_indexed_u32"];
2899 d->icbEncodeFunctionU16 = [lib newFunctionWithName:@"encode_icb_indexed_u16"];
2900 [lib release];
2901 if (!d->icbEncodeFunction || !d->icbEncodeFunctionU32 || !d->icbEncodeFunctionU16) {
2902 qWarning("ICB encode kernel functions not found");
2903 d->icbSetupFailed = true;
2904 return false;
2905 }
2906 NSError *errU32 = nil;
2907 NSError *errU16 = nil;
2908 d->icbEncodePipeline = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunction error:&err];
2909 d->icbEncodePipelineU32 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU32 error:&errU32];
2910 d->icbEncodePipelineU16 = [d->dev newComputePipelineStateWithFunction:d->icbEncodeFunctionU16 error:&errU16];
2911 if (!d->icbEncodePipeline || !d->icbEncodePipelineU32 || !d->icbEncodePipelineU16) {
2912 NSError *firstErr = !d->icbEncodePipeline ? err
2913 : (!d->icbEncodePipelineU32 ? errU32 : errU16);
2914 qWarning("Failed to create ICB encode compute pipeline: %s",
2915 qPrintable(QString::fromNSString(firstErr.localizedDescription)));
2916 d->icbSetupFailed = true;
2917 return false;
2918 }
2919 }
2920
2921 if (!d->icbRangeBuffer) {
2922 d->icbRangeBuffer = [d->dev newBufferWithLength:sizeof(MTLIndirectCommandBufferExecutionRange)
2923 options:MTLResourceStorageModePrivate];
2924 static constexpr quint32 noCount = 0xFFFFFFFFu;
2925 d->icbNoCountBuffer = [d->dev newBufferWithBytes:&noCount
2926 length:sizeof(noCount)
2927 options:MTLResourceStorageModeShared];
2928 if (!d->icbRangeBuffer || !d->icbNoCountBuffer) {
2929 qWarning("Failed to create ICB helper buffers");
2930 d->icbSetupFailed = true;
2931 return false;
2932 }
2933 }
2934
2935 return true;
2936}
2937
2938bool QRhiMetal::prepareIcb(quint32 maxDrawCount)
2939{
2941 return false;
2942
2943 if (!d->icb || d->icbCapacity < maxDrawCount) {
2944 if (d->icb) {
2947 e.lastActiveFrameSlot = currentFrameSlot;
2948 e.stagingIcbBuffer.icb = d->icb;
2949 e.stagingIcbBuffer.argBuffer = d->icbArgumentBuffer;
2950 d->releaseQueue.append(e);
2951 }
2952 d->icb = nil;
2953 d->icbArgumentBuffer = nil;
2954
2955 MTLIndirectCommandBufferDescriptor *icbDesc = [MTLIndirectCommandBufferDescriptor new];
2956 icbDesc.commandTypes = MTLIndirectCommandTypeDraw | MTLIndirectCommandTypeDrawIndexed;
2957 icbDesc.inheritPipelineState = YES;
2958 icbDesc.inheritBuffers = YES;
2959 icbDesc.maxVertexBufferBindCount = 0;
2960 icbDesc.maxFragmentBufferBindCount = 0;
2961 d->icb = [d->dev newIndirectCommandBufferWithDescriptor:icbDesc
2962 maxCommandCount:maxDrawCount
2963 options:MTLResourceStorageModePrivate];
2964 [icbDesc release];
2965 if (!d->icb) {
2966 qWarning("Failed to create MTLIndirectCommandBuffer");
2967 d->icbCapacity = 0;
2968 return false;
2969 }
2970 d->icbCapacity = maxDrawCount;
2971
2972 id<MTLArgumentEncoder> argEnc = [d->icbEncodeFunction newArgumentEncoderWithBufferIndex:1];
2973 d->icbArgumentBuffer = [d->dev newBufferWithLength:argEnc.encodedLength
2974 options:MTLResourceStorageModeShared];
2975 [argEnc setArgumentBuffer:d->icbArgumentBuffer offset:0];
2976 [argEnc setIndirectCommandBuffer:d->icb atIndex:0];
2977 [argEnc release];
2978 }
2979
2980 return true;
2981}
2982
2983// Encodes maxDrawCount entries of indirectBufMtl into targetIcb on an already
2984// open compute encoder, writing the encoded count to targetRangeBuffer.
2985// countBufMtl is optional: when null all maxDrawCount commands are encoded.
2987 id<MTLComputeCommandEncoder> computeEncoder,
2988 id<MTLIndirectCommandBuffer> targetIcb,
2989 id<MTLBuffer> targetArgBuffer,
2990 id<MTLBuffer> targetRangeBuffer,
2991 bool indexed,
2992 QRhiCommandBuffer::IndexFormat indexFormat,
2993 MTLPrimitiveType primitiveType,
2994 id<MTLBuffer> indirectBufMtl, quint32 indirectBufferOffset,
2995 id<MTLBuffer> indexBufMtl, quint32 indexBufferOffset,
2996 id<MTLBuffer> countBufMtl, quint32 countBufferOffset,
2997 quint32 maxDrawCount, quint32 stride)
2998{
2999 id<MTLComputePipelineState> computePipeline = d->icbEncodePipeline;
3000 if (indexed) {
3001 computePipeline = indexFormat == QRhiCommandBuffer::IndexUInt16
3002 ? d->icbEncodePipelineU16 : d->icbEncodePipelineU32;
3003 }
3004 uint32_t maxDrawCountVal = maxDrawCount;
3005 uint32_t metalPrimType = uint32_t(primitiveType);
3006 uint32_t strideVal = stride;
3007
3008 [computeEncoder setComputePipelineState:computePipeline];
3009 [computeEncoder setBuffer:indirectBufMtl offset:indirectBufferOffset atIndex:0];
3010 [computeEncoder setBuffer:targetArgBuffer offset:0 atIndex:1];
3011 [computeEncoder setBytes:&maxDrawCountVal length:sizeof(uint32_t) atIndex:2];
3012 if (indexed)
3013 [computeEncoder setBuffer:indexBufMtl offset:indexBufferOffset atIndex:3];
3014 [computeEncoder setBytes:&metalPrimType length:sizeof(uint32_t) atIndex:4];
3015 [computeEncoder setBytes:&strideVal length:sizeof(uint32_t) atIndex:5];
3016 [computeEncoder setBuffer:countBufMtl ? countBufMtl : d->icbNoCountBuffer
3017 offset:countBufMtl ? countBufferOffset : 0
3018 atIndex:6];
3019 [computeEncoder setBuffer:targetRangeBuffer offset:0 atIndex:7];
3020 [computeEncoder useResource:targetIcb usage:MTLResourceUsageWrite];
3021 [computeEncoder useResource:indirectBufMtl usage:MTLResourceUsageRead];
3022 if (indexed)
3023 [computeEncoder useResource:indexBufMtl usage:MTLResourceUsageRead];
3024
3025 NSUInteger tw = computePipeline.threadExecutionWidth;
3026 [computeEncoder dispatchThreads:MTLSizeMake(maxDrawCount, 1, 1)
3027 threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
3028}
3029
3030// Encodes up to maxDrawCount commands into the shared ICB with a compute
3031// dispatch, then executes them on the render encoder. countBufMtl is optional:
3032// when null, all maxDrawCount commands are executed, otherwise the number of
3033// draws is min(maxDrawCount, <uint32 at countBufferOffset>), computed on the
3034// GPU. Returns false when the ICB could not be set up, in which case nothing
3035// was recorded and the pass is left untouched.
3036bool QRhiMetal::icbDraw(QMetalCommandBuffer *cbD, bool indexed,
3037 QMetalBuffer *indirectBufD, quint32 indirectBufferOffset,
3038 QMetalBuffer *countBufD, quint32 countBufferOffset,
3039 quint32 maxDrawCount, quint32 stride)
3040{
3041 if (!maxDrawCount)
3042 return true;
3043
3044 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
3045 if (indexed && !indexBufD)
3046 return false;
3047
3048 if (!prepareIcb(maxDrawCount))
3049 return false;
3050
3052 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3053 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3054
3055 id<MTLBuffer> countBufMtl = nil;
3056 if (countBufD) {
3058 countBufD->lastActiveFrameSlot = currentFrameSlot;
3059 countBufMtl = countBufD->d->buf[countBufD->d->slotted ? currentFrameSlot : 0];
3060 }
3061
3062 // Everything below survives the interruption only if saved and restored.
3064 const QMetalShaderResourceBindingsData savedResourceBindings = cbD->d->currentShaderResourceBindingState;
3065 const int savedFirstVertexBinding = cbD->d->currentFirstVertexBinding;
3066 const auto savedVertexBuffers = cbD->d->currentVertexInputsBuffers;
3067 const auto savedVertexOffsets = cbD->d->currentVertexInputOffsets;
3068 const quint32 savedIndexOffset = cbD->currentIndexOffset;
3069 const QRhiCommandBuffer::IndexFormat savedIndexFormat = cbD->currentIndexFormat;
3070 id<MTLBuffer> indexBufMtl = indexed
3071 ? indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0] : nil;
3072
3073 // End the current render encoder to make room for the compute pass.
3075
3076 id<MTLComputeCommandEncoder> computeEncoder = [cbD->d->cb computeCommandEncoder];
3077 encodeIcbWithCompute(d, computeEncoder, d->icb, d->icbArgumentBuffer, d->icbRangeBuffer,
3078 indexed, savedIndexFormat, savedPipeline->d->primitiveType,
3079 indirectBufMtl, indirectBufferOffset,
3080 indexBufMtl, savedIndexOffset,
3081 countBufMtl, countBufferOffset,
3082 maxDrawCount, stride);
3083
3084 // Restart the render pass with Load actions to preserve existing content.
3085 endTempComputeEncoding(this, cbD, computeEncoder);
3086
3087 // Restore pipeline, shader resources, and vertex bindings on the new encoder.
3090 QMetalShaderResourceBindingsData::VERTEX, &savedResourceBindings);
3092 QMetalShaderResourceBindingsData::FRAGMENT, &savedResourceBindings);
3093
3094 if (savedFirstVertexBinding >= 0) {
3095 cbD->d->currentFirstVertexBinding = savedFirstVertexBinding;
3096 cbD->d->currentVertexInputsBuffers = savedVertexBuffers;
3097 cbD->d->currentVertexInputOffsets = savedVertexOffsets;
3098 for (int i = 0, ie = savedVertexBuffers.batches.count(); i != ie; ++i) {
3099 const auto &bufferBatch(savedVertexBuffers.batches[i]);
3100 const auto &offsetBatch(savedVertexOffsets.batches[i]);
3101 [cbD->d->currentRenderPassEncoder setVertexBuffers:
3102 bufferBatch.resources.constData()
3103 offsets: offsetBatch.resources.constData()
3104 withRange: NSMakeRange(uint(savedFirstVertexBinding) + bufferBatch.startBinding,
3105 NSUInteger(bufferBatch.resources.count()))];
3106 }
3107 }
3108
3109 cbD->currentIndexBuffer = indexBufD;
3110 cbD->currentIndexOffset = savedIndexOffset;
3111 cbD->currentIndexFormat = savedIndexFormat;
3112
3113 // Declare buffer dependencies and execute the GPU-encoded ICB. The range to
3114 // execute is read from icbRangeBuffer, which the kernel just wrote.
3115 [cbD->d->currentRenderPassEncoder useResource:indirectBufMtl
3116 usage:MTLResourceUsageRead
3117 stages:MTLRenderStageVertex | MTLRenderStageFragment];
3118 if (indexed) {
3119 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
3120 usage:MTLResourceUsageRead
3121 stages:MTLRenderStageVertex | MTLRenderStageFragment];
3122 }
3123 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:d->icb
3124 indirectBuffer:d->icbRangeBuffer
3125 indirectBufferOffset:0];
3126 return true;
3127}
3128
3129// The ICB (Indirect Command Buffer) path encodes the draw commands on the GPU
3130// and executes them with a single executeCommandsInBuffer, which removes the
3131// per-draw CPU overhead. The encoding needs a compute pass, so the render pass
3132// has to be interrupted and restarted: that costs about 100-150 microseconds,
3133// whereas an individual indirect draw call takes 1-2. Hence only taking this
3134// path for large batches.
3135static constexpr quint32 ICB_DRAW_COUNT_THRESHOLD = 128;
3136
3137void QRhiMetal::drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
3138 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
3139{
3140 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3142
3143 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
3145 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3146 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3147
3148 if (drawCount > ICB_DRAW_COUNT_THRESHOLD && !icbUnavailableReason(cbD)
3149 && icbDraw(cbD, false, indirectBufD, indirectBufferOffset, nullptr, 0, drawCount, stride))
3150 {
3151 return;
3152 }
3153
3154 // CPU-side for-loop fallback: used when ICB is not applicable or setup failed.
3155 NSUInteger offset = indirectBufferOffset;
3156 for (quint32 i = 0; i < drawCount; ++i) {
3157 [cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
3158 indirectBuffer: indirectBufMtl
3159 indirectBufferOffset: offset];
3160 offset += stride;
3161 }
3162}
3163
3164void QRhiMetal::drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
3165 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
3166{
3167 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3169
3170 if (!cbD->currentIndexBuffer)
3171 return;
3172
3173 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
3174 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
3175
3176 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
3178 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
3179 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
3180
3181 if (drawCount > ICB_DRAW_COUNT_THRESHOLD && !icbUnavailableReason(cbD)
3182 && icbDraw(cbD, true, indirectBufD, indirectBufferOffset, nullptr, 0, drawCount, stride))
3183 {
3184 return;
3185 }
3186
3187 // CPU-side for-loop fallback: used when ICB is not applicable or setup failed.
3188 NSUInteger offset = indirectBufferOffset;
3189 for (quint32 i = 0; i < drawCount; ++i) {
3190 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
3191 indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
3192 indexBuffer: indexBufMtl
3193 indexBufferOffset: cbD->currentIndexOffset
3194 indirectBuffer: indirectBufMtl
3195 indirectBufferOffset: offset];
3196 offset += stride;
3197 }
3198}
3199
3200void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
3201{
3202 if (!debugMarkers)
3203 return;
3204
3205 NSString *str = [NSString stringWithUTF8String: name.constData()];
3206 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3207 switch (cbD->recordingPass) {
3208 case QMetalCommandBuffer::RenderPass:
3209 [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
3210 cbD->d->openDebugGroups.append(name);
3211 break;
3212 case QMetalCommandBuffer::ComputePass:
3213 [cbD->d->currentComputePassEncoder pushDebugGroup: str];
3214 break;
3215 default:
3216 [cbD->d->cb pushDebugGroup: str];
3217 break;
3218 }
3219}
3220
3221void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
3222{
3223 if (!debugMarkers)
3224 return;
3225
3226 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3227 switch (cbD->recordingPass) {
3228 case QMetalCommandBuffer::RenderPass:
3229 [cbD->d->currentRenderPassEncoder popDebugGroup];
3230 if (!cbD->d->openDebugGroups.isEmpty())
3231 cbD->d->openDebugGroups.removeLast();
3232 break;
3233 case QMetalCommandBuffer::ComputePass:
3234 [cbD->d->currentComputePassEncoder popDebugGroup];
3235 break;
3236 default:
3237 [cbD->d->cb popDebugGroup];
3238 break;
3239 }
3240}
3241
3242void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
3243{
3244 if (!debugMarkers)
3245 return;
3246
3247 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3248 NSString *str = [NSString stringWithUTF8String: msg.constData()];
3249 switch (cbD->recordingPass) {
3250 case QMetalCommandBuffer::RenderPass:
3251 [cbD->d->currentRenderPassEncoder insertDebugSignpost: str];
3252 break;
3253 case QMetalCommandBuffer::ComputePass:
3254 [cbD->d->currentComputePassEncoder insertDebugSignpost: str];
3255 break;
3256 default:
3257 break;
3258 }
3259}
3260
3261const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
3262{
3263 return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
3264}
3265
3266void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
3267{
3268 Q_UNUSED(cb);
3269}
3270
3271void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
3272{
3273 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3275}
3276
3277double QRhiMetal::lastCompletedGpuTime(QRhiCommandBuffer *cb)
3278{
3279 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3280 return cbD->d->lastGpuTime;
3281}
3282
3283QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
3284{
3285 Q_UNUSED(flags);
3286
3287 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
3288 currentSwapChain = swapChainD;
3289 currentFrameSlot = swapChainD->currentFrameSlot;
3290
3291 // If we are too far ahead, block. This is also what ensures that any
3292 // resource used in the previous frame for this slot is now not in use
3293 // anymore by the GPU.
3294 dispatch_semaphore_wait(swapChainD->d->sem[currentFrameSlot], DISPATCH_TIME_FOREVER);
3295
3296 // Do this also for any other swapchain's commands with the same frame slot
3297 // While this reduces concurrency, it keeps resource usage safe: swapchain
3298 // A starting its frame 0, followed by swapchain B starting its own frame 0
3299 // will make B wait for A's frame 0 commands, so if a resource is written
3300 // in B's frame or when B checks for pending resource releases, that won't
3301 // mess up A's in-flight commands (as they are not in flight anymore).
3302 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
3303 if (sc != swapChainD)
3304 sc->waitUntilCompleted(currentFrameSlot); // wait+signal
3305 }
3306
3307 [d->captureScope beginScope];
3308
3309 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
3310
3312 if (swapChainD->samples > 1) {
3313 colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
3314 colorAtt.needsDrawableForResolveTex = true;
3315 } else {
3316 colorAtt.needsDrawableForTex = true;
3317 }
3318
3319 swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
3320 swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
3321 swapChainD->rtWrapper.d->fb.dsResolveTex = nil;
3322 swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
3323 swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
3324
3325 if (swapChainD->ds)
3326 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
3327
3328 d->argBufPool[currentFrameSlot].offset = 0;
3329 d->resetAndResizeBufferStagingArea(currentFrameSlot);
3330 d->globalFrameId += 1;
3331
3333 swapChainD->cbWrapper.resetState(swapChainD->d->lastGpuTime[currentFrameSlot]);
3334 swapChainD->d->lastGpuTime[currentFrameSlot] = 0;
3336
3337 return QRhi::FrameOpSuccess;
3338}
3339
3340QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
3341{
3342 QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
3343 Q_ASSERT(currentSwapChain == swapChainD);
3344
3345 // Keep strong reference to command buffer
3346 id<MTLCommandBuffer> commandBuffer = swapChainD->cbWrapper.d->cb;
3347
3348 __block int thisFrameSlot = currentFrameSlot;
3349 [commandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> cb) {
3350 swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
3351 dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
3352 }];
3353
3355 // When Metal API validation diagnostics is enabled in Xcode the texture is
3356 // released before the command buffer is done with it. Manually keep it alive
3357 // to work around this.
3358 id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain];
3359 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
3360 [drawableTexture release];
3361 }];
3362#endif
3363
3364 if (flags.testFlag(QRhi::SkipPresent)) {
3365 // Just need to commit, that's it
3366 [commandBuffer commit];
3367 } else {
3368 if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) {
3369 // Got something to present
3370 if (swapChainD->d->layer.presentsWithTransaction) {
3371 [commandBuffer commit];
3372 // Keep strong reference to Metal layer
3373 auto *metalLayer = swapChainD->d->layer;
3374 auto presentWithTransaction = ^{
3375 [commandBuffer waitUntilScheduled];
3376 // If the layer has been resized while we waited to be scheduled we bail out,
3377 // as the drawable is no longer valid for the layer, and we'll get a follow-up
3378 // display with the right size. We know we are on the main thread here, which
3379 // means we can access the layer directly. We also know that the layer is valid,
3380 // since the block keeps a strong reference to it, compared to the QRhiSwapChain
3381 // that can go away under our feet by the time we're scheduled.
3382 const auto surfaceSize = QSizeF::fromCGSize(metalLayer.bounds.size) * metalLayer.contentsScale;
3383 const auto textureSize = QSizeF(drawable.texture.width, drawable.texture.height);
3384 if (textureSize == surfaceSize) {
3385 [drawable present];
3386 } else {
3387 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable << "due to texture size"
3388 << textureSize << "not matching surface size" << surfaceSize;
3389 }
3390 };
3391
3392 if (NSThread.currentThread == NSThread.mainThread) {
3393 presentWithTransaction();
3394 } else {
3395 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
3396 Q_ASSERT(qtMetalLayer);
3397 // Let the main thread present the drawable from displayLayer
3398 qtMetalLayer.mainThreadPresentation = presentWithTransaction;
3399 }
3400 } else {
3401 // Keep strong reference to Metal layer so it's valid in the block
3402 auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
3403 [commandBuffer addScheduledHandler:^(id<MTLCommandBuffer>) {
3404 if (qtMetalLayer) {
3405 // The schedule handler comes in on the com.Metal.CompletionQueueDispatch
3406 // thread, which means we might be racing against a display cycle on the
3407 // main thread. If the displayLayer is already in progress, we don't want
3408 // to step on its toes.
3409 if (qtMetalLayer.displayLock.tryLockForRead()) {
3410 [drawable present];
3411 qtMetalLayer.displayLock.unlock();
3412 } else {
3413 qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable
3414 << "due to" << qtMetalLayer << "needing display";
3415 }
3416 } else {
3417 [drawable present];
3418 }
3419 }];
3420 [commandBuffer commit];
3421 }
3422 } else {
3423 // Still need to commit, even if we don't have a drawable
3424 [commandBuffer commit];
3425 }
3426
3427 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
3428 }
3429
3430 // Must not hold on to the drawable, regardless of needsPresent
3431 [swapChainD->d->curDrawable release];
3432 swapChainD->d->curDrawable = nil;
3433
3434 [d->captureScope endScope];
3435
3436 swapChainD->frameCount += 1;
3437 currentSwapChain = nullptr;
3438 return QRhi::FrameOpSuccess;
3439}
3440
3441QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
3442{
3443 Q_UNUSED(flags);
3444
3445 currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
3446
3447 for (QMetalSwapChain *sc : std::as_const(swapchains))
3448 sc->waitUntilCompleted(currentFrameSlot);
3449
3450 d->ofr.active = true;
3451 *cb = &d->ofr.cbWrapper;
3452 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
3453
3454 d->argBufPool[currentFrameSlot].offset = 0;
3455 d->resetAndResizeBufferStagingArea(currentFrameSlot);
3456 d->globalFrameId += 1;
3457
3459 d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime);
3460 d->ofr.lastGpuTime = 0;
3462
3463 return QRhi::FrameOpSuccess;
3464}
3465
3466QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags)
3467{
3468 Q_UNUSED(flags);
3469 Q_ASSERT(d->ofr.active);
3470 d->ofr.active = false;
3471
3472 id<MTLCommandBuffer> cb = d->ofr.cbWrapper.d->cb;
3473 [cb commit];
3474
3475 // offscreen frames wait for completion, unlike swapchain ones
3476 [cb waitUntilCompleted];
3477
3478 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
3479
3481
3482 return QRhi::FrameOpSuccess;
3483}
3484
3486{
3487 id<MTLCommandBuffer> cb = nil;
3488 QMetalSwapChain *swapChainD = nullptr;
3489 if (inFrame) {
3490 if (d->ofr.active) {
3491 Q_ASSERT(!currentSwapChain);
3492 Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
3493 cb = d->ofr.cbWrapper.d->cb;
3494 } else {
3495 Q_ASSERT(currentSwapChain);
3496 swapChainD = currentSwapChain;
3497 Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
3498 cb = swapChainD->cbWrapper.d->cb;
3499 }
3500 }
3501
3502 for (QMetalSwapChain *sc : std::as_const(swapchains)) {
3503 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
3504 if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
3505 // no wait as this is the thing we're going to be commit below and
3506 // beginFrame decremented sem already and going to be signaled by endFrame
3507 continue;
3508 }
3509 sc->waitUntilCompleted(i);
3510 }
3511 }
3512
3513 if (cb) {
3514 [cb commit];
3515 [cb waitUntilCompleted];
3516 }
3517
3518 if (inFrame) {
3519 if (d->ofr.active) {
3520 d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
3521 d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
3522 } else {
3523 swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
3524 swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
3525 }
3526 }
3527
3529
3531
3532 return QRhi::FrameOpSuccess;
3533}
3534
3536 const QColor &colorClearValue,
3537 const QRhiDepthStencilClearValue &depthStencilClearValue,
3538 int colorAttCount,
3539 QRhiShadingRateMap *shadingRateMap)
3540{
3541 MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
3542 MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
3543 colorClearValue.alphaF());
3544
3545 for (uint i = 0; i < uint(colorAttCount); ++i) {
3546 rp.colorAttachments[i].loadAction = MTLLoadActionClear;
3547 rp.colorAttachments[i].storeAction = MTLStoreActionStore;
3548 rp.colorAttachments[i].clearColor = c;
3549 }
3550
3551 if (hasDepthStencil) {
3552 rp.depthAttachment.loadAction = MTLLoadActionClear;
3553 rp.depthAttachment.storeAction = MTLStoreActionDontCare;
3554 rp.stencilAttachment.loadAction = MTLLoadActionClear;
3555 rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
3556 rp.depthAttachment.clearDepth = double(depthStencilClearValue.depthClearValue());
3557 rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
3558 }
3559
3560 if (shadingRateMap)
3561 rp.rasterizationRateMap = QRHI_RES(QMetalShadingRateMap, shadingRateMap)->d->rateMap;
3562
3563 return rp;
3564}
3565
3566qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
3567{
3568 qsizetype size = 0;
3569 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
3570 subresDesc.data().size() : subresDesc.image().sizeInBytes();
3571 if (imageSizeBytes > 0)
3572 size += aligned<qsizetype>(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3573 return size;
3574}
3575
3576void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
3577 int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
3578 qsizetype *curOfs)
3579{
3580 const QPoint dp = subresDesc.destinationTopLeft();
3581 const QByteArray rawData = subresDesc.data();
3582 QImage img = subresDesc.image();
3583 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3584 id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
3585
3586 if (!img.isNull()) {
3587 const qsizetype fullImageSizeBytes = img.sizeInBytes();
3588 QSize size = img.size();
3589 int bpl = img.bytesPerLine();
3590
3591 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
3592 const int sx = subresDesc.sourceTopLeft().x();
3593 const int sy = subresDesc.sourceTopLeft().y();
3594 if (!subresDesc.sourceSize().isEmpty())
3595 size = subresDesc.sourceSize();
3596 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3597 if (size.width() == img.width()) {
3598 const int bpc = qMax(1, img.depth() / 8);
3599 Q_ASSERT(size.height() * img.bytesPerLine() <= fullImageSizeBytes);
3600 memcpy(reinterpret_cast<char *>(mp) + *curOfs,
3601 img.constBits() + sy * img.bytesPerLine() + sx * bpc,
3602 size.height() * img.bytesPerLine());
3603 } else {
3604 img = img.copy(sx, sy, size.width(), size.height());
3605 bpl = img.bytesPerLine();
3606 Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
3607 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(img.sizeInBytes()));
3608 }
3609 } else {
3610 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
3611 memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
3612 }
3613
3614 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3615 sourceOffset: NSUInteger(*curOfs)
3616 sourceBytesPerRow: NSUInteger(bpl)
3617 sourceBytesPerImage: 0
3618 sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1)
3619 toTexture: texD->d->tex
3620 destinationSlice: NSUInteger(is3D ? 0 : layer)
3621 destinationLevel: NSUInteger(level)
3622 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3623 options: MTLBlitOptionNone];
3624
3625 *curOfs += aligned<qsizetype>(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
3626 } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
3627 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3628 const int subresw = subresSize.width();
3629 const int subresh = subresSize.height();
3630 int w, h;
3631 if (subresDesc.sourceSize().isEmpty()) {
3632 w = subresw;
3633 h = subresh;
3634 } else {
3635 w = subresDesc.sourceSize().width();
3636 h = subresDesc.sourceSize().height();
3637 }
3638
3639 quint32 bpl = 0;
3640 QSize blockDim;
3641 compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
3642
3643 const int dx = aligned(dp.x(), blockDim.width());
3644 const int dy = aligned(dp.y(), blockDim.height());
3645 if (dx + w != subresw)
3646 w = aligned(w, blockDim.width());
3647 if (dy + h != subresh)
3648 h = aligned(h, blockDim.height());
3649
3650 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3651
3652 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3653 sourceOffset: NSUInteger(*curOfs)
3654 sourceBytesPerRow: bpl
3655 sourceBytesPerImage: 0
3656 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3657 toTexture: texD->d->tex
3658 destinationSlice: NSUInteger(is3D ? 0 : layer)
3659 destinationLevel: NSUInteger(level)
3660 destinationOrigin: MTLOriginMake(NSUInteger(dx), NSUInteger(dy), NSUInteger(is3D ? layer : 0))
3661 options: MTLBlitOptionNone];
3662
3663 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3664 } else if (!rawData.isEmpty()) {
3665 const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
3666 const int subresw = subresSize.width();
3667 const int subresh = subresSize.height();
3668 int w, h;
3669 if (subresDesc.sourceSize().isEmpty()) {
3670 w = subresw;
3671 h = subresh;
3672 } else {
3673 w = subresDesc.sourceSize().width();
3674 h = subresDesc.sourceSize().height();
3675 }
3676
3677 QSize size = clampedSubResourceUploadSize(QSize(w, h), dp, level, texD->m_pixelSize);
3678 quint32 bytesPerPixel = 0;
3679 textureFormatInfo(texD->m_format, size, nullptr, nullptr, &bytesPerPixel);
3680 size = clampedSubResourceUploadSizeForSourceData(size, subresDesc.dataStride(),
3681 bytesPerPixel, rawData.size());
3682 w = size.width();
3683 h = size.height();
3684
3685 quint32 bpl = 0;
3686 if (subresDesc.dataStride())
3687 bpl = subresDesc.dataStride();
3688 else
3689 textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, nullptr);
3690
3691 memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
3692
3693 if (!size.isEmpty()) {
3694 [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
3695 sourceOffset: NSUInteger(*curOfs)
3696 sourceBytesPerRow: bpl
3697 sourceBytesPerImage: 0
3698 sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
3699 toTexture: texD->d->tex
3700 destinationSlice: NSUInteger(is3D ? 0 : layer)
3701 destinationLevel: NSUInteger(level)
3702 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(is3D ? layer : 0))
3703 options: MTLBlitOptionNone];
3704 }
3705
3706 *curOfs += aligned<qsizetype>(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
3707 } else {
3708 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
3709 }
3710}
3711
3712void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3713{
3714 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
3716
3717 id<MTLBlitCommandEncoder> blitEnc = nil;
3718 auto ensureBlit = [&blitEnc, cbD, this]() {
3719 if (!blitEnc) {
3720 blitEnc = [cbD->d->cb blitCommandEncoder];
3721 if (debugMarkers)
3722 [blitEnc pushDebugGroup: @"Resource updates"];
3723 }
3724 };
3725
3726 for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
3727 const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
3729 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3730 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
3731 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) {
3732 if (u.offset == 0 && u.data.size() == bufD->m_size)
3733 bufD->d->pendingUpdates[i].clear();
3734 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3735 }
3737 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3738 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
3739 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
3740 if (bufD->d->isDeviceLocal) {
3741 const quint32 size = quint32(u.data.size());
3742 if (size) { // a zero size blit is invalid
3743 quint32 stagingOffset = 0;
3744 id<MTLBuffer> stagingBuf = d->allocBufferStaging(size, currentFrameSlot, &stagingOffset);
3745 if (stagingBuf) {
3746 char *p = reinterpret_cast<char *>([stagingBuf contents]);
3747 memcpy(p + stagingOffset, u.data.constData(), size);
3748 ensureBlit();
3749 [blitEnc copyFromBuffer: stagingBuf
3750 sourceOffset: stagingOffset
3751 toBuffer: bufD->d->buf[0]
3752 destinationOffset: u.offset
3753 size: size];
3754 } else {
3755 qWarning("Failed to allocate staging buffer of size %u for buffer upload", size);
3756 }
3757 }
3758 bufD->lastActiveFrameSlot = currentFrameSlot;
3759 } else {
3760 for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
3761 bufD->d->pendingUpdates[i].append({ u.offset, u.data });
3762 }
3764 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3766 const int idx = bufD->d->slotted ? currentFrameSlot : 0;
3767 if (bufD->m_type == QRhiBuffer::Dynamic) {
3768 char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]);
3769 if (p) {
3770 u.result->data.resize(u.readSize);
3771 memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize));
3772 }
3773 if (u.result->completed)
3774 u.result->completed();
3775 } else {
3776 // Copy into a dedicated staging buffer, here and now, instead
3777 // of holding on to the buffer and reading it when the readback
3778 // completes. The contents have to be the ones at this point in
3779 // the command stream: a buffer that is not slotted - which is
3780 // every buffer with StorageBuffer usage - has one native buffer
3781 // shared by all frames, so by the time the readback completes a
3782 // later frame may well be writing it.
3783 QRhiMetalData::BufferReadback readback;
3784 readback.activeFrameSlot = currentFrameSlot;
3785 readback.readSize = u.readSize;
3786 readback.result = u.result;
3787 readback.buf = [d->dev newBufferWithLength: u.readSize
3788 options: MTLResourceStorageModeShared];
3789
3790 ensureBlit();
3791 [blitEnc copyFromBuffer: bufD->d->buf[idx]
3792 sourceOffset: u.offset
3793 toBuffer: readback.buf
3794 destinationOffset: 0
3795 size: u.readSize];
3796
3797 d->activeBufferReadbacks.append(readback);
3798 bufD->lastActiveFrameSlot = currentFrameSlot;
3799 }
3801 QMetalBuffer *dstD = QRHI_RES(QMetalBuffer, u.buf);
3802 QMetalBuffer *srcD = QRHI_RES(QMetalBuffer, u.src);
3803 Q_ASSERT(dstD->m_type != QRhiBuffer::Dynamic && srcD->m_type != QRhiBuffer::Dynamic);
3804
3806 const int srcIdx = srcD->d->slotted ? currentFrameSlot : 0;
3807 const int dstSlotCount = dstD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1;
3808 // With device private buffers dstSlotCount is 1 and there are no
3809 // pending host writes at all, so this is a no-op. On the host
3810 // visible path every slot has to be flushed, not just the current
3811 // one: a partial copy cannot discard the pending writes outside the
3812 // copied range, and leaving them queued would let them land on top
3813 // of the copy in a later frame. Writing the slot that is not the
3814 // current one races a still in-flight frame reading it, which is
3815 // why mixing uploadStaticBuffer() and copyBuffer() on one buffer
3816 // within a frame stays unsupported there.
3817 for (int i = 0; i != dstSlotCount; ++i)
3819
3820 ensureBlit();
3821 for (int i = 0; i != dstSlotCount; ++i) {
3822 [blitEnc copyFromBuffer: srcD->d->buf[srcIdx]
3823 sourceOffset: u.srcOffset
3824 toBuffer: dstD->d->buf[i]
3825 destinationOffset: u.offset
3826 size: u.readSize];
3827 }
3828
3829 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3831 QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
3832 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic && !bufD->d->slotted);
3834 ensureBlit();
3835 [blitEnc fillBuffer: bufD->d->buf[0]
3836 range: NSMakeRange(u.offset, u.readSize)
3837 value: u.fillValue];
3838 bufD->lastActiveFrameSlot = currentFrameSlot;
3839 }
3840 }
3841
3842 for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
3843 const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
3845 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
3846 qsizetype stagingSize = 0;
3847 for (const auto &subres : u.subresDesc)
3848 stagingSize += subresUploadByteSize(subres.desc);
3849
3850 ensureBlit();
3851 Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
3852 utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: NSUInteger(stagingSize)
3853 options: MTLResourceStorageModeShared];
3854
3855 void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
3856 qsizetype curOfs = 0;
3857 for (const auto &subres : u.subresDesc)
3858 enqueueSubresUpload(utexD, mp, blitEnc, subres.layer, subres.level, subres.desc, &curOfs);
3859
3860 utexD->lastActiveFrameSlot = currentFrameSlot;
3861
3864 e.lastActiveFrameSlot = currentFrameSlot;
3865 e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
3866 utexD->d->stagingBuf[currentFrameSlot] = nil;
3867 d->releaseQueue.append(e);
3869 Q_ASSERT(u.src && u.dst);
3870 QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
3871 QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
3872 const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3873 const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3874 const QPoint dp = u.desc.destinationTopLeft();
3875 const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
3876 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
3877 const QPoint sp = u.desc.sourceTopLeft();
3878
3879 ensureBlit();
3880 [blitEnc copyFromTexture: srcD->d->tex
3881 sourceSlice: NSUInteger(srcIs3D ? 0 : u.desc.sourceLayer())
3882 sourceLevel: NSUInteger(u.desc.sourceLevel())
3883 sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), NSUInteger(srcIs3D ? u.desc.sourceLayer() : 0))
3884 sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1)
3885 toTexture: dstD->d->tex
3886 destinationSlice: NSUInteger(dstIs3D ? 0 : u.desc.destinationLayer())
3887 destinationLevel: NSUInteger(u.desc.destinationLevel())
3888 destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), NSUInteger(dstIs3D ? u.desc.destinationLayer() : 0))];
3889
3890 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3893 readback.activeFrameSlot = currentFrameSlot;
3894 readback.desc = u.rb;
3895 readback.result = u.result;
3896
3897 QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
3898 QMetalSwapChain *swapChainD = nullptr;
3899 id<MTLTexture> src;
3900 QRect rect;
3901 bool is3D = false;
3902 if (texD) {
3903 if (texD->samples > 1) {
3904 qWarning("Multisample texture cannot be read back");
3905 continue;
3906 }
3907 is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
3908 if (u.rb.rect().isValid())
3909 rect = u.rb.rect();
3910 else
3911 rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
3912 readback.format = texD->m_format;
3913 src = texD->d->tex;
3914 texD->lastActiveFrameSlot = currentFrameSlot;
3915 } else {
3916 Q_ASSERT(currentSwapChain);
3918 if (u.rb.rect().isValid())
3919 rect = u.rb.rect();
3920 else
3921 rect = QRect({0, 0}, swapChainD->pixelSize);
3922 readback.format = swapChainD->d->rhiColorFormat;
3923 // Multisample swapchains need nothing special since resolving
3924 // happens when ending a renderpass.
3925 const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
3926 src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
3927 }
3928 readback.pixelSize = rect.size();
3929
3930 quint32 bpl = 0;
3931 textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize, nullptr);
3932
3933 // The depth and stencil aspects of a texture with a combined
3934 // depth/stencil Metal pixel format cannot be copied out with
3935 // MTLBlitOptionNone, they need separate blits, each into its own
3936 // densely packed plane of the staging buffer. In addition, the
3937 // layout of what the blit produces does not necessarily match
3938 // what QRhi returns for the format (e.g. D16 and D24 may be backed
3939 // by Depth32Float). The decisions are thus based on the actual Metal
3940 // pixel format. finishActiveReadbacks() then converts the planes
3941 // into the layout of the result.
3942 const MTLPixelFormat mtlFormat = texD ? texD->d->format : MTLPixelFormatInvalid;
3943 const bool isDepthFormat = texD && (readback.format == QRhiTexture::D16
3944 || readback.format == QRhiTexture::D24
3945 || readback.format == QRhiTexture::D24S8
3946 || readback.format == QRhiTexture::D32F
3947 || readback.format == QRhiTexture::D32FS8);
3948 const bool nativeLayout = (readback.format == QRhiTexture::D16 && mtlFormat == MTLPixelFormatDepth16Unorm)
3949 || (readback.format == QRhiTexture::D32F && mtlFormat == MTLPixelFormatDepth32Float);
3950 const bool usePlanes = isDepthFormat && !nativeLayout;
3951 bool combinedMtlFormat = mtlFormat == MTLPixelFormatDepth32Float_Stencil8;
3952#ifdef Q_OS_MACOS
3953 if (mtlFormat == MTLPixelFormatDepth24Unorm_Stencil8)
3954 combinedMtlFormat = true;
3955#endif
3956 const bool hasStencil = isStencilSupportingFormat(readback.format);
3957 quint32 depthPlaneBytesPerRow = 0;
3958 if (usePlanes) {
3959 // The depth blit writes 4 bytes per pixel (a float, or a 24-bit
3960 // unorm value in the low bits), the stencil blit 1 byte per pixel.
3961 depthPlaneBytesPerRow = quint32(rect.width()) * 4;
3962 readback.depthPlaneByteSize = depthPlaneBytesPerRow * quint32(rect.height());
3963 if (hasStencil)
3964 readback.stencilPlaneByteSize = quint32(rect.width()) * quint32(rect.height());
3965 readback.depthPlaneIsFloat = mtlFormat == MTLPixelFormatDepth32Float
3966 || mtlFormat == MTLPixelFormatDepth32Float_Stencil8;
3967 }
3968 const quint32 stagingBufSize = usePlanes
3969 ? readback.depthPlaneByteSize + readback.stencilPlaneByteSize
3970 : readback.bufSize;
3971 readback.buf = [d->dev newBufferWithLength: stagingBufSize options: MTLResourceStorageModeShared];
3972
3973 ensureBlit();
3974 const auto blit = [&](quint32 destinationOffset, quint32 destinationBytesPerRow, MTLBlitOption options) {
3975 [blitEnc copyFromTexture: src
3976 sourceSlice: NSUInteger(is3D ? 0 : u.rb.layer())
3977 sourceLevel: NSUInteger(u.rb.level())
3978 sourceOrigin: MTLOriginMake(NSUInteger(rect.x()), NSUInteger(rect.y()), NSUInteger(is3D ? u.rb.layer() : 0))
3979 sourceSize: MTLSizeMake(NSUInteger(rect.width()), NSUInteger(rect.height()), 1)
3980 toBuffer: readback.buf
3981 destinationOffset: destinationOffset
3982 destinationBytesPerRow: destinationBytesPerRow
3983 destinationBytesPerImage: 0
3984 options: options];
3985 };
3986 if (usePlanes) {
3987 blit(0, depthPlaneBytesPerRow,
3988 combinedMtlFormat ? MTLBlitOptionDepthFromDepthStencil : MTLBlitOptionNone);
3989 if (hasStencil)
3990 blit(readback.depthPlaneByteSize, quint32(rect.width()), MTLBlitOptionStencilFromDepthStencil);
3991 } else {
3992 blit(0, bpl, MTLBlitOptionNone);
3993 }
3994
3995 d->activeTextureReadbacks.append(readback);
3997 QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
3998 ensureBlit();
3999 [blitEnc generateMipmapsForTexture: utexD->d->tex];
4000 utexD->lastActiveFrameSlot = currentFrameSlot;
4001 }
4002 }
4003
4004 if (blitEnc) {
4005 if (debugMarkers)
4006 [blitEnc popDebugGroup];
4007 [blitEnc endEncoding];
4008 }
4009
4010 ud->free();
4011}
4012
4013// this handles all types of buffers, not just Dynamic
4015{
4016 if (bufD->d->pendingUpdates[slot].isEmpty())
4017 return;
4018
4019 Q_ASSERT(!bufD->d->isDeviceLocal);
4020
4021 void *p = [bufD->d->buf[slot] contents];
4022 quint32 changeBegin = UINT32_MAX;
4023 quint32 changeEnd = 0;
4024 for (const QMetalBufferData::BufferUpdate &u : std::as_const(bufD->d->pendingUpdates[slot])) {
4025 memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
4026 if (u.offset < changeBegin)
4027 changeBegin = u.offset;
4028 if (u.offset + u.data.size() > changeEnd)
4029 changeEnd = u.offset + u.data.size();
4030 }
4031#ifdef Q_OS_MACOS
4032 if (changeBegin < UINT32_MAX && changeBegin < changeEnd && bufD->d->managed)
4033 [bufD->d->buf[slot] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))];
4034#endif
4035
4036 bufD->d->pendingUpdates[slot].clear();
4037}
4038
4040{
4041 executeBufferHostWritesForSlot(bufD, bufD->d->slotted ? currentFrameSlot : 0);
4042}
4043
4044void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4045{
4046 Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
4047
4048 enqueueResourceUpdates(cb, resourceUpdates);
4049}
4050
4051void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
4052 QRhiRenderTarget *rt,
4053 const QColor &colorClearValue,
4054 const QRhiDepthStencilClearValue &depthStencilClearValue,
4055 QRhiResourceUpdateBatch *resourceUpdates,
4057{
4058 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4060
4061 if (resourceUpdates)
4062 enqueueResourceUpdates(cb, resourceUpdates);
4063
4064 QMetalRenderTargetData *rtD = nullptr;
4065 switch (rt->resourceType()) {
4066 case QRhiResource::SwapChainRenderTarget:
4067 {
4069 rtD = rtSc->d;
4070 QRhiShadingRateMap *shadingRateMap = rtSc->swapChain()->shadingRateMap();
4071 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
4072 colorClearValue,
4073 depthStencilClearValue,
4074 rtD->colorAttCount,
4075 shadingRateMap);
4076 if (rtD->colorAttCount) {
4077 QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
4079 Q_ASSERT(currentSwapChain);
4081 if (!swapChainD->d->curDrawable) {
4082 QMacAutoReleasePool pool;
4083 swapChainD->d->curDrawable = [[swapChainD->d->layer nextDrawable] retain];
4084 }
4085 if (!swapChainD->d->curDrawable) {
4086 qWarning("No drawable");
4087 return;
4088 }
4089 id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
4090 if (color0.needsDrawableForTex) {
4091 color0.tex = scTex;
4092 color0.needsDrawableForTex = false;
4093 } else {
4094 color0.resolveTex = scTex;
4095 color0.needsDrawableForResolveTex = false;
4096 }
4097 }
4098 }
4099 if (shadingRateMap)
4100 QRHI_RES(QMetalShadingRateMap, shadingRateMap)->lastActiveFrameSlot = currentFrameSlot;
4101 }
4102 break;
4103 case QRhiResource::TextureRenderTarget:
4104 {
4106 rtD = rtTex->d;
4107 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList))
4108 rtTex->create();
4109 cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount,
4110 colorClearValue,
4111 depthStencilClearValue,
4112 rtD->colorAttCount,
4113 rtTex->m_desc.shadingRateMap());
4114 if (rtD->fb.preserveColor) {
4115 for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
4116 cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
4117 }
4118 if (rtD->dsAttCount && rtD->fb.preserveDs) {
4119 cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
4120 cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
4121 }
4122 int colorAttCount = 0;
4123 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
4124 it != itEnd; ++it)
4125 {
4126 colorAttCount += 1;
4127 if (it->texture()) {
4128 QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
4129 if (it->multiViewCount() >= 2)
4130 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(it->multiViewCount());
4131 } else if (it->renderBuffer()) {
4132 QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
4133 }
4134 if (it->resolveTexture())
4135 QRHI_RES(QMetalTexture, it->resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
4136 }
4137 if (rtTex->m_desc.depthStencilBuffer())
4138 QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
4139 if (rtTex->m_desc.depthTexture()) {
4140 QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture());
4141 depthTexture->lastActiveFrameSlot = currentFrameSlot;
4142 if (depthTexture->arraySize() >= 2) {
4143 const int depthLayer = rtTex->m_desc.depthLayer();
4144 if (depthLayer >= 0) {
4145 cbD->d->currentPassRpDesc.depthAttachment.slice = NSUInteger(depthLayer);
4146 cbD->d->currentPassRpDesc.stencilAttachment.slice = NSUInteger(depthLayer);
4147 if (colorAttCount == 0)
4148 cbD->d->currentPassRpDesc.renderTargetArrayLength = 1;
4149 } else if (colorAttCount == 0) {
4150 cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize());
4151 }
4152 }
4153 }
4154 if (rtTex->m_desc.depthResolveTexture())
4155 QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
4156 if (rtTex->m_desc.shadingRateMap())
4157 QRHI_RES(QMetalShadingRateMap, rtTex->m_desc.shadingRateMap())->lastActiveFrameSlot = currentFrameSlot;
4158 }
4159 break;
4160 default:
4161 Q_UNREACHABLE();
4162 break;
4163 }
4164
4165 cbD->d->deferredColorStoreActions.clear();
4166 cbD->d->deferredDepthStoreAction = MTLStoreActionUnknown;
4167 cbD->d->deferredStencilStoreAction = MTLStoreActionUnknown;
4168 for (uint i = 0; i < uint(rtD->colorAttCount); ++i) {
4169 cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
4170 cbD->d->currentPassRpDesc.colorAttachments[i].slice = NSUInteger(rtD->fb.colorAtt[i].arrayLayer);
4171 cbD->d->currentPassRpDesc.colorAttachments[i].depthPlane = NSUInteger(rtD->fb.colorAtt[i].slice);
4172 cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level);
4173 if (rtD->fb.colorAtt[i].resolveTex) {
4174 const MTLStoreAction storeAction = rtD->fb.preserveColor ? MTLStoreActionStoreAndMultisampleResolve
4175 : MTLStoreActionMultisampleResolve;
4176 // Defer, so that the multisample contents can be kept if the pass
4177 // ends up being interrupted. finalizeDeferredStoreActions() sets the
4178 // real action before the encoder ends.
4179 cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionUnknown;
4180 cbD->d->deferredColorStoreActions.append({ i, storeAction });
4181 cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
4182 cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer);
4183 cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel);
4184 }
4185 }
4186
4187 if (rtD->dsAttCount) {
4188 Q_ASSERT(rtD->fb.dsTex);
4189 cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
4190 cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
4191 if (rtD->fb.depthNeedsStore) { // Depth/Stencil is set to DontCare by default, override if needed
4192 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
4193 if (rtD->fb.hasStencil)
4194 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = MTLStoreActionStore;
4195 } else if (canStoreAttachment(rtD->fb.dsTex)) {
4196 // Would be discarded at the end of the pass, but an interruption in
4197 // the middle of it still has to be able to keep the contents. Defer,
4198 // so that nothing is stored unless that actually happens.
4199 cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionUnknown;
4200 cbD->d->deferredDepthStoreAction = MTLStoreActionDontCare;
4201 if (rtD->fb.hasStencil) {
4202 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = MTLStoreActionUnknown;
4203 cbD->d->deferredStencilStoreAction = MTLStoreActionDontCare;
4204 }
4205 }
4206 if (rtD->fb.dsResolveTex) {
4207 const MTLStoreAction dsStoreAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve
4208 : MTLStoreActionMultisampleResolve;
4209 // Deferred for the same reason as the color attachments above, but
4210 // only when there is something to defer to: a memoryless
4211 // depth-stencil buffer, which is what a QRhiRenderBuffer is on Apple
4212 // GPUs, cannot be stored at all, and rejects the store-and-resolve
4213 // action that an interruption would need.
4214 const bool deferrable = canStoreAttachment(rtD->fb.dsTex);
4215 cbD->d->currentPassRpDesc.depthAttachment.storeAction = deferrable ? MTLStoreActionUnknown
4216 : dsStoreAction;
4217 if (deferrable)
4218 cbD->d->deferredDepthStoreAction = dsStoreAction;
4219 cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex;
4220 if (rtD->fb.hasStencil) {
4221 cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex;
4222 cbD->d->currentPassRpDesc.stencilAttachment.storeAction = deferrable ? MTLStoreActionUnknown
4223 : dsStoreAction;
4224 if (deferrable)
4225 cbD->d->deferredStencilStoreAction = dsStoreAction;
4226 }
4227 }
4228 }
4229
4230 cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
4231
4233
4235 cbD->currentTarget = rt;
4236}
4237
4238void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4239{
4240 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4242
4244 [cbD->d->currentRenderPassEncoder endEncoding];
4245
4247 cbD->currentTarget = nullptr;
4248
4249 if (resourceUpdates)
4250 enqueueResourceUpdates(cb, resourceUpdates);
4251}
4252
4253void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb,
4254 QRhiResourceUpdateBatch *resourceUpdates,
4256{
4257 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4259
4260 if (resourceUpdates)
4261 enqueueResourceUpdates(cb, resourceUpdates);
4262
4263 cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
4266}
4267
4268void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4269{
4270 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4272
4273 [cbD->d->currentComputePassEncoder endEncoding];
4275
4276 if (resourceUpdates)
4277 enqueueResourceUpdates(cb, resourceUpdates);
4278}
4279
4280void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
4281{
4282 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4285
4287 cbD->currentGraphicsPipeline = nullptr;
4288 cbD->currentComputePipeline = psD;
4290
4291 [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
4292 }
4293
4294 psD->lastActiveFrameSlot = currentFrameSlot;
4295}
4296
4297void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
4298{
4299 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4302
4303 [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(NSUInteger(x), NSUInteger(y), NSUInteger(z))
4304 threadsPerThreadgroup: psD->d->localSize];
4305}
4306
4307void QRhiMetal::dispatchIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
4308 quint32 indirectBufferOffset)
4309{
4310 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4313
4314 QMetalBuffer *indirectBufD = QRHI_RES(QMetalBuffer, indirectBuffer);
4316 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
4317 id<MTLBuffer> indirectBufMtl = indirectBufD->d->buf[indirectBufD->d->slotted ? currentFrameSlot : 0];
4318
4319 // dispatchThreadgroups still wants threadsPerThreadgroup explicitly; the
4320 // indirect buffer only supplies the grid size in threadgroups. The
4321 // threads-per-threadgroup value comes from the bound compute pipeline
4322 // (set in QMetalComputePipeline::create() from the SPIR-V shader's
4323 // local_size_x/y/z layout qualifiers).
4324 [cbD->d->currentComputePassEncoder
4325 dispatchThreadgroupsWithIndirectBuffer: indirectBufMtl
4326 indirectBufferOffset: indirectBufferOffset
4327 threadsPerThreadgroup: psD->d->localSize];
4328}
4329
4330void QRhiMetal::drawIndirectCount(QRhiCommandBuffer *cb,
4331 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
4332 QRhiBuffer *countBuffer, quint32 countBufferOffset,
4333 quint32 maxDrawCount, quint32 stride)
4334{
4335 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4337
4338 // Implemented on top of indirect command buffers. There can be no CPU-side
4339 // fallback, the count is only known to the device.
4340 if (const char *reason = icbUnavailableReason(cbD)) {
4341 qWarning("drawIndirectCount is not available because %s; skipping", reason);
4342 return;
4343 }
4344
4345 icbDraw(cbD, false, QRHI_RES(QMetalBuffer, indirectBuffer), indirectBufferOffset,
4346 QRHI_RES(QMetalBuffer, countBuffer), countBufferOffset, maxDrawCount, stride);
4347}
4348
4349void QRhiMetal::drawIndexedIndirectCount(QRhiCommandBuffer *cb,
4350 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
4351 QRhiBuffer *countBuffer, quint32 countBufferOffset,
4352 quint32 maxDrawCount, quint32 stride)
4353{
4354 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4356
4357 // Implemented on top of indirect command buffers. There can be no CPU-side
4358 // fallback, the count is only known to the device.
4359 if (const char *reason = icbUnavailableReason(cbD)) {
4360 qWarning("drawIndexedIndirectCount is not available because %s; skipping", reason);
4361 return;
4362 }
4363
4364 if (!cbD->currentIndexBuffer) {
4365 qWarning("drawIndexedIndirectCount called without an index buffer bound; skipping");
4366 return;
4367 }
4368
4369 icbDraw(cbD, true, QRHI_RES(QMetalBuffer, indirectBuffer), indirectBufferOffset,
4370 QRHI_RES(QMetalBuffer, countBuffer), countBufferOffset, maxDrawCount, stride);
4371}
4372
4373static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t);
4374
4376{
4390
4391 enum class Fill { None, Cpu, Gpu };
4393 bool slotSetupFailed = false;
4394 bool created = false;
4395
4396 // Set by buildIndirect(), which makes the CPU-side recording irrelevant:
4397 // gpuFilled means the kernel encoded into frameSlots[builtSlot], fallbackBuild
4398 // that there was no ICB to encode into and executeIndirect() has to use the
4399 // plain indirect draw entry points.
4400 bool gpuFilled = false;
4401 bool fallbackBuild = false;
4402 QRhiIndirectCommandBufferBuildInfo buildInfo;
4403 int builtSlot = -1;
4404};
4405
4407{
4408 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
4409 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[i]);
4410 if (slot.icb) {
4414 e.stagingIcbBuffer.icb = slot.icb;
4415 e.stagingIcbBuffer.argBuffer = slot.argBuffer;
4416 rhiD->d->releaseQueue.append(e);
4417 }
4418 if (slot.rangeBuffer) {
4422 e.stagingBuffer.buffer = slot.rangeBuffer;
4423 rhiD->d->releaseQueue.append(e);
4424 }
4425 slot = {};
4426 }
4428}
4429
4432{
4434
4435 if (icbD->d->slotSetupFailed)
4436 return false;
4437
4438 if (icbD->d->fill == fill)
4439 return icbD->d->frameSlots[0].icb != nil;
4440
4443 icbD->d->gpuFilled = false;
4444 icbD->d->builtSlot = -1;
4445 }
4446
4447 if (!rhiD->caps.indirectCommandBuffers)
4448 return false;
4449
4450 const bool gpu = fill == QMetalIndirectCommandBufferData::Fill::Gpu;
4451 if (gpu && !rhiD->prepareIcbKernels())
4452 return false;
4453
4454 MTLIndirectCommandBufferDescriptor *icbDesc = [MTLIndirectCommandBufferDescriptor new];
4455 icbDesc.commandTypes = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws
4456 ? MTLIndirectCommandTypeDrawIndexed : MTLIndirectCommandTypeDraw;
4457 // All state except the primitive type and the index buffer is inherited.
4458 icbDesc.inheritPipelineState = YES;
4459 icbDesc.inheritBuffers = YES;
4460 icbDesc.maxVertexBufferBindCount = 0;
4461 icbDesc.maxFragmentBufferBindCount = 0;
4462
4463 bool ok = true;
4464 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT && ok; ++i) {
4465 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[i]);
4466 slot.icb = [rhiD->d->dev newIndirectCommandBufferWithDescriptor:icbDesc
4467 maxCommandCount:icbD->maxCommandCount()
4468 options:gpu ? MTLResourceStorageModePrivate
4469 : MTLResourceStorageModeShared];
4470 if (!slot.icb) {
4471 qWarning("Failed to create MTLIndirectCommandBuffer");
4472 ok = false;
4473 break;
4474 }
4475 if (gpu) {
4476 slot.rangeBuffer = [rhiD->d->dev newBufferWithLength:sizeof(MTLIndirectCommandBufferExecutionRange)
4477 options:MTLResourceStorageModePrivate];
4478 id<MTLArgumentEncoder> argEnc = [rhiD->d->icbEncodeFunction newArgumentEncoderWithBufferIndex:1];
4479 slot.argBuffer = [rhiD->d->dev newBufferWithLength:argEnc.encodedLength
4480 options:MTLResourceStorageModeShared];
4481 if (slot.rangeBuffer && slot.argBuffer) {
4482 [argEnc setArgumentBuffer:slot.argBuffer offset:0];
4483 [argEnc setIndirectCommandBuffer:slot.icb atIndex:0];
4484 } else {
4485 qWarning("Failed to create MTLIndirectCommandBuffer helper buffers");
4486 ok = false;
4487 }
4488 [argEnc release];
4489 if (!ok)
4490 break;
4491 }
4492 }
4493 [icbDesc release];
4494
4495 if (!ok) {
4496 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
4497 [icbD->d->frameSlots[i].icb release];
4498 [icbD->d->frameSlots[i].rangeBuffer release];
4499 [icbD->d->frameSlots[i].argBuffer release];
4500 icbD->d->frameSlots[i] = {};
4501 }
4502 icbD->d->slotSetupFailed = true;
4503 return false;
4504 }
4505
4506 icbD->d->fill = fill;
4507 return true;
4508}
4509
4511 quint32 maxCommandCount)
4514{
4515}
4516
4522
4524{
4525 clear();
4526
4527 if (!d->created)
4528 return;
4529
4530 d->slotSetupFailed = false;
4531 d->gpuFilled = false;
4532 d->fallbackBuild = false;
4533 d->buildInfo = {};
4534 d->builtSlot = -1;
4535 d->created = false;
4536 m_gpuBuilt = false;
4537 m_gpuBuiltCommandCount = 0;
4538
4539 QRHI_RES_RHI(QRhiMetal);
4540 if (rhiD) {
4542 rhiD->unregisterResource(this);
4543 }
4544}
4545
4547{
4548 if (d->created)
4549 destroy();
4550 else
4551 clear();
4552
4553 if (!m_maxCommandCount) {
4554 qWarning("QRhiIndirectCommandBuffer: maxCommandCount is 0");
4555 return false;
4556 }
4557
4558 QRHI_RES_RHI(QRhiMetal);
4560 d->created = true;
4561 rhiD->registerResource(this);
4562 return true;
4563}
4564
4565QRhiIndirectCommandBuffer *QRhiMetal::createIndirectCommandBuffer(QRhiIndirectCommandBuffer::Type type,
4566 quint32 maxCommandCount)
4567{
4568 return new QMetalIndirectCommandBuffer(this, type, maxCommandCount);
4569}
4570
4571void QRhiMetal::commitIndirectCommandBuffer(QRhiResourceUpdateBatch *u,
4572 QRhiIndirectCommandBuffer *icb)
4573{
4574 // Nothing to upload: executeIndirect() encodes straight into the ICB, and
4575 // only there are the primitive type and index buffer known.
4576 Q_UNUSED(u);
4577 Q_UNUSED(icb);
4578}
4579
4580// True when the slot holds an encoding that is already good for these
4581// contents and this state, and so does not need to be re-encoded.
4584 MTLPrimitiveType primitiveType,
4585 id<MTLBuffer> indexBufMtl, quint32 indexOffset,
4586 QRhiCommandBuffer::IndexFormat indexFormat)
4587{
4588 const bool indexed = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4589
4590 return slot.encoded
4591 && slot.generation == icbD->contentsGeneration()
4592 && slot.primitiveType == primitiveType
4593 && (!indexed || (slot.indexBuf == indexBufMtl
4594 && slot.indexOffset == indexOffset
4595 && slot.indexFormat == indexFormat));
4596}
4597
4598// Re-encodes the CPU-recorded commands unless the slot already matches.
4601 MTLPrimitiveType primitiveType,
4602 id<MTLBuffer> indexBufMtl, quint32 indexOffset,
4603 QRhiCommandBuffer::IndexFormat indexFormat)
4604{
4605 const bool indexed = icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4606
4607 if (qrhimtl_icbSlotMatches(icbD, slot, primitiveType, indexBufMtl, indexOffset, indexFormat))
4608 return;
4609
4610 const quint32 count = icbD->recordedCommandCount();
4611 if (indexed) {
4612 const MTLIndexType indexType = indexFormat == QRhiCommandBuffer::IndexUInt16
4613 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
4614 const quint32 indexSize = indexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4;
4616 for (quint32 i = 0; i < count; ++i) {
4617 const QRhiIndexedIndirectDrawCommand &c(cmds[i]);
4618 id<MTLIndirectRenderCommand> rc = [slot.icb indirectRenderCommandAtIndex:i];
4619 [rc drawIndexedPrimitives:primitiveType
4620 indexCount:c.indexCount
4621 indexType:indexType
4622 indexBuffer:indexBufMtl
4623 indexBufferOffset:indexOffset + c.firstIndex * indexSize
4624 instanceCount:c.instanceCount
4625 baseVertex:c.vertexOffset
4626 baseInstance:c.firstInstance];
4627 }
4628 } else {
4629 const QRhiIndirectDrawCommand *cmds = icbD->drawCommands();
4630 for (quint32 i = 0; i < count; ++i) {
4631 const QRhiIndirectDrawCommand &c(cmds[i]);
4632 id<MTLIndirectRenderCommand> rc = [slot.icb indirectRenderCommandAtIndex:i];
4633 [rc drawPrimitives:primitiveType
4634 vertexStart:c.firstVertex
4635 vertexCount:c.vertexCount
4636 instanceCount:c.instanceCount
4637 baseInstance:c.firstInstance];
4638 }
4639 }
4640
4641 // Commands past the current count may be left over from a longer batch.
4642 if (count < icbD->maxCommandCount())
4643 [slot.icb resetWithRange:NSMakeRange(count, icbD->maxCommandCount() - count)];
4644
4645 slot.generation = icbD->contentsGeneration();
4646 slot.primitiveType = primitiveType;
4647 slot.indexBuf = indexBufMtl;
4648 slot.indexOffset = indexOffset;
4649 slot.indexFormat = indexFormat;
4650 slot.encoded = true;
4651}
4652
4653// Fallback for when there is no usable ICB.
4655 quint32 firstCommand, quint32 count,
4656 int currentFrameSlot)
4657{
4658 const MTLPrimitiveType primitiveType = cbD->currentGraphicsPipeline->d->primitiveType;
4659
4660 if (icbD->type() == QRhiIndirectCommandBuffer::IndexedDraws) {
4661 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
4662 if (!indexBufD)
4663 return;
4664 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4665 const MTLIndexType indexType = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16
4666 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
4667 const quint32 indexSize = cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4;
4669 for (quint32 i = 0; i < count; ++i) {
4670 const QRhiIndexedIndirectDrawCommand &c(cmds[firstCommand + i]);
4671 [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: primitiveType
4672 indexCount: c.indexCount
4673 indexType: indexType
4674 indexBuffer: indexBufMtl
4675 indexBufferOffset: cbD->currentIndexOffset + c.firstIndex * indexSize
4676 instanceCount: c.instanceCount
4677 baseVertex: c.vertexOffset
4678 baseInstance: c.firstInstance];
4679 }
4680 } else {
4681 const QRhiIndirectDrawCommand *cmds = icbD->drawCommands();
4682 for (quint32 i = 0; i < count; ++i) {
4683 const QRhiIndirectDrawCommand &c(cmds[firstCommand + i]);
4684 [cbD->d->currentRenderPassEncoder drawPrimitives: primitiveType
4685 vertexStart: c.firstVertex
4686 vertexCount: c.vertexCount
4687 instanceCount: c.instanceCount
4688 baseInstance: c.firstInstance];
4689 }
4690 }
4691}
4692
4693void QRhiMetal::executeIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb,
4694 quint32 firstCommand, quint32 commandCount)
4695{
4696 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4698
4699 QMetalIndirectCommandBuffer *icbD = QRHI_RES(QMetalIndirectCommandBuffer, icb);
4700 icbD->lastActiveFrameSlot = currentFrameSlot;
4701
4702 const bool indexed = icb->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4703
4704 if (icbD->d->gpuFilled) {
4705 if (icbD->d->buildInfo.topology != cbD->currentGraphicsPipeline->topology()) {
4706 qWarning("executeIndirect: the indirect command buffer was built for a different "
4707 "topology than the current graphics pipeline uses; skipping");
4708 return;
4709 }
4710 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[icbD->d->builtSlot]);
4711
4712 const quint32 total = icbD->commandCount();
4713 const bool deviceCount = icbD->d->buildInfo.countBuffer != nullptr;
4714 NSRange range = NSMakeRange(0, total);
4715 if (deviceCount) {
4716 // The count is only known to the device, and applying it is what
4717 // the compute kernel-written range is for. That form of
4718 // executeCommandsInBuffer takes no CPU-side range, so a subrange
4719 // and a device-side count cannot be combined.
4720 if (firstCommand != 0 || commandCount < total) {
4721 qWarning("executeIndirect: firstCommand and commandCount cannot be honoured "
4722 "together with a device-side count; executing all %u command(s)", total);
4723 }
4724 } else {
4725 if (firstCommand >= total)
4726 return;
4727 const quint32 count = qMin(commandCount, total - firstCommand);
4728 if (!count)
4729 return;
4730 range = NSMakeRange(firstCommand, count);
4731 }
4732
4733 if (indexed && icbD->d->buildInfo.indexBuffer) {
4734 QMetalBuffer *indexBufD = QRHI_RES(QMetalBuffer, icbD->d->buildInfo.indexBuffer);
4735 id<MTLBuffer> indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4736 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
4737 usage:MTLResourceUsageRead
4738 stages:MTLRenderStageVertex | MTLRenderStageFragment];
4739 }
4740 if (deviceCount) {
4741 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb
4742 indirectBuffer:slot.rangeBuffer
4743 indirectBufferOffset:0];
4744 } else {
4745 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb withRange:range];
4746 }
4747 return;
4748 }
4749
4750 if (icbD->d->fallbackBuild) {
4751 const QRhiIndirectCommandBufferBuildInfo &info(icbD->d->buildInfo);
4752 if (info.countBuffer) {
4753 qWarning("executeIndirect: a device-side count needs an indirect command buffer, "
4754 "which is not available here; skipping");
4755 return;
4756 }
4757 const quint32 canonicalStride = indexed ? sizeof(QRhiIndexedIndirectDrawCommand)
4758 : sizeof(QRhiIndirectDrawCommand);
4759 const quint32 stride = info.stride ? info.stride : canonicalStride;
4760 const quint32 total = icbD->commandCount();
4761 if (firstCommand >= total)
4762 return;
4763 const quint32 count = qMin(commandCount, total - firstCommand);
4764 if (!count)
4765 return;
4766 const quint32 offset = info.sourceBufferOffset + firstCommand * stride;
4767 if (indexed)
4768 drawIndexedIndirect(cb, info.sourceBuffer, offset, count, stride);
4769 else
4770 drawIndirect(cb, info.sourceBuffer, offset, count, stride);
4771 return;
4772 }
4773
4774 const quint32 total = icbD->recordedCommandCount();
4775 if (firstCommand >= total)
4776 return;
4777 const quint32 count = qMin(commandCount, total - firstCommand);
4778 if (!count)
4779 return;
4780
4783 {
4784 qrhimtl_replayIcbOnCpu(cbD, icbD, firstCommand, count, currentFrameSlot);
4785 return;
4786 }
4787 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[currentFrameSlot]);
4788
4789 id<MTLBuffer> indexBufMtl = nil;
4790 quint32 indexOffset = 0;
4791 if (indexed) {
4792 QMetalBuffer *indexBufD = cbD->currentIndexBuffer;
4793 if (!indexBufD)
4794 return;
4795 indexBufD->lastActiveFrameSlot = currentFrameSlot;
4796 indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4797 indexOffset = cbD->currentIndexOffset;
4798 }
4799
4800 const MTLPrimitiveType primitiveType = cbD->currentGraphicsPipeline->d->primitiveType;
4801 if (slot.usedInFrameId == d->globalFrameId
4802 && !qrhimtl_icbSlotMatches(icbD, slot, primitiveType, indexBufMtl, indexOffset,
4803 cbD->currentIndexFormat))
4804 {
4805 // An executeCommandsInBuffer recorded earlier in this frame references
4806 // this slot, and re-encoding it now would change what that one executes
4807 // once submitted. Replaying as ordinary draws keeps both correct.
4808 qWarning("executeIndirect: the same indirect command buffer is executed more than once "
4809 "in a frame, with different contents, topology or index buffer state; "
4810 "falling back to individual draw calls");
4811 qrhimtl_replayIcbOnCpu(cbD, icbD, firstCommand, count, currentFrameSlot);
4812 return;
4813 }
4814
4815 qrhimtl_encodeIcbFromCpu(icbD, slot, primitiveType,
4816 indexBufMtl, indexOffset, cbD->currentIndexFormat);
4817
4818 if (indexed) {
4819 // Index buffer is not inherited from the encoder, and the ICB commands reference it directly,
4820 // so it needs the useResource.
4821 [cbD->d->currentRenderPassEncoder useResource:indexBufMtl
4822 usage:MTLResourceUsageRead
4823 stages:MTLRenderStageVertex | MTLRenderStageFragment];
4824 }
4825 [cbD->d->currentRenderPassEncoder executeCommandsInBuffer:slot.icb
4826 withRange:NSMakeRange(firstCommand, count)];
4827 slot.usedInFrameId = d->globalFrameId;
4828}
4829
4830void QRhiMetal::buildIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb,
4831 const QRhiIndirectCommandBufferBuildInfo &info)
4832{
4833 QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
4835
4836 QMetalIndirectCommandBuffer *icbD = QRHI_RES(QMetalIndirectCommandBuffer, icb);
4837 icbD->lastActiveFrameSlot = currentFrameSlot;
4838
4839 const bool indexed = icb->type() == QRhiIndirectCommandBuffer::IndexedDraws;
4840
4841 if (indexed && !info.indexBuffer) {
4842 qWarning("buildIndirect: an IndexedDraws indirect command buffer needs an "
4843 "index buffer in QRhiIndirectCommandBufferBuildInfo; skipping");
4844 return;
4845 }
4846
4847 quint32 count = info.commandCount ? info.commandCount : icbD->m_maxCommandCount;
4848 if (count > icbD->m_maxCommandCount) {
4849 qWarning("QRhiIndirectCommandBuffer: buildIndirect() with commandCount %u exceeds "
4850 "maxCommandCount %u; clamping", count, icbD->m_maxCommandCount);
4851 count = icbD->m_maxCommandCount;
4852 }
4853 icbD->m_gpuBuilt = true;
4854 icbD->m_gpuBuiltCommandCount = count;
4855
4857 icbD->d->gpuFilled = false;
4858 icbD->d->fallbackBuild = true;
4859 icbD->d->buildInfo = info;
4860 icbD->d->builtSlot = -1;
4861 return;
4862 }
4863
4864 QMetalIndirectCommandBufferData::Slot &slot(icbD->d->frameSlots[currentFrameSlot]);
4865
4866 QMetalBuffer *srcBufD = QRHI_RES(QMetalBuffer, info.sourceBuffer);
4868 srcBufD->lastActiveFrameSlot = currentFrameSlot;
4869 id<MTLBuffer> srcBufMtl = srcBufD->d->buf[srcBufD->d->slotted ? currentFrameSlot : 0];
4870
4871 id<MTLBuffer> indexBufMtl = nil;
4872 if (indexed) {
4873 QMetalBuffer *indexBufD = QRHI_RES(QMetalBuffer, info.indexBuffer);
4874 indexBufD->lastActiveFrameSlot = currentFrameSlot;
4875 indexBufMtl = indexBufD->d->buf[indexBufD->d->slotted ? currentFrameSlot : 0];
4876 }
4877
4878 id<MTLBuffer> countBufMtl = nil;
4879 if (info.countBuffer) {
4880 QMetalBuffer *countBufD = QRHI_RES(QMetalBuffer, info.countBuffer);
4882 countBufD->lastActiveFrameSlot = currentFrameSlot;
4883 countBufMtl = countBufD->d->buf[countBufD->d->slotted ? currentFrameSlot : 0];
4884 }
4885
4886 const quint32 stride = info.stride ? info.stride
4887 : (indexed ? sizeof(QRhiIndexedIndirectDrawCommand)
4888 : sizeof(QRhiIndirectDrawCommand));
4889
4890 // Outside a render pass, so the compute encoder costs no interruption, no
4891 // store action juggling and no per-pass state to restore.
4892 id<MTLComputeCommandEncoder> computeEncoder = [cbD->d->cb computeCommandEncoder];
4893 encodeIcbWithCompute(d, computeEncoder, slot.icb, slot.argBuffer, slot.rangeBuffer,
4894 indexed, info.indexFormat,
4895 toMetalPrimitiveType(info.topology),
4896 srcBufMtl, info.sourceBufferOffset,
4897 indexBufMtl, info.indexBufferOffset,
4898 countBufMtl, info.countBufferOffset,
4899 icbD->commandCount(), stride);
4900 [computeEncoder endEncoding];
4901
4902 icbD->d->gpuFilled = true;
4903 icbD->d->fallbackBuild = false;
4904 icbD->d->buildInfo = info;
4905 icbD->d->builtSlot = currentFrameSlot;
4906 // The CPU-encoded contents of this slot, if any, are gone now.
4907 slot.encoded = false;
4908}
4909
4911{
4912 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
4913 [e.buffer.buffers[i] release];
4914}
4915
4917{
4918 [e.renderbuffer.texture release];
4919}
4920
4922{
4923 [e.texture.texture release];
4924 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
4925 [e.texture.stagingBuffers[i] release];
4926 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
4927 [e.texture.views[i] release];
4928 [e.texture.samplingView release];
4929 [e.texture.writeView release];
4930}
4931
4933{
4934 [e.sampler.samplerState release];
4935}
4936
4938{
4939 qsizetype keepBegin = d->releaseQueue.size();
4940 for (qsizetype i = keepBegin - 1; i >= 0; --i) {
4941 const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
4942 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
4943 switch (e.type) {
4946 break;
4949 break;
4952 break;
4955 break;
4956 case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
4957 [e.stagingBuffer.buffer release];
4958 break;
4959 case QRhiMetalData::DeferredReleaseEntry::GraphicsPipeline:
4960 [e.graphicsPipeline.pipelineState release];
4961 [e.graphicsPipeline.depthStencilState release];
4962 [e.graphicsPipeline.tessVertexComputeState[0] release];
4963 [e.graphicsPipeline.tessVertexComputeState[1] release];
4964 [e.graphicsPipeline.tessVertexComputeState[2] release];
4965 [e.graphicsPipeline.tessTessControlComputeState release];
4966 break;
4967 case QRhiMetalData::DeferredReleaseEntry::ComputePipeline:
4968 [e.computePipeline.pipelineState release];
4969 break;
4970 case QRhiMetalData::DeferredReleaseEntry::ShadingRateMap:
4971 [e.shadingRateMap.rateMap release];
4972 break;
4973 case QRhiMetalData::DeferredReleaseEntry::StagingIcbBuffer:
4974 [e.stagingIcbBuffer.icb release];
4975 [e.stagingIcbBuffer.argBuffer release];
4976 break;
4977 default:
4978 break;
4979 }
4980 } else if (--keepBegin != i) {
4981 d->releaseQueue[keepBegin] = std::move(d->releaseQueue[i]);
4982 }
4983 }
4984 if (keepBegin)
4985 d->releaseQueue.remove(0, keepBegin);
4986}
4987
4989{
4990 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
4991
4992 for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) {
4993 const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]);
4994 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
4995 readback.result->format = readback.format;
4996 readback.result->pixelSize = readback.pixelSize;
4997 readback.result->data.resize(int(readback.bufSize));
4998 const void *p = [readback.buf contents];
4999 if (readback.depthPlaneByteSize) {
5000 // The staging buffer holds a densely packed depth plane, followed
5001 // by a densely packed stencil plane when the format has stencil
5002 // (see enqueueResourceUpdates()). Convert into the layout QRhi
5003 // guarantees: D16 is a 16-bit unorm value, D24 and D24S8 have the
5004 // depth in the upper 24 bits and the stencil (or zero) in the lower
5005 // 8 bits, and D32FS8 is the float depth followed by a 32-bit word
5006 // with the stencil in its low 8 bits.
5007 const quint32 pixelCount = quint32(readback.pixelSize.width()) * quint32(readback.pixelSize.height());
5008 const char *depthPlane = static_cast<const char *>(p);
5009 const quint8 *stencilPlane = reinterpret_cast<const quint8 *>(depthPlane + readback.depthPlaneByteSize);
5010 char *dst = readback.result->data.data();
5011 for (quint32 px = 0; px < pixelCount; ++px) {
5012 const quint32 depthWord = qFromUnaligned<quint32>(depthPlane + px * 4);
5013 const float depthF = readback.depthPlaneIsFloat ? qFromUnaligned<float>(depthPlane + px * 4) : 0.0f;
5014 const quint32 stencil = readback.stencilPlaneByteSize ? stencilPlane[px] : 0;
5015 switch (readback.format) {
5016 case QRhiTexture::D16:
5017 qToUnaligned(quint16(qRound(qBound(0.0f, depthF, 1.0f) * 65535.0f)), dst + px * 2);
5018 break;
5019 case QRhiTexture::D32FS8:
5020 qToUnaligned(depthWord, dst + px * 8);
5021 qToUnaligned(stencil, dst + px * 8 + 4);
5022 break;
5023 default: // D24, D24S8
5024 {
5025 const quint32 depth24 = readback.depthPlaneIsFloat
5026 ? quint32(qBound(0.0f, depthF, 1.0f) * 16777215.0f + 0.5f)
5027 : depthWord & 0x00FFFFFFu;
5028 qToUnaligned((depth24 << 8) | stencil, dst + px * 4);
5029 }
5030 break;
5031 }
5032 }
5033 } else {
5034 memcpy(readback.result->data.data(), p, readback.bufSize);
5035 }
5036 [readback.buf release];
5037
5038 if (readback.result->completed)
5039 completedCallbacks.append(readback.result->completed);
5040
5041 d->activeTextureReadbacks.remove(i);
5042 }
5043 }
5044
5045 for (int i = d->activeBufferReadbacks.count() - 1; i >= 0; --i) {
5046 const QRhiMetalData::BufferReadback &readback(d->activeBufferReadbacks[i]);
5047 if (forced || currentFrameSlot == readback.activeFrameSlot
5048 || readback.activeFrameSlot < 0) {
5049 readback.result->data.resize(readback.readSize);
5050 char *p = reinterpret_cast<char *>([readback.buf contents]);
5051 Q_ASSERT(p);
5052 memcpy(readback.result->data.data(), p, size_t(readback.readSize));
5053 [readback.buf release];
5054
5055 if (readback.result->completed)
5056 completedCallbacks.append(readback.result->completed);
5057
5058 d->activeBufferReadbacks.remove(i);
5059 }
5060 }
5061
5062 for (auto f : completedCallbacks)
5063 f();
5064}
5065
5066QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
5068 d(new QMetalBufferData)
5069{
5070 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
5071 d->buf[i] = nil;
5072}
5073
5075{
5076 destroy();
5077 delete d;
5078}
5079
5081{
5082 if (!d->buf[0])
5083 return;
5084
5088
5089 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5090 e.buffer.buffers[i] = d->buf[i];
5091 d->buf[i] = nil;
5092 d->pendingUpdates[i].clear();
5093 }
5094
5095 QRHI_RES_RHI(QRhiMetal);
5096 if (rhiD) {
5097 rhiD->d->releaseQueue.append(e);
5098 rhiD->unregisterResource(this);
5099 }
5100}
5101
5103{
5104 if (d->buf[0])
5105 destroy();
5106
5107 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
5108 qWarning("StorageBuffer cannot be combined with Dynamic");
5109 return false;
5110 }
5111
5112 const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
5113 const quint32 roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256u) : nonZeroSize;
5114
5115 d->managed = false;
5116 d->isDeviceLocal = false;
5117 MTLResourceOptions opts = MTLResourceStorageModeShared;
5118
5119 QRHI_RES_RHI(QRhiMetal);
5120
5121 const bool internalHostWritable = (int(m_usage) & (WorkBufPoolUsage | InternalHostWritable)) != 0;
5122
5123 if (m_type != Dynamic && !internalHostWritable && rhiD->caps.usePrivateStaticBuffers) {
5124 opts = MTLResourceStorageModePrivate;
5125 d->isDeviceLocal = true;
5126 d->slotted = false;
5127 } else {
5128#ifdef Q_OS_MACOS
5129 if (!rhiD->caps.isAppleGPU && m_type != Dynamic) {
5130 opts = MTLResourceStorageModeManaged;
5131 d->managed = true;
5132 }
5133#endif
5134 // Have QMTL_FRAMES_IN_FLIGHT versions regardless of the type. This is
5135 // because writing to a host visible buffer is not safe when another
5136 // frame reading from the same buffer is still in flight.
5137 d->slotted = !m_usage.testFlag(QRhiBuffer::StorageBuffer) // except for SSBOs written in the shader
5138 && !internalHostWritable; // and the internal buffers
5139 }
5140
5141 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5142 if (i == 0 || d->slotted) {
5143 d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
5144 if (!m_objectName.isEmpty()) {
5145 if (!d->slotted) {
5146 d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
5147 } else {
5148 const QByteArray name = m_objectName + '/' + QByteArray::number(i);
5149 d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
5150 }
5151 }
5152 }
5153 }
5154
5156 generation += 1;
5157 rhiD->registerResource(this);
5158 return true;
5159}
5160
5162{
5163 if (d->slotted) {
5164 NativeBuffer b;
5165 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT));
5166 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5167 QRHI_RES_RHI(QRhiMetal);
5169 b.objects[i] = &d->buf[i];
5170 }
5171 b.slotCount = QMTL_FRAMES_IN_FLIGHT;
5172 return b;
5173 }
5174 if (!d->isDeviceLocal) {
5175 QRHI_RES_RHI(QRhiMetal);
5177 }
5178 return { { &d->buf[0] }, 1 };
5179}
5180
5182{
5183 // Shortcut the entire buffer update mechanism and allow the client to do
5184 // the host writes directly to the buffer. This will lead to unexpected
5185 // results when combined with QRhiResourceUpdateBatch-based updates for the
5186 // buffer, but provides a fast path for dynamic buffers that have all their
5187 // content changed in every frame.
5188 Q_ASSERT(m_type == Dynamic);
5189 Q_ASSERT(!d->isDeviceLocal);
5190 QRHI_RES_RHI(QRhiMetal);
5191 Q_ASSERT(rhiD->inFrame);
5192 const int slot = d->slotted ? rhiD->currentFrameSlot : 0;
5193 void *p = [d->buf[slot] contents];
5194 return static_cast<char *>(p);
5195}
5196
5198{
5199 // Nothing to do: managed is never set for Dynamic buffers, and so there is
5200 // no didModifyRange: to issue.
5201}
5202
5203static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags, const QRhiMetal *d)
5204{
5205#ifndef Q_OS_MACOS
5206 Q_UNUSED(d);
5207#endif
5208
5209 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
5210 switch (format) {
5211 case QRhiTexture::RGBA8:
5212 return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
5213 case QRhiTexture::BGRA8:
5214 return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
5215 case QRhiTexture::R8:
5216#ifdef Q_OS_MACOS
5217 return MTLPixelFormatR8Unorm;
5218#else
5219 return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
5220#endif
5221 case QRhiTexture::R8SI:
5222 return MTLPixelFormatR8Sint;
5223 case QRhiTexture::R8UI:
5224 return MTLPixelFormatR8Uint;
5225 case QRhiTexture::RG8:
5226#ifdef Q_OS_MACOS
5227 return MTLPixelFormatRG8Unorm;
5228#else
5229 return srgb ? MTLPixelFormatRG8Unorm_sRGB : MTLPixelFormatRG8Unorm;
5230#endif
5231 case QRhiTexture::R16:
5232 return MTLPixelFormatR16Unorm;
5233 case QRhiTexture::RG16:
5234 return MTLPixelFormatRG16Unorm;
5235 case QRhiTexture::RED_OR_ALPHA8:
5236 return MTLPixelFormatR8Unorm;
5237
5238 case QRhiTexture::RGBA16F:
5239 return MTLPixelFormatRGBA16Float;
5240 case QRhiTexture::RGBA32F:
5241 return MTLPixelFormatRGBA32Float;
5242 case QRhiTexture::R16F:
5243 return MTLPixelFormatR16Float;
5244 case QRhiTexture::R32F:
5245 return MTLPixelFormatR32Float;
5246
5247 case QRhiTexture::RGB10A2:
5248 return MTLPixelFormatRGB10A2Unorm;
5249
5250 case QRhiTexture::R32SI:
5251 return MTLPixelFormatR32Sint;
5252 case QRhiTexture::R32UI:
5253 return MTLPixelFormatR32Uint;
5254 case QRhiTexture::RG32SI:
5255 return MTLPixelFormatRG32Sint;
5256 case QRhiTexture::RG32UI:
5257 return MTLPixelFormatRG32Uint;
5258 case QRhiTexture::RGBA32SI:
5259 return MTLPixelFormatRGBA32Sint;
5260 case QRhiTexture::RGBA32UI:
5261 return MTLPixelFormatRGBA32Uint;
5262
5263#ifdef Q_OS_MACOS
5264 case QRhiTexture::D16:
5265 return MTLPixelFormatDepth16Unorm;
5266 case QRhiTexture::D24:
5267 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
5268 case QRhiTexture::D24S8:
5269 return [d->d->dev isDepth24Stencil8PixelFormatSupported] ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
5270#else
5271 case QRhiTexture::D16:
5272 return MTLPixelFormatDepth32Float;
5273 case QRhiTexture::D24:
5274 return MTLPixelFormatDepth32Float;
5275 case QRhiTexture::D24S8:
5276 return MTLPixelFormatDepth32Float_Stencil8;
5277#endif
5278 case QRhiTexture::D32F:
5279 return MTLPixelFormatDepth32Float;
5280 case QRhiTexture::D32FS8:
5281 return MTLPixelFormatDepth32Float_Stencil8;
5282
5283#ifdef Q_OS_MACOS
5284 case QRhiTexture::BC1:
5285 return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
5286 case QRhiTexture::BC2:
5287 return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
5288 case QRhiTexture::BC3:
5289 return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
5290 case QRhiTexture::BC4:
5291 return MTLPixelFormatBC4_RUnorm;
5292 case QRhiTexture::BC5:
5293 qWarning("QRhiMetal does not support BC5");
5294 return MTLPixelFormatInvalid;
5295 case QRhiTexture::BC6H:
5296 return MTLPixelFormatBC6H_RGBUfloat;
5297 case QRhiTexture::BC7:
5298 return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
5299#else
5300 case QRhiTexture::BC1:
5301 case QRhiTexture::BC2:
5302 case QRhiTexture::BC3:
5303 case QRhiTexture::BC4:
5304 case QRhiTexture::BC5:
5305 case QRhiTexture::BC6H:
5306 case QRhiTexture::BC7:
5307 qWarning("QRhiMetal: BCx compression not supported on this platform");
5308 return MTLPixelFormatInvalid;
5309#endif
5310
5311#ifndef Q_OS_MACOS
5312 case QRhiTexture::ETC2_RGB8:
5313 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
5314 case QRhiTexture::ETC2_RGB8A1:
5315 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
5316 case QRhiTexture::ETC2_RGBA8:
5317 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
5318
5319 case QRhiTexture::ASTC_4x4:
5320 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
5321 case QRhiTexture::ASTC_5x4:
5322 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
5323 case QRhiTexture::ASTC_5x5:
5324 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
5325 case QRhiTexture::ASTC_6x5:
5326 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
5327 case QRhiTexture::ASTC_6x6:
5328 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
5329 case QRhiTexture::ASTC_8x5:
5330 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
5331 case QRhiTexture::ASTC_8x6:
5332 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
5333 case QRhiTexture::ASTC_8x8:
5334 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
5335 case QRhiTexture::ASTC_10x5:
5336 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
5337 case QRhiTexture::ASTC_10x6:
5338 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
5339 case QRhiTexture::ASTC_10x8:
5340 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
5341 case QRhiTexture::ASTC_10x10:
5342 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
5343 case QRhiTexture::ASTC_12x10:
5344 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
5345 case QRhiTexture::ASTC_12x12:
5346 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
5347#else
5348 case QRhiTexture::ETC2_RGB8:
5349 if (d->caps.isAppleGPU)
5350 return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
5351 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5352 return MTLPixelFormatInvalid;
5353 case QRhiTexture::ETC2_RGB8A1:
5354 if (d->caps.isAppleGPU)
5355 return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
5356 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5357 return MTLPixelFormatInvalid;
5358 case QRhiTexture::ETC2_RGBA8:
5359 if (d->caps.isAppleGPU)
5360 return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
5361 qWarning("QRhiMetal: ETC2 compression not supported on this platform");
5362 return MTLPixelFormatInvalid;
5363 case QRhiTexture::ASTC_4x4:
5364 if (d->caps.isAppleGPU)
5365 return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
5366 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5367 return MTLPixelFormatInvalid;
5368 case QRhiTexture::ASTC_5x4:
5369 if (d->caps.isAppleGPU)
5370 return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
5371 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5372 return MTLPixelFormatInvalid;
5373 case QRhiTexture::ASTC_5x5:
5374 if (d->caps.isAppleGPU)
5375 return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
5376 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5377 return MTLPixelFormatInvalid;
5378 case QRhiTexture::ASTC_6x5:
5379 if (d->caps.isAppleGPU)
5380 return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
5381 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5382 return MTLPixelFormatInvalid;
5383 case QRhiTexture::ASTC_6x6:
5384 if (d->caps.isAppleGPU)
5385 return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
5386 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5387 return MTLPixelFormatInvalid;
5388 case QRhiTexture::ASTC_8x5:
5389 if (d->caps.isAppleGPU)
5390 return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
5391 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5392 return MTLPixelFormatInvalid;
5393 case QRhiTexture::ASTC_8x6:
5394 if (d->caps.isAppleGPU)
5395 return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
5396 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5397 return MTLPixelFormatInvalid;
5398 case QRhiTexture::ASTC_8x8:
5399 if (d->caps.isAppleGPU)
5400 return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
5401 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5402 return MTLPixelFormatInvalid;
5403 case QRhiTexture::ASTC_10x5:
5404 if (d->caps.isAppleGPU)
5405 return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
5406 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5407 return MTLPixelFormatInvalid;
5408 case QRhiTexture::ASTC_10x6:
5409 if (d->caps.isAppleGPU)
5410 return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
5411 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5412 return MTLPixelFormatInvalid;
5413 case QRhiTexture::ASTC_10x8:
5414 if (d->caps.isAppleGPU)
5415 return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
5416 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5417 return MTLPixelFormatInvalid;
5418 case QRhiTexture::ASTC_10x10:
5419 if (d->caps.isAppleGPU)
5420 return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
5421 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5422 return MTLPixelFormatInvalid;
5423 case QRhiTexture::ASTC_12x10:
5424 if (d->caps.isAppleGPU)
5425 return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
5426 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5427 return MTLPixelFormatInvalid;
5428 case QRhiTexture::ASTC_12x12:
5429 if (d->caps.isAppleGPU)
5430 return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
5431 qWarning("QRhiMetal: ASTC compression not supported on this platform");
5432 return MTLPixelFormatInvalid;
5433#endif
5434
5435 default:
5436 Q_UNREACHABLE();
5437 return MTLPixelFormatInvalid;
5438 }
5439}
5440
5441QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
5442 int sampleCount, QRhiRenderBuffer::Flags flags,
5443 QRhiTexture::Format backingFormatHint)
5446{
5447}
5448
5450{
5451 destroy();
5452 delete d;
5453}
5454
5456{
5457 if (!d->tex)
5458 return;
5459
5463
5464 e.renderbuffer.texture = d->tex;
5465 d->tex = nil;
5466
5467 QRHI_RES_RHI(QRhiMetal);
5468 if (rhiD) {
5469 rhiD->d->releaseQueue.append(e);
5470 rhiD->unregisterResource(this);
5471 }
5472}
5473
5475{
5476 if (d->tex)
5477 destroy();
5478
5479 if (m_pixelSize.isEmpty())
5480 return false;
5481
5482 QRHI_RES_RHI(QRhiMetal);
5483 samples = rhiD->effectiveSampleCount(m_sampleCount);
5484
5485 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
5486 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
5487 desc.width = NSUInteger(m_pixelSize.width());
5488 desc.height = NSUInteger(m_pixelSize.height());
5489 if (samples > 1)
5490 desc.sampleCount = NSUInteger(samples);
5491 desc.resourceOptions = MTLResourceStorageModePrivate;
5492 desc.usage = MTLTextureUsageRenderTarget;
5493
5494 // Memoryless contents cannot survive the render pass getting interrupted and
5495 // continued on another command encoder, which is what NoTransientBacking is
5496 // there to avoid.
5497 const bool canBeMemoryless = !m_flags.testFlag(QRhiRenderBuffer::NoTransientBacking);
5498
5499 switch (m_type) {
5500 case DepthStencil:
5501#ifdef Q_OS_MACOS
5502 if (rhiD->caps.isAppleGPU && canBeMemoryless) {
5503 desc.storageMode = MTLStorageModeMemoryless;
5504 d->format = MTLPixelFormatDepth32Float_Stencil8;
5505 } else {
5506 desc.storageMode = MTLStorageModePrivate;
5507 d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
5508 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
5509 }
5510#else
5511 desc.storageMode = canBeMemoryless ? MTLStorageModeMemoryless : MTLStorageModePrivate;
5512 d->format = MTLPixelFormatDepth32Float_Stencil8;
5513#endif
5514 desc.pixelFormat = d->format;
5515 break;
5516 case Color:
5517 desc.storageMode = MTLStorageModePrivate;
5518 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5519 d->format = toMetalTextureFormat(m_backingFormatHint, {}, rhiD);
5520 else
5521 d->format = MTLPixelFormatRGBA8Unorm;
5522 desc.pixelFormat = d->format;
5523 break;
5524 default:
5525 Q_UNREACHABLE();
5526 break;
5527 }
5528
5529 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
5530 [desc release];
5531
5532 if (!m_objectName.isEmpty())
5533 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
5534
5536 generation += 1;
5537 rhiD->registerResource(this);
5538 return true;
5539}
5540
5542{
5543 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5544 return m_backingFormatHint;
5545 else
5546 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
5547}
5548
5549QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
5550 int arraySize, int sampleCount, Flags flags)
5552 d(new QMetalTextureData(this))
5553{
5554 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
5555 d->stagingBuf[i] = nil;
5556
5557 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
5558 d->perLevelViews[i] = nil;
5559}
5560
5562{
5563 destroy();
5564 delete d;
5565}
5566
5568{
5569 if (!d->tex)
5570 return;
5571
5575
5576 e.texture.texture = d->owns ? d->tex : nil;
5577 d->tex = nil;
5578
5579 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
5580 e.texture.stagingBuffers[i] = d->stagingBuf[i];
5581 d->stagingBuf[i] = nil;
5582 }
5583
5584 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
5585 e.texture.views[i] = d->perLevelViews[i];
5586 d->perLevelViews[i] = nil;
5587 }
5588
5589 e.texture.samplingView = d->samplingView;
5590 d->samplingView = nil;
5591 e.texture.writeView = d->writeView;
5592 d->writeView = nil;
5593
5594 QRHI_RES_RHI(QRhiMetal);
5595 if (rhiD) {
5596 rhiD->d->releaseQueue.append(e);
5597 rhiD->unregisterResource(this);
5598 }
5599}
5600
5601bool QMetalTexture::prepareCreate(QSize *adjustedSize)
5602{
5603 if (d->tex)
5604 destroy();
5605
5606 const bool isCube = m_flags.testFlag(CubeMap);
5607 const bool is3D = m_flags.testFlag(ThreeDimensional);
5608 const bool isArray = m_flags.testFlag(TextureArray);
5609 const bool hasMipMaps = m_flags.testFlag(MipMapped);
5610 const bool is1D = m_flags.testFlag(OneDimensional);
5611
5612 const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
5613 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
5614
5615 QRHI_RES_RHI(QRhiMetal);
5616 d->format = toMetalTextureFormat(m_format, m_flags, rhiD);
5617 if (m_writeViewFormat.format != UnknownFormat) {
5618 d->viewFormat = toMetalTextureFormat(m_writeViewFormat.format,
5619 m_writeViewFormat.srgb ? sRGB : Flags(), rhiD);
5620 } else {
5621 d->viewFormat = d->format;
5622 }
5623 if (m_readViewFormat.format != UnknownFormat) {
5624 d->viewFormatForSampling = toMetalTextureFormat(m_readViewFormat.format,
5625 m_readViewFormat.srgb ? sRGB : Flags(), rhiD);
5626 } else {
5627 d->viewFormatForSampling = d->format;
5628 }
5629 mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
5630 samples = rhiD->effectiveSampleCount(m_sampleCount);
5631 if (samples > 1) {
5632 if (isCube) {
5633 qWarning("Cubemap texture cannot be multisample");
5634 return false;
5635 }
5636 if (is3D) {
5637 qWarning("3D texture cannot be multisample");
5638 return false;
5639 }
5640 if (hasMipMaps) {
5641 qWarning("Multisample texture cannot have mipmaps");
5642 return false;
5643 }
5644 }
5645 if (isCube && is3D) {
5646 qWarning("Texture cannot be both cube and 3D");
5647 return false;
5648 }
5649 if (isArray && is3D) {
5650 qWarning("Texture cannot be both array and 3D");
5651 return false;
5652 }
5653 if (is1D && is3D) {
5654 qWarning("Texture cannot be both 1D and 3D");
5655 return false;
5656 }
5657 if (is1D && isCube) {
5658 qWarning("Texture cannot be both 1D and cube");
5659 return false;
5660 }
5661 if (m_depth > 1 && !is3D) {
5662 qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
5663 return false;
5664 }
5665 if (m_arraySize > 0 && !isArray) {
5666 qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
5667 return false;
5668 }
5669 if (m_arraySize < 1 && isArray) {
5670 qWarning("Texture is an array but array size is %d", m_arraySize);
5671 return false;
5672 }
5673
5674 if (!rhiD->textureFormatInfo(m_format, size, nullptr, nullptr, nullptr))
5675 return false;
5676
5677 if (adjustedSize)
5678 *adjustedSize = size;
5679
5680 return true;
5681}
5682
5684{
5685 QSize size;
5686 if (!prepareCreate(&size))
5687 return false;
5688
5689 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
5690
5691 const bool isCube = m_flags.testFlag(CubeMap);
5692 const bool is3D = m_flags.testFlag(ThreeDimensional);
5693 const bool isArray = m_flags.testFlag(TextureArray);
5694 const bool is1D = m_flags.testFlag(OneDimensional);
5695 if (isCube) {
5696 desc.textureType = MTLTextureTypeCube;
5697 } else if (is3D) {
5698 desc.textureType = MTLTextureType3D;
5699 } else if (is1D) {
5700 desc.textureType = isArray ? MTLTextureType1DArray : MTLTextureType1D;
5701 } else if (isArray) {
5702 desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
5703 } else {
5704 desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
5705 }
5706 desc.pixelFormat = d->format;
5707 desc.width = NSUInteger(size.width());
5708 desc.height = NSUInteger(size.height());
5709 desc.depth = is3D ? qMax(1, m_depth) : 1;
5710 desc.mipmapLevelCount = NSUInteger(mipLevelCount);
5711 if (samples > 1)
5712 desc.sampleCount = NSUInteger(samples);
5713 if (isArray)
5714 desc.arrayLength = NSUInteger(qMax(0, m_arraySize));
5715 desc.resourceOptions = MTLResourceStorageModePrivate;
5716 desc.storageMode = MTLStorageModePrivate;
5717 desc.usage = MTLTextureUsageShaderRead;
5718 if (m_flags.testFlag(RenderTarget))
5719 desc.usage |= MTLTextureUsageRenderTarget;
5720 if (m_flags.testFlag(UsedWithLoadStore))
5721 desc.usage |= MTLTextureUsageShaderWrite;
5722
5723 // Metal requires this only when the view changes the component layout, and
5724 // explicitly says not to set it for linear <-> sRGB views. A view of the
5725 // same QRhi format with a different sRGB setting, or over an array slice
5726 // range, therefore does not need it.
5727 const bool writeViewChangesFormat = m_writeViewFormat.format != UnknownFormat
5728 && m_writeViewFormat.format != m_format;
5729 const bool readViewChangesFormat = m_readViewFormat.format != UnknownFormat
5730 && m_readViewFormat.format != m_format;
5731 if (writeViewChangesFormat || readViewChangesFormat)
5732 desc.usage |= MTLTextureUsagePixelFormatView;
5733
5734 QRHI_RES_RHI(QRhiMetal);
5735 d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
5736 [desc release];
5737
5738 if (!m_objectName.isEmpty())
5739 d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
5740
5741 d->owns = true;
5742
5743 if (!d->createViews())
5744 return false;
5745
5747 generation += 1;
5748 rhiD->registerResource(this);
5749 return true;
5750}
5751
5752bool QMetalTexture::createFrom(QRhiTexture::NativeTexture src)
5753{
5754 id<MTLTexture> tex = id<MTLTexture>(src.object);
5755 if (tex == 0)
5756 return false;
5757
5758 if (!prepareCreate())
5759 return false;
5760
5761 d->tex = tex;
5762
5763 d->owns = false;
5764
5765 if (!d->createViews())
5766 return false;
5767
5769 generation += 1;
5770 QRHI_RES_RHI(QRhiMetal);
5771 rhiD->registerResource(this);
5772 return true;
5773}
5774
5776{
5777 return {quint64(d->tex), 0};
5778}
5779
5781{
5782 Q_ASSERT(!samplingView && !writeView);
5783
5784 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
5785 const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
5786 const bool hasArrayRange = isArray && q->m_arrayRangeStart >= 0 && q->m_arrayRangeLength >= 0;
5787 const NSUInteger sliceCount = isCube ? 6 : (isArray ? NSUInteger(qMax(0, q->m_arraySize)) : 1);
5788 const NSRange levels = NSMakeRange(0, NSUInteger(q->mipLevelCount));
5789 const MTLTextureType type = [tex textureType];
5790
5791 id<MTLTexture> newSamplingView = nil;
5792 id<MTLTexture> newWriteView = nil;
5793
5794 if (viewFormatForSampling != format || hasArrayRange) {
5795 const NSRange slices = hasArrayRange
5796 ? NSMakeRange(NSUInteger(q->m_arrayRangeStart), NSUInteger(q->m_arrayRangeLength))
5797 : NSMakeRange(0, sliceCount);
5798 newSamplingView = [tex newTextureViewWithPixelFormat: viewFormatForSampling
5799 textureType: type levels: levels slices: slices];
5800 if (!newSamplingView) {
5801 qWarning("QRhiMetal: Failed to create texture view used for sampling");
5802 return false;
5803 }
5804 }
5805
5806 if (viewFormat != format) {
5807 newWriteView = [tex newTextureViewWithPixelFormat: viewFormat
5808 textureType: type levels: levels
5809 slices: NSMakeRange(0, sliceCount)];
5810 if (!newWriteView) {
5811 qWarning("QRhiMetal: Failed to create texture view used for rendering");
5812 [newSamplingView release];
5813 return false;
5814 }
5815 }
5816
5817 samplingView = newSamplingView;
5818 writeView = newWriteView;
5819 return true;
5820}
5821
5823{
5824 Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
5825 if (perLevelViews[level])
5826 return perLevelViews[level];
5827
5828 const MTLTextureType type = [tex textureType];
5829 const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
5830 const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
5831 id<MTLTexture> view = [tex newTextureViewWithPixelFormat: viewFormat textureType: type
5832 levels: NSMakeRange(NSUInteger(level), 1)
5833 slices: NSMakeRange(0, isCube ? 6 : (isArray ? qMax(0, q->m_arraySize) : 1))];
5834
5835 perLevelViews[level] = view;
5836 return view;
5837}
5838
5839QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
5840 AddressMode u, AddressMode v, AddressMode w)
5842 d(new QMetalSamplerData)
5843{
5844}
5845
5847{
5848 destroy();
5849 delete d;
5850}
5851
5853{
5854 if (!d->samplerState)
5855 return;
5856
5860
5861 e.sampler.samplerState = d->samplerState;
5862 d->samplerState = nil;
5863
5864 QRHI_RES_RHI(QRhiMetal);
5865 if (rhiD) {
5866 rhiD->d->releaseQueue.append(e);
5867 rhiD->unregisterResource(this);
5868 }
5869}
5870
5871static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
5872{
5873 switch (f) {
5874 case QRhiSampler::Nearest:
5875 return MTLSamplerMinMagFilterNearest;
5876 case QRhiSampler::Linear:
5877 return MTLSamplerMinMagFilterLinear;
5878 default:
5879 Q_UNREACHABLE();
5880 return MTLSamplerMinMagFilterNearest;
5881 }
5882}
5883
5884static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
5885{
5886 switch (f) {
5887 case QRhiSampler::None:
5888 return MTLSamplerMipFilterNotMipmapped;
5889 case QRhiSampler::Nearest:
5890 return MTLSamplerMipFilterNearest;
5891 case QRhiSampler::Linear:
5892 return MTLSamplerMipFilterLinear;
5893 default:
5894 Q_UNREACHABLE();
5895 return MTLSamplerMipFilterNotMipmapped;
5896 }
5897}
5898
5899static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
5900{
5901 switch (m) {
5902 case QRhiSampler::Repeat:
5903 return MTLSamplerAddressModeRepeat;
5904 case QRhiSampler::ClampToEdge:
5905 return MTLSamplerAddressModeClampToEdge;
5906 case QRhiSampler::Mirror:
5907 return MTLSamplerAddressModeMirrorRepeat;
5908 default:
5909 Q_UNREACHABLE();
5910 return MTLSamplerAddressModeClampToEdge;
5911 }
5912}
5913
5914static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
5915{
5916 switch (op) {
5917 case QRhiSampler::Never:
5918 return MTLCompareFunctionNever;
5919 case QRhiSampler::Less:
5920 return MTLCompareFunctionLess;
5921 case QRhiSampler::Equal:
5922 return MTLCompareFunctionEqual;
5923 case QRhiSampler::LessOrEqual:
5924 return MTLCompareFunctionLessEqual;
5925 case QRhiSampler::Greater:
5926 return MTLCompareFunctionGreater;
5927 case QRhiSampler::NotEqual:
5928 return MTLCompareFunctionNotEqual;
5929 case QRhiSampler::GreaterOrEqual:
5930 return MTLCompareFunctionGreaterEqual;
5931 case QRhiSampler::Always:
5932 return MTLCompareFunctionAlways;
5933 default:
5934 Q_UNREACHABLE();
5935 return MTLCompareFunctionNever;
5936 }
5937}
5938
5940{
5941 if (d->samplerState)
5942 destroy();
5943
5944 MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
5945 desc.minFilter = toMetalFilter(m_minFilter);
5946 desc.magFilter = toMetalFilter(m_magFilter);
5947 desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
5948 desc.sAddressMode = toMetalAddressMode(m_addressU);
5949 desc.tAddressMode = toMetalAddressMode(m_addressV);
5950 desc.rAddressMode = toMetalAddressMode(m_addressW);
5951 desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
5952 QRHI_RES_RHI(QRhiMetal);
5953 // Whether this sampler ends up in an argument buffer is not known here, and
5954 // a sampler state cannot be changed afterwards. Metal gives no warning when
5955 // one without this is used with an argument buffer, it just faults the GPU.
5956 // So set it always as long as ICBs are supported.
5957 desc.supportArgumentBuffers = rhiD->caps.indirectCommandBuffers ? YES : NO;
5958 d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
5959 [desc release];
5960 if (!d->samplerState) {
5961 // Sampler states with supportArgumentBuffers set draw on a per-process
5962 // quota (MTLDevice.maxArgumentBufferSamplerCount), so mention that as
5963 // the likely reason for a failure that is otherwise hard to explain.
5964 qWarning("Failed to create Metal sampler state. The number of unique sampler "
5965 "states with argument buffer support may have exceeded the limit of %u.",
5966 uint(rhiD->d->dev.maxArgumentBufferSamplerCount));
5967 return false;
5968 }
5969
5971 generation += 1;
5972 rhiD->registerResource(this);
5973 return true;
5974}
5975
5979{
5980}
5981
5983{
5984 destroy();
5985 delete d;
5986}
5987
5989{
5990 if (!d->rateMap)
5991 return;
5992
5996
5997 e.shadingRateMap.rateMap = d->rateMap;
5998 d->rateMap = nil;
5999
6000 QRHI_RES_RHI(QRhiMetal);
6001 if (rhiD) {
6002 rhiD->d->releaseQueue.append(e);
6003 rhiD->unregisterResource(this);
6004 }
6005}
6006
6007bool QMetalShadingRateMap::createFrom(NativeShadingRateMap src)
6008{
6009 if (d->rateMap)
6010 destroy();
6011
6012 d->rateMap = (id<MTLRasterizationRateMap>) (quintptr(src.object));
6013 if (!d->rateMap)
6014 return false;
6015
6016 [d->rateMap retain];
6017
6019 generation += 1;
6020 QRHI_RES_RHI(QRhiMetal);
6021 rhiD->registerResource(this);
6022 return true;
6023}
6024
6026{
6027 if (!d->rateMap)
6028 return QSize();
6029
6030 const MTLSize screenSize = [d->rateMap screenSize];
6031 return QSize(int(screenSize.width), int(screenSize.height));
6032}
6033
6034// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
6035// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
6038{
6039 serializedFormatData.reserve(16);
6040}
6041
6046
6048{
6049 QRHI_RES_RHI(QRhiMetal);
6050 if (rhiD)
6051 rhiD->unregisterResource(this);
6052}
6053
6054bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
6055{
6056 if (!other)
6057 return false;
6058
6060
6062 return false;
6063
6065 return false;
6066
6067 for (int i = 0; i < colorAttachmentCount; ++i) {
6068 if (colorFormat[i] != o->colorFormat[i])
6069 return false;
6070 }
6071
6072 if (hasDepthStencil) {
6073 if (dsFormat != o->dsFormat)
6074 return false;
6075 }
6076
6078 return false;
6079
6080 return true;
6081}
6082
6084{
6085 serializedFormatData.clear();
6086 auto p = std::back_inserter(serializedFormatData);
6087
6088 *p++ = colorAttachmentCount;
6089 *p++ = hasDepthStencil;
6090 for (int i = 0; i < colorAttachmentCount; ++i)
6091 *p++ = colorFormat[i];
6092 *p++ = hasDepthStencil ? dsFormat : 0;
6093 *p++ = hasShadingRateMap;
6094}
6095
6097{
6098 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
6101 memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
6102 rpD->dsFormat = dsFormat;
6104
6106
6107 QRHI_RES_RHI(QRhiMetal);
6108 rhiD->registerResource(rpD, false);
6109 return rpD;
6110}
6111
6113{
6114 return serializedFormatData;
6115}
6116
6117QMetalSwapChainRenderTarget::QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
6120{
6121}
6122
6128
6130{
6131 // nothing to do here
6132}
6133
6135{
6136 return d->pixelSize;
6137}
6138
6140{
6141 return d->dpr;
6142}
6143
6145{
6146 return d->sampleCount;
6147}
6148
6150 const QRhiTextureRenderTargetDescription &desc,
6151 Flags flags)
6154{
6155}
6156
6162
6164{
6165 QRHI_RES_RHI(QRhiMetal);
6166 if (rhiD)
6167 rhiD->unregisterResource(this);
6168}
6169
6171{
6172 const int colorAttachmentCount = int(m_desc.colorAttachmentCount());
6173 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
6174 rpD->colorAttachmentCount = colorAttachmentCount;
6175 rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
6176
6177 for (int i = 0; i < colorAttachmentCount; ++i) {
6178 const QRhiColorAttachment *colorAtt = m_desc.colorAttachmentAt(i);
6179 QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAtt->texture());
6180 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAtt->renderBuffer());
6181 rpD->colorFormat[i] = int(texD ? texD->d->viewFormat : rbD->d->format);
6182 }
6183
6184 if (m_desc.depthTexture())
6185 rpD->dsFormat = int(QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->viewFormat);
6186 else if (m_desc.depthStencilBuffer())
6187 rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format);
6188
6189 rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr;
6190
6192
6193 QRHI_RES_RHI(QRhiMetal);
6194 rhiD->registerResource(rpD, false);
6195 return rpD;
6196}
6197
6199{
6200 QRHI_RES_RHI(QRhiMetal);
6201 Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
6202 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
6203 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
6204
6205 d->colorAttCount = 0;
6206 int attIndex = 0;
6207 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
6208 d->colorAttCount += 1;
6209 QMetalTexture *texD = QRHI_RES(QMetalTexture, it->texture());
6210 QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, it->renderBuffer());
6211 Q_ASSERT(texD || rbD);
6212 id<MTLTexture> dst = nil;
6213 bool is3D = false;
6214 if (texD) {
6215 dst = texD->d->textureForWrite();
6216 if (attIndex == 0) {
6217 d->pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
6219 }
6220 is3D = texD->flags().testFlag(QRhiTexture::ThreeDimensional);
6221 } else if (rbD) {
6222 dst = rbD->d->tex;
6223 if (attIndex == 0) {
6224 d->pixelSize = rbD->pixelSize();
6226 }
6227 }
6229 colorAtt.tex = dst;
6230 colorAtt.arrayLayer = is3D ? 0 : it->layer();
6231 colorAtt.slice = is3D ? it->layer() : 0;
6232 colorAtt.level = it->level();
6233 QMetalTexture *resTexD = QRHI_RES(QMetalTexture, it->resolveTexture());
6234 colorAtt.resolveTex = resTexD ? resTexD->d->textureForWrite() : nil;
6235 colorAtt.resolveLayer = it->resolveLayer();
6236 colorAtt.resolveLevel = it->resolveLevel();
6237 d->fb.colorAtt[attIndex] = colorAtt;
6238 }
6239 d->dpr = 1;
6240
6241 if (hasDepthStencil) {
6242 if (m_desc.depthTexture()) {
6243 QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
6244 d->fb.dsTex = depthTexD->d->textureForWrite();
6245 d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format());
6246 d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture();
6247 d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
6248 if (d->colorAttCount == 0) {
6249 d->pixelSize = depthTexD->pixelSize();
6250 d->sampleCount = depthTexD->samples;
6251 }
6252 } else {
6253 QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
6254 d->fb.dsTex = depthRbD->d->tex;
6255 d->fb.hasStencil = true;
6256 d->fb.depthNeedsStore = false;
6257 d->fb.preserveDs = false;
6258 if (d->colorAttCount == 0) {
6259 d->pixelSize = depthRbD->pixelSize();
6260 d->sampleCount = depthRbD->samples;
6261 }
6262 }
6263 if (m_desc.depthResolveTexture()) {
6264 QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture());
6265 d->fb.dsResolveTex = depthResolveTexD->d->textureForWrite();
6266 }
6267 d->dsAttCount = 1;
6268 } else {
6269 d->dsAttCount = 0;
6270 }
6271
6272 if (d->colorAttCount > 0)
6273 d->fb.preserveColor = m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
6274
6275 QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList);
6276
6277 rhiD->registerResource(this, false);
6278 return true;
6279}
6280
6282{
6283 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(m_desc, d->currentResIdList))
6284 const_cast<QMetalTextureRenderTarget *>(this)->create();
6285
6286 return d->pixelSize;
6287}
6288
6290{
6291 return d->dpr;
6292}
6293
6295{
6296 return d->sampleCount;
6297}
6298
6303
6308
6310{
6311 sortedBindings.clear();
6312 maxBinding = -1;
6313
6314 QRHI_RES_RHI(QRhiMetal);
6315 if (rhiD)
6316 rhiD->unregisterResource(this);
6317}
6318
6320{
6321 if (!sortedBindings.isEmpty())
6322 destroy();
6323
6324 QRHI_RES_RHI(QRhiMetal);
6325 if (!rhiD->sanityCheckShaderResourceBindings(this))
6326 return false;
6327
6328 rhiD->updateLayoutDesc(this);
6329
6330 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
6331 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
6332 if (!sortedBindings.isEmpty())
6333 maxBinding = QRhiImplementation::shaderResourceBindingData(sortedBindings.last())->binding;
6334 else
6335 maxBinding = -1;
6336
6337 boundResourceData.resize(sortedBindings.count());
6338
6339 for (BoundResourceData &bd : boundResourceData)
6340 bd = {};
6341
6342 generation += 1;
6343 rhiD->registerResource(this, false);
6344 return true;
6345}
6346
6348{
6349 sortedBindings.clear();
6350 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
6351 if (!flags.testFlag(BindingsAreSorted))
6352 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
6353
6354 for (BoundResourceData &bd : boundResourceData)
6355 bd = {};
6356
6357 generation += 1;
6358}
6359
6363{
6364 d->q = this;
6365 d->tess.q = d;
6366}
6367
6373
6375{
6376 d->vs.destroy();
6377 d->fs.destroy();
6378
6379 d->icbCapable = false;
6380
6381 d->tess.compVs[0].destroy();
6382 d->tess.compVs[1].destroy();
6383 d->tess.compVs[2].destroy();
6384
6385 d->tess.compTesc.destroy();
6386 d->tess.vertTese.destroy();
6387
6388 qDeleteAll(d->extraBufMgr.deviceLocalWorkBuffers);
6389 d->extraBufMgr.deviceLocalWorkBuffers.clear();
6390 qDeleteAll(d->extraBufMgr.hostVisibleWorkBuffers);
6391 d->extraBufMgr.hostVisibleWorkBuffers.clear();
6392
6393 delete d->bufferSizeBuffer;
6394 d->bufferSizeBuffer = nullptr;
6395
6396 if (!d->ps && !d->ds
6397 && !d->tess.vertexComputeState[0] && !d->tess.vertexComputeState[1] && !d->tess.vertexComputeState[2]
6398 && !d->tess.tessControlComputeState)
6399 {
6400 return;
6401 }
6402
6406 e.graphicsPipeline.pipelineState = d->ps;
6407 e.graphicsPipeline.depthStencilState = d->ds;
6408 e.graphicsPipeline.tessVertexComputeState = d->tess.vertexComputeState;
6409 e.graphicsPipeline.tessTessControlComputeState = d->tess.tessControlComputeState;
6410 d->ps = nil;
6411 d->ds = nil;
6412 d->tess.vertexComputeState = {};
6413 d->tess.tessControlComputeState = nil;
6414
6415 QRHI_RES_RHI(QRhiMetal);
6416 if (rhiD) {
6417 rhiD->d->releaseQueue.append(e);
6418 rhiD->unregisterResource(this);
6419 }
6420}
6421
6422static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
6423{
6424 switch (format) {
6425 case QRhiVertexInputAttribute::Float4:
6426 return MTLVertexFormatFloat4;
6427 case QRhiVertexInputAttribute::Float3:
6428 return MTLVertexFormatFloat3;
6429 case QRhiVertexInputAttribute::Float2:
6430 return MTLVertexFormatFloat2;
6431 case QRhiVertexInputAttribute::Float:
6432 return MTLVertexFormatFloat;
6433 case QRhiVertexInputAttribute::UNormByte4:
6434 return MTLVertexFormatUChar4Normalized;
6435 case QRhiVertexInputAttribute::UNormByte2:
6436 return MTLVertexFormatUChar2Normalized;
6437 case QRhiVertexInputAttribute::UNormByte:
6438 return MTLVertexFormatUCharNormalized;
6439 case QRhiVertexInputAttribute::UInt4:
6440 return MTLVertexFormatUInt4;
6441 case QRhiVertexInputAttribute::UInt3:
6442 return MTLVertexFormatUInt3;
6443 case QRhiVertexInputAttribute::UInt2:
6444 return MTLVertexFormatUInt2;
6445 case QRhiVertexInputAttribute::UInt:
6446 return MTLVertexFormatUInt;
6447 case QRhiVertexInputAttribute::SInt4:
6448 return MTLVertexFormatInt4;
6449 case QRhiVertexInputAttribute::SInt3:
6450 return MTLVertexFormatInt3;
6451 case QRhiVertexInputAttribute::SInt2:
6452 return MTLVertexFormatInt2;
6453 case QRhiVertexInputAttribute::SInt:
6454 return MTLVertexFormatInt;
6455 case QRhiVertexInputAttribute::Half4:
6456 return MTLVertexFormatHalf4;
6457 case QRhiVertexInputAttribute::Half3:
6458 return MTLVertexFormatHalf3;
6459 case QRhiVertexInputAttribute::Half2:
6460 return MTLVertexFormatHalf2;
6461 case QRhiVertexInputAttribute::Half:
6462 return MTLVertexFormatHalf;
6463 case QRhiVertexInputAttribute::UShort4:
6464 return MTLVertexFormatUShort4;
6465 case QRhiVertexInputAttribute::UShort3:
6466 return MTLVertexFormatUShort3;
6467 case QRhiVertexInputAttribute::UShort2:
6468 return MTLVertexFormatUShort2;
6469 case QRhiVertexInputAttribute::UShort:
6470 return MTLVertexFormatUShort;
6471 case QRhiVertexInputAttribute::SShort4:
6472 return MTLVertexFormatShort4;
6473 case QRhiVertexInputAttribute::SShort3:
6474 return MTLVertexFormatShort3;
6475 case QRhiVertexInputAttribute::SShort2:
6476 return MTLVertexFormatShort2;
6477 case QRhiVertexInputAttribute::SShort:
6478 return MTLVertexFormatShort;
6479 default:
6480 Q_UNREACHABLE();
6481 return MTLVertexFormatFloat4;
6482 }
6483}
6484
6485static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
6486{
6487 switch (f) {
6488 case QRhiGraphicsPipeline::Zero:
6489 return MTLBlendFactorZero;
6490 case QRhiGraphicsPipeline::One:
6491 return MTLBlendFactorOne;
6492 case QRhiGraphicsPipeline::SrcColor:
6493 return MTLBlendFactorSourceColor;
6494 case QRhiGraphicsPipeline::OneMinusSrcColor:
6495 return MTLBlendFactorOneMinusSourceColor;
6496 case QRhiGraphicsPipeline::DstColor:
6497 return MTLBlendFactorDestinationColor;
6498 case QRhiGraphicsPipeline::OneMinusDstColor:
6499 return MTLBlendFactorOneMinusDestinationColor;
6500 case QRhiGraphicsPipeline::SrcAlpha:
6501 return MTLBlendFactorSourceAlpha;
6502 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
6503 return MTLBlendFactorOneMinusSourceAlpha;
6504 case QRhiGraphicsPipeline::DstAlpha:
6505 return MTLBlendFactorDestinationAlpha;
6506 case QRhiGraphicsPipeline::OneMinusDstAlpha:
6507 return MTLBlendFactorOneMinusDestinationAlpha;
6508 case QRhiGraphicsPipeline::ConstantColor:
6509 return MTLBlendFactorBlendColor;
6510 case QRhiGraphicsPipeline::ConstantAlpha:
6511 return MTLBlendFactorBlendAlpha;
6512 case QRhiGraphicsPipeline::OneMinusConstantColor:
6513 return MTLBlendFactorOneMinusBlendColor;
6514 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
6515 return MTLBlendFactorOneMinusBlendAlpha;
6516 case QRhiGraphicsPipeline::SrcAlphaSaturate:
6517 return MTLBlendFactorSourceAlphaSaturated;
6518 case QRhiGraphicsPipeline::Src1Color:
6519 return MTLBlendFactorSource1Color;
6520 case QRhiGraphicsPipeline::OneMinusSrc1Color:
6521 return MTLBlendFactorOneMinusSource1Color;
6522 case QRhiGraphicsPipeline::Src1Alpha:
6523 return MTLBlendFactorSource1Alpha;
6524 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
6525 return MTLBlendFactorOneMinusSource1Alpha;
6526 default:
6527 Q_UNREACHABLE();
6528 return MTLBlendFactorZero;
6529 }
6530}
6531
6532static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
6533{
6534 switch (op) {
6535 case QRhiGraphicsPipeline::Add:
6536 return MTLBlendOperationAdd;
6537 case QRhiGraphicsPipeline::Subtract:
6538 return MTLBlendOperationSubtract;
6539 case QRhiGraphicsPipeline::ReverseSubtract:
6540 return MTLBlendOperationReverseSubtract;
6541 case QRhiGraphicsPipeline::Min:
6542 return MTLBlendOperationMin;
6543 case QRhiGraphicsPipeline::Max:
6544 return MTLBlendOperationMax;
6545 default:
6546 Q_UNREACHABLE();
6547 return MTLBlendOperationAdd;
6548 }
6549}
6550
6551static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
6552{
6553 uint f = 0;
6554 if (c.testFlag(QRhiGraphicsPipeline::R))
6555 f |= MTLColorWriteMaskRed;
6556 if (c.testFlag(QRhiGraphicsPipeline::G))
6557 f |= MTLColorWriteMaskGreen;
6558 if (c.testFlag(QRhiGraphicsPipeline::B))
6559 f |= MTLColorWriteMaskBlue;
6560 if (c.testFlag(QRhiGraphicsPipeline::A))
6561 f |= MTLColorWriteMaskAlpha;
6562 return f;
6563}
6564
6565static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
6566{
6567 switch (op) {
6568 case QRhiGraphicsPipeline::Never:
6569 return MTLCompareFunctionNever;
6570 case QRhiGraphicsPipeline::Less:
6571 return MTLCompareFunctionLess;
6572 case QRhiGraphicsPipeline::Equal:
6573 return MTLCompareFunctionEqual;
6574 case QRhiGraphicsPipeline::LessOrEqual:
6575 return MTLCompareFunctionLessEqual;
6576 case QRhiGraphicsPipeline::Greater:
6577 return MTLCompareFunctionGreater;
6578 case QRhiGraphicsPipeline::NotEqual:
6579 return MTLCompareFunctionNotEqual;
6580 case QRhiGraphicsPipeline::GreaterOrEqual:
6581 return MTLCompareFunctionGreaterEqual;
6582 case QRhiGraphicsPipeline::Always:
6583 return MTLCompareFunctionAlways;
6584 default:
6585 Q_UNREACHABLE();
6586 return MTLCompareFunctionAlways;
6587 }
6588}
6589
6590static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
6591{
6592 switch (op) {
6593 case QRhiGraphicsPipeline::StencilZero:
6594 return MTLStencilOperationZero;
6595 case QRhiGraphicsPipeline::Keep:
6596 return MTLStencilOperationKeep;
6597 case QRhiGraphicsPipeline::Replace:
6598 return MTLStencilOperationReplace;
6599 case QRhiGraphicsPipeline::IncrementAndClamp:
6600 return MTLStencilOperationIncrementClamp;
6601 case QRhiGraphicsPipeline::DecrementAndClamp:
6602 return MTLStencilOperationDecrementClamp;
6603 case QRhiGraphicsPipeline::Invert:
6604 return MTLStencilOperationInvert;
6605 case QRhiGraphicsPipeline::IncrementAndWrap:
6606 return MTLStencilOperationIncrementWrap;
6607 case QRhiGraphicsPipeline::DecrementAndWrap:
6608 return MTLStencilOperationDecrementWrap;
6609 default:
6610 Q_UNREACHABLE();
6611 return MTLStencilOperationKeep;
6612 }
6613}
6614
6615static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
6616{
6617 switch (t) {
6618 case QRhiGraphicsPipeline::Triangles:
6619 return MTLPrimitiveTypeTriangle;
6620 case QRhiGraphicsPipeline::TriangleStrip:
6621 return MTLPrimitiveTypeTriangleStrip;
6622 case QRhiGraphicsPipeline::Lines:
6623 return MTLPrimitiveTypeLine;
6624 case QRhiGraphicsPipeline::LineStrip:
6625 return MTLPrimitiveTypeLineStrip;
6626 case QRhiGraphicsPipeline::Points:
6627 return MTLPrimitiveTypePoint;
6628 default:
6629 Q_UNREACHABLE();
6630 return MTLPrimitiveTypeTriangle;
6631 }
6632}
6633
6634static inline MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t)
6635{
6636 switch (t) {
6637 case QRhiGraphicsPipeline::Triangles:
6638 case QRhiGraphicsPipeline::TriangleStrip:
6639 case QRhiGraphicsPipeline::TriangleFan:
6640 return MTLPrimitiveTopologyClassTriangle;
6641 case QRhiGraphicsPipeline::Lines:
6642 case QRhiGraphicsPipeline::LineStrip:
6643 return MTLPrimitiveTopologyClassLine;
6644 case QRhiGraphicsPipeline::Points:
6645 return MTLPrimitiveTopologyClassPoint;
6646 default:
6647 Q_UNREACHABLE();
6648 return MTLPrimitiveTopologyClassTriangle;
6649 }
6650}
6651
6652static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
6653{
6654 switch (c) {
6655 case QRhiGraphicsPipeline::None:
6656 return MTLCullModeNone;
6657 case QRhiGraphicsPipeline::Front:
6658 return MTLCullModeFront;
6659 case QRhiGraphicsPipeline::Back:
6660 return MTLCullModeBack;
6661 default:
6662 Q_UNREACHABLE();
6663 return MTLCullModeNone;
6664 }
6665}
6666
6667static inline MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::PolygonMode mode)
6668{
6669 switch (mode) {
6670 case QRhiGraphicsPipeline::Fill:
6671 return MTLTriangleFillModeFill;
6672 case QRhiGraphicsPipeline::Line:
6673 return MTLTriangleFillModeLines;
6674 default:
6675 Q_UNREACHABLE();
6676 return MTLTriangleFillModeFill;
6677 }
6678}
6679
6680static inline MTLWinding toMetalTessellationWindingOrder(QShaderDescription::TessellationWindingOrder w)
6681{
6682 switch (w) {
6683 case QShaderDescription::CwTessellationWindingOrder:
6684 return MTLWindingClockwise;
6685 case QShaderDescription::CcwTessellationWindingOrder:
6686 return MTLWindingCounterClockwise;
6687 default:
6688 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
6689 return MTLWindingCounterClockwise;
6690 }
6691}
6692
6693static inline MTLTessellationPartitionMode toMetalTessellationPartitionMode(QShaderDescription::TessellationPartitioning p)
6694{
6695 switch (p) {
6696 case QShaderDescription::EqualTessellationPartitioning:
6697 return MTLTessellationPartitionModePow2;
6698 case QShaderDescription::FractionalEvenTessellationPartitioning:
6699 return MTLTessellationPartitionModeFractionalEven;
6700 case QShaderDescription::FractionalOddTessellationPartitioning:
6701 return MTLTessellationPartitionModeFractionalOdd;
6702 default:
6703 // this is reachable, consider a tess.eval. shader not declaring it, the value is then Unknown
6704 return MTLTessellationPartitionModePow2;
6705 }
6706}
6707
6708static inline MTLLanguageVersion toMetalLanguageVersion(const QShaderVersion &version)
6709{
6710 int v = version.version();
6711 return MTLLanguageVersion(((v / 10) << 16) + (v % 10));
6712}
6713
6714id<MTLBuffer> QRhiMetalData::allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment,
6715 int frameSlot, quint32 minBlockSize, quint32 *offset)
6716{
6717 auto &pool(*area);
6718 const quint32 alignedSize = aligned<quint32>(size, alignment);
6719 if (pool.offset + alignedSize > pool.capacity) {
6720 if (pool.buf) {
6721 // Reusing the StagingBuffer entry type: the handler just releases
6722 // the buffer. lastActiveFrameSlot = frameSlot is what keeps the old
6723 // buffer alive for the rest of this frame, since
6724 // executeDeferredReleases() only gets to this slot after the
6725 // semaphore wait in the next beginFrame() for it.
6728 e.lastActiveFrameSlot = frameSlot;
6729 e.stagingBuffer.buffer = pool.buf;
6730 releaseQueue.append(e);
6731 }
6732 pool.capacity = qMax(pool.capacity * 2, qMax(alignedSize, minBlockSize));
6733 pool.buf = [dev newBufferWithLength: pool.capacity options: MTLResourceStorageModeShared];
6734 pool.offset = 0;
6735 if (!pool.buf) {
6736 pool.capacity = 0;
6737 return nil;
6738 }
6739 }
6740 *offset = pool.offset;
6741 pool.offset += alignedSize;
6742 return pool.buf;
6743}
6744
6745id<MTLBuffer> QRhiMetalData::allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset)
6746{
6747 return allocFromStagingArea(&argBufPool[frameSlot], size, alignment, frameSlot, 16384, offset);
6748}
6749
6751{
6752 id<MTLBuffer> buf = [dev newBufferWithLength: size options: MTLResourceStorageModeShared];
6753 if (!buf)
6754 return nil;
6757 e.lastActiveFrameSlot = frameSlot;
6758 e.stagingBuffer.buffer = buf;
6759 releaseQueue.append(e);
6760 return buf;
6761}
6762
6763id<MTLBuffer> QRhiMetalData::allocBufferStaging(quint32 size, int frameSlot, quint32 *offset)
6764{
6765 if (size > LARGE_STAGING_ALLOC) {
6766 *offset = 0;
6767 return newOneShotStagingBuffer(size, frameSlot);
6768 }
6769
6770 StagingArea &area(bufStagingPool[frameSlot]);
6771 // 4 byte alignment is what MTLBlitCommandEncoder wants on non-Apple GPUs.
6772 const quint32 alignedSize = aligned<quint32>(size, 4u);
6773 area.bytesNeeded += alignedSize;
6774
6775 if (area.offset + alignedSize > area.capacity) {
6776 *offset = 0;
6777 return newOneShotStagingBuffer(size, frameSlot);
6778 }
6779
6780 *offset = area.offset;
6781 area.offset += alignedSize;
6782 return area.buf;
6783}
6784
6786{
6787 StagingArea &area(bufStagingPool[frameSlot]);
6788 const quint32 needed = area.bytesNeeded;
6789 area.bytesNeeded = 0;
6790 area.offset = 0;
6791
6792 bool resize = false;
6793 quint32 newCapacity = 0;
6794 if (needed > area.capacity) {
6795 // Fell back to one-shot buffers last time. Only grow once the demand
6796 // has proven to be recurring: a one-time burst, which is what loading a
6797 // scene looks like, would otherwise size the area permanently right at
6798 // the point where it is not needed any more.
6799 area.lowDemandFrames = 0;
6801 newCapacity = qMin(qNextPowerOfTwo(needed), STAGING_AREA_MAX);
6802 resize = true;
6803 }
6804 } else if (needed <= area.capacity / 4 && area.capacity > 0) {
6805 area.highDemandFrames = 0;
6807 area.lowDemandFrames = 0;
6808 // Nothing at all for a while, e.g. an app that only uploads during
6809 // startup: give the memory back entirely.
6810 newCapacity = needed ? qMax(area.capacity / 2, STAGING_AREA_MIN) : 0;
6811 resize = true;
6812 }
6813 } else {
6814 area.lowDemandFrames = 0;
6815 area.highDemandFrames = 0;
6816 }
6817
6818 if (resize && newCapacity != area.capacity) {
6819 if (area.buf) {
6822 e.lastActiveFrameSlot = frameSlot;
6823 e.stagingBuffer.buffer = area.buf;
6824 releaseQueue.append(e);
6825 area.buf = nil;
6826 }
6827 area.capacity = 0;
6828 if (newCapacity) {
6829 area.buf = [dev newBufferWithLength: newCapacity options: MTLResourceStorageModeShared];
6830 if (area.buf)
6831 area.capacity = newCapacity;
6832 }
6833 }
6834}
6835
6836id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
6837 bool preferArgumentBuffers,
6838 QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
6839{
6840 QVarLengthArray<int, 8> versions;
6841 versions << 30 << 24 << 23 << 22 << 21 << 20 << 12;
6842
6843 // preferArgumentBuffers overrides whatever variant is set in the QShader.
6844 // This is by design, since we cannot expect the client to start specifying
6845 // the ArgumentBuffer variant that is only relevant for Metal. Also not
6846 // compatible with BatchableVertexShader and other variants since
6847 // ArgumentBufferShader is expected to be the argument-buffers version of
6848 // StandardShader, and it cannot be a combination of multiple variants. That
6849 // limitation should be fine for now.
6850
6851 QVarLengthArray<QShader::Variant, 2> variants;
6852 if (preferArgumentBuffers)
6853 variants << QShader::ArgumentBufferShader;
6854 variants << shaderVariant;
6855
6856 const QList<QShaderKey> shaders = shader.availableShaders();
6857
6858 auto findKey = [&shaders, &versions, &variants](QShader::Source source, QShaderKey *result) {
6859 for (const QShader::Variant &variant : variants) {
6860 for (const int &version : versions) {
6861 const QShaderKey key = { source, version, variant };
6862 if (shaders.contains(key)) {
6863 *result = key;
6864 return true;
6865 }
6866 }
6867 }
6868 return false;
6869 };
6870
6871 QShaderKey key;
6872
6873 if (findKey(QShader::Source::MetalLibShader, &key)) {
6874 QShaderCode mtllib = shader.shader(key);
6875 dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
6876 size_t(mtllib.shader().size()),
6877 dispatch_get_global_queue(0, 0),
6878 DISPATCH_DATA_DESTRUCTOR_DEFAULT);
6879 NSError *err = nil;
6880 id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
6881 dispatch_release(data);
6882 if (!err) {
6883 *entryPoint = mtllib.entryPoint();
6884 *activeKey = key;
6885 return lib;
6886 } else {
6887 const QString msg = QString::fromNSString(err.localizedDescription);
6888 qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
6889 }
6890 }
6891
6892 if (!findKey(QShader::Source::MslShader, &key)) {
6893 qWarning() << "No MSL code found in baked shader" << shader;
6894 return nil;
6895 }
6896
6897 QShaderCode mslSource = shader.shader(key);
6898
6899 NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
6900 MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
6901 opts.languageVersion = toMetalLanguageVersion(key.sourceVersion());
6902 NSError *err = nil;
6903 id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
6904 [opts release];
6905 // src is autoreleased
6906
6907 // if lib is null and err is non-null, we had errors (fail)
6908 // if lib is non-null and err is non-null, we had warnings (success)
6909 // if lib is non-null and err is null, there were no errors or warnings (success)
6910 if (!lib) {
6911 const QString msg = QString::fromNSString(err.localizedDescription);
6912 *error = msg;
6913 return nil;
6914 }
6915
6916 *entryPoint = mslSource.entryPoint();
6917 *activeKey = key;
6918 return lib;
6919}
6920
6921id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
6922{
6923 return [lib newFunctionWithName:[NSString stringWithUTF8String:entryPoint.constData()]];
6924}
6925
6927{
6928 MTLRenderPipelineDescriptor *rpDesc = reinterpret_cast<MTLRenderPipelineDescriptor *>(metalRpDesc);
6929
6930 if (rpD->colorAttachmentCount) {
6931 // defaults when no targetBlends are provided
6932 rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
6933 rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
6934 rpDesc.colorAttachments[0].blendingEnabled = false;
6935
6936 Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
6937 || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
6938
6939 for (uint i = 0, ie = uint(m_targetBlends.count()); i != ie; ++i) {
6940 const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[int(i)]);
6941 rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
6942 rpDesc.colorAttachments[i].blendingEnabled = b.enable;
6943 rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
6944 rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
6945 rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
6946 rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
6947 rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
6948 rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
6949 rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
6950 }
6951 }
6952
6953 if (rpD->hasDepthStencil) {
6954 // Must only be set when a depth-stencil buffer will actually be bound,
6955 // validation blows up otherwise.
6956 MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
6957 rpDesc.depthAttachmentPixelFormat = fmt;
6958#if defined(Q_OS_MACOS)
6959 if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
6960#else
6961 if (fmt != MTLPixelFormatDepth32Float)
6962#endif
6963 rpDesc.stencilAttachmentPixelFormat = fmt;
6964 }
6965
6966 QRHI_RES_RHI(QRhiMetal);
6967 rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
6968}
6969
6971{
6972 MTLDepthStencilDescriptor *dsDesc = reinterpret_cast<MTLDepthStencilDescriptor *>(metalDsDesc);
6973
6974 dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
6975 dsDesc.depthWriteEnabled = m_depthWrite;
6976 if (m_stencilTest) {
6977 dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
6978 dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
6979 dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
6980 dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
6981 dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
6982 dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
6983 dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
6984
6985 dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
6986 dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
6987 dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
6988 dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
6989 dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
6990 dsDesc.backFaceStencil.readMask = m_stencilReadMask;
6991 dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
6992 }
6993}
6994
6996{
6997 d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
6998 d->cullMode = toMetalCullMode(m_cullMode);
6999 d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
7000 d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
7001 d->depthBias = float(m_depthBias);
7002 d->slopeScaledDepthBias = m_slopeScaledDepthBias;
7003}
7004
7006{
7007 // same binding space for vertex and constant buffers - work it around
7008 // should be in native resource binding not SPIR-V, but this will work anyway
7009 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
7010
7011 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
7012 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
7013 it != itEnd; ++it)
7014 {
7015 const uint loc = uint(it->location());
7016 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
7017 desc.attributes[loc].offset = NSUInteger(it->offset());
7018 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
7019 }
7020 int bindingIndex = 0;
7021 const NSUInteger viewCount = qMax<NSUInteger>(1, q->multiViewCount());
7022 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
7023 it != itEnd; ++it, ++bindingIndex)
7024 {
7025 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
7026 desc.layouts[layoutIdx].stepFunction =
7027 it->classification() == QRhiVertexInputBinding::PerInstance
7028 ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
7029 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
7030 if (desc.layouts[layoutIdx].stepFunction == MTLVertexStepFunctionPerInstance)
7031 desc.layouts[layoutIdx].stepRate *= viewCount;
7032 desc.layouts[layoutIdx].stride = it->stride();
7033 }
7034}
7035
7036void QMetalGraphicsPipelineData::setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
7037{
7038 // same binding space for vertex and constant buffers - work it around
7039 // should be in native resource binding not SPIR-V, but this will work anyway
7040 const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, q->shaderResourceBindings())->maxBinding + 1;
7041
7042 QRhiVertexInputLayout vertexInputLayout = q->vertexInputLayout();
7043 for (auto it = vertexInputLayout.cbeginAttributes(), itEnd = vertexInputLayout.cendAttributes();
7044 it != itEnd; ++it)
7045 {
7046 const uint loc = uint(it->location());
7047 desc.attributes[loc].format = decltype(desc.attributes[loc].format)(toMetalAttributeFormat(it->format()));
7048 desc.attributes[loc].offset = NSUInteger(it->offset());
7049 desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
7050 }
7051 int bindingIndex = 0;
7052 for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
7053 it != itEnd; ++it, ++bindingIndex)
7054 {
7055 const uint layoutIdx = uint(firstVertexBinding + bindingIndex);
7056 if (desc.indexBufferIndex) {
7057 desc.layouts[layoutIdx].stepFunction =
7058 it->classification() == QRhiVertexInputBinding::PerInstance
7059 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridXIndexed;
7060 } else {
7061 desc.layouts[layoutIdx].stepFunction =
7062 it->classification() == QRhiVertexInputBinding::PerInstance
7063 ? MTLStepFunctionThreadPositionInGridY : MTLStepFunctionThreadPositionInGridX;
7064 }
7065 desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
7066 desc.layouts[layoutIdx].stride = it->stride();
7067 }
7068}
7069
7070void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
7071{
7072 if (binArch) {
7073 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
7074 rpDesc.binaryArchives = binArchArray;
7075 }
7076}
7077
7078void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
7079{
7080 if (binArch) {
7081 NSError *err = nil;
7082 if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) {
7083 const QString msg = QString::fromNSString(err.localizedDescription);
7084 qWarning("Failed to collect render pipeline functions to binary archive: %s", qPrintable(msg));
7085 }
7086 }
7087}
7088
7089static inline bool usesTextures(const QShaderDescription &desc)
7090{
7091 return !desc.combinedImageSamplers().isEmpty()
7092 || !desc.separateImages().isEmpty()
7093 || !desc.storageImages().isEmpty();
7094}
7095
7096static bool hasArgumentBufferVariant(const QShader &shader)
7097{
7098 for (const QShaderKey &k : shader.availableShaders()) {
7099 if (k.sourceVariant() == QShader::ArgumentBufferShader)
7100 return true;
7101 }
7102 return false;
7103}
7104
7106{
7107 const int index = shader->nativeShaderInfo.extraBufferBindings.value(QShaderPrivate::MslArgumentBufferBinding, -1);
7108 if (index >= 0) {
7109 shader->argumentBufferIndex = index;
7110 shader->argumentEncoder = [shader->func newArgumentEncoderWithBufferIndex: NSUInteger(index)];
7111 }
7112}
7113
7115{
7116 QRHI_RES_RHI(QRhiMetal);
7117
7118 // A pipeline that supports indirect command buffers cannot have textures
7119 // bound directly to its functions, so prefer the shader variant that
7120 // reaches them through an argument buffer.
7121 const bool wantArgumentBuffers = m_flags.testFlag(UsesIndirectDraws) && rhiD->caps.indirectCommandBuffers;
7122
7123 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
7124 d->setupVertexInputDescriptor(vertexDesc);
7125
7126 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
7127 rpDesc.vertexDescriptor = vertexDesc;
7128
7129 // Mutability cannot be determined (slotted buffers could be set as
7130 // MTLMutabilityImmutable, but then we potentially need a different
7131 // descriptor for each buffer combination as this depends on the actual
7132 // buffers not just the resource binding layout), so leave
7133 // rpDesc.vertex/fragmentBuffers at the defaults.
7134
7135 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
7136 const QShader shader = shaderStage.shader();
7137 const bool argumentBufferBuild = wantArgumentBuffers && hasArgumentBufferVariant(shader);
7138 auto cacheIt = rhiD->d->shaderCache.constFind({ shaderStage, argumentBufferBuild });
7139 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
7140 switch (shaderStage.type()) {
7141 case QRhiShaderStage::Vertex:
7142 d->vs = *cacheIt;
7143 [d->vs.lib retain];
7144 [d->vs.func retain];
7145 [d->vs.argumentEncoder retain];
7146 rpDesc.vertexFunction = d->vs.func;
7147 break;
7148 case QRhiShaderStage::Fragment:
7149 d->fs = *cacheIt;
7150 [d->fs.lib retain];
7151 [d->fs.func retain];
7152 [d->fs.argumentEncoder retain];
7153 rpDesc.fragmentFunction = d->fs.func;
7154 break;
7155 default:
7156 break;
7157 }
7158 } else {
7159 QString error;
7160 QByteArray entryPoint;
7161 QShaderKey activeKey;
7162 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(),
7163 argumentBufferBuild,
7164 &error, &entryPoint, &activeKey);
7165 if (!lib) {
7166 qWarning("MSL shader compilation failed: %s", qPrintable(error));
7167 return false;
7168 }
7169 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
7170 if (!func) {
7171 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7172 [lib release];
7173 return false;
7174 }
7175 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
7176 // Use the simplest strategy: too many cached shaders -> drop them all.
7177 for (QMetalShader &s : rhiD->d->shaderCache)
7178 s.destroy();
7179 rhiD->d->shaderCache.clear();
7180 }
7181 switch (shaderStage.type()) {
7182 case QRhiShaderStage::Vertex:
7183 d->vs.lib = lib;
7184 d->vs.func = func;
7185 d->vs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
7186 d->vs.desc = shader.description();
7187 d->vs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
7188 setupArgumentBufferEncoder(&d->vs);
7189 rhiD->d->shaderCache.insert({ shaderStage, argumentBufferBuild }, d->vs);
7190 [d->vs.lib retain];
7191 [d->vs.func retain];
7192 [d->vs.argumentEncoder retain];
7193 rpDesc.vertexFunction = func;
7194 break;
7195 case QRhiShaderStage::Fragment:
7196 d->fs.lib = lib;
7197 d->fs.func = func;
7198 d->fs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
7199 d->fs.desc = shader.description();
7200 d->fs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
7201 setupArgumentBufferEncoder(&d->fs);
7202 rhiD->d->shaderCache.insert({ shaderStage, argumentBufferBuild }, d->fs);
7203 [d->fs.lib retain];
7204 [d->fs.func retain];
7205 [d->fs.argumentEncoder retain];
7206 rpDesc.fragmentFunction = func;
7207 break;
7208 default:
7209 [func release];
7210 [lib release];
7211 break;
7212 }
7213 }
7214 }
7215
7216 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
7218
7219 // Safe for an indirect command buffer if the stage has no textures at all,
7220 // or reaches them through an argument buffer.
7221 const auto icbSafe = [](const QMetalShader &s) {
7222 return !usesTextures(s.desc) || s.argumentBufferIndex >= 0;
7223 };
7224 d->icbCapable = wantArgumentBuffers && icbSafe(d->vs) && icbSafe(d->fs);
7225 if (d->icbCapable)
7226 rpDesc.supportIndirectCommandBuffers = YES;
7227
7228 if (m_multiViewCount >= 2)
7229 rpDesc.inputPrimitiveTopology = toMetalPrimitiveTopologyClass(m_topology);
7230
7231 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
7232
7233 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7234 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
7235
7236 NSError *err = nil;
7237 d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
7238 [rpDesc release];
7239 if (!d->ps) {
7240 const QString msg = QString::fromNSString(err.localizedDescription);
7241 qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
7242 return false;
7243 }
7244
7245 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
7247 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
7248 [dsDesc release];
7249
7250 d->primitiveType = toMetalPrimitiveType(m_topology);
7252
7253 return true;
7254}
7255
7256int QMetalGraphicsPipelineData::Tessellation::vsCompVariantToIndex(QShader::Variant vertexCompVariant)
7257{
7258 switch (vertexCompVariant) {
7259 case QShader::NonIndexedVertexAsComputeShader:
7260 return 0;
7261 case QShader::UInt32IndexedVertexAsComputeShader:
7262 return 1;
7263 case QShader::UInt16IndexedVertexAsComputeShader:
7264 return 2;
7265 default:
7266 break;
7267 }
7268 return -1;
7269}
7270
7272{
7273 const int varIndex = vsCompVariantToIndex(vertexCompVariant);
7274 if (varIndex >= 0 && vertexComputeState[varIndex])
7275 return vertexComputeState[varIndex];
7276
7277 id<MTLFunction> func = nil;
7278 if (varIndex >= 0)
7279 func = compVs[varIndex].func;
7280
7281 if (!func) {
7282 qWarning("No compute function found for vertex shader translated for tessellation, this should not happen");
7283 return nil;
7284 }
7285
7286 const QMap<int, int> &ebb(compVs[varIndex].nativeShaderInfo.extraBufferBindings);
7287 const int indexBufferBinding = ebb.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1);
7288
7289 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
7290 cpDesc.computeFunction = func;
7291 cpDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
7292 cpDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
7293 if (indexBufferBinding >= 0) {
7294 if (vertexCompVariant == QShader::UInt32IndexedVertexAsComputeShader) {
7295 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt32;
7296 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
7297 } else if (vertexCompVariant == QShader::UInt16IndexedVertexAsComputeShader) {
7298 cpDesc.stageInputDescriptor.indexType = MTLIndexTypeUInt16;
7299 cpDesc.stageInputDescriptor.indexBufferIndex = indexBufferBinding;
7300 }
7301 }
7302 q->setupStageInputDescriptor(cpDesc.stageInputDescriptor);
7303
7304 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
7305
7306 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7307 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
7308
7309 NSError *err = nil;
7310 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
7311 options: MTLPipelineOptionNone
7312 reflection: nil
7313 error: &err];
7314 [cpDesc release];
7315 if (!ps) {
7316 const QString msg = QString::fromNSString(err.localizedDescription);
7317 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
7318 } else {
7319 vertexComputeState[varIndex] = ps;
7320 }
7321 // not retained, the only owner is vertexComputeState and so the QRhiGraphicsPipeline
7322 return ps;
7323}
7324
7326{
7327 if (tessControlComputeState)
7328 return tessControlComputeState;
7329
7330 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
7331 cpDesc.computeFunction = compTesc.func;
7332
7333 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
7334
7335 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7336 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
7337
7338 NSError *err = nil;
7339 id<MTLComputePipelineState> ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
7340 options: MTLPipelineOptionNone
7341 reflection: nil
7342 error: &err];
7343 [cpDesc release];
7344 if (!ps) {
7345 const QString msg = QString::fromNSString(err.localizedDescription);
7346 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
7347 } else {
7348 tessControlComputeState = ps;
7349 }
7350 // not retained, the only owner is tessControlComputeState and so the QRhiGraphicsPipeline
7351 return ps;
7352}
7353
7354static inline bool indexTaken(quint32 index, quint64 indices)
7355{
7356 return (indices >> index) & 0x1;
7357}
7358
7359static inline void takeIndex(quint32 index, quint64 &indices)
7360{
7361 indices |= 1 << index;
7362}
7363
7364static inline int nextAttributeIndex(quint64 indices)
7365{
7366 // Maximum number of vertex attributes per vertex descriptor. There does
7367 // not appear to be a way to query this from the implementation.
7368 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf indicates
7369 // that all GPU families have a value of 31.
7370 static const int maxVertexAttributes = 31;
7371
7372 for (int index = 0; index < maxVertexAttributes; ++index) {
7373 if (!indexTaken(index, indices))
7374 return index;
7375 }
7376
7377 Q_UNREACHABLE_RETURN(-1);
7378}
7379
7380static inline int aligned(quint32 offset, quint32 alignment)
7381{
7382 return ((offset + alignment - 1) / alignment) * alignment;
7383}
7384
7385template<typename T>
7386static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
7387{
7388
7389 int elements = 1;
7390 for (const int dim : variable.arrayDims)
7391 elements *= dim;
7392
7393 if (variable.type == QShaderDescription::VariableType::Struct) {
7394 for (int element = 0; element < elements; ++element) {
7395 for (const auto &member : variable.structMembers) {
7396 addUnusedVertexAttribute(member, rhiD, offset, vertexAlignment);
7397 }
7398 }
7399 } else {
7400 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
7401 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
7402
7403 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
7404 const quint32 alignment = size;
7405 vertexAlignment = std::max(vertexAlignment, alignment);
7406
7407 for (int element = 0; element < elements; ++element) {
7408 // adjust alignment
7409 offset = aligned(offset, alignment);
7410 offset += size;
7411 }
7412 }
7413}
7414
7415template<typename T>
7416static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment)
7417{
7418
7419 int elements = 1;
7420 for (const int dim : variable.arrayDims)
7421 elements *= dim;
7422
7423 if (variable.type == QShaderDescription::VariableType::Struct) {
7424 for (int element = 0; element < elements; ++element) {
7425 for (const auto &member : variable.structMembers) {
7426 addVertexAttribute(member, binding, rhiD, index, offset, attributes, indices, vertexAlignment);
7427 }
7428 }
7429 } else {
7430 const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
7431 const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
7432
7433 // MSL specification 3.0 says alignment = size for non packed scalars and vectors
7434 const quint32 alignment = size;
7435 vertexAlignment = std::max(vertexAlignment, alignment);
7436
7437 for (int element = 0; element < elements; ++element) {
7438 Q_ASSERT(!indexTaken(index, indices));
7439
7440 // adjust alignment
7441 offset = aligned(offset, alignment);
7442
7443 attributes[index].bufferIndex = binding;
7444 attributes[index].format = toMetalAttributeFormat(format);
7445 attributes[index].offset = offset;
7446
7447 takeIndex(index, indices);
7448 index++;
7449 if (indexTaken(index, indices))
7450 index = nextAttributeIndex(indices);
7451
7452 offset += size;
7453 }
7454 }
7455}
7456
7457static inline bool matches(const QList<QShaderDescription::BlockVariable> &a, const QList<QShaderDescription::BlockVariable> &b)
7458{
7459 if (a.size() == b.size()) {
7460 bool match = true;
7461 for (int i = 0; i < a.size() && match; ++i) {
7462 match &= a[i].type == b[i].type
7463 && a[i].arrayDims == b[i].arrayDims
7464 && matches(a[i].structMembers, b[i].structMembers);
7465 }
7466 return match;
7467 }
7468
7469 return false;
7470}
7471
7472static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b)
7473{
7474 return a.location == b.location
7475 && a.type == b.type
7476 && a.perPatch == b.perPatch
7477 && matches(a.structMembers, b.structMembers);
7478}
7479
7480//
7481// Create the tessellation evaluation render pipeline state
7482//
7483// The tesc runs as a compute shader in a compute pipeline and writes per patch and per patch
7484// control point data into separate storage buffers. The tese runs as a vertex shader in a render
7485// pipeline. Our task is to generate a render pipeline descriptor for the tese that pulls vertices
7486// from these buffers.
7487//
7488// As the buffers we are pulling vertices from are written by a compute pipeline, they follow the
7489// MSL alignment conventions which we must take into account when generating our
7490// MTLVertexDescriptor. We must include the user defined tese input attributes, and any builtins
7491// that were used.
7492//
7493// SPIRV-Cross generates the MSL tese shader code with input attribute indices that reflect the
7494// specified GLSL locations. Interface blocks are flattened with each member having an incremented
7495// attribute index. SPIRV-Cross reports an error on compilation if there are clashes in the index
7496// address space.
7497//
7498// After the user specified attributes are processed, SPIRV-Cross places the in-use builtins at the
7499// next available (lowest value) attribute index. Tese builtins are processed in the following
7500// order:
7501//
7502// in gl_PerVertex
7503// {
7504// vec4 gl_Position;
7505// float gl_PointSize;
7506// float gl_ClipDistance[];
7507// };
7508//
7509// patch in float gl_TessLevelOuter[4];
7510// patch in float gl_TessLevelInner[2];
7511//
7512// Enumerations in QShaderDescription::BuiltinType are defined in this order.
7513//
7514// For quads, SPIRV-Cross places MTLQuadTessellationFactorsHalf per patch in the tessellation
7515// factor buffer. For triangles it uses MTLTriangleTessellationFactorsHalf.
7516//
7517// It should be noted that SPIRV-Cross handles the following builtin inputs internally, with no
7518// host side support required.
7519//
7520// in vec3 gl_TessCoord;
7521// in int gl_PatchVerticesIn;
7522// in int gl_PrimitiveID;
7523//
7525{
7526 if (pipeline->d->ps)
7527 return pipeline->d->ps;
7528
7529 MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
7530 MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
7531
7532 // tesc output buffers
7533 const QMap<int, int> &ebb(compTesc.nativeShaderInfo.extraBufferBindings);
7534 const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
7535 const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
7536 const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
7537 quint32 offsetInTescOutput = 0;
7538 quint32 offsetInTescPatchOutput = 0;
7539 quint32 offsetInTessFactorBuffer = 0;
7540 quint32 tescOutputAlignment = 0;
7541 quint32 tescPatchOutputAlignment = 0;
7542 quint32 tessFactorAlignment = 0;
7543 QSet<int> usedBuffers;
7544
7545 // tesc output variables in ascending location order
7546 QMap<int, QShaderDescription::InOutVariable> tescOutVars;
7547 for (const auto &tescOutVar : compTesc.desc.outputVariables())
7548 tescOutVars[tescOutVar.location] = tescOutVar;
7549
7550 // tese input variables in ascending location order
7551 QMap<int, QShaderDescription::InOutVariable> teseInVars;
7552 for (const auto &teseInVar : vertTese.desc.inputVariables())
7553 teseInVars[teseInVar.location] = teseInVar;
7554
7555 // bit mask tracking usage of vertex attribute indices
7556 quint64 indices = 0;
7557
7558 for (QShaderDescription::InOutVariable &tescOutVar : tescOutVars) {
7559
7560 int index = tescOutVar.location;
7561 int binding = -1;
7562 quint32 *offset = nullptr;
7563 quint32 *alignment = nullptr;
7564
7565 if (tescOutVar.perPatch) {
7566 binding = tescPatchOutputBufferBinding;
7567 offset = &offsetInTescPatchOutput;
7568 alignment = &tescPatchOutputAlignment;
7569 } else {
7570 tescOutVar.arrayDims.removeLast();
7571 binding = tescOutputBufferBinding;
7572 offset = &offsetInTescOutput;
7573 alignment = &tescOutputAlignment;
7574 }
7575
7576 if (teseInVars.contains(index)) {
7577
7578 if (!matches(teseInVars[index], tescOutVar)) {
7579 qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << index;
7580 qWarning() << " tesc out:" << tescOutVar;
7581 qWarning() << " tese in:" << teseInVars[index];
7582 }
7583
7584 if (binding != -1) {
7585 addVertexAttribute(tescOutVar, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
7586 usedBuffers << binding;
7587 } else {
7588 qWarning() << "baked tessellation control shader missing output buffer binding information";
7589 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
7590 }
7591
7592 } else {
7593 qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar;
7594 addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
7595 }
7596
7597 teseInVars.remove(tescOutVar.location);
7598 }
7599
7600 for (const QShaderDescription::InOutVariable &teseInVar : teseInVars)
7601 qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar;
7602
7603 // tesc output builtins in ascending location order
7604 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> tescOutBuiltins;
7605 for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables())
7606 tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin;
7607
7608 // tese input builtins in ascending location order
7609 QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> teseInBuiltins;
7610 for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables())
7611 teseInBuiltins[teseInBuiltin.type] = teseInBuiltin;
7612
7613 const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode;
7614 bool tessLevelAdded = false;
7615
7616 for (const QShaderDescription::BuiltinVariable &builtin : tescOutBuiltins) {
7617
7618 QShaderDescription::InOutVariable variable;
7619 int binding = -1;
7620 quint32 *offset = nullptr;
7621 quint32 *alignment = nullptr;
7622
7623 switch (builtin.type) {
7624 case QShaderDescription::BuiltinType::PositionBuiltin:
7625 variable.type = QShaderDescription::VariableType::Vec4;
7626 binding = tescOutputBufferBinding;
7627 offset = &offsetInTescOutput;
7628 alignment = &tescOutputAlignment;
7629 break;
7630 case QShaderDescription::BuiltinType::PointSizeBuiltin:
7631 variable.type = QShaderDescription::VariableType::Float;
7632 binding = tescOutputBufferBinding;
7633 offset = &offsetInTescOutput;
7634 alignment = &tescOutputAlignment;
7635 break;
7636 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
7637 variable.type = QShaderDescription::VariableType::Float;
7638 variable.arrayDims = builtin.arrayDims;
7639 binding = tescOutputBufferBinding;
7640 offset = &offsetInTescOutput;
7641 alignment = &tescOutputAlignment;
7642 break;
7643 case QShaderDescription::BuiltinType::TessLevelOuterBuiltin:
7644 variable.type = QShaderDescription::VariableType::Half4;
7645 binding = tessFactorBufferBinding;
7646 offset = &offsetInTessFactorBuffer;
7647 tessLevelAdded = trianglesMode;
7648 alignment = &tessFactorAlignment;
7649 break;
7650 case QShaderDescription::BuiltinType::TessLevelInnerBuiltin:
7651 if (trianglesMode) {
7652 if (!tessLevelAdded) {
7653 variable.type = QShaderDescription::VariableType::Half4;
7654 binding = tessFactorBufferBinding;
7655 offsetInTessFactorBuffer = 0;
7656 offset = &offsetInTessFactorBuffer;
7657 alignment = &tessFactorAlignment;
7658 tessLevelAdded = true;
7659 } else {
7660 teseInBuiltins.remove(builtin.type);
7661 continue;
7662 }
7663 } else {
7664 variable.type = QShaderDescription::VariableType::Half2;
7665 binding = tessFactorBufferBinding;
7666 offsetInTessFactorBuffer = 8;
7667 offset = &offsetInTessFactorBuffer;
7668 alignment = &tessFactorAlignment;
7669 }
7670 break;
7671 default:
7672 Q_UNREACHABLE();
7673 break;
7674 }
7675
7676 if (teseInBuiltins.contains(builtin.type)) {
7677 if (binding != -1) {
7678 int index = nextAttributeIndex(indices);
7679 addVertexAttribute(variable, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
7680 usedBuffers << binding;
7681 } else {
7682 qWarning() << "baked tessellation control shader missing output buffer binding information";
7683 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
7684 }
7685 } else {
7686 addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
7687 }
7688
7689 teseInBuiltins.remove(builtin.type);
7690 }
7691
7692 for (const QShaderDescription::BuiltinVariable &builtin : teseInBuiltins) {
7693 switch (builtin.type) {
7694 case QShaderDescription::BuiltinType::PositionBuiltin:
7695 case QShaderDescription::BuiltinType::PointSizeBuiltin:
7696 case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
7697 qWarning() << "missing tessellation control output for tessellation evaluation builtin input:" << builtin;
7698 break;
7699 default:
7700 break;
7701 }
7702 }
7703
7704 if (usedBuffers.contains(tescOutputBufferBinding)) {
7705 vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint;
7706 vertexDesc.layouts[tescOutputBufferBinding].stride = aligned(offsetInTescOutput, tescOutputAlignment);
7707 }
7708
7709 if (usedBuffers.contains(tescPatchOutputBufferBinding)) {
7710 vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
7711 vertexDesc.layouts[tescPatchOutputBufferBinding].stride = aligned(offsetInTescPatchOutput, tescPatchOutputAlignment);
7712 }
7713
7714 if (usedBuffers.contains(tessFactorBufferBinding)) {
7715 vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
7716 vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? sizeof(MTLTriangleTessellationFactorsHalf) : sizeof(MTLQuadTessellationFactorsHalf);
7717 }
7718
7719 rpDesc.vertexDescriptor = vertexDesc;
7720 rpDesc.vertexFunction = vertTese.func;
7721 rpDesc.fragmentFunction = pipeline->d->fs.func;
7722
7723 // The portable, cross-API approach is to use CCW, the results are then
7724 // identical (assuming the applied clipSpaceCorrMatrix) for all the 3D
7725 // APIs. The tess.eval. GLSL shader is thus expected to specify ccw. If it
7726 // doesn't, things may not work as expected.
7727 rpDesc.tessellationOutputWindingOrder = toMetalTessellationWindingOrder(vertTese.desc.tessellationWindingOrder());
7728
7729 rpDesc.tessellationPartitionMode = toMetalTessellationPartitionMode(vertTese.desc.tessellationPartitioning());
7730
7731 QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, pipeline->renderPassDescriptor());
7733
7734 rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
7735
7736 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
7737 rhiD->d->addRenderPipelineToBinaryArchive(rpDesc);
7738
7739 NSError *err = nil;
7740 id<MTLRenderPipelineState> ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
7741 [rpDesc release];
7742 if (!ps) {
7743 const QString msg = QString::fromNSString(err.localizedDescription);
7744 qWarning("Failed to create render pipeline state for tessellation: %s", qPrintable(msg));
7745 } else {
7746 // ps is stored in the QMetalGraphicsPipelineData so the end result in this
7747 // regard is no different from what createVertexFragmentPipeline does
7748 pipeline->d->ps = ps;
7749 }
7750 return ps;
7751}
7752
7754{
7755 QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers;
7756
7757 // Check if something is reusable as-is.
7758 for (QMetalBuffer *workBuf : *workBuffers) {
7759 if (workBuf && workBuf->lastActiveFrameSlot == -1 && workBuf->size() >= size) {
7760 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7761 return workBuf;
7762 }
7763 }
7764
7765 // Once the pool is above a certain threshold, see if there is something
7766 // unused (but too small) and recreate that our size.
7767 if (workBuffers->count() > QMTL_FRAMES_IN_FLIGHT * 8) {
7768 for (QMetalBuffer *workBuf : *workBuffers) {
7769 if (workBuf && workBuf->lastActiveFrameSlot == -1) {
7770 workBuf->setSize(size);
7771 if (workBuf->create()) {
7772 workBuf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7773 return workBuf;
7774 }
7775 }
7776 }
7777 }
7778
7779 // Add a new buffer to the pool.
7780 QMetalBuffer *buf;
7781 if (type == WorkBufType::DeviceLocal) {
7782 // for GPU->GPU data (non-slotted, not necessarily host writable)
7783 buf = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
7784 } else {
7785 // for CPU->GPU (non-slotted, host writable/coherent)
7786 buf = new QMetalBuffer(rhiD, QRhiBuffer::Dynamic, QRhiBuffer::UsageFlags(QMetalBuffer::WorkBufPoolUsage), size);
7787 }
7788 if (buf->create()) {
7789 buf->lastActiveFrameSlot = rhiD->currentFrameSlot;
7790 workBuffers->append(buf);
7791 return buf;
7792 }
7793
7794 qWarning("Failed to acquire work buffer of size %u", size);
7795 return nullptr;
7796}
7797
7798bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag)
7799{
7800 QRHI_RES_RHI(QRhiMetal);
7801 QString error;
7802 QByteArray entryPoint;
7803 QShaderKey activeKey;
7804
7805 const QShaderDescription tescDesc = tesc.description();
7806 const QShaderDescription teseDesc = tese.description();
7807 d->tess.inControlPointCount = uint(m_patchControlPointCount);
7808 d->tess.outControlPointCount = tescDesc.tessellationOutputVertexCount();
7809 if (!d->tess.outControlPointCount)
7810 d->tess.outControlPointCount = teseDesc.tessellationOutputVertexCount();
7811
7812 if (!d->tess.outControlPointCount) {
7813 qWarning("Failed to determine output vertex count from the tessellation control or evaluation shader, cannot tessellate");
7814 d->tess.enabled = false;
7815 d->tess.failed = true;
7816 return false;
7817 }
7818
7819 if (m_multiViewCount >= 2)
7820 qWarning("Multiview is not supported with tessellation");
7821
7822 // Now the vertex shader is a compute shader.
7823 // It should have three dedicated *VertexAsComputeShader variants.
7824 // What the requested variant was (Standard or Batchable) plays no role here.
7825 // (the Qt Quick scenegraph does not use tessellation with its materials)
7826 // Create all three versions.
7827
7828 bool variantsPresent[3] = {};
7829 const QVector<QShaderKey> tessVertKeys = tessVert.availableShaders();
7830 for (const QShaderKey &k : tessVertKeys) {
7831 switch (k.sourceVariant()) {
7832 case QShader::NonIndexedVertexAsComputeShader:
7833 variantsPresent[0] = true;
7834 break;
7835 case QShader::UInt32IndexedVertexAsComputeShader:
7836 variantsPresent[1] = true;
7837 break;
7838 case QShader::UInt16IndexedVertexAsComputeShader:
7839 variantsPresent[2] = true;
7840 break;
7841 default:
7842 break;
7843 }
7844 }
7845 if (!(variantsPresent[0] && variantsPresent[1] && variantsPresent[2])) {
7846 qWarning("Vertex shader is not prepared for Metal tessellation. Cannot tessellate. "
7847 "Perhaps the relevant variants (UInt32IndexedVertexAsComputeShader et al) were not generated? "
7848 "Try passing --msltess to qsb.");
7849 d->tess.enabled = false;
7850 d->tess.failed = true;
7851 return false;
7852 }
7853
7854 int varIndex = 0; // Will map NonIndexed as 0, UInt32 as 1, UInt16 as 2. Do not change this ordering.
7855 for (QShader::Variant variant : {
7856 QShader::NonIndexedVertexAsComputeShader,
7857 QShader::UInt32IndexedVertexAsComputeShader,
7858 QShader::UInt16IndexedVertexAsComputeShader })
7859 {
7860 id<MTLLibrary> lib = rhiD->d->createMetalLib(tessVert, variant, false, &error, &entryPoint, &activeKey);
7861 if (!lib) {
7862 qWarning("MSL shader compilation failed for vertex-as-compute shader %d: %s", int(variant), qPrintable(error));
7863 d->tess.enabled = false;
7864 d->tess.failed = true;
7865 return false;
7866 }
7867 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
7868 if (!func) {
7869 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7870 [lib release];
7871 d->tess.enabled = false;
7872 d->tess.failed = true;
7873 return false;
7874 }
7875 QMetalShader &compVs(d->tess.compVs[varIndex]);
7876 compVs.lib = lib;
7877 compVs.func = func;
7878 compVs.desc = tessVert.description();
7879 compVs.nativeResourceBindingMap = tessVert.nativeResourceBindingMap(activeKey);
7880 compVs.nativeShaderInfo = tessVert.nativeShaderInfo(activeKey);
7881
7882 // pre-create all three MTLComputePipelineStates
7883 if (!d->tess.vsCompPipeline(rhiD, variant)) {
7884 qWarning("Failed to pre-generate compute pipeline for vertex compute shader (tessellation variant %d)", int(variant));
7885 d->tess.enabled = false;
7886 d->tess.failed = true;
7887 return false;
7888 }
7889
7890 ++varIndex;
7891 }
7892
7893 // Pipeline #2 is a compute that runs the tessellation control (compute) shader
7894 id<MTLLibrary> tessControlLib = rhiD->d->createMetalLib(tesc, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7895 if (!tessControlLib) {
7896 qWarning("MSL shader compilation failed for tessellation control compute shader: %s", qPrintable(error));
7897 d->tess.enabled = false;
7898 d->tess.failed = true;
7899 return false;
7900 }
7901 id<MTLFunction> tessControlFunc = rhiD->d->createMSLShaderFunction(tessControlLib, entryPoint);
7902 if (!tessControlFunc) {
7903 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7904 [tessControlLib release];
7905 d->tess.enabled = false;
7906 d->tess.failed = true;
7907 return false;
7908 }
7909 d->tess.compTesc.lib = tessControlLib;
7910 d->tess.compTesc.func = tessControlFunc;
7911 d->tess.compTesc.desc = tesc.description();
7912 d->tess.compTesc.nativeResourceBindingMap = tesc.nativeResourceBindingMap(activeKey);
7913 d->tess.compTesc.nativeShaderInfo = tesc.nativeShaderInfo(activeKey);
7914 if (!d->tess.tescCompPipeline(rhiD)) {
7915 qWarning("Failed to pre-generate compute pipeline for tessellation control shader");
7916 d->tess.enabled = false;
7917 d->tess.failed = true;
7918 return false;
7919 }
7920
7921 // Pipeline #3 is a render pipeline with the tessellation evaluation (vertex) + the fragment shader
7922 id<MTLLibrary> tessEvalLib = rhiD->d->createMetalLib(tese, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7923 if (!tessEvalLib) {
7924 qWarning("MSL shader compilation failed for tessellation evaluation vertex shader: %s", qPrintable(error));
7925 d->tess.enabled = false;
7926 d->tess.failed = true;
7927 return false;
7928 }
7929 id<MTLFunction> tessEvalFunc = rhiD->d->createMSLShaderFunction(tessEvalLib, entryPoint);
7930 if (!tessEvalFunc) {
7931 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7932 [tessEvalLib release];
7933 d->tess.enabled = false;
7934 d->tess.failed = true;
7935 return false;
7936 }
7937 d->tess.vertTese.lib = tessEvalLib;
7938 d->tess.vertTese.func = tessEvalFunc;
7939 d->tess.vertTese.desc = tese.description();
7940 d->tess.vertTese.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey);
7941 d->tess.vertTese.nativeShaderInfo = tese.nativeShaderInfo(activeKey);
7942
7943 id<MTLLibrary> fragLib = rhiD->d->createMetalLib(tessFrag, QShader::StandardShader, false, &error, &entryPoint, &activeKey);
7944 if (!fragLib) {
7945 qWarning("MSL shader compilation failed for fragment shader: %s", qPrintable(error));
7946 d->tess.enabled = false;
7947 d->tess.failed = true;
7948 return false;
7949 }
7950 id<MTLFunction> fragFunc = rhiD->d->createMSLShaderFunction(fragLib, entryPoint);
7951 if (!fragFunc) {
7952 qWarning("MSL function for entry point %s not found", entryPoint.constData());
7953 [fragLib release];
7954 d->tess.enabled = false;
7955 d->tess.failed = true;
7956 return false;
7957 }
7958 d->fs.lib = fragLib;
7959 d->fs.func = fragFunc;
7960 d->fs.desc = tessFrag.description();
7961 d->fs.nativeShaderInfo = tessFrag.nativeShaderInfo(activeKey);
7962 d->fs.nativeResourceBindingMap = tessFrag.nativeResourceBindingMap(activeKey);
7963
7964 if (!d->tess.teseFragRenderPipeline(rhiD, this)) {
7965 qWarning("Failed to pre-generate render pipeline for tessellation evaluation + fragment shader");
7966 d->tess.enabled = false;
7967 d->tess.failed = true;
7968 return false;
7969 }
7970
7971 MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
7973 d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
7974 [dsDesc release];
7975
7976 // no primitiveType
7978
7979 return true;
7980}
7981
7983{
7984 destroy(); // no early test, always invoke and leave it to destroy to decide what to clean up
7985
7986 QRHI_RES_RHI(QRhiMetal);
7987 rhiD->pipelineCreationStart();
7988 if (!rhiD->sanityCheckGraphicsPipeline(this))
7989 return false;
7990
7991 // See if tessellation is involved. Things will be very different, if so.
7992 QShader tessVert;
7993 QShader tesc;
7994 QShader tese;
7995 QShader tessFrag;
7996 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
7997 switch (shaderStage.type()) {
7998 case QRhiShaderStage::Vertex:
7999 tessVert = shaderStage.shader();
8000 break;
8001 case QRhiShaderStage::TessellationControl:
8002 tesc = shaderStage.shader();
8003 break;
8004 case QRhiShaderStage::TessellationEvaluation:
8005 tese = shaderStage.shader();
8006 break;
8007 case QRhiShaderStage::Fragment:
8008 tessFrag = shaderStage.shader();
8009 break;
8010 default:
8011 break;
8012 }
8013 }
8014 d->tess.enabled = tesc.isValid() && tese.isValid() && m_topology == Patches && m_patchControlPointCount > 0;
8015 d->tess.failed = false;
8016
8017 bool ok = d->tess.enabled ? createTessellationPipelines(tessVert, tesc, tese, tessFrag) : createVertexFragmentPipeline();
8018 if (!ok)
8019 return false;
8020
8021 // SPIRV-Cross buffer size buffers
8022 int buffers = 0;
8023 QVarLengthArray<QMetalShader *, 6> shaders;
8024 if (d->tess.enabled) {
8025 shaders.append(&d->tess.compVs[0]);
8026 shaders.append(&d->tess.compVs[1]);
8027 shaders.append(&d->tess.compVs[2]);
8028 shaders.append(&d->tess.compTesc);
8029 shaders.append(&d->tess.vertTese);
8030 } else {
8031 shaders.append(&d->vs);
8032 }
8033 shaders.append(&d->fs);
8034
8035 for (QMetalShader *shader : shaders) {
8036 if (shader->nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8037 const int binding = shader->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
8038 shader->nativeResourceBindingMap[binding] = {binding, -1};
8039 int maxNativeBinding = 0;
8040 for (const QShaderDescription::StorageBlock &block : shader->desc.storageBlocks())
8041 maxNativeBinding = qMax(maxNativeBinding, shader->nativeResourceBindingMap[block.binding].first);
8042
8043 // we use one buffer to hold data for all graphics shader stages, each with a different offset.
8044 // buffer offsets must be 32byte aligned - adjust buffer count accordingly
8045 buffers += ((maxNativeBinding + 1 + 7) / 8) * 8;
8046 }
8047 }
8048
8049 if (buffers) {
8050 if (!d->bufferSizeBuffer)
8051 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static,
8052 QRhiBuffer::UsageFlags(int(QRhiBuffer::StorageBuffer)
8053 | QMetalBuffer::InternalHostWritable),
8054 buffers * sizeof(int));
8055
8056 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
8058 }
8059
8060 rhiD->pipelineCreationEnd();
8062 generation += 1;
8063 rhiD->registerResource(this);
8064 return true;
8065}
8066
8072
8074{
8075 destroy();
8076 delete d;
8077}
8078
8080{
8081 d->cs.destroy();
8082
8083 if (!d->ps)
8084 return;
8085
8086 delete d->bufferSizeBuffer;
8087 d->bufferSizeBuffer = nullptr;
8088
8092 e.computePipeline.pipelineState = d->ps;
8093 d->ps = nil;
8094
8095 QRHI_RES_RHI(QRhiMetal);
8096 if (rhiD) {
8097 rhiD->d->releaseQueue.append(e);
8098 rhiD->unregisterResource(this);
8099 }
8100}
8101
8102void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
8103{
8104 if (binArch) {
8105 NSArray *binArchArray = [NSArray arrayWithObjects: binArch, nil];
8106 cpDesc.binaryArchives = binArchArray;
8107 }
8108}
8109
8110void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
8111{
8112 if (binArch) {
8113 NSError *err = nil;
8114 if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) {
8115 const QString msg = QString::fromNSString(err.localizedDescription);
8116 qWarning("Failed to collect compute pipeline functions to binary archive: %s", qPrintable(msg));
8117 }
8118 }
8119}
8120
8122{
8123 if (d->ps)
8124 destroy();
8125
8126 QRHI_RES_RHI(QRhiMetal);
8127 rhiD->pipelineCreationStart();
8128
8129 auto cacheIt = rhiD->d->shaderCache.constFind({ m_shaderStage, false });
8130 if (cacheIt != rhiD->d->shaderCache.constEnd()) {
8131 d->cs = *cacheIt;
8132 } else {
8133 const QShader shader = m_shaderStage.shader();
8134 QString error;
8135 QByteArray entryPoint;
8136 QShaderKey activeKey;
8137 id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), false,
8138 &error, &entryPoint, &activeKey);
8139 if (!lib) {
8140 qWarning("MSL shader compilation failed: %s", qPrintable(error));
8141 return false;
8142 }
8143 id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
8144 if (!func) {
8145 qWarning("MSL function for entry point %s not found", entryPoint.constData());
8146 [lib release];
8147 return false;
8148 }
8149 d->cs.lib = lib;
8150 d->cs.func = func;
8151 d->cs.localSize = shader.description().computeShaderLocalSize();
8152 d->cs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey);
8153 d->cs.desc = shader.description();
8154 d->cs.nativeShaderInfo = shader.nativeShaderInfo(activeKey);
8155
8156 // Compute never needs argument buffers on its own (they are there for
8157 // indirect command buffers, which are graphics-only), hence not asking
8158 // for that shader variant above. It can still be requested explicitly
8159 // via the QRhiShaderStage, in which case the textures and samplers have
8160 // to go through an argument buffer here as well.
8161 setupArgumentBufferEncoder(&d->cs);
8162
8163 // SPIRV-Cross buffer size buffers
8164 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8165 const int binding = d->cs.nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding];
8166 d->cs.nativeResourceBindingMap[binding] = {binding, -1};
8167 }
8168
8169 if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
8170 for (QMetalShader &s : rhiD->d->shaderCache)
8171 s.destroy();
8172 rhiD->d->shaderCache.clear();
8173 }
8174 rhiD->d->shaderCache.insert({ m_shaderStage, false }, d->cs);
8175 }
8176
8177 [d->cs.lib retain];
8178 [d->cs.func retain];
8179 [d->cs.argumentEncoder retain];
8180
8181 if (d->cs.argumentBufferIndex >= 0 && !rhiD->caps.indirectCommandBuffers) {
8182 // Sampler states are created with supportArgumentBuffers only when this
8183 // cap is present, and Metal faults the GPU without any warning when one
8184 // that lacks it is used with an argument buffer.
8185 qWarning("The ArgumentBufferShader variant of a compute shader cannot be used on this device");
8186 return false;
8187 }
8188
8189 d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]);
8190
8191 MTLComputePipelineDescriptor *cpDesc = [MTLComputePipelineDescriptor new];
8192 cpDesc.computeFunction = d->cs.func;
8193
8194 rhiD->d->trySeedingComputePipelineFromBinaryArchive(cpDesc);
8195
8196 if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
8197 rhiD->d->addComputePipelineToBinaryArchive(cpDesc);
8198
8199 NSError *err = nil;
8200 d->ps = [rhiD->d->dev newComputePipelineStateWithDescriptor: cpDesc
8201 options: MTLPipelineOptionNone
8202 reflection: nil
8203 error: &err];
8204 [cpDesc release];
8205 if (!d->ps) {
8206 const QString msg = QString::fromNSString(err.localizedDescription);
8207 qWarning("Failed to create compute pipeline state: %s", qPrintable(msg));
8208 return false;
8209 }
8210
8211 // SPIRV-Cross buffer size buffers
8212 if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) {
8213 int buffers = 0;
8214 for (const QShaderDescription::StorageBlock &block : d->cs.desc.storageBlocks())
8215 buffers = qMax(buffers, d->cs.nativeResourceBindingMap[block.binding].first);
8216
8217 buffers += 1;
8218
8219 if (!d->bufferSizeBuffer)
8220 d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static,
8221 QRhiBuffer::UsageFlags(int(QRhiBuffer::StorageBuffer)
8222 | QMetalBuffer::InternalHostWritable),
8223 buffers * sizeof(int));
8224
8225 d->bufferSizeBuffer->setSize(buffers * sizeof(int));
8227 }
8228
8229 rhiD->pipelineCreationEnd();
8231 generation += 1;
8232 rhiD->registerResource(this);
8233 return true;
8234}
8235
8239{
8241}
8242
8244{
8245 destroy();
8246 delete d;
8247}
8248
8250{
8251 // nothing to do here, we do not own the MTL cb object
8252}
8253
8255{
8256 nativeHandlesStruct.commandBuffer = (MTLCommandBuffer *) d->cb;
8257 nativeHandlesStruct.encoder = (MTLRenderCommandEncoder *) d->currentRenderPassEncoder;
8258 return &nativeHandlesStruct;
8259}
8260
8261void QMetalCommandBuffer::resetState(double lastGpuTime)
8262{
8263 d->lastGpuTime = lastGpuTime;
8264 d->currentRenderPassEncoder = nil;
8265 d->currentComputePassEncoder = nil;
8266 d->tessellationComputeEncoder = nil;
8267 d->currentPassRpDesc = nil;
8269}
8270
8272{
8274 currentTarget = nullptr;
8275 d->openDebugGroups.clear();
8277}
8278
8280{
8281 currentGraphicsPipeline = nullptr;
8282 currentComputePipeline = nullptr;
8284 pushConstantData.clear();
8286 currentGraphicsSrb = nullptr;
8287 currentComputeSrb = nullptr;
8289 currentResSlot = -1;
8290 currentIndexBuffer = nullptr;
8291 currentIndexOffset = 0;
8292 currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
8293 currentCullMode = -1;
8297 currentDepthBiasValues = { 0.0f, 0.0f };
8298 hasCustomScissorSet = false;
8299 currentScissor = {};
8300 currentViewport = {};
8301 hasBlendConstantsSet = false;
8302 currentBlendConstants = {};
8303 hasStencilRefSet = false;
8304 currentStencilRef = 0;
8305
8306 d->currentShaderResourceBindingState = {};
8307 d->currentDepthStencilState = nil;
8309 d->currentVertexInputsBuffers.clear();
8310 d->currentVertexInputOffsets.clear();
8311}
8312
8313QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
8314 : QRhiSwapChain(rhi),
8315 rtWrapper(rhi, this),
8316 cbWrapper(rhi),
8318{
8319 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8320 d->sem[i] = nullptr;
8321 d->msaaTex[i] = nil;
8322 }
8323}
8324
8326{
8327 destroy();
8328 delete d;
8329}
8330
8332{
8333 if (!d->layer)
8334 return;
8335
8336 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8337 if (d->sem[i]) {
8338 // the semaphores cannot be released if they do not have the initial value
8340
8341 dispatch_release(d->sem[i]);
8342 d->sem[i] = nullptr;
8343 }
8344 }
8345
8346 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8347 [d->msaaTex[i] release];
8348 d->msaaTex[i] = nil;
8349 }
8350
8351 d->layer = nullptr;
8352 m_proxyData = {};
8353
8354 [d->curDrawable release];
8355 d->curDrawable = nil;
8356
8357 QRHI_RES_RHI(QRhiMetal);
8358 if (rhiD) {
8359 rhiD->swapchains.remove(this);
8360 rhiD->unregisterResource(this);
8361 }
8362}
8363
8365{
8366 return &cbWrapper;
8367}
8368
8373
8374// view.layer should ideally be called on the main thread, otherwise the UI
8375// Thread Checker in Xcode drops a warning. Hence trying to proxy it through
8376// QRhiSwapChainProxyData instead of just calling this function directly.
8377static inline CAMetalLayer *layerForWindow(QWindow *window)
8378{
8379 Q_ASSERT(window);
8380 CALayer *layer = nullptr;
8381#ifdef Q_OS_MACOS
8382 if (auto *cocoaWindow = window->nativeInterface<QNativeInterface::Private::QCocoaWindow>())
8383 layer = cocoaWindow->contentLayer();
8384#else
8385 layer = reinterpret_cast<UIView *>(window->winId()).layer;
8386#endif
8387 Q_ASSERT(layer);
8388 return static_cast<CAMetalLayer *>(layer);
8389}
8390
8391// If someone calls this, it is hopefully from the main thread, and they will
8392// then set the returned data on the QRhiSwapChain, so it won't need to query
8393// the layer on its own later on.
8395{
8397 d.reserved[0] = layerForWindow(window);
8398 return d;
8399}
8400
8402{
8403 Q_ASSERT(m_window);
8404 CAMetalLayer *layer = d->layer;
8405 if (!layer)
8406 layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, m_window, QRhi::Metal, 0);
8407
8408 Q_ASSERT(layer);
8409 int height = (int)layer.bounds.size.height;
8410 int width = (int)layer.bounds.size.width;
8411 width *= layer.contentsScale;
8412 height *= layer.contentsScale;
8413 return QSize(width, height);
8414}
8415
8417{
8418 if (f == HDRExtendedSrgbLinear) {
8419 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8420 } else if (f == HDR10) {
8421 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8422 } else if (f == HDRExtendedDisplayP3Linear) {
8423 return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
8424 }
8425 return f == SDR;
8426}
8427
8429{
8430 QRHI_RES_RHI(QRhiMetal);
8431
8432 chooseFormats(); // ensure colorFormat and similar are filled out
8433
8434 QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
8436 rpD->hasDepthStencil = m_depthStencil != nullptr;
8437
8438 rpD->colorFormat[0] = int(d->colorFormat);
8439
8440#ifdef Q_OS_MACOS
8441 // m_depthStencil may not be built yet so cannot rely on computed fields in it
8442 rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
8443 ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
8444#else
8445 rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8;
8446#endif
8447
8448 rpD->hasShadingRateMap = m_shadingRateMap != nullptr;
8449
8451
8452 rhiD->registerResource(rpD, false);
8453 return rpD;
8454}
8455
8457{
8458 QRHI_RES_RHI(QRhiMetal);
8459 samples = rhiD->effectiveSampleCount(m_sampleCount);
8460 // pick a format that is allowed for CAMetalLayer.pixelFormat
8461 if (m_format == HDRExtendedSrgbLinear || m_format == HDRExtendedDisplayP3Linear) {
8462 d->colorFormat = MTLPixelFormatRGBA16Float;
8463 d->rhiColorFormat = QRhiTexture::RGBA16F;
8464 return;
8465 }
8466 if (m_format == HDR10) {
8467 d->colorFormat = MTLPixelFormatRGB10A2Unorm;
8468 d->rhiColorFormat = QRhiTexture::RGB10A2;
8469 return;
8470 }
8471 d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
8472 d->rhiColorFormat = QRhiTexture::BGRA8;
8473}
8474
8476{
8477 // wait+signal is the general pattern to ensure the commands for a
8478 // given frame slot have completed (if sem is 1, we go 0 then 1; if
8479 // sem is 0 we go -1, block, completion increments to 0, then us to 1)
8480
8481 dispatch_semaphore_t sem = d->sem[slot];
8482 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
8483 dispatch_semaphore_signal(sem);
8484}
8485
8487{
8488 Q_ASSERT(m_window);
8489
8490 const bool needsRegistration = !window || window != m_window;
8491
8492 if (window && window != m_window)
8493 destroy();
8494 // else no destroy(), this is intentional
8495
8496 QRHI_RES_RHI(QRhiMetal);
8497 if (needsRegistration || !rhiD->swapchains.contains(this))
8498 rhiD->swapchains.insert(this);
8499
8500 window = m_window;
8501
8502 if (window->surfaceType() != QSurface::MetalSurface) {
8503 qWarning("QMetalSwapChain only supports MetalSurface windows");
8504 return false;
8505 }
8506
8507 d->layer = qrhi_objectFromProxyData<CAMetalLayer>(&m_proxyData, window, QRhi::Metal, 0);
8508 Q_ASSERT(d->layer);
8509
8511 if (d->colorFormat != d->layer.pixelFormat)
8512 d->layer.pixelFormat = d->colorFormat;
8513
8514 if (m_format == HDRExtendedSrgbLinear) {
8515 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
8516 d->layer.wantsExtendedDynamicRangeContent = YES;
8517 } else if (m_format == HDR10) {
8518 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ);
8519 d->layer.wantsExtendedDynamicRangeContent = YES;
8520 } else if (m_format == HDRExtendedDisplayP3Linear) {
8521 d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
8522 d->layer.wantsExtendedDynamicRangeContent = YES;
8523 }
8524
8525 if (m_flags.testFlag(UsedAsTransferSource))
8526 d->layer.framebufferOnly = NO;
8527
8528#ifdef Q_OS_MACOS
8529 if (m_flags.testFlag(NoVSync))
8530 d->layer.displaySyncEnabled = NO;
8531#endif
8532
8533 if (m_flags.testFlag(SurfaceHasPreMulAlpha)) {
8534 d->layer.opaque = NO;
8535 } else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
8536 // The CoreAnimation compositor is said to expect premultiplied alpha,
8537 // so this is then wrong when it comes to the blending operations but
8538 // there's nothing we can do. Fortunately Qt Quick always outputs
8539 // premultiplied alpha so it is not a problem there.
8540 d->layer.opaque = NO;
8541 } else {
8542 d->layer.opaque = YES;
8543 }
8544
8545 // Now set the layer's drawableSize which will stay set to the same value
8546 // until the next createOrResize(), thus ensuring atomicity with regards to
8547 // the drawable size in frames.
8548 int width = (int)d->layer.bounds.size.width;
8549 int height = (int)d->layer.bounds.size.height;
8550 CGSize layerSize = CGSizeMake(width, height);
8551 const float scaleFactor = d->layer.contentsScale;
8552 layerSize.width *= scaleFactor;
8553 layerSize.height *= scaleFactor;
8554 d->layer.drawableSize = layerSize;
8555
8556 m_currentPixelSize = QSizeF::fromCGSize(layerSize).toSize();
8557 pixelSize = m_currentPixelSize;
8558
8559 [d->layer setDevice: rhiD->d->dev];
8560
8561 [d->curDrawable release];
8562 d->curDrawable = nil;
8563
8564 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8565 d->lastGpuTime[i] = 0;
8566 if (!d->sem[i])
8567 d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
8568 }
8569
8570 currentFrameSlot = 0;
8571 frameCount = 0;
8572
8573 ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
8574 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
8575 qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
8576 m_depthStencil->sampleCount(), m_sampleCount);
8577 }
8578 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
8579 if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
8580 m_depthStencil->setPixelSize(pixelSize);
8581 if (!m_depthStencil->create())
8582 qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
8583 pixelSize.width(), pixelSize.height());
8584 } else {
8585 qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
8586 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
8587 pixelSize.width(), pixelSize.height());
8588 }
8589 }
8590
8591 rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
8592 rtWrapper.d->pixelSize = pixelSize;
8593 rtWrapper.d->dpr = scaleFactor;
8596 rtWrapper.d->dsAttCount = ds ? 1 : 0;
8597
8598 qCDebug(QRHI_LOG_INFO, "got CAMetalLayer, pixel size %dx%d (scale %.2f)",
8599 pixelSize.width(), pixelSize.height(), scaleFactor);
8600
8601 if (samples > 1) {
8602 MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
8603 desc.textureType = MTLTextureType2DMultisample;
8604 desc.pixelFormat = d->colorFormat;
8605 desc.width = NSUInteger(pixelSize.width());
8606 desc.height = NSUInteger(pixelSize.height());
8607 desc.sampleCount = NSUInteger(samples);
8608 desc.resourceOptions = MTLResourceStorageModePrivate;
8609 desc.storageMode = MTLStorageModePrivate;
8610 desc.usage = MTLTextureUsageRenderTarget;
8611 for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
8612 if (d->msaaTex[i]) {
8615 e.lastActiveFrameSlot = 1; // because currentFrameSlot is reset to 0
8616 e.renderbuffer.texture = d->msaaTex[i];
8617 rhiD->d->releaseQueue.append(e);
8618 }
8619 d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
8620 }
8621 [desc release];
8622 }
8623
8624 rhiD->registerResource(this);
8625
8626 return true;
8627}
8628
8630{
8633 info.limits.colorComponentValue.maxColorComponentValue = 1;
8634 info.limits.colorComponentValue.maxPotentialColorComponentValue = 1;
8636 info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred
8637
8638 if (m_window) {
8639 // Must use m_window, not window, given this may be called before createOrResize().
8640#if defined(Q_OS_MACOS)
8641 NSView *view = reinterpret_cast<NSView *>(m_window->winId());
8642 NSScreen *screen = view.window.screen;
8643 info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue;
8644 info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
8645#elif defined(Q_OS_IOS)
8646 UIView *view = reinterpret_cast<UIView *>(m_window->winId());
8647 UIScreen *screen = view.window.windowScene.screen;
8648 info.limits.colorComponentValue.maxColorComponentValue =
8649 view.window.windowScene.screen.currentEDRHeadroom;
8650 info.limits.colorComponentValue.maxPotentialColorComponentValue =
8651 screen.potentialEDRHeadroom;
8652#endif
8653 }
8654
8655 return info;
8656}
8657
8658QT_END_NAMESPACE
const char * constData() const
Definition qrhi_p.h:477
void buildIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb, const QRhiIndirectCommandBufferBuildInfo &info) override
void drawIndirectCount(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, QRhiBuffer *countBuffer, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride) override
static QRhiSwapChainProxyData updateSwapChainProxyData(QWindow *window)
QMetalSwapChain * currentSwapChain
bool isDeviceLost() const override
void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override
QRhiStats statistics() override
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:862
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override
Definition qrhimetal.mm:893
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:577
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)
void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, bool offsetOnlyChange, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES], const QMetalShader *shaders[SUPPORTED_STAGES])
QRhiSwapChain * createSwapChain() override
Definition qrhimetal.mm:852
QRhiGraphicsPipeline * createGraphicsPipeline() override
const char * icbUnavailableReason(QMetalCommandBuffer *cbD) const
bool create(QRhi::Flags flags) override
Definition qrhimetal.mm:653
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override
void dispatchIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset) 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
void setPushConstants(QRhiCommandBuffer *cb, quint32 offset, quint32 size, const void *data) override
static const int SUPPORTED_STAGES
void setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) override
bool isYUpInNDC() const override
Definition qrhimetal.mm:872
void commitIndirectCommandBuffer(QRhiResourceUpdateBatch *u, QRhiIndirectCommandBuffer *icb) override
int resourceLimit(QRhi::ResourceLimit limit) const override
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:882
void drawIndexedIndirectCount(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, QRhiBuffer *countBuffer, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride) override
const QRhiNativeHandles * nativeHandles() override
void interruptRenderPass(QMetalCommandBuffer *cbD)
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:877
QRhiTextureRenderTarget * createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override
bool isYUpInFramebuffer() const override
Definition qrhimetal.mm:867
bool prepareIcbKernels()
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
void finalizeDeferredStoreActions(QMetalCommandBuffer *cbD, bool passIsEnding)
QByteArray pipelineCacheData() override
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
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
bool icbDraw(QMetalCommandBuffer *cbD, bool indexed, QMetalBuffer *indirectBufD, quint32 indirectBufferOffset, QMetalBuffer *countBufD, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride)
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
double lastCompletedGpuTime(QRhiCommandBuffer *cb) override
QList< int > supportedSampleCounts() const override
void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override
void executeIndirect(QRhiCommandBuffer *cb, QRhiIndirectCommandBuffer *icb, quint32 firstCommand, quint32 commandCount) override
bool prepareIcb(quint32 maxDrawCount)
void finishActiveReadbacks(bool forced=false)
void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override
bool isFeatureSupported(QRhi::Feature feature) const override
Definition qrhimetal.mm:926
void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, qsizetype *curOfs)
void destroy() override
Definition qrhimetal.mm:775
QList< QSize > supportedShadingRates(int sampleCount) const override
Definition qrhimetal.mm:846
void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
static QRhiResourceUpdateBatchPrivate * get(QRhiResourceUpdateBatch *b)
Definition qrhi_p.h:745
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:325
\inmodule QtGui
Definition qshader.h:81
Combined button and popup list for selecting options.
#define __has_feature(x)
@ UnBounded
Definition qrhi_p.h:390
@ Bounded
Definition qrhi_p.h:391
#define QRHI_RES_RHI(t)
Definition qrhi_p.h:33
#define QRHI_RES(t, x)
Definition qrhi_p.h:32
Int aligned(Int v, Int byteAlign)
\variable QRhiVulkanQueueSubmitParams::waitSemaphoreCount
static int mtlPushConstantBufferIndex(const QMetalShader &s)
static bool usesTextures(const QShaderDescription &desc)
static MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
static void qrhimtl_releaseIcbSlots(QRhiMetal *rhiD, QMetalIndirectCommandBuffer *icbD)
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 void qrhimtl_encodeIcbFromCpu(QMetalIndirectCommandBuffer *icbD, QMetalIndirectCommandBufferData::Slot &slot, MTLPrimitiveType primitiveType, id< MTLBuffer > indexBufMtl, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
static bool hasArgumentBufferVariant(const QShader &shader)
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 QMetalRenderTargetData * currentRenderTargetData(QMetalCommandBuffer *cbD)
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 constexpr quint32 ICB_DRAW_COUNT_THRESHOLD
BindingType
static MTLTriangleFillMode toMetalTriangleFillMode(QRhiGraphicsPipeline::PolygonMode mode)
static quint32 mtlPushConstantBlockSize(const QMetalShader &s)
static MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
static MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
static void endTempComputeEncoding(QRhiMetal *rhiD, QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > computeEncoder)
static void takeIndex(quint32 index, quint64 &indices)
static int mapBinding(int binding, int stageIndex, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[], BindingType type)
static id< MTLComputeCommandEncoder > tempComputeEncoder(QRhiMetal *rhiD, QMetalCommandBuffer *cbD, id< MTLComputeCommandEncoder > maybeComputeEncoder)
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 setupArgumentBufferEncoder(QMetalShader *shader)
static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
static uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
static MTLStoreAction interruptionStoreAction(MTLStoreAction finalAction, bool passIsEnding)
#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
Definition qrhimetal.mm:62
static void qrhimtl_replayIcbOnCpu(QMetalCommandBuffer *cbD, QMetalIndirectCommandBuffer *icbD, quint32 firstCommand, quint32 count, int currentFrameSlot)
static bool qrhimtl_ensureIcbSlots(QRhiMetal *rhiD, QMetalIndirectCommandBuffer *icbD, QMetalIndirectCommandBufferData::Fill fill)
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)
static void declareStageArgumentBufferResources(QMetalCommandBuffer *cbD, int encoderStage, const QMetalShaderResourceBindingsData::Stage &res)
static bool canStoreAttachment(id< MTLTexture > tex)
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_RELOCATABLE_TYPE)
static MTLResourceUsage storageImageUsage(QRhiShaderResourceBinding::Type type)
static bool qrhimtl_icbSlotMatches(const QMetalIndirectCommandBuffer *icbD, const QMetalIndirectCommandBufferData::Slot &slot, MTLPrimitiveType primitiveType, id< MTLBuffer > indexBufMtl, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
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 void encodeIcbWithCompute(QRhiMetalData *d, id< MTLComputeCommandEncoder > computeEncoder, id< MTLIndirectCommandBuffer > targetIcb, id< MTLBuffer > targetArgBuffer, id< MTLBuffer > targetRangeBuffer, bool indexed, QRhiCommandBuffer::IndexFormat indexFormat, MTLPrimitiveType primitiveType, id< MTLBuffer > indirectBufMtl, quint32 indirectBufferOffset, id< MTLBuffer > indexBufMtl, quint32 indexBufferOffset, id< MTLBuffer > countBufMtl, quint32 countBufferOffset, quint32 maxDrawCount, quint32 stride)
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:57
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:365
id< MTLBuffer > buf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:360
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:441
id< MTLDepthStencilState > currentDepthStencilState
Definition qrhimetal.mm:445
QMetalShaderResourceBindingsData currentShaderResourceBindingState
Definition qrhimetal.mm:446
MTLStoreAction deferredStencilStoreAction
Definition qrhimetal.mm:454
QVarLengthArray< std::pair< uint, MTLStoreAction >, 4 > deferredColorStoreActions
Definition qrhimetal.mm:452
MTLStoreAction deferredDepthStoreAction
Definition qrhimetal.mm:453
id< MTLComputeCommandEncoder > tessellationComputeEncoder
Definition qrhimetal.mm:440
QRhiBatchedBindings< id< MTLBuffer > > currentVertexInputsBuffers
Definition qrhimetal.mm:443
id< MTLRenderCommandEncoder > currentRenderPassEncoder
Definition qrhimetal.mm:438
id< MTLCommandBuffer > cb
Definition qrhimetal.mm:436
QRhiBatchedBindings< NSUInteger > currentVertexInputOffsets
Definition qrhimetal.mm:444
QVarLengthArray< QByteArray, 4 > openDebugGroups
Definition qrhimetal.mm:447
id< MTLComputeCommandEncoder > currentComputePassEncoder
Definition qrhimetal.mm:439
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:557
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:562
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:511
QMetalBuffer * acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type=WorkBufType::DeviceLocal)
QVector< QMetalBuffer * > hostVisibleWorkBuffers
Definition qrhimetal.mm:512
quint32 tescCompOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:530
std::array< id< MTLComputePipelineState >, 3 > vertexComputeState
Definition qrhimetal.mm:521
quint32 tescCompPatchOutputBufferSize(quint32 patchCount) const
Definition qrhimetal.mm:534
static int vsCompVariantToIndex(QShader::Variant vertexCompVariant)
id< MTLComputePipelineState > tescCompPipeline(QRhiMetal *rhiD)
id< MTLRenderPipelineState > teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline)
QMetalGraphicsPipelineData * q
Definition qrhimetal.mm:515
id< MTLComputePipelineState > vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant)
quint32 patchCountForDrawCall(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:539
quint32 vsCompOutputBufferSize(quint32 vertexOrIndexCount, quint32 instanceCount) const
Definition qrhimetal.mm:525
id< MTLComputePipelineState > tessControlComputeState
Definition qrhimetal.mm:522
QMetalGraphicsPipeline * q
Definition qrhimetal.mm:492
MTLDepthClipMode depthClipMode
Definition qrhimetal.mm:500
MTLPrimitiveType primitiveType
Definition qrhimetal.mm:496
id< MTLRenderPipelineState > ps
Definition qrhimetal.mm:493
QMetalBuffer * bufferSizeBuffer
Definition qrhimetal.mm:552
void setupVertexInputDescriptor(MTLVertexDescriptor *desc)
void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc)
id< MTLDepthStencilState > ds
Definition qrhimetal.mm:494
MTLTriangleFillMode triangleFillMode
Definition qrhimetal.mm:499
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)
QRhiCommandBuffer::IndexFormat indexFormat
id< MTLIndirectCommandBuffer > icb
QRhiIndirectCommandBufferBuildInfo buildInfo
Slot frameSlots[QMTL_FRAMES_IN_FLIGHT]
QMetalIndirectCommandBuffer(QRhiImplementation *rhi, Type type, quint32 maxCommandCount)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalIndirectCommandBufferData * d
const QRhiIndexedIndirectDrawCommand * indexedDrawCommands() const
bool create() override
Creates the corresponding native objects.
id< MTLTexture > tex
Definition qrhimetal.mm:371
MTLPixelFormat format
Definition qrhimetal.mm:370
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QMetalRenderBufferData * d
Definition qrhimetal_p.h:67
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:478
id< MTLTexture > dsResolveTex
Definition qrhimetal.mm:480
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList
Definition qrhimetal.mm:487
id< MTLTexture > dsTex
Definition qrhimetal.mm:479
id< MTLSamplerState > samplerState
Definition qrhimetal.mm:398
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:422
QVarLengthArray< Sampler, 8 > samplers
Definition qrhimetal.mm:424
QRhiBatchedBindings< NSUInteger > bufferOffsetBatches
Definition qrhimetal.mm:426
QVarLengthArray< Texture, 8 > textures
Definition qrhimetal.mm:423
QRhiBatchedBindings< id< MTLSamplerState > > samplerBatches
Definition qrhimetal.mm:428
QRhiBatchedBindings< id< MTLTexture > > textureBatches
Definition qrhimetal.mm:427
QRhiBatchedBindings< id< MTLBuffer > > bufferBatches
Definition qrhimetal.mm:425
bool create() override
Creates the corresponding resource binding set.
QMetalComputePipeline * lastUsedComputePipeline
QMetalShaderResourceBindings(QRhiImplementation *rhi)
QMetalGraphicsPipeline * lastUsedGraphicsPipeline
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
void updateResources(UpdateFlags flags) override
\variable QRhiMetalCommandBufferNativeHandles::commandBuffer
Definition qrhimetal.mm:157
void destroy()
Definition qrhimetal.mm:168
id< MTLLibrary > lib
Definition qrhimetal.mm:158
uint outputVertexCount
Definition qrhimetal.mm:161
std::array< uint, 3 > localSize
Definition qrhimetal.mm:160
QShaderDescription desc
Definition qrhimetal.mm:162
id< MTLFunction > func
Definition qrhimetal.mm:159
int argumentBufferIndex
Definition qrhimetal.mm:166
id< MTLArgumentEncoder > argumentEncoder
Definition qrhimetal.mm:165
id< MTLRasterizationRateMap > rateMap
Definition qrhimetal.mm:403
QMetalShadingRateMapData * d
QMetalShadingRateMap(QRhiImplementation *rhi)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QSize logicalSize() const override
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:568
dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:569
MTLPixelFormat colorFormat
Definition qrhimetal.mm:574
MTLRenderPassDescriptor * rp
Definition qrhimetal.mm:571
CAMetalLayer * layer
Definition qrhimetal.mm:567
double lastGpuTime[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:570
id< MTLTexture > msaaTex[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:572
QRhiTexture::Format rhiColorFormat
Definition qrhimetal.mm:573
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
MTLPixelFormat viewFormat
Definition qrhimetal.mm:380
id< MTLTexture > tex
Definition qrhimetal.mm:382
MTLPixelFormat viewFormatForSampling
Definition qrhimetal.mm:381
id< MTLTexture > viewForLevel(int level)
QMetalTexture * q
Definition qrhimetal.mm:378
id< MTLTexture > perLevelViews[QRhi::MAX_MIP_LEVELS]
Definition qrhimetal.mm:387
id< MTLBuffer > stagingBuf[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:385
id< MTLTexture > writeView
Definition qrhimetal.mm:384
QMetalTextureData(QMetalTexture *t)
Definition qrhimetal.mm:376
id< MTLTexture > samplingView
Definition qrhimetal.mm:383
id< MTLTexture > textureForWrite() const
Definition qrhimetal.mm:390
MTLPixelFormat format
Definition qrhimetal.mm:379
id< MTLTexture > textureForSampling() const
Definition qrhimetal.mm:389
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:88
bool create() override
Creates the corresponding native graphics resources.
int lastActiveFrameSlot
Definition qrhimetal_p.h:92
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.
\variable QRhiIndirectDrawCommand::vertexCount
Definition qrhi.h:1740
QRhiReadbackResult * result
Definition qrhimetal.mm:290
id< MTLComputePipelineState > pipelineState
Definition qrhimetal.mm:246
id< MTLDepthStencilState > depthStencilState
Definition qrhimetal.mm:241
std::array< id< MTLComputePipelineState >, 3 > tessVertexComputeState
Definition qrhimetal.mm:242
id< MTLRasterizationRateMap > rateMap
Definition qrhimetal.mm:249
id< MTLSamplerState > samplerState
Definition qrhimetal.mm:234
id< MTLBuffer > stagingBuffers[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:228
id< MTLComputePipelineState > tessTessControlComputeState
Definition qrhimetal.mm:243
id< MTLIndirectCommandBuffer > icb
Definition qrhimetal.mm:252
id< MTLRenderPipelineState > pipelineState
Definition qrhimetal.mm:240
id< MTLBuffer > buffers[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:221
id< MTLTexture > views[QRhi::MAX_MIP_LEVELS]
Definition qrhimetal.mm:229
QMetalCommandBuffer cbWrapper
Definition qrhimetal.mm:263
OffscreenFrame(QRhiImplementation *rhi)
Definition qrhimetal.mm:260
QRhiReadbackDescription desc
Definition qrhimetal.mm:268
QRhiReadbackResult * result
Definition qrhimetal.mm:269
QRhiTexture::Format format
Definition qrhimetal.mm:273
void trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLComputePipelineState > icbEncodePipelineU32
Definition qrhimetal.mm:304
static constexpr quint32 STAGING_AREA_MIN
Definition qrhimetal.mm:342
QRhiMetalData(QRhiMetal *rhi)
Definition qrhimetal.mm:182
QVarLengthArray< BufferReadback, 2 > activeBufferReadbacks
Definition qrhimetal.mm:295
QHash< ShaderCacheKey, QMetalShader > shaderCache
Definition qrhimetal.mm:319
bool setupBinaryArchive(NSURL *sourceFileUrl=nil)
Definition qrhimetal.mm:633
bool icbSetupFailed
Definition qrhimetal.mm:314
id< MTLFunction > icbEncodeFunctionU16
Definition qrhimetal.mm:308
void addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
id< MTLFunction > icbEncodeFunctionU32
Definition qrhimetal.mm:307
MTLCaptureManager * captureMgr
Definition qrhimetal.mm:297
id< MTLBuffer > icbArgumentBuffer
Definition qrhimetal.mm:309
static constexpr quint32 LARGE_STAGING_ALLOC
Definition qrhimetal.mm:341
NSUInteger icbCapacity
Definition qrhimetal.mm:302
void trySeedingComputePipelineFromBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLIndirectCommandBuffer > icb
Definition qrhimetal.mm:301
QVector< DeferredReleaseEntry > releaseQueue
Definition qrhimetal.mm:257
id< MTLBuffer > allocArgumentBuffer(quint32 size, quint32 alignment, int frameSlot, quint32 *offset)
static constexpr quint32 STAGING_AREA_MAX
Definition qrhimetal.mm:343
id< MTLLibrary > createMetalLib(const QShader &shader, QShader::Variant shaderVariant, bool preferArgumentBuffers, QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
id< MTLFunction > createMSLShaderFunction(id< MTLLibrary > lib, const QByteArray &entryPoint)
id< MTLBuffer > allocBufferStaging(quint32 size, int frameSlot, quint32 *offset)
id< MTLCaptureScope > captureScope
Definition qrhimetal.mm:298
void resetAndResizeBufferStagingArea(int frameSlot)
MTLRenderPassDescriptor * createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, int colorAttCount, QRhiShadingRateMap *shadingRateMap)
id< MTLBuffer > newOneShotStagingBuffer(quint32 size, int frameSlot)
QRhiMetal * q
Definition qrhimetal.mm:184
id< MTLComputePipelineState > icbEncodePipeline
Definition qrhimetal.mm:303
static constexpr int STAGING_AREA_HIGH_DEMAND_FRAMES
Definition qrhimetal.mm:345
id< MTLFunction > icbEncodeFunction
Definition qrhimetal.mm:306
id< MTLComputePipelineState > icbEncodePipelineU16
Definition qrhimetal.mm:305
static const int TEXBUF_ALIGN
Definition qrhimetal.mm:316
id< MTLBuffer > icbRangeBuffer
Definition qrhimetal.mm:311
id< MTLBuffer > allocFromStagingArea(StagingArea *area, quint32 size, quint32 alignment, int frameSlot, quint32 minBlockSize, quint32 *offset)
StagingArea bufStagingPool[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:330
id< MTLBinaryArchive > binArch
Definition qrhimetal.mm:187
id< MTLCommandBuffer > newCommandBuffer()
Definition qrhimetal.mm:621
QVarLengthArray< TextureReadback, 2 > activeTextureReadbacks
Definition qrhimetal.mm:285
StagingArea argBufPool[QMTL_FRAMES_IN_FLIGHT]
Definition qrhimetal.mm:329
id< MTLDevice > dev
Definition qrhimetal.mm:185
quint64 globalFrameId
Definition qrhimetal.mm:349
static constexpr int STAGING_AREA_LOW_DEMAND_FRAMES
Definition qrhimetal.mm:344
void addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
id< MTLCommandQueue > cmdQueue
Definition qrhimetal.mm:186
id< MTLBuffer > icbNoCountBuffer
Definition qrhimetal.mm:313
QMetalCommandBuffer * cbD
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1988
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1588
LimitsType limitsType
Definition qrhi.h:1599
float maxPotentialColorComponentValue
Definition qrhi.h:1607
LuminanceBehavior luminanceBehavior
Definition qrhi.h:1610
float maxColorComponentValue
Definition qrhi.h:1606
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1621