Commit 03e1d09c authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

macOS HDR: Add HDRCopierLayer

It used to be that setting the contents of a CALayer to an IOSurface
that has an HDR color space, and setting that the CALayer wants
extended range content was sufficient to display HDR content. This was
an undocumented behavior, and macOS has recently fixed it (made it not
work).

The documented way to get HDR is to use a CAMetalLayer. Unfortunately,
one cannot call setContents (or provide a pre-existing IOSurface) to
this API. As a consequence, we need to perform a copy from the
IOSurface to the CAMetalLayer. Add HDRCopierLayer subclass of
CAMetalLayer, which overrides setContents to blit the specified
contents to the CAMetalLayer.

Add tests for this. It appears that the test suite is not running,
because most of the tests are broken. Fix all the tests (next step
there is to add them to bots, but that will get a separate patch).

This was previously landed as crrev.com/737885 and reverted because
of failures on 10.10. This re-land links using -weak_framework.

Bug: 976426
Change-Id: Ie3bf8438770dc58e19b1401a54356693fc22274a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2038172Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738593}
parent 50fdef03
...@@ -4,6 +4,15 @@ ...@@ -4,6 +4,15 @@
import("//testing/test.gni") import("//testing/test.gni")
config("metal_weak_framework") {
ldflags = [
"-weak_framework",
"Metal",
"-weak_framework",
"MetalKit",
]
}
component("metal_util") { component("metal_util") {
output_name = "metal" output_name = "metal"
...@@ -14,6 +23,8 @@ component("metal_util") { ...@@ -14,6 +23,8 @@ component("metal_util") {
"device.mm", "device.mm",
"device_removal.h", "device_removal.h",
"device_removal.mm", "device_removal.mm",
"hdr_copier_layer.h",
"hdr_copier_layer.mm",
"metal_util_export.h", "metal_util_export.h",
"test_shader.h", "test_shader.h",
"test_shader.mm", "test_shader.mm",
...@@ -27,6 +38,9 @@ component("metal_util") { ...@@ -27,6 +38,9 @@ component("metal_util") {
libs = [ libs = [
"Cocoa.framework", "Cocoa.framework",
"Metal.framework", "IOSurface.framework",
"QuartzCore.framework",
] ]
configs += [ ":metal_weak_framework" ]
} }
// Copyright 2020 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.
#ifndef COMPONENTS_METAL_UTIL_HDR_COPIER_LAYER_H_
#define COMPONENTS_METAL_UTIL_HDR_COPIER_LAYER_H_
#include "components/metal_util/metal_util_export.h"
@class CALayer;
namespace metal {
// Create a layer which may have its contents set an HDR IOSurface via
// the -[CALayer setContents:] method.
// - The IOSurface specified to setContents must have pixel format
// kCVPixelFormatType_64RGBAHalf or kCVPixelFormatType_ARGB2101010LEPacked,
// any other pixel formats will be NOTREACHED.
// - This layer will, in setContents, blit the contents of the specified
// IOSurface to an HDR-capable CAMetalLayer.
CALayer* METAL_UTIL_EXPORT CreateHDRCopierLayer();
} // namespace metal
#endif // COMPONENTS_METAL_UTIL_HDR_COPIER_LAYER_H_
// Copyright 2020 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 "components/metal_util/hdr_copier_layer.h"
#include <CoreVideo/CVPixelBuffer.h>
#include <Metal/Metal.h>
#include <MetalKit/MetalKit.h>
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "components/metal_util/device.h"
#include "base/strings/sys_string_conversions.h"
namespace {
// Convert from an IOSurface's pixel format to a MTLPixelFormat. Crash on any
// unsupported formats.
MTLPixelFormat IOSurfaceGetMTLPixelFormat(IOSurfaceRef buffer)
API_AVAILABLE(macos(10.13)) {
uint32_t format = IOSurfaceGetPixelFormat(buffer);
switch (format) {
case kCVPixelFormatType_64RGBAHalf:
return MTLPixelFormatRGBA16Float;
case kCVPixelFormatType_ARGB2101010LEPacked:
return MTLPixelFormatBGR10A2Unorm;
default:
break;
}
NOTREACHED();
return MTLPixelFormatInvalid;
}
// Retrieve the named color space from an IOSurface and convert it to a
// CGColorSpace. Return nullptr on failure.
CGColorSpaceRef IOSurfaceCopyCGColorSpace(IOSurfaceRef buffer) {
base::ScopedCFTypeRef<CFTypeRef> color_space_value(
IOSurfaceCopyValue(buffer, CFSTR("IOSurfaceColorSpace")));
if (!color_space_value)
return nullptr;
CFStringRef color_space_string =
base::mac::CFCast<CFStringRef>(color_space_value);
if (!color_space_string)
return nullptr;
base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
CGColorSpaceCreateWithName(color_space_string));
if (!color_space)
return nullptr;
return color_space.release();
}
} // namespace
#if !defined(MAC_OS_X_VERSION_10_15)
API_AVAILABLE(macos(10.15))
@interface CAMetalLayer (Forward)
@property(readonly) id<MTLDevice> preferredDevice;
@end
#endif
API_AVAILABLE(macos(10.15))
@interface HDRCopierLayer : CAMetalLayer
- (id)init;
- (void)setContents:(id)contents;
@end
@implementation HDRCopierLayer
- (id)init {
if (self = [super init]) {
base::scoped_nsprotocol<id<MTLDevice>> device(metal::CreateDefaultDevice());
[self setWantsExtendedDynamicRangeContent:YES];
[self setDevice:device];
[self setOpaque:NO];
[self setDisplaySyncEnabled:NO];
[self setPresentsWithTransaction:YES];
}
return self;
}
- (void)setContents:(id)contents {
IOSurfaceRef buffer = reinterpret_cast<IOSurfaceRef>(contents);
// Retrieve information about the IOSurface.
size_t width = IOSurfaceGetWidth(buffer);
size_t height = IOSurfaceGetHeight(buffer);
MTLPixelFormat mtl_format = IOSurfaceGetMTLPixelFormat(buffer);
if (mtl_format == MTLPixelFormatInvalid) {
DLOG(ERROR) << "Unsupported IOSurface format.";
return;
}
base::ScopedCFTypeRef<CGColorSpaceRef> cg_color_space(
IOSurfaceCopyCGColorSpace(buffer));
if (!cg_color_space) {
DLOG(ERROR) << "Unsupported IOSurface color space.";
}
// Migrate to the MTLDevice on which the CAMetalLayer is being composited, if
// known.
if ([self respondsToSelector:@selector(preferredDevice)]) {
id<MTLDevice> preferred_device = nil;
if (preferred_device)
[self setDevice:preferred_device];
}
id<MTLDevice> device = [self device];
// Update the layer's properties to match the IOSurface.
[self setDrawableSize:CGSizeMake(width, height)];
[self setPixelFormat:mtl_format];
[self setColorspace:cg_color_space];
// Create a texture to wrap the IOSurface.
base::scoped_nsprotocol<id<MTLTexture>> buffer_texture;
{
base::scoped_nsobject<MTLTextureDescriptor> tex_desc(
[MTLTextureDescriptor new]);
[tex_desc setTextureType:MTLTextureType2D];
[tex_desc setUsage:MTLTextureUsageShaderRead];
[tex_desc setPixelFormat:mtl_format];
[tex_desc setWidth:width];
[tex_desc setHeight:height];
[tex_desc setDepth:1];
[tex_desc setMipmapLevelCount:1];
[tex_desc setArrayLength:1];
[tex_desc setSampleCount:1];
[tex_desc setStorageMode:MTLStorageModeManaged];
buffer_texture.reset([device newTextureWithDescriptor:tex_desc
iosurface:buffer
plane:0]);
}
// Create a texture to wrap the drawable.
id<CAMetalDrawable> drawable = [self nextDrawable];
id<MTLTexture> drawable_texture = [drawable texture];
// Copy from the IOSurface to the drawable.
base::scoped_nsprotocol<id<MTLCommandQueue>> command_queue(
[device newCommandQueue]);
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> encoder = [command_buffer blitCommandEncoder];
[encoder copyFromTexture:buffer_texture
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(width, height, 1)
toTexture:drawable_texture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[encoder endEncoding];
[command_buffer presentDrawable:drawable];
[command_buffer commit];
}
@end
namespace metal {
CALayer* CreateHDRCopierLayer() {
// If this is hit by non-10.15 paths (e.g, for testing), then return an
// ordinary CALayer. Calling setContents on that CALayer will work fine
// (HDR content will be clipped, but that would have happened anyway).
if (@available(macos 10.15, *))
return [[HDRCopierLayer alloc] init];
else
return [[CALayer alloc] init];
}
} // namespace metal
...@@ -29,6 +29,7 @@ component("accelerated_widget_mac") { ...@@ -29,6 +29,7 @@ component("accelerated_widget_mac") {
deps = [ deps = [
"//base", "//base",
"//components/metal_util",
"//skia", "//skia",
"//ui/base", "//ui/base",
"//ui/events", "//ui/events",
......
include_rules = [ include_rules = [
"+components/metal_util",
"+gpu/GLES2", "+gpu/GLES2",
"+third_party/skia", "+third_party/skia",
"+ui/base/cocoa", "+ui/base/cocoa",
......
...@@ -27,6 +27,15 @@ namespace ui { ...@@ -27,6 +27,15 @@ namespace ui {
struct CARendererLayerParams; struct CARendererLayerParams;
enum class CALayerType {
// A CALayer with contents set to an IOSurface by setContents.
kDefault,
// An AVSampleBufferDisplayLayer.
kVideo,
// A CAMetalLayer that copies half-float or 10-bit IOSurfaces.
kHDRCopier,
};
// The CARendererLayerTree will construct a hierarchy of CALayers from a linear // The CARendererLayerTree will construct a hierarchy of CALayers from a linear
// list provided by the CoreAnimation renderer using the algorithm and structure // list provided by the CoreAnimation renderer using the algorithm and structure
// referenced described in // referenced described in
...@@ -160,7 +169,7 @@ class ACCELERATED_WIDGET_MAC_EXPORT CARendererLayerTree { ...@@ -160,7 +169,7 @@ class ACCELERATED_WIDGET_MAC_EXPORT CARendererLayerTree {
const gfx::RectF& contents_rect, const gfx::RectF& contents_rect,
const gfx::Rect& rect, const gfx::Rect& rect,
unsigned background_color, unsigned background_color,
bool triggers_hdr, bool has_hdr_color_space,
unsigned edge_aa_mask, unsigned edge_aa_mask,
float opacity, float opacity,
unsigned filter); unsigned filter);
...@@ -182,18 +191,18 @@ class ACCELERATED_WIDGET_MAC_EXPORT CARendererLayerTree { ...@@ -182,18 +191,18 @@ class ACCELERATED_WIDGET_MAC_EXPORT CARendererLayerTree {
gfx::RectF contents_rect; gfx::RectF contents_rect;
gfx::RectF rect; gfx::RectF rect;
unsigned background_color = 0; unsigned background_color = 0;
const bool triggers_hdr;
// Note that the CoreAnimation edge antialiasing mask is not the same as // Note that the CoreAnimation edge antialiasing mask is not the same as
// the edge antialiasing mask passed to the constructor. // the edge antialiasing mask passed to the constructor.
CAEdgeAntialiasingMask ca_edge_aa_mask = 0; CAEdgeAntialiasingMask ca_edge_aa_mask = 0;
float opacity = 1; float opacity = 1;
NSString* const ca_filter = nil; NSString* const ca_filter = nil;
CALayerType type = CALayerType::kDefault;
base::scoped_nsobject<CALayer> ca_layer; base::scoped_nsobject<CALayer> ca_layer;
// If this layer's contents can be represented as an // If this layer's contents can be represented as an
// AVSampleBufferDisplayLayer, then |ca_layer| will point to |av_layer|. // AVSampleBufferDisplayLayer, then |ca_layer| will point to |av_layer|.
base::scoped_nsobject<AVSampleBufferDisplayLayer> av_layer; base::scoped_nsobject<AVSampleBufferDisplayLayer> av_layer;
bool use_av_layer = false;
private: private:
DISALLOW_COPY_AND_ASSIGN(ContentLayer); DISALLOW_COPY_AND_ASSIGN(ContentLayer);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment