blob: b726596b926d5cd4d7131997dc07beaf04757612 [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 "gpu/ipc/service/image_transport_surface_overlay_mac.h"
#include <memory>
#include <sstream>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#import "base/task/single_thread_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "gpu/ipc/service/gpu_channel_manager_delegate.h"
#include "gpu/ipc/service/image_transport_surface_delegate.h"
#include "ui/accelerated_widget_mac/ca_layer_tree_coordinator.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/video_types.h"
#include "ui/gl/ca_renderer_layer_params.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gpu_switching_manager.h"
#if BUILDFLAG(IS_MAC)
#include "ui/accelerated_widget_mac/io_surface_context.h"
#include "ui/base/cocoa/remote_layer_api.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/scoped_cgl.h"
#endif
// From ANGLE's EGL/eglext_angle.h. This should be included instead of being
// redefined here.
#ifndef EGL_ANGLE_device_metal
#define EGL_ANGLE_device_metal 1
#define EGL_METAL_DEVICE_ANGLE 0x34A6
#endif /* EGL_ANGLE_device_metal */
namespace gpu {
namespace {
// Control use of AVFoundation to draw video content.
BASE_FEATURE(kAVFoundationOverlays,
"avfoundation-overlays",
base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace
ImageTransportSurfaceOverlayMacEGL::ImageTransportSurfaceOverlayMacEGL(
gl::GLDisplayEGL* display,
base::WeakPtr<ImageTransportSurfaceDelegate> delegate)
: gl::Presenter(display, gfx::Size()),
delegate_(delegate),
#if BUILDFLAG(IS_MAC)
use_remote_layer_api_(ui::RemoteLayerAPISupported()),
#endif
scale_factor_(1),
vsync_callback_(delegate->GetGpuVSyncCallback()),
gl_renderer_id_(0),
weak_ptr_factory_(this) {
ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
static bool av_disabled_at_command_line =
!base::FeatureList::IsEnabled(kAVFoundationOverlays);
bool allow_av_sample_buffer_display_layer =
!av_disabled_at_command_line &&
!delegate_->GetFeatureInfo()
->workarounds()
.disable_av_sample_buffer_display_layer;
#if BUILDFLAG(IS_MAC)
ca_layer_tree_coordinator_ = std::make_unique<ui::CALayerTreeCoordinator>(
use_remote_layer_api_, allow_av_sample_buffer_display_layer);
// Create the CAContext to send this to the GPU process, and the layer for
// the context.
if (use_remote_layer_api_) {
CGSConnectionID connection_id = CGSMainConnectionID();
ca_context_.reset([[CAContext contextWithCGSConnection:connection_id
options:@{}] retain]);
[ca_context_ setLayer:ca_layer_tree_coordinator_->GetCALayerForDisplay()];
}
#else
ca_layer_tree_coordinator_ = std::make_unique<ui::CALayerTreeCoordinator>(
/*allow_remote_layers=*/false, allow_av_sample_buffer_display_layer);
#endif
}
ImageTransportSurfaceOverlayMacEGL::~ImageTransportSurfaceOverlayMacEGL() {
ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
Destroy();
}
bool ImageTransportSurfaceOverlayMacEGL::Initialize(
gl::GLSurfaceFormat format) {
return true;
}
void ImageTransportSurfaceOverlayMacEGL::PrepareToDestroy(bool have_context) {}
void ImageTransportSurfaceOverlayMacEGL::Destroy() {
ca_layer_tree_coordinator_.reset();
}
void ImageTransportSurfaceOverlayMacEGL::ApplyBackpressure() {
TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::ApplyBackpressure");
// Create the fence for the current frame before waiting on the previous
// frame's fence (to maximize CPU and GPU execution overlap).
gl::GLContext* current_context = gl::GLContext::GetCurrent();
uint64_t this_frame_fence = current_context->BackpressureFenceCreate();
current_context->BackpressureFenceWait(previous_frame_fence_);
previous_frame_fence_ = this_frame_fence;
}
void ImageTransportSurfaceOverlayMacEGL::BufferPresented(
gl::GLSurface::PresentationCallback callback,
const gfx::PresentationFeedback& feedback) {
DCHECK(!callback.is_null());
std::move(callback).Run(feedback);
}
void ImageTransportSurfaceOverlayMacEGL::Present(
gl::GLSurface::SwapCompletionCallback completion_callback,
gl::GLSurface::PresentationCallback presentation_callback,
gfx::FrameData data) {
TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::Present");
constexpr base::TimeDelta kHistogramMinTime = base::Microseconds(5);
constexpr base::TimeDelta kHistogramMaxTime = base::Milliseconds(16);
constexpr int kHistogramTimeBuckets = 50;
// Query the underlying Metal device, if one exists. This is needed to ensure
// synchronization between the display compositor and the HDRCopierLayer.
// https://crbug.com/1372898
if (gl::GLDisplayEGL* display =
gl::GLDisplayEGL::GetDisplayForCurrentContext()) {
EGLAttrib angle_device_attrib = 0;
if (eglQueryDisplayAttribEXT(display->GetDisplay(), EGL_DEVICE_EXT,
&angle_device_attrib)) {
EGLDeviceEXT angle_device =
reinterpret_cast<EGLDeviceEXT>(angle_device_attrib);
EGLAttrib metal_device = 0;
if (eglQueryDeviceAttribEXT(angle_device, EGL_METAL_DEVICE_ANGLE,
&metal_device)) {
ca_layer_tree_coordinator_->GetPendingCARendererLayerTree()
->SetMetalDevice(metal_device);
}
}
}
// Do a GL fence for flush to apply back-pressure before drawing.
{
base::TimeTicks start_time = base::TimeTicks::Now();
ApplyBackpressure();
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Gpu.Mac.BackpressureUs", base::TimeTicks::Now() - start_time,
kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets);
}
// Update the CALayer tree in the GPU process.
base::TimeTicks before_transaction_time = base::TimeTicks::Now();
{
TRACE_EVENT0("gpu", "CommitPendingTreesToCA");
ca_layer_tree_coordinator_->CommitPendingTreesToCA();
base::TimeDelta transaction_time =
base::TimeTicks::Now() - before_transaction_time;
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"GPU.IOSurface.CATransactionTimeUs", transaction_time,
kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets);
}
// Populate the CA layer parameters to send to the browser.
gfx::CALayerParams params;
{
TRACE_EVENT_INSTANT2("test_gpu", "SwapBuffers", TRACE_EVENT_SCOPE_THREAD,
"GLImpl", static_cast<int>(gl::GetGLImplementation()),
"width", pixel_size_.width());
#if BUILDFLAG(IS_MAC)
if (use_remote_layer_api_) {
params.ca_context_id = [ca_context_ contextId];
} else {
#else
if (true) {
#endif
IOSurfaceRef io_surface =
ca_layer_tree_coordinator_->GetIOSurfaceForDisplay();
if (io_surface) {
params.io_surface_mach_port.reset(IOSurfaceCreateMachPort(io_surface));
}
}
params.pixel_size = pixel_size_;
params.scale_factor = scale_factor_;
params.is_empty = false;
}
// Send the swap parameters to the browser.
if (completion_callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(completion_callback),
gfx::SwapCompletionResult(
gfx::SwapResult::SWAP_ACK,
std::make_unique<gfx::CALayerParams>(params))));
}
gfx::PresentationFeedback feedback(base::TimeTicks::Now(), base::Hertz(60),
/*flags=*/0);
feedback.ca_layer_error_code = ca_layer_error_code_;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ImageTransportSurfaceOverlayMacEGL::BufferPresented,
weak_ptr_factory_.GetWeakPtr(),
std::move(presentation_callback), feedback));
}
bool ImageTransportSurfaceOverlayMacEGL::SupportsCommitOverlayPlanes() {
return true;
}
gfx::Size ImageTransportSurfaceOverlayMacEGL::GetSize() {
return gfx::Size();
}
void* ImageTransportSurfaceOverlayMacEGL::GetHandle() {
return nullptr;
}
gl::GLSurfaceFormat ImageTransportSurfaceOverlayMacEGL::GetFormat() {
return gl::GLSurfaceFormat();
}
bool ImageTransportSurfaceOverlayMacEGL::OnMakeCurrent(gl::GLContext* context) {
// Ensure that the context is on the appropriate GL renderer. The GL renderer
// will generally only change when the GPU changes.
if (gl_renderer_id_ && context)
context->share_group()->SetRendererID(gl_renderer_id_);
return true;
}
bool ImageTransportSurfaceOverlayMacEGL::ScheduleOverlayPlane(
gl::OverlayImage image,
std::unique_ptr<gfx::GpuFence> gpu_fence,
const gfx::OverlayPlaneData& overlay_plane_data) {
if (overlay_plane_data.plane_transform != gfx::OVERLAY_TRANSFORM_NONE) {
DLOG(ERROR) << "Invalid overlay plane transform.";
return false;
}
if (overlay_plane_data.z_order) {
DLOG(ERROR) << "Invalid non-zero Z order.";
return false;
}
// TODO(1290313): the display_bounds might not need to be rounded to the
// nearest rect as this eventually gets made into a CALayer. CALayers work in
// floats.
const ui::CARendererLayerParams overlay_as_calayer_params(
false, // is_clipped
gfx::Rect(), // clip_rect
gfx::RRectF(), // rounded_corner_bounds
0, // sorting_context_id
gfx::Transform(), image, overlay_plane_data.color_space,
overlay_plane_data.crop_rect, // contents_rect
gfx::ToNearestRect(overlay_plane_data.display_bounds), // rect
SkColors::kTransparent, // background_color
0, // edge_aa_mask
1.f, // opacity
GL_LINEAR, // filter
gfx::HDRMode::kDefault,
absl::nullopt, // hdr_metadata
gfx::ProtectedVideoType::kClear); // protected_video_type
return ca_layer_tree_coordinator_->GetPendingCARendererLayerTree()
->ScheduleCALayer(overlay_as_calayer_params);
}
bool ImageTransportSurfaceOverlayMacEGL::ScheduleCALayer(
const ui::CARendererLayerParams& params) {
return ca_layer_tree_coordinator_->GetPendingCARendererLayerTree()
->ScheduleCALayer(params);
}
bool ImageTransportSurfaceOverlayMacEGL::Resize(
const gfx::Size& pixel_size,
float scale_factor,
const gfx::ColorSpace& color_space,
bool has_alpha) {
pixel_size_ = pixel_size;
scale_factor_ = scale_factor;
ca_layer_tree_coordinator_->Resize(pixel_size, scale_factor);
return true;
}
void ImageTransportSurfaceOverlayMacEGL::OnGpuSwitched(
gl::GpuPreference active_gpu_heuristic) {
#if BUILDFLAG(IS_MAC)
// Create a new context, and use the GL renderer ID that the new context gets.
scoped_refptr<ui::IOSurfaceContext> context_on_new_gpu =
ui::IOSurfaceContext::Get(ui::IOSurfaceContext::kCALayerContext);
if (!context_on_new_gpu)
return;
GLint context_renderer_id = -1;
if (CGLGetParameter(context_on_new_gpu->cgl_context(),
kCGLCPCurrentRendererID,
&context_renderer_id) != kCGLNoError) {
LOG(ERROR) << "Failed to create test context after GPU switch";
return;
}
gl_renderer_id_ = context_renderer_id & kCGLRendererIDMatchingMask;
// Delay releasing the reference to the new GL context. The reason for this
// is to avoid creating-then-destroying the context for every image transport
// surface that is observing the GPU switch.
base::SingleThreadTaskRunner::GetCurrentDefault()->ReleaseSoon(
FROM_HERE, std::move(context_on_new_gpu));
#endif
}
void ImageTransportSurfaceOverlayMacEGL::SetCALayerErrorCode(
gfx::CALayerResult ca_layer_error_code) {
ca_layer_error_code_ = ca_layer_error_code;
}
void ImageTransportSurfaceOverlayMacEGL::SetVSyncDisplayID(int64_t display_id) {
}
bool ImageTransportSurfaceOverlayMacEGL::SupportsGpuVSync() const {
return features::UseGpuVsync();
}
void ImageTransportSurfaceOverlayMacEGL::SetGpuVSyncEnabled(bool enabled) {
if (gpu_vsync_enabled_ == enabled) {
return;
}
gpu_vsync_enabled_ = enabled;
}
} // namespace gpu