chromium/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm

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

#include "gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.h"

#include <optional>
#include "base/memory/scoped_refptr.h"
#include "build/build_config.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/service_utils.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/iosurface_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gfx/mac/io_surface.h"
#include "ui/gl/buildflags.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "ui/gfx/mac/display_icc_profiles.h"
#endif

#import <Metal/Metal.h>

namespace gpu {

namespace {
bool UsageWillResultInGLWrite(gpu::SharedImageUsageSet usage,
                              GrContextType gr_context_type) {
  return (usage & SHARED_IMAGE_USAGE_GLES2_WRITE) ||
         ((gr_context_type == GrContextType::kGL) &&
          (usage & (SHARED_IMAGE_USAGE_RASTER_WRITE |
                    SHARED_IMAGE_USAGE_DISPLAY_WRITE)));
}

bool IsFormatSupported(viz::SharedImageFormat format) {
  return (format == viz::SinglePlaneFormat::kRGBA_8888) ||
         (format == viz::SinglePlaneFormat::kRGBX_8888) ||
         (format == viz::SinglePlaneFormat::kBGRA_8888) ||
         (format == viz::SinglePlaneFormat::kBGRX_8888) ||
         (format == viz::SinglePlaneFormat::kRGBA_F16) ||
         (format == viz::SinglePlaneFormat::kR_8) ||
         (format == viz::SinglePlaneFormat::kRG_88) ||
         (format == viz::SinglePlaneFormat::kR_16) ||
         (format == viz::SinglePlaneFormat::kRG_1616) ||
         (format == viz::SinglePlaneFormat::kBGRA_1010102) ||
         (format == viz::SinglePlaneFormat::kRGBA_1010102);
}

void SetIOSurfaceColorSpace(IOSurfaceRef io_surface,
                            const gfx::ColorSpace& color_space) {
  if (!color_space.IsValid()) {
    return;
  }

#if BUILDFLAG(IS_MAC)
  base::apple::ScopedCFTypeRef<CFDataRef> cf_data =
      gfx::DisplayICCProfiles::GetInstance()->GetDataForColorSpace(color_space);
  if (cf_data) {
    IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"), cf_data.get());
  } else {
    IOSurfaceSetColorSpace(io_surface, color_space);
  }
#else
  IOSurfaceSetColorSpace(io_surface, color_space);
#endif
}

bool IsValidSize(const gfx::Size& size, int32_t max_texture_size) {
  if (size.width() < 1 || size.height() < 1 ||
      size.width() > max_texture_size || size.height() > max_texture_size) {
    LOG(ERROR) << "Invalid size=" << size.ToString()
               << ", max_texture_size=" << max_texture_size;
    return false;
  }
  return true;
}

bool IsPixelDataValid(viz::SharedImageFormat format,
                      const gfx::Size& size,
                      base::span<const uint8_t> pixel_data) {
  if (pixel_data.empty()) {
    return true;
  }
  // If we have initial data to upload, ensure it is sized appropriately
  size_t estimated_size;
  if (!viz::ResourceSizes::MaybeSizeInBytes(size, format, &estimated_size)) {
    LOG(ERROR) << "Failed to calculate SharedImage size";
    return false;
  }
  if (pixel_data.size() != estimated_size) {
    LOG(ERROR) << "Initial data does not have expected size.";
    return false;
  }
  return true;
}

constexpr SharedImageUsageSet kSupportedUsage =
    SHARED_IMAGE_USAGE_GLES2_READ | SHARED_IMAGE_USAGE_GLES2_WRITE |
    SHARED_IMAGE_USAGE_GLES2_FOR_RASTER_ONLY |
    SHARED_IMAGE_USAGE_DISPLAY_WRITE | SHARED_IMAGE_USAGE_DISPLAY_READ |
    SHARED_IMAGE_USAGE_RASTER_READ | SHARED_IMAGE_USAGE_RASTER_WRITE |
    SHARED_IMAGE_USAGE_RASTER_OVER_GLES2_ONLY |
    SHARED_IMAGE_USAGE_OOP_RASTERIZATION | SHARED_IMAGE_USAGE_SCANOUT |
    SHARED_IMAGE_USAGE_WEBGPU_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE |
    SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE | SHARED_IMAGE_USAGE_VIDEO_DECODE |
    SHARED_IMAGE_USAGE_WEBGPU_SWAP_CHAIN_TEXTURE |
    SHARED_IMAGE_USAGE_MACOS_VIDEO_TOOLBOX |
    SHARED_IMAGE_USAGE_RASTER_DELEGATED_COMPOSITING |
    SHARED_IMAGE_USAGE_HIGH_PERFORMANCE_GPU | SHARED_IMAGE_USAGE_CPU_WRITE |
    SHARED_IMAGE_USAGE_WEBGPU_STORAGE_TEXTURE;

}  // anonymous namespace

///////////////////////////////////////////////////////////////////////////////
// IOSurfaceImageBackingFactory

IOSurfaceImageBackingFactory::IOSurfaceImageBackingFactory(
    GrContextType gr_context_type,
    int32_t max_texture_size,
    const gles2::FeatureInfo* feature_info,
    gl::ProgressReporter* progress_reporter,
    uint32_t texture_target)
    : SharedImageBackingFactory(kSupportedUsage),
      gr_context_type_(gr_context_type),
      max_texture_size_(max_texture_size),
      angle_texture_usage_(feature_info->feature_flags().angle_texture_usage),
      gpu_memory_buffer_formats_(
          feature_info->feature_flags().gpu_memory_buffer_formats),
      progress_reporter_(progress_reporter),
      texture_target_(texture_target) {
  for (gfx::BufferFormat buffer_format : gpu_memory_buffer_formats_) {
    viz::SharedImageFormat format = viz::GetSharedImageFormat(buffer_format);
    // Add supported single-plane formats.
    if (format.is_single_plane() && IsFormatSupported(format)) {
      supported_formats_.insert(format);
    }
  }

  // Add supported multi-plane formats.
  supported_formats_.insert(viz::MultiPlaneFormat::kNV12);
  supported_formats_.insert(viz::MultiPlaneFormat::kP210);
  supported_formats_.insert(viz::MultiPlaneFormat::kP410);
  if (feature_info->feature_flags().chromium_image_ycbcr_p010) {
    supported_formats_.insert(viz::MultiPlaneFormat::kP010);
  }
  supported_formats_.insert(viz::MultiPlaneFormat::kNV12A);
  supported_formats_.insert(viz::MultiPlaneFormat::kNV16);
  supported_formats_.insert(viz::MultiPlaneFormat::kNV24);
}

IOSurfaceImageBackingFactory::~IOSurfaceImageBackingFactory() = default;

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImage(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    SurfaceHandle surface_handle,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    SharedImageUsageSet usage,
    std::string debug_label,
    bool is_thread_safe) {
  CHECK(!is_thread_safe);
  return CreateSharedImageInternal(
      mailbox, format, surface_handle, size, color_space, surface_origin,
      alpha_type, usage, std::move(debug_label), base::span<const uint8_t>());
}

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImage(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    SharedImageUsageSet usage,
    std::string debug_label,
    bool is_thread_safe,
    base::span<const uint8_t> pixel_data) {
  CHECK(!is_thread_safe);
  return CreateSharedImageInternal(mailbox, format, kNullSurfaceHandle, size,
                                   color_space, surface_origin, alpha_type,
                                   usage, std::move(debug_label), pixel_data);
}

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImage(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    SharedImageUsageSet usage,
    std::string debug_label,
    gfx::GpuMemoryBufferHandle handle) {
  // MacOS does not support external sampler.
  CHECK(!format.PrefersExternalSampler());
  return CreateSharedImageGMBs(mailbox, format, size, color_space,
                               surface_origin, alpha_type, usage,
                               std::move(debug_label), std::move(handle));
}

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImage(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    SurfaceHandle surface_handle,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    SharedImageUsageSet usage,
    std::string debug_label,
    bool is_thread_safe,
    gfx::BufferUsage buffer_usage) {
  // |scoped_progress_reporter| will notify |progress_reporter_| upon
  // construction and destruction. We limit the scope so that progress is
  // reported immediately after allocation/upload and before other GL
  // operations.
  gfx::ScopedIOSurface io_surface;
  {
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    const gfx::BufferFormat buffer_format = ToBufferFormat(format);
    const bool should_clear = true;
    const bool override_rgba_to_bgra =
#if BUILDFLAG(IS_IOS)
        false;
#else
        gr_context_type_ == GrContextType::kGL;
#endif
    io_surface = gfx::CreateIOSurface(size, buffer_format, should_clear,
                                      override_rgba_to_bgra);
    if (!io_surface) {
      LOG(ERROR) << "CreateSharedImage: Failed to create bindable image";
      return nullptr;
    }
  }
  SetIOSurfaceColorSpace(io_surface.get(), color_space);

  gfx::GpuMemoryBufferHandle handle;
  handle.type = gfx::IO_SURFACE_BUFFER;
  handle.io_surface = std::move(io_surface);
  handle.id = gfx::GpuMemoryBufferHandle::kInvalidId;

  CHECK(!format.PrefersExternalSampler());
  return CreateSharedImageGMBs(
      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
      std::move(debug_label), std::move(handle), std::move(buffer_usage));
}

bool IOSurfaceImageBackingFactory::IsSupported(
    SharedImageUsageSet usage,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    bool thread_safe,
    gfx::GpuMemoryBufferType gmb_type,
    GrContextType gr_context_type,
    base::span<const uint8_t> pixel_data) {
  if (thread_safe) {
    return false;
  }

  // Never used with shared memory GMBs.
  if (gmb_type != gfx::EMPTY_BUFFER && gmb_type != gfx::IO_SURFACE_BUFFER) {
    return false;
  }

  if (usage & SHARED_IMAGE_USAGE_CPU_WRITE &&
      gmb_type != gfx::IO_SURFACE_BUFFER) {
    // Only CPU writable when the client provides a IOSurface.
    return false;
  }

  // On macOS, there is no separate interop factory. Any GpuMemoryBuffer-backed
  // image can be used with both OpenGL and Metal

  // In certain modes on Mac, Angle needs the image to be released when ending a
  // write. To avoid that release resulting in the GLES2 command decoders
  // needing to perform on-demand binding, we disallow concurrent read/write in
  // these modes. See
  // IOSurfaceImageBacking::GLTextureImageRepresentationEndAccess() for further
  // details.
  // TODO(https://anglebug.com/7626): Adjust the Metal-related conditions here
  // if/as they are adjusted in
  // IOSurfaceImageBacking::GLTextureImageRepresentationEndAccess().
  if (gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader ||
      gl::GetANGLEImplementation() == gl::ANGLEImplementation::kMetal) {
    if (usage & SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE) {
      return false;
    }
  }

  return true;
}

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImageInternal(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    SurfaceHandle surface_handle,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    gpu::SharedImageUsageSet usage,
    std::string debug_label,
    base::span<const uint8_t> pixel_data) {
  if (!base::Contains(supported_formats_, format)) {
    LOG(ERROR) << "CreateSharedImage: Unable to create SharedImage with format "
               << format.ToString();
    return nullptr;
  }

  if (format.is_multi_plane() && !pixel_data.empty()) {
    LOG(ERROR) << "CreateSharedImage: Creation from pixel data is not "
                  "supported for multiplanar format "
               << format.ToString();
    return nullptr;
  }

  if (!IsValidSize(size, max_texture_size_) ||
      !IsPixelDataValid(format, size, pixel_data)) {
    return nullptr;
  }

  const bool for_framebuffer_attachment =
      UsageWillResultInGLWrite(usage, gr_context_type_);

  // |scoped_progress_reporter| will notify |progress_reporter_| upon
  // construction and destruction. We limit the scope so that progress is
  // reported immediately after allocation/upload and before other GL
  // operations.
  gfx::ScopedIOSurface io_surface;
  const gfx::GenericSharedMemoryId io_surface_id;
  {
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    const gfx::BufferFormat buffer_format = ToBufferFormat(format);
    const bool should_clear = false;
    const bool override_rgba_to_bgra =
#if BUILDFLAG(IS_IOS)
        false;
#else
        gr_context_type_ == GrContextType::kGL;
#endif
    io_surface = gfx::CreateIOSurface(size, buffer_format, should_clear,
                                      override_rgba_to_bgra);
    if (!io_surface) {
      LOG(ERROR) << "CreateSharedImage: Failed to create bindable image";
      return nullptr;
    }
  }
  SetIOSurfaceColorSpace(io_surface.get(), color_space);

  const bool is_cleared = !pixel_data.empty();
  const bool framebuffer_attachment_angle =
      for_framebuffer_attachment && angle_texture_usage_;

  auto backing = std::make_unique<IOSurfaceImageBacking>(
      io_surface, io_surface_id, mailbox, format, size, color_space,
      surface_origin, alpha_type, usage, std::move(debug_label),
      texture_target_, framebuffer_attachment_angle, is_cleared,
      gr_context_type_);
  if (!pixel_data.empty()) {
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    backing->InitializePixels(pixel_data);
  }
  return std::move(backing);
}

std::unique_ptr<SharedImageBacking>
IOSurfaceImageBackingFactory::CreateSharedImageGMBs(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    gpu::SharedImageUsageSet usage,
    std::string debug_label,
    gfx::GpuMemoryBufferHandle handle,
    std::optional<gfx::BufferUsage> buffer_usage) {
  if (handle.type != gfx::IO_SURFACE_BUFFER || !handle.io_surface) {
    LOG(ERROR) << "Invalid IOSurface GpuMemoryBufferHandle.";
    return nullptr;
  }

  if (!base::Contains(supported_formats_, format)) {
    LOG(ERROR) << "CreateSharedImage: Unable to create SharedImage with format "
               << format.ToString();
    return nullptr;
  }

  auto io_surface = handle.io_surface;
  const auto io_surface_id = handle.id;

  // Ensure that the IOSurface has the same size and pixel format as those
  // specified by `size` and `format`. A malicious client could lie about
  // this, which, if subsequently used to determine parameters for bounds
  // checking, could result in an out-of-bounds memory access.
  {
    uint32_t io_surface_format = IOSurfaceGetPixelFormat(io_surface.get());
    const bool override_rgba_to_bgra =
#if BUILDFLAG(IS_IOS)
        false;
#else
        gr_context_type_ == GrContextType::kGL;
#endif
    if (io_surface_format != SharedImageFormatToIOSurfacePixelFormat(
                                 format, override_rgba_to_bgra)) {
      LOG(ERROR) << "IOSurface pixel format does not match specified shared "
                    "image format.";
      return nullptr;
    }
    gfx::Size io_surface_size(IOSurfaceGetWidth(io_surface.get()),
                              IOSurfaceGetHeight(io_surface.get()));
    if (io_surface_size != size) {
      LOG(ERROR) << "IOSurface size does not match specified size.";
      return nullptr;
    }
  }

  const bool for_framebuffer_attachment =
      UsageWillResultInGLWrite(usage, gr_context_type_);
  const bool framebuffer_attachment_angle =
      for_framebuffer_attachment && angle_texture_usage_;

  return std::make_unique<IOSurfaceImageBacking>(
      io_surface, io_surface_id, mailbox, format, size, color_space,
      surface_origin, alpha_type, usage, std::move(debug_label),
      texture_target_, framebuffer_attachment_angle, /*is_cleared=*/true,
      gr_context_type_, std::move(buffer_usage));
}

SharedImageBackingType IOSurfaceImageBackingFactory::GetBackingType() {
  return SharedImageBackingType::kIOSurface;
}

}  // namespace gpu