blob: 9b0a2f4c405d83d10d15846b73106929e164682a [file] [log] [blame]
// Copyright 2015 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/gfx/mac/io_surface.h"
#include <Availability.h>
#include <CoreGraphics/CoreGraphics.h>
#include <stddef.h>
#include <stdint.h>
#include "base/apple/mach_logging.h"
#include "base/bits.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/mac/color_space_util.h"
namespace gfx {
namespace {
void AddIntegerValue(CFMutableDictionaryRef dictionary,
const CFStringRef key,
int32_t value) {
base::apple::ScopedCFTypeRef<CFNumberRef> number(
CFNumberCreate(nullptr, kCFNumberSInt32Type, &value));
CFDictionaryAddValue(dictionary, key, number.get());
}
// Return the expected four character code pixel format for an IOSurface with
// the specified viz::SharedImageFormat.
uint32_t SharedImageFormatToIOSurfacePixelFormat(viz::SharedImageFormat format,
bool override_rgba_to_bgra) {
if (format == viz::SinglePlaneFormat::kR_8) {
return 'L008';
} else if (format == viz::SinglePlaneFormat::kR_F16) {
return 'L00h';
} else if (format == viz::SinglePlaneFormat::kRG_88) {
return '2C08';
} else if (format == viz::SinglePlaneFormat::kR_16) {
return 'L016';
} else if (format == viz::SinglePlaneFormat::kRG_1616) {
return '2C16';
} else if (format == viz::SinglePlaneFormat::kBGRA_1010102) {
return 'l10r'; // little-endian ARGB2101010 full-range ARGB
} else if (format == viz::SinglePlaneFormat::kBGRA_8888 ||
format == viz::SinglePlaneFormat::kBGRX_8888) {
return 'BGRA';
} else if (format == viz::SinglePlaneFormat::kRGBA_8888 ||
format == viz::SinglePlaneFormat::kRGBX_8888) {
return override_rgba_to_bgra ? 'BGRA' : 'RGBA';
} else if (format == viz::SinglePlaneFormat::kRGBA_F16) {
return 'RGhA';
} else if (format == viz::MultiPlaneFormat::kNV12) {
return '420v';
} else if (format == viz::MultiPlaneFormat::kNV12A) {
return 'v0a8';
} else if (format == viz::MultiPlaneFormat::kP010) {
return 'x420';
} else {
// Technically RGBA_1010102 should be accepted as 'R10k', but then it won't
// be supported by CGLTexImageIOSurface2D(), so it's best to reject it here.
NOTREACHED();
}
}
} // namespace
namespace internal {
// static
mach_port_t IOSurfaceMachPortTraits::Retain(mach_port_t port) {
kern_return_t kr =
mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1);
MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
<< "IOSurfaceMachPortTraits::Retain mach_port_mod_refs";
return port;
}
// static
void IOSurfaceMachPortTraits::Release(mach_port_t port) {
kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
<< "IOSurfaceMachPortTraits::Release mach_port_deallocate";
}
// Common method used by IOSurfaceSetColorSpace and IOSurfaceCanSetColorSpace.
bool IOSurfaceSetColorSpace(IOSurfaceRef io_surface,
const ColorSpace& color_space) {
// Allow but ignore invalid color spaces.
if (!color_space.IsValid())
return true;
// Prefer using named spaces.
CFStringRef color_space_name = nullptr;
if (color_space == ColorSpace::CreateSRGB() ||
color_space == ColorSpace::CreateREC709()) {
color_space_name = kCGColorSpaceSRGB;
} else if (color_space == ColorSpace::CreateDisplayP3D65()) {
color_space_name = kCGColorSpaceDisplayP3;
} else if (color_space == ColorSpace::CreateExtendedSRGB()) {
color_space_name = kCGColorSpaceExtendedSRGB;
} else if (color_space == ColorSpace::CreateSRGBLinear()) {
color_space_name = kCGColorSpaceExtendedLinearSRGB;
}
// The symbols kCGColorSpaceITUR_2020_PQ_EOTF and kCGColorSpaceITUR_2020_HLG
// have been deprecated. Claim that we were able to set the color space,
// because the path that will render these color spaces will use the
// HDRCopier, which will manually convert them to a non-deprecated format.
// https://crbug.com/1108627: Bug wherein these symbols are deprecated and
// also not available in some SDK versions.
// https://crbug.com/1101041: Introduces the HDR copier.
// https://crbug.com/1061723: Discussion of issues related to HLG.
if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020,
ColorSpace::TransferID::PQ,
ColorSpace::MatrixID::BT2020_NCL,
ColorSpace::RangeID::LIMITED)) {
color_space_name = kCGColorSpaceITUR_2100_PQ;
} else if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020,
ColorSpace::TransferID::HLG,
ColorSpace::MatrixID::BT2020_NCL,
ColorSpace::RangeID::LIMITED)) {
color_space_name = kCGColorSpaceITUR_2100_HLG;
}
// https://crbug.com/1488397: Set parameters that will be rendering YUV
// content.
// TODO(b/304442486): Add gamma support here.
{
CFStringRef primaries = nullptr;
CFStringRef transfer = nullptr;
CFStringRef matrix = nullptr;
if (ColorSpaceToCVImageBufferKeys(color_space, /*prefer_srgb_trfn=*/true,
&primaries, &transfer, &matrix)) {
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorPrimaries"),
primaries);
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceTransferFunction"),
transfer);
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceYCbCrMatrix"), matrix);
}
}
if (color_space_name) {
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
color_space_name);
return true;
}
gfx::ColorSpace as_rgb = color_space.GetAsRGB();
gfx::ColorSpace as_full_range_rgb = color_space.GetAsFullRangeRGB();
// IOSurfaces do not support full-range YUV video. Fortunately, the hardware
// decoders never produce full-range video.
// https://crbug.com/882627
if (color_space != as_rgb && as_rgb == as_full_range_rgb)
return false;
// Generate an ICCProfile from the parametric color space.
ICCProfile icc_profile = ICCProfile::FromColorSpace(as_full_range_rgb);
if (!icc_profile.IsValid())
return false;
// Package it as a CFDataRef and send it to the IOSurface.
std::vector<char> icc_profile_data = icc_profile.GetData();
base::apple::ScopedCFTypeRef<CFDataRef> cf_data_icc_profile(CFDataCreate(
nullptr, reinterpret_cast<const UInt8*>(icc_profile_data.data()),
icc_profile_data.size()));
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
cf_data_icc_profile.get());
return true;
}
} // namespace internal
ScopedIOSurface CreateIOSurface(const gfx::Size& size,
viz::SharedImageFormat format,
bool should_clear,
bool override_rgba_to_bgra) {
TRACE_EVENT0("ui", "CreateIOSurface");
base::TimeTicks start_time = base::TimeTicks::Now();
base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> properties(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
AddIntegerValue(properties.get(), kIOSurfaceWidth, size.width());
AddIntegerValue(properties.get(), kIOSurfaceHeight, size.height());
AddIntegerValue(
properties.get(), kIOSurfacePixelFormat,
SharedImageFormatToIOSurfacePixelFormat(format, override_rgba_to_bgra));
// Don't specify plane information unless there are indeed multiple planes
// because DisplayLink drivers do not support this.
// http://crbug.com/527556
size_t num_planes = format.NumberOfPlanes();
if (num_planes > 1) {
base::apple::ScopedCFTypeRef<CFMutableArrayRef> planes(CFArrayCreateMutable(
kCFAllocatorDefault, num_planes, &kCFTypeArrayCallBacks));
size_t total_bytes_alloc = 0;
for (size_t plane = 0; plane < num_planes; ++plane) {
const gfx::Size plane_size = format.GetPlaneSize(plane, size);
const size_t plane_width = plane_size.width();
const size_t plane_height = plane_size.height();
size_t plane_bytes_per_element = format.NumChannelsInPlane(plane);
if (format.channel_format() !=
viz::SharedImageFormat::ChannelFormat::k8) {
plane_bytes_per_element *= 2;
}
const size_t plane_bytes_per_row =
IOSurfaceAlignProperty(kIOSurfacePlaneBytesPerRow,
base::bits::AlignUp(plane_width, size_t{2}) *
plane_bytes_per_element);
const size_t plane_bytes_alloc = IOSurfaceAlignProperty(
kIOSurfacePlaneSize,
base::bits::AlignUp(plane_height, size_t{2}) * plane_bytes_per_row);
const size_t plane_offset =
IOSurfaceAlignProperty(kIOSurfacePlaneOffset, total_bytes_alloc);
base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> plane_info(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
AddIntegerValue(plane_info.get(), kIOSurfacePlaneWidth, plane_width);
AddIntegerValue(plane_info.get(), kIOSurfacePlaneHeight, plane_height);
AddIntegerValue(plane_info.get(), kIOSurfacePlaneBytesPerElement,
plane_bytes_per_element);
AddIntegerValue(plane_info.get(), kIOSurfacePlaneBytesPerRow,
plane_bytes_per_row);
AddIntegerValue(plane_info.get(), kIOSurfacePlaneSize, plane_bytes_alloc);
AddIntegerValue(plane_info.get(), kIOSurfacePlaneOffset, plane_offset);
CFArrayAppendValue(planes.get(), plane_info.get());
total_bytes_alloc = plane_offset + plane_bytes_alloc;
}
CFDictionaryAddValue(properties.get(), kIOSurfacePlaneInfo, planes.get());
total_bytes_alloc =
IOSurfaceAlignProperty(kIOSurfaceAllocSize, total_bytes_alloc);
AddIntegerValue(properties.get(), kIOSurfaceAllocSize, total_bytes_alloc);
} else {
const size_t bytes_per_element = format.BytesPerPixel();
const size_t bytes_per_row = IOSurfaceAlignProperty(
kIOSurfaceBytesPerRow,
base::bits::AlignUp(static_cast<size_t>(size.width()), size_t{2}) *
bytes_per_element);
const size_t bytes_alloc = IOSurfaceAlignProperty(
kIOSurfaceAllocSize,
base::bits::AlignUp(static_cast<size_t>(size.height()), size_t{2}) *
bytes_per_row);
AddIntegerValue(properties.get(), kIOSurfaceBytesPerElement,
bytes_per_element);
AddIntegerValue(properties.get(), kIOSurfaceBytesPerRow, bytes_per_row);
AddIntegerValue(properties.get(), kIOSurfaceAllocSize, bytes_alloc);
}
ScopedIOSurface io_surface(IOSurfaceCreate(properties.get()));
if (!io_surface) {
LOG(ERROR) << "Failed to allocate IOSurface of size " << size.ToString()
<< ".";
return ScopedIOSurface();
}
if (should_clear) {
// Zero-initialize the IOSurface. Calling IOSurfaceLock/IOSurfaceUnlock
// appears to be sufficient. https://crbug.com/584760#c17
kern_return_t r = IOSurfaceLock(io_surface.get(), 0, nullptr);
DCHECK_EQ(KERN_SUCCESS, r);
r = IOSurfaceUnlock(io_surface.get(), 0, nullptr);
DCHECK_EQ(KERN_SUCCESS, r);
}
// Ensure that all IOSurfaces start as sRGB.
IOSurfaceSetValue(io_surface.get(), CFSTR("IOSurfaceColorSpace"),
kCGColorSpaceSRGB);
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"GPU.IOSurface.CreationTimeUs", base::TimeTicks::Now() - start_time,
base::Microseconds(1), base::Milliseconds(50), /*bucket_count=*/100);
return io_surface;
}
#if BUILDFLAG(IS_IOS)
void ExportIOSurfaceSharedMemoryRegion(
IOSurfaceRef io_surface,
base::UnsafeSharedMemoryRegion& shared_memory_region,
std::array<uint32_t, kMaxIOSurfacePlanes>& plane_strides,
std::array<uint32_t, kMaxIOSurfacePlanes>& plane_offsets) {
CHECK(io_surface);
const void* io_surface_base_addr = IOSurfaceGetBaseAddress(io_surface);
const size_t io_surface_alloc_size = IOSurfaceGetAllocSize(io_surface);
memory_object_size_t alloc_size = io_surface_alloc_size;
base::apple::ScopedMachSendRight named_right;
kern_return_t kr = mach_make_memory_entry_64(
mach_task_self(), &alloc_size,
reinterpret_cast<memory_object_offset_t>(io_surface_base_addr),
VM_PROT_READ | VM_PROT_WRITE,
base::apple::ScopedMachSendRight::Receiver(named_right).get(),
MACH_PORT_NULL);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_make_memory_entry_64";
CHECK_GE(alloc_size, io_surface_alloc_size);
using base::subtle::PlatformSharedMemoryRegion;
auto platform_shared_memory_region = PlatformSharedMemoryRegion::Take(
std::move(named_right), PlatformSharedMemoryRegion::Mode::kUnsafe,
alloc_size, base::UnguessableToken::Create());
CHECK(platform_shared_memory_region.IsValid());
shared_memory_region = base::UnsafeSharedMemoryRegion::Deserialize(
std::move(platform_shared_memory_region));
CHECK(shared_memory_region.IsValid());
// IOSurfaceGetPlaneCount returns 0 for single-plane surfaces.
const size_t plane_count = std::max(1ul, IOSurfaceGetPlaneCount(io_surface));
plane_strides = {};
plane_offsets = {};
for (size_t plane = 0; plane < plane_count; plane++) {
plane_strides[plane] = base::checked_cast<uint32_t>(
IOSurfaceGetBytesPerRowOfPlane(io_surface, plane));
const void* io_surface_plane_addr =
IOSurfaceGetBaseAddressOfPlane(io_surface, plane);
plane_offsets[plane] = base::checked_cast<uint32_t>(
reinterpret_cast<intptr_t>(io_surface_plane_addr) -
reinterpret_cast<intptr_t>(io_surface_base_addr));
}
}
#endif
bool IOSurfaceCanSetColorSpace(const ColorSpace& color_space) {
return internal::IOSurfaceSetColorSpace(nullptr, color_space);
}
void IOSurfaceSetColorSpace(IOSurfaceRef io_surface,
const ColorSpace& color_space) {
if (!internal::IOSurfaceSetColorSpace(io_surface, color_space)) {
DLOG(ERROR) << "Failed to set color space for IOSurface: "
<< color_space.ToString();
}
}
ScopedIOSurface IOSurfaceMachPortToIOSurface(
ScopedRefCountedIOSurfaceMachPort io_surface_mach_port) {
ScopedIOSurface io_surface;
if (!io_surface_mach_port) {
DLOG(ERROR) << "Invalid mach port.";
return io_surface;
}
io_surface.reset(IOSurfaceLookupFromMachPort(io_surface_mach_port.get()));
if (!io_surface) {
DLOG(ERROR) << "Unable to lookup IOSurface.";
return io_surface;
}
return io_surface;
}
} // namespace gfx