blob: 7c0ccdf388819405059073c1c12d9faf04063af8 [file] [log] [blame]
//
// 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 <TargetConditionals.h>
#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<IOSurfaceFormatInfo, 9> 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<int>(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<int>(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<GLenum>(internalFormat), static_cast<GLenum>(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<size_t>(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<size_t>(width) > IOSurfaceGetWidthOfPlane(ioSurface, plane) ||
height <= 0 || static_cast<size_t>(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<GLenum>(internalFormat), static_cast<GLenum>(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