// Copyright 2015 The Chromium Authors. All rights reserved. // 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 #include #include #include #include "base/command_line.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/mac/mac_util.h" #include "base/mac/mach_logging.h" #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" #include "base/trace_event/trace_event.h" #include "ui/gfx/buffer_format_util.h" #include "ui/gfx/color_space.h" #include "ui/gfx/icc_profile.h" namespace gfx { namespace { const base::Feature kIOSurfaceUseNamedSRGBForREC709{ "IOSurfaceUseNamedSRGBForREC709", base::FEATURE_ENABLED_BY_DEFAULT}; void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef key, int32_t value) { base::ScopedCFTypeRef number( CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); CFDictionaryAddValue(dictionary, key, number.get()); } int32_t BytesPerElement(gfx::BufferFormat format, int plane) { switch (format) { case gfx::BufferFormat::R_8: DCHECK_EQ(plane, 0); return 1; case gfx::BufferFormat::BGRA_8888: case gfx::BufferFormat::BGRX_8888: case gfx::BufferFormat::RGBA_8888: case gfx::BufferFormat::BGRA_1010102: DCHECK_EQ(plane, 0); return 4; case gfx::BufferFormat::RGBA_F16: DCHECK_EQ(plane, 0); return 8; case gfx::BufferFormat::YUV_420_BIPLANAR: { constexpr int32_t bytes_per_element[] = {1, 2}; DCHECK_LT(static_cast(plane), base::size(bytes_per_element)); return bytes_per_element[plane]; } case gfx::BufferFormat::P010: { constexpr int32_t bytes_per_element[] = {2, 4}; DCHECK_LT(static_cast(plane), base::size(bytes_per_element)); return bytes_per_element[plane]; } case gfx::BufferFormat::R_16: case gfx::BufferFormat::RG_88: case gfx::BufferFormat::BGR_565: case gfx::BufferFormat::RGBA_4444: case gfx::BufferFormat::RGBX_8888: case gfx::BufferFormat::RGBA_1010102: case gfx::BufferFormat::YVU_420: NOTREACHED(); return 0; } NOTREACHED(); return 0; } } // namespace uint32_t BufferFormatToIOSurfacePixelFormat(gfx::BufferFormat format) { switch (format) { case gfx::BufferFormat::R_8: return 'L008'; case gfx::BufferFormat::BGRA_1010102: return 'l10r'; // little-endian ARGB2101010 full-range ARGB case gfx::BufferFormat::BGRA_8888: case gfx::BufferFormat::BGRX_8888: case gfx::BufferFormat::RGBA_8888: return 'BGRA'; case gfx::BufferFormat::RGBA_F16: return 'RGhA'; case gfx::BufferFormat::YUV_420_BIPLANAR: return '420v'; case gfx::BufferFormat::P010: return 'x420'; case gfx::BufferFormat::R_16: case gfx::BufferFormat::RG_88: case gfx::BufferFormat::BGR_565: case gfx::BufferFormat::RGBA_4444: case gfx::BufferFormat::RGBX_8888: case gfx::BufferFormat::RGBA_1010102: // 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. case gfx::BufferFormat::YVU_420: return 0; } NOTREACHED(); return 0; } 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_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1); MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "IOSurfaceMachPortTraits::Release mach_port_mod_refs"; } // 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 (__builtin_available(macos 10.12, *)) { if (color_space == ColorSpace::CreateSRGB() || (base::FeatureList::IsEnabled(kIOSurfaceUseNamedSRGBForREC709) && 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::CreateSCRGBLinear()) { 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 (base::mac::IsAtLeastOS10_15()) { if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::SMPTEST2084, ColorSpace::MatrixID::BT2020_NCL, ColorSpace::RangeID::LIMITED)) { return true; } else if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::ARIB_STD_B67, ColorSpace::MatrixID::BT2020_NCL, ColorSpace::RangeID::LIMITED)) { return true; } } if (color_space_name) { if (io_surface) { 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 icc_profile_data = icc_profile.GetData(); base::ScopedCFTypeRef cf_data_icc_profile(CFDataCreate( nullptr, reinterpret_cast(icc_profile_data.data()), icc_profile_data.size())); IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"), cf_data_icc_profile); return true; } } // namespace internal IOSurfaceRef CreateIOSurface(const gfx::Size& size, gfx::BufferFormat format, bool should_clear) { TRACE_EVENT0("ui", "CreateIOSurface"); base::TimeTicks start_time = base::TimeTicks::Now(); base::ScopedCFTypeRef properties( CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); AddIntegerValue(properties, kIOSurfaceWidth, size.width()); AddIntegerValue(properties, kIOSurfaceHeight, size.height()); AddIntegerValue(properties, kIOSurfacePixelFormat, BufferFormatToIOSurfacePixelFormat(format)); // 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 = gfx::NumberOfPlanesForLinearBufferFormat(format); if (num_planes > 1) { base::ScopedCFTypeRef planes(CFArrayCreateMutable( kCFAllocatorDefault, num_planes, &kCFTypeArrayCallBacks)); size_t total_bytes_alloc = 0; for (size_t plane = 0; plane < num_planes; ++plane) { const size_t factor = gfx::SubsamplingFactorForBufferFormat(format, plane); const size_t plane_width = size.width() / factor; const size_t plane_height = size.height() / factor; const size_t plane_bytes_per_element = BytesPerElement(format, plane); const size_t plane_bytes_per_row = IOSurfaceAlignProperty( kIOSurfacePlaneBytesPerRow, plane_width * plane_bytes_per_element); const size_t plane_bytes_alloc = IOSurfaceAlignProperty( kIOSurfacePlaneSize, plane_height * plane_bytes_per_row); const size_t plane_offset = IOSurfaceAlignProperty(kIOSurfacePlaneOffset, total_bytes_alloc); base::ScopedCFTypeRef plane_info( CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); AddIntegerValue(plane_info, kIOSurfacePlaneWidth, plane_width); AddIntegerValue(plane_info, kIOSurfacePlaneHeight, plane_height); AddIntegerValue(plane_info, kIOSurfacePlaneBytesPerElement, plane_bytes_per_element); AddIntegerValue(plane_info, kIOSurfacePlaneBytesPerRow, plane_bytes_per_row); AddIntegerValue(plane_info, kIOSurfacePlaneSize, plane_bytes_alloc); AddIntegerValue(plane_info, kIOSurfacePlaneOffset, plane_offset); CFArrayAppendValue(planes, plane_info); total_bytes_alloc = plane_offset + plane_bytes_alloc; } CFDictionaryAddValue(properties, kIOSurfacePlaneInfo, planes); total_bytes_alloc = IOSurfaceAlignProperty(kIOSurfaceAllocSize, total_bytes_alloc); AddIntegerValue(properties, kIOSurfaceAllocSize, total_bytes_alloc); } else { const size_t bytes_per_element = BytesPerElement(format, 0); const size_t bytes_per_row = IOSurfaceAlignProperty( kIOSurfaceBytesPerRow, size.width() * bytes_per_element); const size_t bytes_alloc = IOSurfaceAlignProperty( kIOSurfaceAllocSize, size.height() * bytes_per_row); AddIntegerValue(properties, kIOSurfaceBytesPerElement, bytes_per_element); AddIntegerValue(properties, kIOSurfaceBytesPerRow, bytes_per_row); AddIntegerValue(properties, kIOSurfaceAllocSize, bytes_alloc); } IOSurfaceRef surface = IOSurfaceCreate(properties); if (!surface) { LOG(ERROR) << "Failed to allocate IOSurface of size " << size.ToString() << "."; return nullptr; } // IOSurface clearing causes significant performance regression on about half // of all devices running Yosemite. https://crbug.com/606850#c22. if (base::mac::IsOS10_10()) should_clear = false; if (should_clear) { // Zero-initialize the IOSurface. Calling IOSurfaceLock/IOSurfaceUnlock // appears to be sufficient. https://crbug.com/584760#c17 IOReturn r = IOSurfaceLock(surface, 0, nullptr); DCHECK_EQ(kIOReturnSuccess, r); r = IOSurfaceUnlock(surface, 0, nullptr); DCHECK_EQ(kIOReturnSuccess, r); } // Ensure that all IOSurfaces start as sRGB. if (__builtin_available(macos 10.12, *)) { IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); } else { CGColorSpaceRef color_space = base::mac::GetSRGBColorSpace(); base::ScopedCFTypeRef color_space_icc( CGColorSpaceCopyICCProfile(color_space)); IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), color_space_icc); } UMA_HISTOGRAM_TIMES("GPU.IOSurface.CreateTime", base::TimeTicks::Now() - start_time); return surface; } 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(); } } GFX_EXPORT base::ScopedCFTypeRef IOSurfaceMachPortToIOSurface( ScopedRefCountedIOSurfaceMachPort io_surface_mach_port) { base::ScopedCFTypeRef io_surface; if (!io_surface_mach_port) { DLOG(ERROR) << "Invalid mach port."; return io_surface; } io_surface.reset(IOSurfaceLookupFromMachPort(io_surface_mach_port)); if (!io_surface) { DLOG(ERROR) << "Unable to lookup IOSurface."; return io_surface; } return io_surface; } } // namespace gfx