Commit 52d096fb authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

HDR/macOS Tonemapping: Change from Metal blit to shader

There exists code that copies an IOSurface to a CAMetalLayer,
because simply setting a CALayer's contents to an IOSurface that
has an HDR color space is not sufficient to guarantee that the
content will be displayed as HDR (it did in early versions of
10.16, but now does not).

Separately, there is now desire to have tonemapping on lower
end HDR devices. This is fairly easy to wrangle into the
pipeline in the place where we do the copy.

In this patch, replace the blit with a shader that does
the blit. Also, change the CAMetalLayer from matching the
IOSurface to being float16. Patches will follow will include
- Change the CAMetalLayer to be in extended-linear-sRGB
  color space, by adding limited color conversion to the
  shader.
- Add tonemapping in the extended-linear-sRGB color space.
- Set the tonemapping parameters based on the NSScreen
  properties.

Bug: 1101041
Change-Id: If1aaa6081730212611cb4cf58d8f90d5957dddf7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2352027
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798466}
parent 0c3c73f6
......@@ -17,6 +17,43 @@
namespace {
// Source of the shader to perform tonemapping.
// TODO(https://crbug.com/1101041): This shader just does a copy for now.
const char* tonemapping_shader_source =
"#include <metal_stdlib>\n"
"#include <simd/simd.h>\n"
"using metal::float2;\n"
"using metal::float3x3;\n"
"using metal::float4;\n"
"using metal::sampler;\n"
"using metal::texture2d;\n"
"\n"
"typedef struct {\n"
" float4 clipSpacePosition [[position]];\n"
" float2 texcoord;\n"
"} RasterizerData;\n"
"\n"
"vertex RasterizerData vertexShader(\n"
" uint vertexID [[vertex_id]],\n"
" constant float2 *positions[[buffer(0)]]) {\n"
" RasterizerData out;\n"
" out.clipSpacePosition = vector_float4(0.f, 0.f, 0.f, 1.f);\n"
" out.clipSpacePosition.x = 2.f * positions[vertexID].x - 1.f;\n"
" out.clipSpacePosition.y = -2.f * positions[vertexID].y + 1.f;\n"
" out.texcoord = positions[vertexID];\n"
" return out;\n"
"}\n"
"\n"
"fragment float4 fragmentShader(RasterizerData in [[stage_in]],\n"
" texture2d<float> t [[texture(0)]],\n"
" constant float3x3& m [[buffer(0)]],\n"
" constant uint32_t& f [[buffer(1)]]) {\n"
" constexpr sampler s(metal::mag_filter::nearest,\n"
" metal::min_filter::nearest);\n"
" float4 color = t.sample(s, in.texcoord);\n"
" return color;\n"
"}\n";
// Convert from an IOSurface's pixel format to a MTLPixelFormat. Crash on any
// unsupported formats.
MTLPixelFormat IOSurfaceGetMTLPixelFormat(IOSurfaceRef buffer)
......@@ -52,6 +89,49 @@ CGColorSpaceRef IOSurfaceCopyCGColorSpace(IOSurfaceRef buffer) {
return color_space.release();
}
base::scoped_nsprotocol<id<MTLRenderPipelineState>> CreateRenderPipelineState(
id<MTLDevice> device) API_AVAILABLE(macos(10.13)) {
base::scoped_nsprotocol<id<MTLRenderPipelineState>> render_pipeline_state;
base::scoped_nsprotocol<id<MTLLibrary>> library;
{
NSError* error = nil;
base::scoped_nsobject<NSString> source([[NSString alloc]
initWithCString:tonemapping_shader_source
encoding:NSASCIIStringEncoding]);
base::scoped_nsobject<MTLCompileOptions> options(
[[MTLCompileOptions alloc] init]);
library.reset([device newLibraryWithSource:source
options:options
error:&error]);
if (error) {
NSLog(@"Failed to compile shader: %@", error);
return render_pipeline_state;
}
}
{
base::scoped_nsprotocol<id<MTLFunction>> vertex_function(
[library newFunctionWithName:@"vertexShader"]);
base::scoped_nsprotocol<id<MTLFunction>> fragment_function(
[library newFunctionWithName:@"fragmentShader"]);
NSError* error = nil;
base::scoped_nsobject<MTLRenderPipelineDescriptor> desc(
[[MTLRenderPipelineDescriptor alloc] init]);
[desc setVertexFunction:vertex_function];
[desc setFragmentFunction:fragment_function];
[[desc colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA16Float];
render_pipeline_state.reset(
[device newRenderPipelineStateWithDescriptor:desc error:&error]);
if (error) {
NSLog(@"Failed to create render pipeline state: %@", error);
return render_pipeline_state;
}
}
return render_pipeline_state;
}
} // namespace
#if !defined(MAC_OS_X_VERSION_10_15)
......@@ -62,7 +142,9 @@ API_AVAILABLE(macos(10.15))
#endif
API_AVAILABLE(macos(10.15))
@interface HDRCopierLayer : CAMetalLayer
@interface HDRCopierLayer : CAMetalLayer {
base::scoped_nsprotocol<id<MTLRenderPipelineState>> _render_pipeline_state;
}
- (id)init;
- (void)setContents:(id)contents;
@end
......@@ -72,9 +154,6 @@ API_AVAILABLE(macos(10.15))
if (self = [super init]) {
base::scoped_nsprotocol<id<MTLDevice>> device(metal::CreateDefaultDevice());
[self setWantsExtendedDynamicRangeContent:YES];
[self setEDRMetadata:[CAEDRMetadata HDR10MetadataWithMinLuminance:0.005
maxLuminance:1000
opticalOutputScale:100]];
[self setDevice:device];
[self setOpaque:NO];
[self setPresentsWithTransaction:YES];
......@@ -108,9 +187,15 @@ API_AVAILABLE(macos(10.15))
}
id<MTLDevice> device = [self device];
// When the device changes, rebuild the RenderPipelineState.
if (device != [_render_pipeline_state device])
_render_pipeline_state = CreateRenderPipelineState(device);
if (!_render_pipeline_state)
return;
// Update the layer's properties to match the IOSurface.
[self setDrawableSize:CGSizeMake(width, height)];
[self setPixelFormat:mtl_format];
[self setPixelFormat:MTLPixelFormatRGBA16Float];
[self setColorspace:cg_color_space];
// Create a texture to wrap the IOSurface.
......@@ -141,16 +226,42 @@ API_AVAILABLE(macos(10.15))
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)];
id<MTLRenderCommandEncoder> encoder = nil;
{
MTLRenderPassDescriptor* desc =
[MTLRenderPassDescriptor renderPassDescriptor];
desc.colorAttachments[0].texture = drawable_texture;
desc.colorAttachments[0].loadAction = MTLLoadActionClear;
desc.colorAttachments[0].storeAction = MTLStoreActionStore;
desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 0.0);
encoder = [command_buffer renderCommandEncoderWithDescriptor:desc];
MTLViewport viewport;
viewport.originX = 0;
viewport.originY = 0;
viewport.width = width;
viewport.height = height;
viewport.znear = -1.0;
viewport.zfar = 1.0;
[encoder setViewport:viewport];
[encoder setRenderPipelineState:_render_pipeline_state];
[encoder setFragmentTexture:buffer_texture atIndex:0];
}
{
simd::float2 positions[6] = {
simd::make_float2(0, 0), simd::make_float2(0, 1),
simd::make_float2(1, 1), simd::make_float2(1, 1),
simd::make_float2(1, 0), simd::make_float2(0, 0),
};
simd::float3x3 matrix(simd::make_float3(1, 0, 0),
simd::make_float3(0, 1, 0),
simd::make_float3(0, 0, 1));
[encoder setVertexBytes:positions length:sizeof(positions) atIndex:0];
[encoder setFragmentBytes:&matrix length:sizeof(matrix) atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:6];
}
[encoder endEncoding];
[command_buffer commit];
[command_buffer waitUntilScheduled];
......
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