// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // IOSurfaceSurfaceMtl.mm: // Implements the class methods for IOSurfaceSurfaceMtl. // #include "libANGLE/renderer/metal/IOSurfaceSurfaceMtl.h" #include #include "libANGLE/Display.h" #include "libANGLE/Surface.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/FrameBufferMtl.h" #include "libANGLE/renderer/metal/mtl_format_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h" // Compiler can turn on programmatical frame capture in release build by defining // ANGLE_METAL_FRAME_CAPTURE flag. #if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE) # define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0 #else # define ANGLE_METAL_FRAME_CAPTURE_ENABLED 1 #endif namespace rx { namespace { struct IOSurfaceFormatInfo { GLenum internalFormat; GLenum type; size_t componentBytes; angle::FormatID nativeAngleFormatId; }; // clang-format off // GL_RGB is a special case. The native angle::FormatID would be either R8G8B8X8_UNORM // or B8G8R8X8_UNORM based on the IOSurface's pixel format. constexpr std::array kIOSurfaceFormats = {{ {GL_RED, GL_UNSIGNED_BYTE, 1, angle::FormatID::R8_UNORM}, {GL_RED, GL_UNSIGNED_SHORT, 2, angle::FormatID::R16_UNORM}, {GL_RG, GL_UNSIGNED_BYTE, 2, angle::FormatID::R8G8_UNORM}, {GL_RG, GL_UNSIGNED_SHORT, 4, angle::FormatID::R16G16_UNORM}, {GL_RGB, GL_UNSIGNED_BYTE, 4, angle::FormatID::NONE}, {GL_RGBA, GL_UNSIGNED_BYTE, 4, angle::FormatID::R8G8B8A8_UNORM}, {GL_BGRA_EXT, GL_UNSIGNED_BYTE, 4, angle::FormatID::B8G8R8A8_UNORM}, {GL_RGBA, GL_HALF_FLOAT, 8, angle::FormatID::R16G16B16A16_FLOAT}, {GL_RGB10_A2, GL_UNSIGNED_INT_2_10_10_10_REV, 4, angle::FormatID::B10G10R10A2_UNORM}, }}; // clang-format on int FindIOSurfaceFormatIndex(GLenum internalFormat, GLenum type) { for (int i = 0; i < static_cast(kIOSurfaceFormats.size()); ++i) { const auto &formatInfo = kIOSurfaceFormats[i]; if (formatInfo.internalFormat == internalFormat && formatInfo.type == type) { return i; } } return -1; } } // anonymous namespace // IOSurfaceSurfaceMtl implementation. IOSurfaceSurfaceMtl::IOSurfaceSurfaceMtl(DisplayMtl *display, const egl::SurfaceState &state, EGLClientBuffer buffer, const egl::AttributeMap &attribs) : OffscreenSurfaceMtl(display, state, attribs), mIOSurface((__bridge IOSurfaceRef)(buffer)) { CFRetain(mIOSurface); mIOSurfacePlane = static_cast(attribs.get(EGL_IOSURFACE_PLANE_ANGLE)); EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE); EGLAttrib type = attribs.get(EGL_TEXTURE_TYPE_ANGLE); mIOSurfaceFormatIdx = FindIOSurfaceFormatIndex(static_cast(internalFormat), static_cast(type)); ASSERT(mIOSurfaceFormatIdx >= 0); angle::FormatID actualAngleFormatId = kIOSurfaceFormats[mIOSurfaceFormatIdx].nativeAngleFormatId; if (actualAngleFormatId == angle::FormatID::NONE) { // The actual angle::Format depends on the IOSurface's format. ASSERT(internalFormat == GL_RGB); switch (IOSurfaceGetPixelFormat(mIOSurface)) { case 'BGRA': actualAngleFormatId = angle::FormatID::B8G8R8X8_UNORM; break; case 'RGBA': actualAngleFormatId = angle::FormatID::R8G8B8X8_UNORM; break; default: UNREACHABLE(); } } mColorFormat = display->getPixelFormat(actualAngleFormatId); } IOSurfaceSurfaceMtl::~IOSurfaceSurfaceMtl() { if (mIOSurface != nullptr) { CFRelease(mIOSurface); mIOSurface = nullptr; } } egl::Error IOSurfaceSurfaceMtl::bindTexImage(const gl::Context *context, gl::Texture *texture, EGLint buffer) { ContextMtl *contextMtl = mtl::GetImpl(context); StartFrameCapture(contextMtl); // Initialize offscreen texture if needed: ANGLE_TO_EGL_TRY(ensureColorTextureCreated(context)); return OffscreenSurfaceMtl::bindTexImage(context, texture, buffer); } egl::Error IOSurfaceSurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer) { egl::Error re = OffscreenSurfaceMtl::releaseTexImage(context, buffer); StopFrameCapture(); return re; } angle::Result IOSurfaceSurfaceMtl::getAttachmentRenderTarget( const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { // Initialize offscreen texture if needed: ANGLE_TRY(ensureColorTextureCreated(context)); return OffscreenSurfaceMtl::getAttachmentRenderTarget(context, binding, imageIndex, samples, rtOut); } angle::Result IOSurfaceSurfaceMtl::ensureColorTextureCreated(const gl::Context *context) { if (mColorTexture) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); ANGLE_MTL_OBJC_SCOPE { auto texDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mColorFormat.metalFormat width:mSize.width height:mSize.height mipmapped:NO]; texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; mColorTexture = mtl::Texture::MakeFromMetal(contextMtl->getMetalDevice().newTextureWithDescriptor( texDesc, mIOSurface, mIOSurfacePlane)); if (mColorTexture) { size_t resourceSize = EstimateTextureSizeInBytes( mColorFormat, mColorTexture->widthAt0(), mColorTexture->heightAt0(), mColorTexture->depthAt0(), mColorTexture->samples(), mColorTexture->mipmapLevels()); mColorTexture->setEstimatedByteSize(resourceSize); } } mColorRenderTarget.set(mColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat); if (kIOSurfaceFormats[mIOSurfaceFormatIdx].internalFormat == GL_RGB) { // This format has emulated alpha channel. Initialize texture's alpha channel to 1.0. const mtl::Format &rgbClearFormat = contextMtl->getPixelFormat(angle::FormatID::R8G8B8_UNORM); ANGLE_TRY(mtl::InitializeTextureContentsGPU( context, mColorTexture, rgbClearFormat, mtl::ImageNativeIndex::FromBaseZeroGLIndex(gl::ImageIndex::Make2D(0)), MTLColorWriteMaskAlpha)); // Disable subsequent rendering to alpha channel. mColorTexture->setColorWritableMask(MTLColorWriteMaskAll & (~MTLColorWriteMaskAlpha)); } // Robust resource init: currently we do not allow passing contents with IOSurfaces. mColorTextureInitialized = false; return angle::Result::Continue; } // static bool IOSurfaceSurfaceMtl::ValidateAttributes(EGLClientBuffer buffer, const egl::AttributeMap &attribs) { IOSurfaceRef ioSurface = (__bridge IOSurfaceRef)(buffer); // The plane must exist for this IOSurface. IOSurfaceGetPlaneCount can return 0 for non-planar // ioSurfaces but we will treat non-planar like it is a single plane. size_t surfacePlaneCount = std::max(size_t(1), IOSurfaceGetPlaneCount(ioSurface)); EGLAttrib plane = attribs.get(EGL_IOSURFACE_PLANE_ANGLE); if (plane < 0 || static_cast(plane) >= surfacePlaneCount) { return false; } // The width height specified must be at least (1, 1) and at most the plane size EGLAttrib width = attribs.get(EGL_WIDTH); EGLAttrib height = attribs.get(EGL_HEIGHT); if (width <= 0 || static_cast(width) > IOSurfaceGetWidthOfPlane(ioSurface, plane) || height <= 0 || static_cast(height) > IOSurfaceGetHeightOfPlane(ioSurface, plane)) { return false; } // Find this IOSurface format EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE); EGLAttrib type = attribs.get(EGL_TEXTURE_TYPE_ANGLE); int formatIndex = FindIOSurfaceFormatIndex(static_cast(internalFormat), static_cast(type)); if (formatIndex < 0) { return false; } // FIXME: Check that the format matches this IOSurface plane for pixel formats that we know of. // We could map IOSurfaceGetPixelFormat to expected type plane and format type. // However, the caller might supply us non-public pixel format, which makes exhaustive checks // problematic. if (IOSurfaceGetBytesPerElementOfPlane(ioSurface, plane) != kIOSurfaceFormats[formatIndex].componentBytes) { WARN() << "IOSurface bytes per elements does not match the pbuffer internal format."; } return true; } } // namespace rx