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// IOSurfaceSurfaceMtl.mm:
7//    Implements the class methods for IOSurfaceSurfaceMtl.
8//
9
10#include "libANGLE/renderer/metal/IOSurfaceSurfaceMtl.h"
11
12#include <TargetConditionals.h>
13
14#include "libANGLE/Display.h"
15#include "libANGLE/Surface.h"
16#include "libANGLE/renderer/metal/ContextMtl.h"
17#include "libANGLE/renderer/metal/DisplayMtl.h"
18#include "libANGLE/renderer/metal/FrameBufferMtl.h"
19#include "libANGLE/renderer/metal/mtl_format_utils.h"
20#include "libANGLE/renderer/metal/mtl_utils.h"
21
22// Compiler can turn on programmatical frame capture in release build by defining
23// ANGLE_METAL_FRAME_CAPTURE flag.
24#if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE)
25#    define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0
26#else
27#    define ANGLE_METAL_FRAME_CAPTURE_ENABLED ANGLE_WITH_MODERN_METAL_API
28#endif
29namespace rx
30{
31
32namespace
33{
34
35struct IOSurfaceFormatInfo
36{
37    GLenum internalFormat;
38    GLenum type;
39    size_t componentBytes;
40
41    angle::FormatID nativeAngleFormatId;
42};
43
44// clang-format off
45// NOTE(hqle): Support R16_UINT once GLES3 is complete.
46constexpr std::array<IOSurfaceFormatInfo, 8> kIOSurfaceFormats = {{
47    {GL_RED,      GL_UNSIGNED_BYTE,               1, angle::FormatID::R8_UNORM},
48    {GL_RED,      GL_UNSIGNED_SHORT,              2, angle::FormatID::R16_UNORM},
49    {GL_RG,       GL_UNSIGNED_BYTE,               2, angle::FormatID::R8G8_UNORM},
50    {GL_RG,       GL_UNSIGNED_SHORT,              4, angle::FormatID::R16G16_UNORM},
51    {GL_RGB,      GL_UNSIGNED_BYTE,               4, angle::FormatID::B8G8R8A8_UNORM},
52    {GL_BGRA_EXT, GL_UNSIGNED_BYTE,               4, angle::FormatID::B8G8R8A8_UNORM},
53    {GL_RGBA,     GL_HALF_FLOAT,                  8, angle::FormatID::R16G16B16A16_FLOAT},
54    {GL_RGB10_A2, GL_UNSIGNED_INT_2_10_10_10_REV, 4, angle::FormatID::B10G10R10A2_UNORM},
55}};
56// clang-format on
57
58int FindIOSurfaceFormatIndex(GLenum internalFormat, GLenum type)
59{
60    for (int i = 0; i < static_cast<int>(kIOSurfaceFormats.size()); ++i)
61    {
62        const auto &formatInfo = kIOSurfaceFormats[i];
63        if (formatInfo.internalFormat == internalFormat && formatInfo.type == type)
64        {
65            return i;
66        }
67    }
68    return -1;
69}
70
71}  // anonymous namespace
72
73// IOSurfaceSurfaceMtl implementation.
74IOSurfaceSurfaceMtl::IOSurfaceSurfaceMtl(DisplayMtl *display,
75                                         const egl::SurfaceState &state,
76                                         EGLClientBuffer buffer,
77                                         const egl::AttributeMap &attribs)
78    : OffscreenSurfaceMtl(display, state, attribs), mIOSurface((__bridge IOSurfaceRef)(buffer))
79{
80    CFRetain(mIOSurface);
81
82    mIOSurfacePlane = static_cast<int>(attribs.get(EGL_IOSURFACE_PLANE_ANGLE));
83
84    EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE);
85    EGLAttrib type           = attribs.get(EGL_TEXTURE_TYPE_ANGLE);
86    mIOSurfaceFormatIdx =
87        FindIOSurfaceFormatIndex(static_cast<GLenum>(internalFormat), static_cast<GLenum>(type));
88    ASSERT(mIOSurfaceFormatIdx >= 0);
89
90    mColorFormat =
91        display->getPixelFormat(kIOSurfaceFormats[mIOSurfaceFormatIdx].nativeAngleFormatId);
92}
93IOSurfaceSurfaceMtl::~IOSurfaceSurfaceMtl()
94{
95    if (mIOSurface != nullptr)
96    {
97        CFRelease(mIOSurface);
98        mIOSurface = nullptr;
99    }
100}
101
102egl::Error IOSurfaceSurfaceMtl::bindTexImage(const gl::Context *context,
103                                             gl::Texture *texture,
104                                             EGLint buffer)
105{
106    ContextMtl *contextMtl = mtl::GetImpl(context);
107    StartFrameCapture(contextMtl);
108
109    // Initialize offscreen texture if needed:
110    ANGLE_TO_EGL_TRY(ensureColorTextureCreated(context));
111
112    return OffscreenSurfaceMtl::bindTexImage(context, texture, buffer);
113}
114
115egl::Error IOSurfaceSurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer)
116{
117    egl::Error re = OffscreenSurfaceMtl::releaseTexImage(context, buffer);
118    StopFrameCapture();
119    return re;
120}
121
122angle::Result IOSurfaceSurfaceMtl::getAttachmentRenderTarget(
123    const gl::Context *context,
124    GLenum binding,
125    const gl::ImageIndex &imageIndex,
126    GLsizei samples,
127    FramebufferAttachmentRenderTarget **rtOut)
128{
129    // Initialize offscreen texture if needed:
130    ANGLE_TRY(ensureColorTextureCreated(context));
131
132    return OffscreenSurfaceMtl::getAttachmentRenderTarget(context, binding, imageIndex, samples,
133                                                          rtOut);
134}
135
136angle::Result IOSurfaceSurfaceMtl::ensureColorTextureCreated(const gl::Context *context)
137{
138    if (mColorTexture)
139    {
140        return angle::Result::Continue;
141    }
142    ContextMtl *contextMtl = mtl::GetImpl(context);
143    ANGLE_MTL_OBJC_SCOPE
144    {
145        auto texDesc =
146            [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mColorFormat.metalFormat
147                                                               width:mSize.width
148                                                              height:mSize.height
149                                                           mipmapped:NO];
150
151        texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
152
153        id<MTLTexture> texture =
154            [contextMtl->getMetalDevice() newTextureWithDescriptor:texDesc
155                                                         iosurface:mIOSurface
156                                                             plane:mIOSurfacePlane];
157
158        mColorTexture = mtl::Texture::MakeFromMetal([texture ANGLE_MTL_AUTORELEASE]);
159    }
160
161    mColorRenderTarget.set(mColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat);
162
163    if (kIOSurfaceFormats[mIOSurfaceFormatIdx].internalFormat == GL_RGB)
164    {
165        // This format has emulated alpha channel. Initialize texture's alpha channel to 1.0.
166        const mtl::Format &rgbClearFormat =
167            contextMtl->getPixelFormat(angle::FormatID::R8G8B8_UNORM);
168        ANGLE_TRY(mtl::InitializeTextureContentsGPU(
169            context, mColorTexture, rgbClearFormat,
170            mtl::ImageNativeIndex::FromBaseZeroGLIndex(gl::ImageIndex::Make2D(0)),
171            MTLColorWriteMaskAlpha));
172
173        // Disable subsequent rendering to alpha channel.
174        mColorTexture->setColorWritableMask(MTLColorWriteMaskAll & (~MTLColorWriteMaskAlpha));
175    }
176
177    return angle::Result::Continue;
178}
179
180// static
181bool IOSurfaceSurfaceMtl::ValidateAttributes(EGLClientBuffer buffer,
182                                             const egl::AttributeMap &attribs)
183{
184    IOSurfaceRef ioSurface = (__bridge IOSurfaceRef)(buffer);
185
186    // The plane must exist for this IOSurface. IOSurfaceGetPlaneCount can return 0 for non-planar
187    // ioSurfaces but we will treat non-planar like it is a single plane.
188    size_t surfacePlaneCount = std::max(size_t(1), IOSurfaceGetPlaneCount(ioSurface));
189    EGLAttrib plane          = attribs.get(EGL_IOSURFACE_PLANE_ANGLE);
190    if (plane < 0 || static_cast<size_t>(plane) >= surfacePlaneCount)
191    {
192        return false;
193    }
194
195    // The width height specified must be at least (1, 1) and at most the plane size
196    EGLAttrib width  = attribs.get(EGL_WIDTH);
197    EGLAttrib height = attribs.get(EGL_HEIGHT);
198    if (width <= 0 || static_cast<size_t>(width) > IOSurfaceGetWidthOfPlane(ioSurface, plane) ||
199        height <= 0 || static_cast<size_t>(height) > IOSurfaceGetHeightOfPlane(ioSurface, plane))
200    {
201        return false;
202    }
203
204    // Find this IOSurface format
205    EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE);
206    EGLAttrib type           = attribs.get(EGL_TEXTURE_TYPE_ANGLE);
207
208    int formatIndex =
209        FindIOSurfaceFormatIndex(static_cast<GLenum>(internalFormat), static_cast<GLenum>(type));
210
211    if (formatIndex < 0)
212    {
213        return false;
214    }
215
216    // Check that the format matches this IOSurface plane
217    if (IOSurfaceGetBytesPerElementOfPlane(ioSurface, plane) !=
218        kIOSurfaceFormats[formatIndex].componentBytes)
219    {
220        return false;
221    }
222
223    return true;
224}
225}
226