• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright 2019 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// mtl_utils.mm:
7//    Implements utilities functions that create Metal shaders, convert from angle enums
8//    to Metal enums and so on.
9//
10
11#include "libANGLE/renderer/metal/mtl_utils.h"
12
13#include <Availability.h>
14#include <TargetConditionals.h>
15
16#include "common/MemoryBuffer.h"
17#include "common/string_utils.h"
18#include "common/system_utils.h"
19#include "gpu_info_util/SystemInfo_internal.h"
20#include "libANGLE/histogram_macros.h"
21#include "libANGLE/renderer/metal/ContextMtl.h"
22#include "libANGLE/renderer/metal/DisplayMtl.h"
23#include "libANGLE/renderer/metal/RenderTargetMtl.h"
24#include "libANGLE/renderer/metal/mtl_render_utils.h"
25#include "libANGLE/renderer/metal/process.h"
26#include "platform/PlatformMethods.h"
27
28// Compiler can turn on programmatical frame capture in release build by defining
29// ANGLE_METAL_FRAME_CAPTURE flag.
30#if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE)
31#    define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0
32#else
33#    define ANGLE_METAL_FRAME_CAPTURE_ENABLED ANGLE_WITH_MODERN_METAL_API
34#endif
35
36namespace rx
37{
38
39ANGLE_APPLE_UNUSED
40bool IsFrameCaptureEnabled()
41{
42#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED
43    return false;
44#else
45    // We only support frame capture programmatically if the ANGLE_METAL_FRAME_CAPTURE
46    // environment flag is set. Otherwise, it will slow down the rendering. This allows user to
47    // finely control whether they want to capture the frame for particular application or not.
48    auto var                  = std::getenv("ANGLE_METAL_FRAME_CAPTURE");
49    static const bool enabled = var ? (strcmp(var, "1") == 0) : false;
50
51    return enabled;
52#endif
53}
54
55ANGLE_APPLE_UNUSED
56std::string GetMetalCaptureFile()
57{
58#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED
59    return {};
60#else
61    auto var                   = std::getenv("ANGLE_METAL_FRAME_CAPTURE_FILE");
62    const std::string filePath = var ? var : "";
63
64    return filePath;
65#endif
66}
67
68ANGLE_APPLE_UNUSED
69size_t MaxAllowedFrameCapture()
70{
71#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED
72    return 0;
73#else
74    auto var                      = std::getenv("ANGLE_METAL_FRAME_CAPTURE_MAX");
75    static const size_t maxFrames = var ? std::atoi(var) : 100;
76
77    return maxFrames;
78#endif
79}
80
81ANGLE_APPLE_UNUSED
82size_t MinAllowedFrameCapture()
83{
84#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED
85    return 0;
86#else
87    auto var                     = std::getenv("ANGLE_METAL_FRAME_CAPTURE_MIN");
88    static const size_t minFrame = var ? std::atoi(var) : 0;
89
90    return minFrame;
91#endif
92}
93
94ANGLE_APPLE_UNUSED
95bool FrameCaptureDeviceScope()
96{
97#if !ANGLE_METAL_FRAME_CAPTURE_ENABLED
98    return false;
99#else
100    auto var                      = std::getenv("ANGLE_METAL_FRAME_CAPTURE_SCOPE");
101    static const bool scopeDevice = var ? (strcmp(var, "device") == 0) : false;
102
103    return scopeDevice;
104#endif
105}
106
107ANGLE_APPLE_UNUSED
108std::atomic<size_t> gFrameCaptured(0);
109
110ANGLE_APPLE_UNUSED
111void StartFrameCapture(id<MTLDevice> metalDevice, id<MTLCommandQueue> metalCmdQueue)
112{
113#if ANGLE_METAL_FRAME_CAPTURE_ENABLED
114    if (!IsFrameCaptureEnabled())
115    {
116        return;
117    }
118
119    if (gFrameCaptured >= MaxAllowedFrameCapture())
120    {
121        return;
122    }
123
124    MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager];
125    if (captureManager.isCapturing)
126    {
127        return;
128    }
129
130    gFrameCaptured++;
131
132    if (gFrameCaptured < MinAllowedFrameCapture())
133    {
134        return;
135    }
136
137#    ifdef __MAC_10_15
138    if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13))
139    {
140        auto captureDescriptor = mtl::adoptObjCObj([[MTLCaptureDescriptor alloc] init]);
141        captureDescriptor.get().captureObject = metalDevice;
142        const std::string filePath            = GetMetalCaptureFile();
143        if (filePath != "")
144        {
145            const std::string numberedPath =
146                filePath + std::to_string(gFrameCaptured - 1) + ".gputrace";
147            captureDescriptor.get().destination = MTLCaptureDestinationGPUTraceDocument;
148            captureDescriptor.get().outputURL =
149                [NSURL fileURLWithPath:[NSString stringWithUTF8String:numberedPath.c_str()]
150                           isDirectory:false];
151        }
152        else
153        {
154            // This will pause execution only if application is being debugged inside Xcode
155            captureDescriptor.get().destination = MTLCaptureDestinationDeveloperTools;
156        }
157
158        NSError *error;
159        if (![captureManager startCaptureWithDescriptor:captureDescriptor.get() error:&error])
160        {
161            NSLog(@"Failed to start capture, error %@", error);
162        }
163    }
164    else
165#    endif  // __MAC_10_15
166        if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13))
167        {
168            auto captureDescriptor = mtl::adoptObjCObj([[MTLCaptureDescriptor alloc] init]);
169            captureDescriptor.get().captureObject = metalDevice;
170
171            NSError *error;
172            if (![captureManager startCaptureWithDescriptor:captureDescriptor.get() error:&error])
173            {
174                NSLog(@"Failed to start capture, error %@", error);
175            }
176        }
177#endif  // ANGLE_METAL_FRAME_CAPTURE_ENABLED
178}
179
180void StartFrameCapture(ContextMtl *context)
181{
182    StartFrameCapture(context->getMetalDevice(), context->cmdQueue().get());
183}
184
185void StopFrameCapture()
186{
187#if ANGLE_METAL_FRAME_CAPTURE_ENABLED
188    if (!IsFrameCaptureEnabled())
189    {
190        return;
191    }
192    MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager];
193    if (captureManager.isCapturing)
194    {
195        [captureManager stopCapture];
196    }
197#endif
198}
199
200namespace mtl
201{
202
203constexpr char kANGLEPrintMSLEnv[]        = "ANGLE_METAL_PRINT_MSL_ENABLE";
204constexpr char kANGLEMSLVersionMajorEnv[] = "ANGLE_MSL_VERSION_MAJOR";
205constexpr char kANGLEMSLVersionMinorEnv[] = "ANGLE_MSL_VERSION_MINOR";
206
207namespace
208{
209
210uint32_t GetDeviceVendorIdFromName(id<MTLDevice> metalDevice)
211{
212    struct Vendor
213    {
214        NSString *const trademark;
215        uint32_t vendorId;
216    };
217
218    constexpr Vendor kVendors[] = {
219        {@"AMD", angle::kVendorID_AMD},        {@"Apple", angle::kVendorID_Apple},
220        {@"Radeon", angle::kVendorID_AMD},     {@"Intel", angle::kVendorID_Intel},
221        {@"Geforce", angle::kVendorID_NVIDIA}, {@"Quadro", angle::kVendorID_NVIDIA}};
222    ANGLE_MTL_OBJC_SCOPE
223    {
224        if (metalDevice)
225        {
226            for (const Vendor &it : kVendors)
227            {
228                if ([metalDevice.name rangeOfString:it.trademark].location != NSNotFound)
229                {
230                    return it.vendorId;
231                }
232            }
233        }
234
235        return 0;
236    }
237}
238
239#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
240uint32_t GetDeviceVendorIdFromIOKit(id<MTLDevice> device)
241{
242    return angle::GetVendorIDFromMetalDeviceRegistryID(device.registryID);
243}
244#endif
245
246void GetSliceAndDepth(const ImageNativeIndex &index, GLint *layer, GLint *startDepth)
247{
248    *layer = *startDepth = 0;
249    if (!index.hasLayer())
250    {
251        return;
252    }
253
254    switch (index.getType())
255    {
256        case gl::TextureType::CubeMap:
257            *layer = index.cubeMapFaceIndex();
258            break;
259        case gl::TextureType::_2DArray:
260            *layer = index.getLayerIndex();
261            break;
262        case gl::TextureType::_3D:
263            *startDepth = index.getLayerIndex();
264            break;
265        default:
266            break;
267    }
268}
269GLint GetSliceOrDepth(const ImageNativeIndex &index)
270{
271    GLint layer, startDepth;
272    GetSliceAndDepth(index, &layer, &startDepth);
273
274    return std::max(layer, startDepth);
275}
276
277bool GetCompressedBufferSizeAndRowLengthForTextureWithFormat(const TextureRef &texture,
278                                                             const Format &textureObjFormat,
279                                                             const ImageNativeIndex &index,
280                                                             size_t *bytesPerRowOut,
281                                                             size_t *bytesPerImageOut)
282{
283    gl::Extents size = texture->size(index);
284    ASSERT(size.depth == 1);
285    GLuint bufferRowInBytes;
286    if (!textureObjFormat.intendedInternalFormat().computeCompressedImageRowPitch(
287            size.width, &bufferRowInBytes))
288    {
289        return false;
290    }
291    GLuint bufferSizeInBytes;
292    if (!textureObjFormat.intendedInternalFormat().computeCompressedImageDepthPitch(
293            size.height, bufferRowInBytes, &bufferSizeInBytes))
294    {
295        return false;
296    }
297    *bytesPerRowOut   = bufferRowInBytes;
298    *bytesPerImageOut = bufferSizeInBytes;
299    return true;
300}
301static angle::Result InitializeCompressedTextureContents(const gl::Context *context,
302                                                         const TextureRef &texture,
303                                                         const Format &textureObjFormat,
304                                                         const ImageNativeIndex &index,
305                                                         const uint layer,
306                                                         const uint startDepth)
307{
308    assert(textureObjFormat.actualAngleFormat().isBlock);
309    size_t bytesPerRow   = 0;
310    size_t bytesPerImage = 0;
311    if (!GetCompressedBufferSizeAndRowLengthForTextureWithFormat(texture, textureObjFormat, index,
312                                                                 &bytesPerRow, &bytesPerImage))
313    {
314        return angle::Result::Stop;
315    }
316    ContextMtl *contextMtl = mtl::GetImpl(context);
317    gl::Extents extents    = texture->size(index);
318    if (texture->isCPUAccessible())
319    {
320        if (textureObjFormat.isPVRTC())
321        {
322            // Replace Region Validation: rowBytes must be 0
323            bytesPerRow = 0;
324        }
325
326        angle::MemoryBuffer buffer;
327        if (!buffer.resize(bytesPerImage))
328        {
329            return angle::Result::Stop;
330        }
331        buffer.fill(0);
332        for (NSUInteger d = 0; d < static_cast<NSUInteger>(extents.depth); ++d)
333        {
334            auto mtlTextureRegion     = MTLRegionMake2D(0, 0, extents.width, extents.height);
335            mtlTextureRegion.origin.z = d + startDepth;
336            texture->replaceRegion(contextMtl, mtlTextureRegion, index.getNativeLevel(), layer,
337                                   buffer.data(), bytesPerRow, 0);
338        }
339    }
340    else
341    {
342        mtl::BufferRef zeroBuffer;
343        ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, bytesPerImage, nullptr, &zeroBuffer));
344        mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
345        for (NSUInteger d = 0; d < static_cast<NSUInteger>(extents.depth); ++d)
346        {
347            auto blitOrigin = MTLOriginMake(0, 0, d + startDepth);
348            blitEncoder->copyBufferToTexture(zeroBuffer, 0, bytesPerRow, 0,
349                                             MTLSizeMake(extents.width, extents.height, 1), texture,
350                                             layer, index.getNativeLevel(), blitOrigin, 0);
351        }
352        blitEncoder->endEncoding();
353    }
354    return angle::Result::Continue;
355}
356
357}  // namespace
358
359bool PreferStagedTextureUploads(const gl::Context *context,
360                                const TextureRef &texture,
361                                const Format &textureObjFormat,
362                                StagingPurpose purpose)
363{
364    // The simulator MUST upload all textures as staged.
365    if (TARGET_OS_SIMULATOR)
366    {
367        return true;
368    }
369
370    ContextMtl *contextMtl             = mtl::GetImpl(context);
371    const angle::FeaturesMtl &features = contextMtl->getDisplay()->getFeatures();
372
373    const gl::InternalFormat &intendedInternalFormat = textureObjFormat.intendedInternalFormat();
374    if (intendedInternalFormat.compressed || textureObjFormat.actualAngleFormat().isBlock)
375    {
376        return false;
377    }
378
379    // If the intended internal format is luminance, we can still
380    // initialize the texture using the GPU. However, if we're
381    // uploading data to it, we avoid using a staging buffer, due to
382    // the (current) need to re-pack the data from L8 -> RGBA8 and LA8
383    // -> RGBA8. This could be better optimized by emulating L8
384    // textures with R8 and LA8 with RG8, and using swizzlig for the
385    // resulting textures.
386    if (intendedInternalFormat.isLUMA())
387    {
388        return (purpose == StagingPurpose::Initialization);
389    }
390
391    if (features.disableStagedInitializationOfPackedTextureFormats.enabled)
392    {
393        switch (intendedInternalFormat.sizedInternalFormat)
394        {
395            case GL_RGB9_E5:
396            case GL_R11F_G11F_B10F:
397                return false;
398
399            default:
400                break;
401        }
402    }
403
404    return (texture->hasIOSurface() && features.uploadDataToIosurfacesWithStagingBuffers.enabled) ||
405           features.alwaysPreferStagedTextureUploads.enabled;
406}
407
408angle::Result InitializeTextureContents(const gl::Context *context,
409                                        const TextureRef &texture,
410                                        const Format &textureObjFormat,
411                                        const ImageNativeIndex &index)
412{
413    ASSERT(texture && texture->valid());
414    // Only one slice can be initialized at a time.
415    ASSERT(!index.isLayered() || index.getType() == gl::TextureType::_3D);
416    ContextMtl *contextMtl = mtl::GetImpl(context);
417
418    const gl::InternalFormat &intendedInternalFormat = textureObjFormat.intendedInternalFormat();
419
420    bool preferGPUInitialization = PreferStagedTextureUploads(context, texture, textureObjFormat,
421                                                              StagingPurpose::Initialization);
422
423    // This function is called in many places to initialize the content of a texture.
424    // So it's better we do the initial check here instead of let the callers do it themselves:
425    if (!textureObjFormat.valid())
426    {
427        return angle::Result::Continue;
428    }
429
430    if ((textureObjFormat.hasDepthOrStencilBits() && !textureObjFormat.getCaps().depthRenderable) ||
431        !textureObjFormat.getCaps().colorRenderable)
432    {
433        // Texture is not appropriately color- or depth-renderable, so do not attempt
434        // to use GPU initialization (clears for initialization).
435        preferGPUInitialization = false;
436    }
437
438    gl::Extents size = texture->size(index);
439
440    // Intiialize the content to black
441    GLint layer, startDepth;
442    GetSliceAndDepth(index, &layer, &startDepth);
443
444    // Use compressed texture initialization only when both the intended and the actual ANGLE
445    // formats are compressed. Emulated opaque ETC2 formats use uncompressed fallbacks and require
446    // custom initialization.
447    if (intendedInternalFormat.compressed && textureObjFormat.actualAngleFormat().isBlock)
448    {
449        return InitializeCompressedTextureContents(context, texture, textureObjFormat, index, layer,
450                                                   startDepth);
451    }
452    else if (texture->isCPUAccessible() && index.getType() != gl::TextureType::_2DMultisample &&
453             index.getType() != gl::TextureType::_2DMultisampleArray && !preferGPUInitialization)
454    {
455        const angle::Format &dstFormat = angle::Format::Get(textureObjFormat.actualFormatId);
456        const size_t dstRowPitch       = dstFormat.pixelBytes * size.width;
457        angle::MemoryBuffer conversionRow;
458        ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch));
459
460        if (textureObjFormat.initFunction)
461        {
462            textureObjFormat.initFunction(size.width, 1, 1, conversionRow.data(), dstRowPitch, 0);
463        }
464        else
465        {
466            const angle::Format &srcFormat = angle::Format::Get(
467                intendedInternalFormat.alphaBits > 0 ? angle::FormatID::R8G8B8A8_UNORM
468                                                     : angle::FormatID::R8G8B8_UNORM);
469            const size_t srcRowPitch = srcFormat.pixelBytes * size.width;
470            angle::MemoryBuffer srcRow;
471            ANGLE_CHECK_GL_ALLOC(contextMtl, srcRow.resize(srcRowPitch));
472            memset(srcRow.data(), 0, srcRowPitch);
473
474            CopyImageCHROMIUM(srcRow.data(), srcRowPitch, srcFormat.pixelBytes, 0,
475                              srcFormat.pixelReadFunction, conversionRow.data(), dstRowPitch,
476                              dstFormat.pixelBytes, 0, dstFormat.pixelWriteFunction,
477                              intendedInternalFormat.format, dstFormat.componentType, size.width, 1,
478                              1, false, false, false);
479        }
480
481        auto mtlRowRegion = MTLRegionMake2D(0, 0, size.width, 1);
482
483        for (NSUInteger d = 0; d < static_cast<NSUInteger>(size.depth); ++d)
484        {
485            mtlRowRegion.origin.z = d + startDepth;
486            for (NSUInteger r = 0; r < static_cast<NSUInteger>(size.height); ++r)
487            {
488                mtlRowRegion.origin.y = r;
489
490                // Upload to texture
491                texture->replace2DRegion(contextMtl, mtlRowRegion, index.getNativeLevel(), layer,
492                                         conversionRow.data(), dstRowPitch);
493            }
494        }
495    }
496    else
497    {
498        ANGLE_TRY(InitializeTextureContentsGPU(context, texture, textureObjFormat, index,
499                                               MTLColorWriteMaskAll));
500    }
501
502    return angle::Result::Continue;
503}
504
505angle::Result InitializeTextureContentsGPU(const gl::Context *context,
506                                           const TextureRef &texture,
507                                           const Format &textureObjFormat,
508                                           const ImageNativeIndex &index,
509                                           MTLColorWriteMask channelsToInit)
510{
511    // Only one slice can be initialized at a time.
512    ASSERT(!index.isLayered() || index.getType() == gl::TextureType::_3D);
513    if (index.isLayered() && index.getType() == gl::TextureType::_3D)
514    {
515        ImageNativeIndexIterator ite =
516            index.getLayerIterator(texture->depth(index.getNativeLevel()));
517        while (ite.hasNext())
518        {
519            ImageNativeIndex depthLayerIndex = ite.next();
520            ANGLE_TRY(InitializeTextureContentsGPU(context, texture, textureObjFormat,
521                                                   depthLayerIndex, MTLColorWriteMaskAll));
522        }
523
524        return angle::Result::Continue;
525    }
526
527    if (textureObjFormat.hasDepthOrStencilBits())
528    {
529        // Depth stencil texture needs dedicated function.
530        return InitializeDepthStencilTextureContentsGPU(context, texture, textureObjFormat, index);
531    }
532
533    ContextMtl *contextMtl = mtl::GetImpl(context);
534    GLint sliceOrDepth     = GetSliceOrDepth(index);
535
536    // Use clear render command
537    RenderTargetMtl tempRtt;
538    tempRtt.set(texture, index.getNativeLevel(), sliceOrDepth, textureObjFormat);
539
540    int clearAlpha = 0;
541    if (!textureObjFormat.intendedAngleFormat().alphaBits)
542    {
543        // if intended format doesn't have alpha, set it to 1.0.
544        clearAlpha = kEmulatedAlphaValue;
545    }
546
547    RenderCommandEncoder *encoder;
548    if (channelsToInit == MTLColorWriteMaskAll)
549    {
550        // If all channels will be initialized, use clear loadOp.
551        Optional<MTLClearColor> blackColor = MTLClearColorMake(0, 0, 0, clearAlpha);
552        encoder = contextMtl->getRenderTargetCommandEncoderWithClear(tempRtt, blackColor);
553    }
554    else
555    {
556        // temporarily enable color channels requested via channelsToInit. Some emulated format has
557        // some channels write mask disabled when the texture is created.
558        MTLColorWriteMask oldMask = texture->getColorWritableMask();
559        texture->setColorWritableMask(channelsToInit);
560
561        // If there are some channels don't need to be initialized, we must use clearWithDraw.
562        encoder = contextMtl->getRenderTargetCommandEncoder(tempRtt);
563
564        const angle::Format &angleFormat = textureObjFormat.actualAngleFormat();
565
566        ClearRectParams clearParams;
567        ClearColorValue clearColor;
568        if (angleFormat.isSint())
569        {
570            clearColor.setAsInt(0, 0, 0, clearAlpha);
571        }
572        else if (angleFormat.isUint())
573        {
574            clearColor.setAsUInt(0, 0, 0, clearAlpha);
575        }
576        else
577        {
578            clearColor.setAsFloat(0, 0, 0, clearAlpha);
579        }
580        clearParams.clearColor     = clearColor;
581        clearParams.dstTextureSize = texture->sizeAt0();
582        clearParams.enabledBuffers.set(0);
583        clearParams.clearArea = gl::Rectangle(0, 0, texture->widthAt0(), texture->heightAt0());
584
585        ANGLE_TRY(
586            contextMtl->getDisplay()->getUtils().clearWithDraw(context, encoder, clearParams));
587
588        // Restore texture's intended write mask
589        texture->setColorWritableMask(oldMask);
590    }
591    encoder->setStoreAction(MTLStoreActionStore);
592
593    return angle::Result::Continue;
594}
595
596angle::Result InitializeDepthStencilTextureContentsGPU(const gl::Context *context,
597                                                       const TextureRef &texture,
598                                                       const Format &textureObjFormat,
599                                                       const ImageNativeIndex &index)
600{
601    const MipmapNativeLevel &level = index.getNativeLevel();
602    // Use clear operation
603    ContextMtl *contextMtl           = mtl::GetImpl(context);
604    const angle::Format &angleFormat = textureObjFormat.actualAngleFormat();
605    RenderTargetMtl rtMTL;
606
607    uint32_t layer = index.hasLayer() ? index.getLayerIndex() : 0;
608    rtMTL.set(texture, level, layer, textureObjFormat);
609    mtl::RenderPassDesc rpDesc;
610    if (angleFormat.depthBits)
611    {
612        rtMTL.toRenderPassAttachmentDesc(&rpDesc.depthAttachment);
613        rpDesc.depthAttachment.loadAction = MTLLoadActionClear;
614        rpDesc.depthAttachment.clearDepth = 1.0;
615    }
616    if (angleFormat.stencilBits)
617    {
618        rtMTL.toRenderPassAttachmentDesc(&rpDesc.stencilAttachment);
619        rpDesc.stencilAttachment.loadAction = MTLLoadActionClear;
620    }
621    rpDesc.sampleCount = texture->samples();
622
623    // End current render pass
624    contextMtl->endEncoding(true);
625
626    RenderCommandEncoder *encoder = contextMtl->getRenderPassCommandEncoder(rpDesc);
627    encoder->setStoreAction(MTLStoreActionStore);
628
629    return angle::Result::Continue;
630}
631
632angle::Result ReadTexturePerSliceBytes(const gl::Context *context,
633                                       const TextureRef &texture,
634                                       size_t bytesPerRow,
635                                       const gl::Rectangle &fromRegion,
636                                       const MipmapNativeLevel &mipLevel,
637                                       uint32_t sliceOrDepth,
638                                       uint8_t *dataOut)
639{
640    ASSERT(texture && texture->valid());
641    ContextMtl *contextMtl = mtl::GetImpl(context);
642    GLint layer            = 0;
643    GLint startDepth       = 0;
644    switch (texture->textureType())
645    {
646        case MTLTextureTypeCube:
647        case MTLTextureType2DArray:
648            layer = sliceOrDepth;
649            break;
650        case MTLTextureType3D:
651            startDepth = sliceOrDepth;
652            break;
653        default:
654            break;
655    }
656
657    MTLRegion mtlRegion = MTLRegionMake3D(fromRegion.x, fromRegion.y, startDepth, fromRegion.width,
658                                          fromRegion.height, 1);
659
660    texture->getBytes(contextMtl, bytesPerRow, 0, mtlRegion, mipLevel, layer, dataOut);
661
662    return angle::Result::Continue;
663}
664
665angle::Result ReadTexturePerSliceBytesToBuffer(const gl::Context *context,
666                                               const TextureRef &texture,
667                                               size_t bytesPerRow,
668                                               const gl::Rectangle &fromRegion,
669                                               const MipmapNativeLevel &mipLevel,
670                                               uint32_t sliceOrDepth,
671                                               uint32_t dstOffset,
672                                               const BufferRef &dstBuffer)
673{
674    ASSERT(texture && texture->valid());
675    ContextMtl *contextMtl = mtl::GetImpl(context);
676    GLint layer            = 0;
677    GLint startDepth       = 0;
678    switch (texture->textureType())
679    {
680        case MTLTextureTypeCube:
681        case MTLTextureType2DArray:
682            layer = sliceOrDepth;
683            break;
684        case MTLTextureType3D:
685            startDepth = sliceOrDepth;
686            break;
687        default:
688            break;
689    }
690
691    MTLRegion mtlRegion = MTLRegionMake3D(fromRegion.x, fromRegion.y, startDepth, fromRegion.width,
692                                          fromRegion.height, 1);
693
694    BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
695    blitEncoder->copyTextureToBuffer(texture, layer, mipLevel, mtlRegion.origin, mtlRegion.size,
696                                     dstBuffer, dstOffset, bytesPerRow, 0, MTLBlitOptionNone);
697
698    return angle::Result::Continue;
699}
700
701MTLViewport GetViewport(const gl::Rectangle &rect, double znear, double zfar)
702{
703    MTLViewport re;
704
705    re.originX = rect.x;
706    re.originY = rect.y;
707    re.width   = rect.width;
708    re.height  = rect.height;
709    re.znear   = znear;
710    re.zfar    = zfar;
711
712    return re;
713}
714
715MTLViewport GetViewportFlipY(const gl::Rectangle &rect,
716                             NSUInteger screenHeight,
717                             double znear,
718                             double zfar)
719{
720    MTLViewport re;
721
722    re.originX = rect.x;
723    re.originY = static_cast<double>(screenHeight) - rect.y1();
724    re.width   = rect.width;
725    re.height  = rect.height;
726    re.znear   = znear;
727    re.zfar    = zfar;
728
729    return re;
730}
731
732MTLViewport GetViewport(const gl::Rectangle &rect,
733                        NSUInteger screenHeight,
734                        bool flipY,
735                        double znear,
736                        double zfar)
737{
738    if (flipY)
739    {
740        return GetViewportFlipY(rect, screenHeight, znear, zfar);
741    }
742
743    return GetViewport(rect, znear, zfar);
744}
745
746MTLScissorRect GetScissorRect(const gl::Rectangle &rect, NSUInteger screenHeight, bool flipY)
747{
748    MTLScissorRect re;
749
750    re.x      = rect.x;
751    re.y      = flipY ? (screenHeight - rect.y1()) : rect.y;
752    re.width  = rect.width;
753    re.height = rect.height;
754
755    return re;
756}
757
758uint32_t GetDeviceVendorId(id<MTLDevice> metalDevice)
759{
760    uint32_t vendorId = 0;
761#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
762    if (ANGLE_APPLE_AVAILABLE_XC(10.13, 13.1))
763    {
764        vendorId = GetDeviceVendorIdFromIOKit(metalDevice);
765    }
766#endif
767    if (!vendorId)
768    {
769        vendorId = GetDeviceVendorIdFromName(metalDevice);
770    }
771
772    return vendorId;
773}
774
775static MTLLanguageVersion GetUserSetOrHighestMSLVersion(const MTLLanguageVersion currentVersion)
776{
777    const std::string major_str = angle::GetEnvironmentVar(kANGLEMSLVersionMajorEnv);
778    const std::string minor_str = angle::GetEnvironmentVar(kANGLEMSLVersionMinorEnv);
779    if (major_str != "" && minor_str != "")
780    {
781        const int major = std::stoi(major_str);
782        const int minor = std::stoi(minor_str);
783#if !defined(NDEBUG)
784        NSLog(@"Forcing MSL Version: MTLLanguageVersion%d_%d\n", major, minor);
785#endif
786        switch (major)
787        {
788            case 1:
789                switch (minor)
790                {
791#if (defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0) &&   \
792    (!defined(__IPHONE_16_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_0) && \
793    (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST
794                    case 0:
795                        return MTLLanguageVersion1_0;
796#endif
797#if (defined(__MAC_10_11) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_11) ||    \
798    (defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0) || \
799    (defined(__TVOS_9_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_9_0)
800                    case 1:
801                        return MTLLanguageVersion1_1;
802#endif
803#if (defined(__MAC_10_12) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12) ||      \
804    (defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0) || \
805    (defined(__TVOS_10_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_10_0)
806                    case 2:
807                        return MTLLanguageVersion1_2;
808#endif
809                    default:
810                        assert(0 && "Unsupported MSL Minor Language Version.");
811                }
812                break;
813            case 2:
814                switch (minor)
815                {
816#if (defined(__MAC_10_13) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13) ||      \
817    (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) || \
818    (defined(__TVOS_11_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_11_0)
819                    case 0:
820                        return MTLLanguageVersion2_0;
821#endif
822#if (defined(__MAC_10_14) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_14) ||      \
823    (defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_12_0) || \
824    (defined(__TVOS_12_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_12_0)
825                    case 1:
826                        return MTLLanguageVersion2_1;
827#endif
828#if (defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) ||      \
829    (defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \
830    (defined(__TVOS_13_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_13_0)
831                    case 2:
832                        return MTLLanguageVersion2_2;
833#endif
834#if (defined(__MAC_11_0) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_11_0) ||        \
835    (defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_14_0) || \
836    (defined(__TVOS_14_0) && __TV_OS_VERSION_MIN_REQUIRED >= __TVOS_14_0)
837                    case 3:
838                        return MTLLanguageVersion2_3;
839#endif
840                    default:
841                        assert(0 && "Unsupported MSL Minor Language Version.");
842                }
843                break;
844            default:
845                assert(0 && "Unsupported MSL Major Language Version.");
846        }
847    }
848    return currentVersion;
849}
850
851AutoObjCPtr<id<MTLLibrary>> CreateShaderLibrary(
852    const mtl::ContextDevice &metalDevice,
853    const std::string &source,
854    const std::map<std::string, std::string> &substitutionMacros,
855    bool disableFastMath,
856    bool usesInvariance,
857    AutoObjCPtr<NSError *> *error)
858{
859    return CreateShaderLibrary(metalDevice, source.c_str(), source.size(), substitutionMacros,
860                               disableFastMath, usesInvariance, error);
861}
862
863AutoObjCPtr<id<MTLLibrary>> CreateShaderLibrary(const mtl::ContextDevice &metalDevice,
864                                                const std::string &source,
865                                                AutoObjCPtr<NSError *> *error)
866{
867    // Use fast math, but conservatively assume the shader uses invariance.
868    return CreateShaderLibrary(metalDevice, source.c_str(), source.size(), {}, false, true, error);
869}
870
871AutoObjCPtr<id<MTLLibrary>> CreateShaderLibrary(
872    const mtl::ContextDevice &metalDevice,
873    const char *source,
874    size_t sourceLen,
875    const std::map<std::string, std::string> &substitutionMacros,
876    bool disableFastMath,
877    bool usesInvariance,
878    AutoObjCPtr<NSError *> *errorOut)
879{
880    ANGLE_MTL_OBJC_SCOPE
881    {
882        NSError *nsError = nil;
883        auto nsSource    = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(source)
884                                                       length:sourceLen
885                                                     encoding:NSUTF8StringEncoding
886                                                 freeWhenDone:NO];
887        auto options     = [[[MTLCompileOptions alloc] init] ANGLE_MTL_AUTORELEASE];
888
889        // Mark all positions in VS with attribute invariant as non-optimizable
890        bool canPerserveInvariance = false;
891#if defined(__MAC_11_0) || defined(__IPHONE_14_0) || defined(__TVOS_14_0)
892        if (ANGLE_APPLE_AVAILABLE_XCI(11.0, 14.0, 14.0))
893        {
894            canPerserveInvariance      = true;
895            options.preserveInvariance = usesInvariance;
896        }
897#endif
898
899        // If either:
900        //   - fastmath is force-disabled
901        // or:
902        //   - preserveInvariance is not available when compiling from
903        //     source, and the sources use invariance
904        // Disable fastmath.
905        //
906        // Write this logic out as if-tests rather than a nested
907        // logical expression to make it clearer.
908        if (disableFastMath)
909        {
910            options.fastMathEnabled = false;
911        }
912        else if (usesInvariance && !canPerserveInvariance)
913        {
914            options.fastMathEnabled = false;
915        }
916
917        options.languageVersion = GetUserSetOrHighestMSLVersion(options.languageVersion);
918
919        if (!substitutionMacros.empty())
920        {
921            auto macroDict = [NSMutableDictionary dictionary];
922            for (const auto &macro : substitutionMacros)
923            {
924                [macroDict setObject:@(macro.second.c_str()) forKey:@(macro.first.c_str())];
925            }
926            options.preprocessorMacros = macroDict;
927        }
928
929        auto *platform   = ANGLEPlatformCurrent();
930        double startTime = platform->currentTime(platform);
931
932        auto library = metalDevice.newLibraryWithSource(nsSource, options, &nsError);
933        if (angle::GetEnvironmentVar(kANGLEPrintMSLEnv)[0] == '1')
934        {
935            NSLog(@"%@\n", nsSource);
936        }
937        [nsSource ANGLE_MTL_AUTORELEASE];
938        *errorOut = std::move(nsError);
939
940        double endTime = platform->currentTime(platform);
941        int us         = static_cast<int>((endTime - startTime) * 1000'000.0);
942        ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.MetalShaderCompilationTimeUs", us);
943
944        return library;
945    }
946}
947
948std::string CompileShaderLibraryToFile(const std::string &source,
949                                       const std::map<std::string, std::string> &macros,
950                                       bool disableFastMath,
951                                       bool usesInvariance)
952{
953    auto tmpDir = angle::GetTempDirectory();
954    if (!tmpDir.valid())
955    {
956        FATAL() << "angle::GetTempDirectory() failed";
957    }
958    // NOTE: metal/metallib seem to require extensions, otherwise they interpret the files
959    // differently.
960    auto metalFileName =
961        angle::CreateTemporaryFileInDirectoryWithExtension(tmpDir.value(), ".metal");
962    auto airFileName = angle::CreateTemporaryFileInDirectoryWithExtension(tmpDir.value(), ".air");
963    auto metallibFileName =
964        angle::CreateTemporaryFileInDirectoryWithExtension(tmpDir.value(), ".metallib");
965    if (!metalFileName.valid() || !airFileName.valid() || !metallibFileName.valid())
966    {
967        FATAL() << "Unable to generate temporary files for compiling metal";
968    }
969    // Save the source.
970    {
971        angle::SaveFileHelper saveFileHelper(metalFileName.value());
972        saveFileHelper << source;
973    }
974
975    // metal -> air
976    std::vector<std::string> metalToAirArgv{"/usr/bin/xcrun",
977                                            "/usr/bin/xcrun",
978                                            "-sdk",
979                                            "macosx",
980                                            "metal",
981                                            "-std=macos-metal2.0",
982                                            "-mmacosx-version-min=10.13",
983                                            "-c",
984                                            metalFileName.value(),
985                                            "-o",
986                                            airFileName.value()};
987    // Macros are passed using `-D key=value`.
988    for (const auto &macro : macros)
989    {
990        metalToAirArgv.push_back("-D");
991        // TODO: not sure if this needs to escape strings or what (for example, might
992        // a space cause problems)?
993        metalToAirArgv.push_back(macro.first + "=" + macro.second);
994    }
995    // TODO: is this right, not sure if MTLCompileOptions.fastMathEnabled is same as -ffast-math.
996    if (!disableFastMath)
997    {
998        metalToAirArgv.push_back("-ffast-math");
999    }
1000    if (usesInvariance)
1001    {
1002        metalToAirArgv.push_back("-fpreserve-invariance");
1003    }
1004    Process metalToAirProcess(metalToAirArgv);
1005    int exitCode = -1;
1006    if (!metalToAirProcess.DidLaunch() || !metalToAirProcess.WaitForExit(exitCode) || exitCode != 0)
1007    {
1008        FATAL() << "Generating air file failed";
1009    }
1010
1011    // air -> metallib
1012    const std::vector<std::string> airToMetallibArgv{
1013        "xcrun",    "/usr/bin/xcrun",    "-sdk", "macosx",
1014        "metallib", airFileName.value(), "-o",   metallibFileName.value()};
1015    Process air_to_metallib_process(airToMetallibArgv);
1016    if (!air_to_metallib_process.DidLaunch() || !air_to_metallib_process.WaitForExit(exitCode) ||
1017        exitCode != 0)
1018    {
1019        FATAL() << "Ggenerating metallib file failed";
1020    }
1021    return metallibFileName.value();
1022}
1023
1024AutoObjCPtr<id<MTLLibrary>> CreateShaderLibrary(id<MTLDevice> metalDevice,
1025                                                const char *source,
1026                                                size_t sourceLen,
1027                                                AutoObjCPtr<NSError *> *errorOut)
1028{
1029    ANGLE_MTL_OBJC_SCOPE
1030    {
1031        auto nsSource = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(source)
1032                                                       length:sourceLen
1033                                                     encoding:NSUTF8StringEncoding
1034                                                 freeWhenDone:NO];
1035        auto options  = [[[MTLCompileOptions alloc] init] ANGLE_MTL_AUTORELEASE];
1036
1037        NSError *nsError = nil;
1038        auto library = [metalDevice newLibraryWithSource:nsSource options:options error:&nsError];
1039
1040        [nsSource ANGLE_MTL_AUTORELEASE];
1041
1042        *errorOut = std::move(nsError);
1043
1044        return [library ANGLE_MTL_AUTORELEASE];
1045    }
1046}
1047
1048AutoObjCPtr<id<MTLLibrary>> CreateShaderLibraryFromBinary(id<MTLDevice> metalDevice,
1049                                                          const uint8_t *binarySource,
1050                                                          size_t binarySourceLen,
1051                                                          AutoObjCPtr<NSError *> *errorOut)
1052{
1053    ANGLE_MTL_OBJC_SCOPE
1054    {
1055        NSError *nsError = nil;
1056        auto shaderSourceData =
1057            dispatch_data_create(binarySource, binarySourceLen, dispatch_get_main_queue(),
1058                                 ^{
1059                                 });
1060
1061        auto library = [metalDevice newLibraryWithData:shaderSourceData error:&nsError];
1062
1063        dispatch_release(shaderSourceData);
1064
1065        *errorOut = std::move(nsError);
1066
1067        return [library ANGLE_MTL_AUTORELEASE];
1068    }
1069}
1070
1071MTLTextureType GetTextureType(gl::TextureType glType)
1072{
1073    switch (glType)
1074    {
1075        case gl::TextureType::_2D:
1076            return MTLTextureType2D;
1077        case gl::TextureType::_2DArray:
1078            return MTLTextureType2DArray;
1079        case gl::TextureType::_3D:
1080            return MTLTextureType3D;
1081        case gl::TextureType::CubeMap:
1082            return MTLTextureTypeCube;
1083        default:
1084            return MTLTextureTypeInvalid;
1085    }
1086}
1087
1088MTLSamplerMinMagFilter GetFilter(GLenum filter)
1089{
1090    switch (filter)
1091    {
1092        case GL_LINEAR_MIPMAP_LINEAR:
1093        case GL_LINEAR_MIPMAP_NEAREST:
1094        case GL_LINEAR:
1095            return MTLSamplerMinMagFilterLinear;
1096        case GL_NEAREST_MIPMAP_LINEAR:
1097        case GL_NEAREST_MIPMAP_NEAREST:
1098        case GL_NEAREST:
1099            return MTLSamplerMinMagFilterNearest;
1100        default:
1101            UNIMPLEMENTED();
1102            return MTLSamplerMinMagFilterNearest;
1103    }
1104}
1105
1106MTLSamplerMipFilter GetMipmapFilter(GLenum filter)
1107{
1108    switch (filter)
1109    {
1110        case GL_LINEAR:
1111        case GL_NEAREST:
1112            return MTLSamplerMipFilterNotMipmapped;
1113        case GL_LINEAR_MIPMAP_LINEAR:
1114        case GL_NEAREST_MIPMAP_LINEAR:
1115            return MTLSamplerMipFilterLinear;
1116        case GL_NEAREST_MIPMAP_NEAREST:
1117        case GL_LINEAR_MIPMAP_NEAREST:
1118            return MTLSamplerMipFilterNearest;
1119        default:
1120            UNIMPLEMENTED();
1121            return MTLSamplerMipFilterNotMipmapped;
1122    }
1123}
1124
1125MTLSamplerAddressMode GetSamplerAddressMode(GLenum wrap)
1126{
1127    switch (wrap)
1128    {
1129        case GL_CLAMP_TO_EDGE:
1130            return MTLSamplerAddressModeClampToEdge;
1131#if !ANGLE_PLATFORM_WATCHOS
1132        case GL_MIRROR_CLAMP_TO_EDGE_EXT:
1133            return MTLSamplerAddressModeMirrorClampToEdge;
1134#endif
1135        case GL_REPEAT:
1136            return MTLSamplerAddressModeRepeat;
1137        case GL_MIRRORED_REPEAT:
1138            return MTLSamplerAddressModeMirrorRepeat;
1139        default:
1140            UNIMPLEMENTED();
1141            return MTLSamplerAddressModeClampToEdge;
1142    }
1143}
1144
1145MTLBlendFactor GetBlendFactor(gl::BlendFactorType factor)
1146{
1147    switch (factor)
1148    {
1149        case gl::BlendFactorType::Zero:
1150            return MTLBlendFactorZero;
1151        case gl::BlendFactorType::One:
1152            return MTLBlendFactorOne;
1153        case gl::BlendFactorType::SrcColor:
1154            return MTLBlendFactorSourceColor;
1155        case gl::BlendFactorType::OneMinusSrcColor:
1156            return MTLBlendFactorOneMinusSourceColor;
1157        case gl::BlendFactorType::SrcAlpha:
1158            return MTLBlendFactorSourceAlpha;
1159        case gl::BlendFactorType::OneMinusSrcAlpha:
1160            return MTLBlendFactorOneMinusSourceAlpha;
1161        case gl::BlendFactorType::DstColor:
1162            return MTLBlendFactorDestinationColor;
1163        case gl::BlendFactorType::OneMinusDstColor:
1164            return MTLBlendFactorOneMinusDestinationColor;
1165        case gl::BlendFactorType::DstAlpha:
1166            return MTLBlendFactorDestinationAlpha;
1167        case gl::BlendFactorType::OneMinusDstAlpha:
1168            return MTLBlendFactorOneMinusDestinationAlpha;
1169        case gl::BlendFactorType::SrcAlphaSaturate:
1170            return MTLBlendFactorSourceAlphaSaturated;
1171        case gl::BlendFactorType::ConstantColor:
1172            return MTLBlendFactorBlendColor;
1173        case gl::BlendFactorType::OneMinusConstantColor:
1174            return MTLBlendFactorOneMinusBlendColor;
1175        case gl::BlendFactorType::ConstantAlpha:
1176            return MTLBlendFactorBlendAlpha;
1177        case gl::BlendFactorType::OneMinusConstantAlpha:
1178            return MTLBlendFactorOneMinusBlendAlpha;
1179        case gl::BlendFactorType::Src1Color:
1180            return MTLBlendFactorSource1Color;
1181        case gl::BlendFactorType::OneMinusSrc1Color:
1182            return MTLBlendFactorOneMinusSource1Color;
1183        case gl::BlendFactorType::Src1Alpha:
1184            return MTLBlendFactorSource1Alpha;
1185        case gl::BlendFactorType::OneMinusSrc1Alpha:
1186            return MTLBlendFactorOneMinusSource1Alpha;
1187        default:
1188            UNREACHABLE();
1189            return MTLBlendFactorZero;
1190    }
1191}
1192
1193MTLBlendOperation GetBlendOp(gl::BlendEquationType op)
1194{
1195    switch (op)
1196    {
1197        case gl::BlendEquationType::Add:
1198            return MTLBlendOperationAdd;
1199        case gl::BlendEquationType::Subtract:
1200            return MTLBlendOperationSubtract;
1201        case gl::BlendEquationType::ReverseSubtract:
1202            return MTLBlendOperationReverseSubtract;
1203        case gl::BlendEquationType::Min:
1204            return MTLBlendOperationMin;
1205        case gl::BlendEquationType::Max:
1206            return MTLBlendOperationMax;
1207        default:
1208            UNREACHABLE();
1209            return MTLBlendOperationAdd;
1210    }
1211}
1212
1213MTLCompareFunction GetCompareFunc(GLenum func)
1214{
1215    switch (func)
1216    {
1217        case GL_NEVER:
1218            return MTLCompareFunctionNever;
1219        case GL_ALWAYS:
1220            return MTLCompareFunctionAlways;
1221        case GL_LESS:
1222            return MTLCompareFunctionLess;
1223        case GL_LEQUAL:
1224            return MTLCompareFunctionLessEqual;
1225        case GL_EQUAL:
1226            return MTLCompareFunctionEqual;
1227        case GL_GREATER:
1228            return MTLCompareFunctionGreater;
1229        case GL_GEQUAL:
1230            return MTLCompareFunctionGreaterEqual;
1231        case GL_NOTEQUAL:
1232            return MTLCompareFunctionNotEqual;
1233        default:
1234            UNREACHABLE();
1235            return MTLCompareFunctionAlways;
1236    }
1237}
1238
1239MTLStencilOperation GetStencilOp(GLenum op)
1240{
1241    switch (op)
1242    {
1243        case GL_KEEP:
1244            return MTLStencilOperationKeep;
1245        case GL_ZERO:
1246            return MTLStencilOperationZero;
1247        case GL_REPLACE:
1248            return MTLStencilOperationReplace;
1249        case GL_INCR:
1250            return MTLStencilOperationIncrementClamp;
1251        case GL_DECR:
1252            return MTLStencilOperationDecrementClamp;
1253        case GL_INCR_WRAP:
1254            return MTLStencilOperationIncrementWrap;
1255        case GL_DECR_WRAP:
1256            return MTLStencilOperationDecrementWrap;
1257        case GL_INVERT:
1258            return MTLStencilOperationInvert;
1259        default:
1260            UNREACHABLE();
1261            return MTLStencilOperationKeep;
1262    }
1263}
1264
1265MTLWinding GetFrontfaceWinding(GLenum frontFaceMode, bool invert)
1266{
1267    switch (frontFaceMode)
1268    {
1269        case GL_CW:
1270            return invert ? MTLWindingCounterClockwise : MTLWindingClockwise;
1271        case GL_CCW:
1272            return invert ? MTLWindingClockwise : MTLWindingCounterClockwise;
1273        default:
1274            UNREACHABLE();
1275            return MTLWindingClockwise;
1276    }
1277}
1278
1279#if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE
1280PrimitiveTopologyClass GetPrimitiveTopologyClass(gl::PrimitiveMode mode)
1281{
1282    // NOTE(hqle): Support layered renderring in future.
1283    // In non-layered rendering mode, unspecified is enough.
1284    return MTLPrimitiveTopologyClassUnspecified;
1285}
1286#else  // ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE
1287PrimitiveTopologyClass GetPrimitiveTopologyClass(gl::PrimitiveMode mode)
1288{
1289    return kPrimitiveTopologyClassTriangle;
1290}
1291#endif
1292
1293MTLPrimitiveType GetPrimitiveType(gl::PrimitiveMode mode)
1294{
1295    switch (mode)
1296    {
1297        case gl::PrimitiveMode::Triangles:
1298            return MTLPrimitiveTypeTriangle;
1299        case gl::PrimitiveMode::Points:
1300            return MTLPrimitiveTypePoint;
1301        case gl::PrimitiveMode::Lines:
1302            return MTLPrimitiveTypeLine;
1303        case gl::PrimitiveMode::LineStrip:
1304        case gl::PrimitiveMode::LineLoop:
1305            return MTLPrimitiveTypeLineStrip;
1306        case gl::PrimitiveMode::TriangleStrip:
1307            return MTLPrimitiveTypeTriangleStrip;
1308        case gl::PrimitiveMode::TriangleFan:
1309            // NOTE(hqle): Emulate triangle fan.
1310        default:
1311            return MTLPrimitiveTypeInvalid;
1312    }
1313}
1314
1315MTLIndexType GetIndexType(gl::DrawElementsType type)
1316{
1317    switch (type)
1318    {
1319        case gl::DrawElementsType::UnsignedShort:
1320            return MTLIndexTypeUInt16;
1321        case gl::DrawElementsType::UnsignedInt:
1322            return MTLIndexTypeUInt32;
1323        case gl::DrawElementsType::UnsignedByte:
1324            // NOTE(hqle): Convert to supported type
1325        default:
1326            return MTLIndexTypeInvalid;
1327    }
1328}
1329
1330#if ANGLE_MTL_SWIZZLE_AVAILABLE
1331MTLTextureSwizzle GetTextureSwizzle(GLenum swizzle)
1332{
1333    switch (swizzle)
1334    {
1335        case GL_RED:
1336            return MTLTextureSwizzleRed;
1337        case GL_GREEN:
1338            return MTLTextureSwizzleGreen;
1339        case GL_BLUE:
1340            return MTLTextureSwizzleBlue;
1341        case GL_ALPHA:
1342            return MTLTextureSwizzleAlpha;
1343        case GL_ZERO:
1344            return MTLTextureSwizzleZero;
1345        case GL_ONE:
1346            return MTLTextureSwizzleOne;
1347        default:
1348            UNREACHABLE();
1349            return MTLTextureSwizzleZero;
1350    }
1351}
1352#endif
1353
1354MTLColorWriteMask GetEmulatedColorWriteMask(const mtl::Format &mtlFormat, bool *isEmulatedOut)
1355{
1356    const angle::Format &intendedFormat = mtlFormat.intendedAngleFormat();
1357    const angle::Format &actualFormat   = mtlFormat.actualAngleFormat();
1358    bool isFormatEmulated               = false;
1359    MTLColorWriteMask colorWritableMask = MTLColorWriteMaskAll;
1360    if (intendedFormat.alphaBits == 0 && actualFormat.alphaBits)
1361    {
1362        isFormatEmulated = true;
1363        // Disable alpha write to this texture
1364        colorWritableMask = colorWritableMask & (~MTLColorWriteMaskAlpha);
1365    }
1366    if (intendedFormat.luminanceBits == 0)
1367    {
1368        if (intendedFormat.redBits == 0 && actualFormat.redBits)
1369        {
1370            isFormatEmulated = true;
1371            // Disable red write to this texture
1372            colorWritableMask = colorWritableMask & (~MTLColorWriteMaskRed);
1373        }
1374        if (intendedFormat.greenBits == 0 && actualFormat.greenBits)
1375        {
1376            isFormatEmulated = true;
1377            // Disable green write to this texture
1378            colorWritableMask = colorWritableMask & (~MTLColorWriteMaskGreen);
1379        }
1380        if (intendedFormat.blueBits == 0 && actualFormat.blueBits)
1381        {
1382            isFormatEmulated = true;
1383            // Disable blue write to this texture
1384            colorWritableMask = colorWritableMask & (~MTLColorWriteMaskBlue);
1385        }
1386    }
1387
1388    *isEmulatedOut = isFormatEmulated;
1389
1390    return colorWritableMask;
1391}
1392
1393MTLColorWriteMask GetEmulatedColorWriteMask(const mtl::Format &mtlFormat)
1394{
1395    // Ignore isFormatEmulated boolean value
1396    bool isFormatEmulated;
1397    return GetEmulatedColorWriteMask(mtlFormat, &isFormatEmulated);
1398}
1399
1400bool IsFormatEmulated(const mtl::Format &mtlFormat)
1401{
1402    bool isFormatEmulated;
1403    (void)GetEmulatedColorWriteMask(mtlFormat, &isFormatEmulated);
1404    return isFormatEmulated;
1405}
1406
1407size_t EstimateTextureSizeInBytes(const mtl::Format &mtlFormat,
1408                                  size_t width,
1409                                  size_t height,
1410                                  size_t depth,
1411                                  size_t sampleCount,
1412                                  size_t numMips)
1413{
1414    size_t textureSizeInBytes;
1415    if (mtlFormat.getCaps().compressed)
1416    {
1417        GLuint textureSize;
1418        gl::Extents size((int)width, (int)height, (int)depth);
1419        if (!mtlFormat.intendedInternalFormat().computeCompressedImageSize(size, &textureSize))
1420        {
1421            return 0;
1422        }
1423        textureSizeInBytes = textureSize;
1424    }
1425    else
1426    {
1427        textureSizeInBytes = mtlFormat.getCaps().pixelBytes * width * height * depth * sampleCount;
1428    }
1429    if (numMips > 1)
1430    {
1431        // Estimate mipmap size.
1432        textureSizeInBytes = textureSizeInBytes * 4 / 3;
1433    }
1434    return textureSizeInBytes;
1435}
1436
1437MTLClearColor EmulatedAlphaClearColor(MTLClearColor color, MTLColorWriteMask colorMask)
1438{
1439    MTLClearColor re = color;
1440
1441    if (!(colorMask & MTLColorWriteMaskAlpha))
1442    {
1443        re.alpha = kEmulatedAlphaValue;
1444    }
1445
1446    return re;
1447}
1448
1449NSUInteger GetMaxRenderTargetSizeForDeviceInBytes(const mtl::ContextDevice &device)
1450{
1451    if (SupportsAppleGPUFamily(device, 4))
1452    {
1453        return 64;
1454    }
1455    else if (SupportsAppleGPUFamily(device, 2))
1456    {
1457        return 32;
1458    }
1459    else
1460    {
1461        return 16;
1462    }
1463}
1464
1465NSUInteger GetMaxNumberOfRenderTargetsForDevice(const mtl::ContextDevice &device)
1466{
1467    if (SupportsAppleGPUFamily(device, 2) || SupportsMacGPUFamily(device, 1))
1468    {
1469        return 8;
1470    }
1471    else
1472    {
1473        return 4;
1474    }
1475}
1476
1477bool DeviceHasMaximumRenderTargetSize(id<MTLDevice> device)
1478{
1479    return !SupportsMacGPUFamily(device, 1);
1480}
1481
1482bool SupportsAppleGPUFamily(id<MTLDevice> device, uint8_t appleFamily)
1483{
1484#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || \
1485    (__TV_OS_VERSION_MAX_ALLOWED >= 130000)
1486    // If device supports [MTLDevice supportsFamily:], then use it.
1487    if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13))
1488    {
1489        MTLGPUFamily family;
1490        switch (appleFamily)
1491        {
1492            case 1:
1493                family = MTLGPUFamilyApple1;
1494                break;
1495            case 2:
1496                family = MTLGPUFamilyApple2;
1497                break;
1498            case 3:
1499                family = MTLGPUFamilyApple3;
1500                break;
1501            case 4:
1502                family = MTLGPUFamilyApple4;
1503                break;
1504            case 5:
1505                family = MTLGPUFamilyApple5;
1506                break;
1507#    if TARGET_OS_IOS || (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000)
1508            case 6:
1509                family = MTLGPUFamilyApple6;
1510                break;
1511#    endif
1512            default:
1513                return false;
1514        }
1515        return [device supportsFamily:family];
1516    }   // Metal 2.2
1517#endif  // __IPHONE_OS_VERSION_MAX_ALLOWED
1518
1519#if (!TARGET_OS_IOS && !TARGET_OS_TV) || TARGET_OS_MACCATALYST || \
1520    (TARGET_OS_IOS && defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_16_0)
1521    return false;
1522#else
1523    // If device doesn't support [MTLDevice supportsFamily:], then use
1524    // [MTLDevice supportsFeatureSet:].
1525    MTLFeatureSet featureSet;
1526    switch (appleFamily)
1527    {
1528#    if TARGET_OS_IOS
1529        case 1:
1530            featureSet = MTLFeatureSet_iOS_GPUFamily1_v1;
1531            break;
1532        case 2:
1533            featureSet = MTLFeatureSet_iOS_GPUFamily2_v1;
1534            break;
1535        case 3:
1536            featureSet = MTLFeatureSet_iOS_GPUFamily3_v1;
1537            break;
1538        case 4:
1539            featureSet = MTLFeatureSet_iOS_GPUFamily4_v1;
1540            break;
1541#        if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
1542        case 5:
1543            featureSet = MTLFeatureSet_iOS_GPUFamily5_v1;
1544            break;
1545#        endif  // __IPHONE_OS_VERSION_MAX_ALLOWED
1546#    elif TARGET_OS_TV
1547        case 1:
1548        case 2:
1549            featureSet = MTLFeatureSet_tvOS_GPUFamily1_v1;
1550            break;
1551#    endif  // TARGET_OS_IOS
1552        default:
1553            return false;
1554    }
1555
1556    return [device supportsFeatureSet:featureSet];
1557#endif      // TARGET_OS_IOS || TARGET_OS_TV
1558}
1559
1560bool SupportsMacGPUFamily(id<MTLDevice> device, uint8_t macFamily)
1561{
1562#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
1563#    if defined(__MAC_10_15)
1564    // If device supports [MTLDevice supportsFamily:], then use it.
1565    if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13))
1566    {
1567        MTLGPUFamily family;
1568
1569        switch (macFamily)
1570        {
1571#        if TARGET_OS_MACCATALYST
1572            ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN
1573            case 1:
1574                family = MTLGPUFamilyMacCatalyst1;
1575                break;
1576            case 2:
1577                family = MTLGPUFamilyMacCatalyst2;
1578                break;
1579                ANGLE_APPLE_ALLOW_DEPRECATED_END
1580#        else   // TARGET_OS_MACCATALYST
1581            ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN
1582            case 1:
1583                family = MTLGPUFamilyMac1;
1584                break;
1585                ANGLE_APPLE_ALLOW_DEPRECATED_END
1586            case 2:
1587                family = MTLGPUFamilyMac2;
1588                break;
1589#        endif  // TARGET_OS_MACCATALYST
1590            default:
1591                return false;
1592        }
1593
1594        return [device supportsFamily:family];
1595    }  // Metal 2.2
1596#    endif
1597
1598    // If device doesn't support [MTLDevice supportsFamily:], then use
1599    // [MTLDevice supportsFeatureSet:].
1600#    if TARGET_OS_MACCATALYST
1601    UNREACHABLE();
1602    return false;
1603#    else
1604
1605    ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN
1606    MTLFeatureSet featureSet;
1607    switch (macFamily)
1608    {
1609        case 1:
1610            featureSet = MTLFeatureSet_macOS_GPUFamily1_v1;
1611            break;
1612#        if defined(__MAC_10_14)
1613        case 2:
1614            featureSet = MTLFeatureSet_macOS_GPUFamily2_v1;
1615            break;
1616#        endif
1617        default:
1618            return false;
1619    }
1620    return [device supportsFeatureSet:featureSet];
1621    ANGLE_APPLE_ALLOW_DEPRECATED_END
1622#    endif  // TARGET_OS_MACCATALYST
1623#else       // #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
1624
1625    return false;
1626
1627#endif
1628}
1629
1630static NSUInteger getNextLocationForFormat(const FormatCaps &caps,
1631                                           bool isMSAA,
1632                                           NSUInteger currentRenderTargetSize)
1633{
1634    assert(!caps.compressed);
1635    uint8_t alignment         = caps.alignment;
1636    NSUInteger pixelBytes     = caps.pixelBytes;
1637    NSUInteger pixelBytesMSAA = caps.pixelBytesMSAA;
1638    pixelBytes                = isMSAA ? pixelBytesMSAA : pixelBytes;
1639
1640    currentRenderTargetSize = (currentRenderTargetSize + (alignment - 1)) & ~(alignment - 1);
1641    currentRenderTargetSize += pixelBytes;
1642    return currentRenderTargetSize;
1643}
1644
1645static NSUInteger getNextLocationForAttachment(const mtl::RenderPassAttachmentDesc &attachment,
1646                                               const Context *context,
1647                                               NSUInteger currentRenderTargetSize)
1648{
1649    mtl::TextureRef texture =
1650        attachment.implicitMSTexture ? attachment.implicitMSTexture : attachment.texture;
1651
1652    if (texture)
1653    {
1654        MTLPixelFormat pixelFormat = texture->pixelFormat();
1655        bool isMsaa                = texture->samples();
1656        const FormatCaps &caps     = context->getDisplay()->getNativeFormatCaps(pixelFormat);
1657        currentRenderTargetSize = getNextLocationForFormat(caps, isMsaa, currentRenderTargetSize);
1658    }
1659    return currentRenderTargetSize;
1660}
1661
1662NSUInteger ComputeTotalSizeUsedForMTLRenderPassDescriptor(const mtl::RenderPassDesc &descriptor,
1663                                                          const Context *context,
1664                                                          const mtl::ContextDevice &device)
1665{
1666    NSUInteger currentRenderTargetSize = 0;
1667
1668    for (NSUInteger i = 0; i < GetMaxNumberOfRenderTargetsForDevice(device); i++)
1669    {
1670        currentRenderTargetSize = getNextLocationForAttachment(descriptor.colorAttachments[i],
1671                                                               context, currentRenderTargetSize);
1672    }
1673    if (descriptor.depthAttachment.texture == descriptor.stencilAttachment.texture)
1674    {
1675        currentRenderTargetSize = getNextLocationForAttachment(descriptor.depthAttachment, context,
1676                                                               currentRenderTargetSize);
1677    }
1678    else
1679    {
1680        currentRenderTargetSize = getNextLocationForAttachment(descriptor.depthAttachment, context,
1681                                                               currentRenderTargetSize);
1682        currentRenderTargetSize = getNextLocationForAttachment(descriptor.stencilAttachment,
1683                                                               context, currentRenderTargetSize);
1684    }
1685
1686    return currentRenderTargetSize;
1687}
1688
1689NSUInteger ComputeTotalSizeUsedForMTLRenderPipelineDescriptor(
1690    const MTLRenderPipelineDescriptor *descriptor,
1691    const Context *context,
1692    const mtl::ContextDevice &device)
1693{
1694    NSUInteger currentRenderTargetSize = 0;
1695    ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN
1696    bool isMsaa = descriptor.sampleCount > 1;
1697    ANGLE_APPLE_ALLOW_DEPRECATED_END
1698    for (NSUInteger i = 0; i < GetMaxNumberOfRenderTargetsForDevice(device); i++)
1699    {
1700        MTLRenderPipelineColorAttachmentDescriptor *color = descriptor.colorAttachments[i];
1701        if (color.pixelFormat != MTLPixelFormatInvalid)
1702        {
1703            const FormatCaps &caps = context->getDisplay()->getNativeFormatCaps(color.pixelFormat);
1704            currentRenderTargetSize =
1705                getNextLocationForFormat(caps, isMsaa, currentRenderTargetSize);
1706        }
1707    }
1708    if (descriptor.depthAttachmentPixelFormat == descriptor.stencilAttachmentPixelFormat)
1709    {
1710        if (descriptor.depthAttachmentPixelFormat != MTLPixelFormatInvalid)
1711        {
1712            const FormatCaps &caps =
1713                context->getDisplay()->getNativeFormatCaps(descriptor.depthAttachmentPixelFormat);
1714            currentRenderTargetSize =
1715                getNextLocationForFormat(caps, isMsaa, currentRenderTargetSize);
1716        }
1717    }
1718    else
1719    {
1720        if (descriptor.depthAttachmentPixelFormat != MTLPixelFormatInvalid)
1721        {
1722            const FormatCaps &caps =
1723                context->getDisplay()->getNativeFormatCaps(descriptor.depthAttachmentPixelFormat);
1724            currentRenderTargetSize =
1725                getNextLocationForFormat(caps, isMsaa, currentRenderTargetSize);
1726        }
1727        if (descriptor.stencilAttachmentPixelFormat != MTLPixelFormatInvalid)
1728        {
1729            const FormatCaps &caps =
1730                context->getDisplay()->getNativeFormatCaps(descriptor.stencilAttachmentPixelFormat);
1731            currentRenderTargetSize =
1732                getNextLocationForFormat(caps, isMsaa, currentRenderTargetSize);
1733        }
1734    }
1735    return currentRenderTargetSize;
1736}
1737
1738gl::Box MTLRegionToGLBox(const MTLRegion &mtlRegion)
1739{
1740    return gl::Box(static_cast<int>(mtlRegion.origin.x), static_cast<int>(mtlRegion.origin.y),
1741                   static_cast<int>(mtlRegion.origin.z), static_cast<int>(mtlRegion.size.width),
1742                   static_cast<int>(mtlRegion.size.height), static_cast<int>(mtlRegion.size.depth));
1743}
1744
1745MipmapNativeLevel GetNativeMipLevel(GLuint level, GLuint base)
1746{
1747    ASSERT(level >= base);
1748    return MipmapNativeLevel(level - base);
1749}
1750
1751GLuint GetGLMipLevel(const MipmapNativeLevel &nativeLevel, GLuint base)
1752{
1753    return nativeLevel.get() + base;
1754}
1755
1756angle::Result TriangleFanBoundCheck(ContextMtl *context, size_t numTris)
1757{
1758    bool indexCheck =
1759        (numTris > std::numeric_limits<unsigned int>::max() / (sizeof(unsigned int) * 3));
1760    ANGLE_CHECK(context, !indexCheck,
1761                "Failed to create a scratch index buffer for GL_TRIANGLE_FAN, "
1762                "too many indices required.",
1763                GL_OUT_OF_MEMORY);
1764    return angle::Result::Continue;
1765}
1766
1767angle::Result GetTriangleFanIndicesCount(ContextMtl *context,
1768                                         GLsizei vetexCount,
1769                                         uint32_t *numElemsOut)
1770{
1771    size_t numTris = vetexCount - 2;
1772    ANGLE_TRY(TriangleFanBoundCheck(context, numTris));
1773    size_t numIndices = numTris * 3;
1774    ANGLE_CHECK(context, numIndices <= std::numeric_limits<uint32_t>::max(),
1775                "Failed to create a scratch index buffer for GL_TRIANGLE_FAN, "
1776                "too many indices required.",
1777                GL_OUT_OF_MEMORY);
1778
1779    *numElemsOut = static_cast<uint32_t>(numIndices);
1780    return angle::Result::Continue;
1781}
1782
1783angle::Result CreateMslShader(mtl::Context *context,
1784                              id<MTLLibrary> shaderLib,
1785                              NSString *shaderName,
1786                              MTLFunctionConstantValues *funcConstants,
1787                              id<MTLFunction> *shaderOut)
1788{
1789    NSError *nsErr = nil;
1790
1791    id<MTLFunction> mtlShader;
1792    if (funcConstants)
1793    {
1794        mtlShader = [shaderLib newFunctionWithName:shaderName
1795                                    constantValues:funcConstants
1796                                             error:&nsErr];
1797    }
1798    else
1799    {
1800        mtlShader = [shaderLib newFunctionWithName:shaderName];
1801    }
1802
1803    [mtlShader ANGLE_MTL_AUTORELEASE];
1804    if (nsErr && !mtlShader)
1805    {
1806        std::ostringstream ss;
1807        ss << "Internal error compiling Metal shader:\n"
1808           << nsErr.localizedDescription.UTF8String << "\n";
1809
1810        ERR() << ss.str();
1811
1812        ANGLE_MTL_CHECK(context, false, GL_INVALID_OPERATION);
1813    }
1814    *shaderOut = mtlShader;
1815    return angle::Result::Continue;
1816}
1817
1818angle::Result CreateMslShader(Context *context,
1819                              id<MTLLibrary> shaderLib,
1820                              NSString *shaderName,
1821                              MTLFunctionConstantValues *funcConstants,
1822                              AutoObjCPtr<id<MTLFunction>> *shaderOut)
1823{
1824    id<MTLFunction> outFunction;
1825    ANGLE_TRY(CreateMslShader(context, shaderLib, shaderName, funcConstants, &outFunction));
1826    shaderOut->retainAssign(outFunction);
1827    return angle::Result::Continue;
1828}
1829}  // namespace mtl
1830}  // namespace rx
1831