chromium/ui/gl/egl_surface_io_surface.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gl/egl_surface_io_surface.h"

#include "base/check_op.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gl/gl_bindings.h"

// Enums for the EGL_ANGLE_iosurface_client_buffer extension
#define EGL_IOSURFACE_ANGLE 0x3454
#define EGL_IOSURFACE_PLANE_ANGLE 0x345A
#define EGL_TEXTURE_RECTANGLE_ANGLE 0x345B
#define EGL_TEXTURE_TYPE_ANGLE 0x345C
#define EGL_TEXTURE_INTERNAL_FORMAT_ANGLE 0x345D
#define EGL_BIND_TO_TEXTURE_TARGET_ANGLE 0x348D

using gfx::BufferFormat;

namespace gl {

namespace {

struct InternalFormatType {
  InternalFormatType(GLenum format, GLenum type) : format(format), type(type) {}

  GLenum format;
  GLenum type;
};

// Convert a BufferFormat to a (internal format, type) combination from the
// EGL_ANGLE_iosurface_client_buffer extension spec.
InternalFormatType BufferFormatToInternalFormatType(BufferFormat format) {
  switch (format) {
    case BufferFormat::R_8:
      return {GL_RED, GL_UNSIGNED_BYTE};
    case BufferFormat::R_16:
      return {GL_RED, GL_UNSIGNED_SHORT};
    case BufferFormat::RG_88:
      return {GL_RG, GL_UNSIGNED_BYTE};
    case BufferFormat::RG_1616:
      return {GL_RG, GL_UNSIGNED_SHORT};
    case BufferFormat::BGRX_8888:
    case BufferFormat::RGBX_8888:
      return {GL_RGB, GL_UNSIGNED_BYTE};
    case BufferFormat::BGRA_8888:
      return {GL_BGRA_EXT, GL_UNSIGNED_BYTE};
    case BufferFormat::RGBA_8888:
      return {GL_RGBA, GL_UNSIGNED_BYTE};
    case BufferFormat::RGBA_F16:
      return {GL_RGBA, GL_HALF_FLOAT};
    case BufferFormat::BGRA_1010102:
      return {GL_RGB10_A2, GL_UNSIGNED_INT_2_10_10_10_REV};
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::YVU_420:
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::YUVA_420_TRIPLANAR:
    case BufferFormat::P010:
      break;
      return {GL_NONE, GL_NONE};
  }

  LOG(ERROR) << "Invalid format: " << BufferFormatToString(format);
  return {GL_NONE, GL_NONE};
}

}  // anonymous namespace

////////////////////////////////////////////////////////////////////////////////
// ScopedEGLSurfaceIOSurface

// static
std::unique_ptr<ScopedEGLSurfaceIOSurface> ScopedEGLSurfaceIOSurface::Create(
    EGLDisplay display,
    unsigned target,
    IOSurfaceRef io_surface,
    uint32_t plane,
    gfx::BufferFormat format) {
  if (display == EGL_NO_DISPLAY) {
    LOG(ERROR) << "Invalid GLDisplayEGL.";
    return nullptr;
  }

  std::unique_ptr<ScopedEGLSurfaceIOSurface> result(
      new ScopedEGLSurfaceIOSurface(display));

  if (!result->ValidateTarget(target)) {
    return nullptr;
  }

  if (!result->CreatePBuffer(io_surface, plane, format)) {
    LOG(ERROR) << "Failed to create PBuffer for IOSurface.";
    return nullptr;
  }

  return result;
}

ScopedEGLSurfaceIOSurface::ScopedEGLSurfaceIOSurface(EGLDisplay display)
    : display_(display) {
  // When creating a pbuffer we need to supply an EGLConfig. On ANGLE and
  // Swiftshader on Mac, there's only ever one config. Query it from EGL.
  EGLint numConfigs = 0;
  EGLBoolean result =
      eglChooseConfig(display_, nullptr, &dummy_config_, 1, &numConfigs);
  DCHECK_EQ(result, static_cast<EGLBoolean>(EGL_TRUE));
  DCHECK_EQ(numConfigs, 1);
  DCHECK_NE(dummy_config_, EGL_NO_CONFIG_KHR);

  // If EGL_BIND_TO_TEXTURE_TARGET_ANGLE has already been queried, then don't
  // query it again, since that depends only on the ANGLE backend (and we will
  // not be mixing backends in a single process).
  if (texture_target_ == EGL_NO_TEXTURE) {
    texture_target_ = EGL_TEXTURE_RECTANGLE_ANGLE;
    eglGetConfigAttrib(display_, dummy_config_,
                       EGL_BIND_TO_TEXTURE_TARGET_ANGLE, &texture_target_);
  }
  DCHECK_NE(texture_target_, EGL_NO_TEXTURE);
}

bool ScopedEGLSurfaceIOSurface::ValidateTarget(unsigned target) const {
  switch (target) {
    case GL_TEXTURE_2D:
      if (texture_target_ != EGL_TEXTURE_2D) {
        LOG(ERROR) << "eglBindTexImage requires 2D, got: " << target;
        return false;
      }
      break;
    case GL_TEXTURE_RECTANGLE_ARB:
      if (texture_target_ != EGL_TEXTURE_RECTANGLE_ANGLE) {
        LOG(ERROR) << "eglBindTexImage requires RECTANGLE, got: " << target;
        return false;
      }
      break;
    default:
      LOG(ERROR) << "Invalid texture target: " << target;
      return false;
  }
  return true;
}

bool ScopedEGLSurfaceIOSurface::CreatePBuffer(IOSurfaceRef io_surface,
                                              uint32_t plane,
                                              gfx::BufferFormat format) {
  // Create the pbuffer representing this IOSurface lazily because we don't know
  // in the constructor if we're going to be used to bind plane 0 to a texture,
  // or to transform YUV to RGB.
  if (pbuffer_ == EGL_NO_SURFACE) {
    InternalFormatType formatType = BufferFormatToInternalFormatType(format);
    if (formatType.format == GL_NONE || formatType.type == GL_NONE) {
      LOG(ERROR) << "Invalid resource format.";
      return false;
    }
    EGLint width =
        static_cast<EGLint>(IOSurfaceGetWidthOfPlane(io_surface, plane));
    EGLint height =
        static_cast<EGLint>(IOSurfaceGetHeightOfPlane(io_surface, plane));

    // clang-format off
    const EGLint attribs[] = {
      EGL_WIDTH,                         width,
      EGL_HEIGHT,                        height,
      EGL_IOSURFACE_PLANE_ANGLE,         static_cast<EGLint>(plane),
      EGL_TEXTURE_TARGET,                texture_target_,
      EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, static_cast<EGLint>(formatType.format),
      EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
      EGL_TEXTURE_TYPE_ANGLE,            static_cast<EGLint>(formatType.type),
      EGL_NONE,                          EGL_NONE,
    };
    // clang-format on

    pbuffer_ = eglCreatePbufferFromClientBuffer(
        display_, EGL_IOSURFACE_ANGLE, io_surface, dummy_config_, attribs);
    if (pbuffer_ == EGL_NO_SURFACE) {
      LOG(ERROR) << "eglCreatePbufferFromClientBuffer failed, EGL error is "
                 << eglGetError();
      return false;
    }
  }

  return true;
}

bool ScopedEGLSurfaceIOSurface::BindTexImage() {
  CHECK(!texture_bound_);
  EGLBoolean result = eglBindTexImage(display_, pbuffer_, EGL_BACK_BUFFER);
  if (result != EGL_TRUE) {
    LOG(ERROR) << "eglBindTexImage failed, EGL error is " << eglGetError();
    return false;
  }

  texture_bound_ = true;
  return true;
}

void ScopedEGLSurfaceIOSurface::ReleaseTexImage() {
  if (!texture_bound_)
    return;

  EGLBoolean result = eglReleaseTexImage(display_, pbuffer_, EGL_BACK_BUFFER);
  DCHECK_EQ(result, static_cast<EGLBoolean>(EGL_TRUE));
  texture_bound_ = false;
}

void ScopedEGLSurfaceIOSurface::DestroyPBuffer() {
  if (pbuffer_ == EGL_NO_SURFACE)
    return;

  EGLBoolean result = eglDestroySurface(display_, pbuffer_);
  DCHECK_EQ(result, static_cast<EGLBoolean>(EGL_TRUE));
  pbuffer_ = EGL_NO_SURFACE;
}

ScopedEGLSurfaceIOSurface::~ScopedEGLSurfaceIOSurface() {
  ReleaseTexImage();
  DestroyPBuffer();
}

}  // namespace gl