chromium/content/browser/android/selection/magnifier_surface_control.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/android/selection/magnifier_surface_control.h"

#include <algorithm>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "cc/layers/deadline_policy.h"
#include "cc/slim/frame_sink.h"
#include "cc/slim/solid_color_layer.h"
#include "cc/slim/surface_layer.h"
#include "components/viz/common/frame_timing_details_map.h"
#include "components/viz/common/surfaces/surface_range.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
#include "content/browser/renderer_host/compositor_dependencies_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/gpu_stream_constants.h"
#include "gpu/command_buffer/client/shared_memory_limits.h"
#include "gpu/ipc/common/gpu_surface_tracker.h"
#include "services/viz/privileged/mojom/compositing/display_private.mojom.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_manager.mojom.h"
#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/android/window_android.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/mask_filter_info.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gl/android/scoped_java_surface_control.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/MagnifierSurfaceControl_jni.h"

namespace content {

namespace {
// These values are passed to `gfx::LinearGradient::AddStep`. They are alpha
// values ranging from 0 to 255. These are the max and min alpha applied to
// opaque black that represent the darkest and lightest part of the shadow.
constexpr uint8_t kDarkestAlpha = 64;
constexpr uint8_t kLightestAlpha = 0;
}  // namespace

static jlong JNI_MagnifierSurfaceControl_Create(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& j_web_contents,
    const base::android::JavaParamRef<jobject>& j_surface_control,
    jfloat device_scale,
    jint width,
    jint height,
    jfloat corner_radius,
    float zoom,
    int top_shadow_height,
    int bottom_shadow_height,
    int bottom_shadow_width_reduction) {
  WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
      WebContents::FromJavaWebContents(j_web_contents));

  // Java MagnifierSurfaceControl calls release.
  bool release_on_destroy = false;
  gl::ScopedJavaSurfaceControl scoped_java_surface_control(j_surface_control,
                                                           release_on_destroy);
  gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
  gpu::SurfaceHandle surface_handle =
      tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord(
          std::move(scoped_java_surface_control)));

  return reinterpret_cast<jlong>(new MagnifierSurfaceControl(
      web_contents, surface_handle, device_scale, width, height, corner_radius,
      zoom, top_shadow_height, bottom_shadow_height,
      bottom_shadow_width_reduction));
}

static void JNI_MagnifierSurfaceControl_Destroy(
    JNIEnv* env,
    jlong magnifier_surface_control) {
  delete reinterpret_cast<MagnifierSurfaceControl*>(magnifier_surface_control);
}

MagnifierSurfaceControl::MagnifierSurfaceControl(
    WebContentsImpl* web_contents,
    gpu::SurfaceHandle surface_handle,
    float device_scale,
    int width,
    int height,
    float corner_radius,
    float zoom,
    int top_shadow_height,
    int bottom_shadow_height,
    int bottom_shadow_width_reduction)
    : HostDisplayClient(gfx::kNullAcceleratedWidget),
      web_contents_(web_contents),
      surface_handle_(surface_handle),
      frame_sink_id_(AllocateFrameSinkId()),
      surface_size_(width, height + top_shadow_height + bottom_shadow_height),
      root_layer_(cc::slim::Layer::Create()),
      rounded_corner_layer_(cc::slim::SolidColorLayer::Create()),
      zoom_layer_(cc::slim::Layer::Create()),
      surface_layer_(cc::slim::SurfaceLayer::Create()) {
  local_surface_id_allocator_.GenerateId();

  layer_tree_ = cc::slim::LayerTree::Create(this);
  layer_tree_->set_background_color(SkColors::kTransparent);
  layer_tree_->SetViewportRectAndScale(
      gfx::Rect(surface_size_), device_scale,
      local_surface_id_allocator_.GetCurrentLocalSurfaceId());

  GetHostFrameSinkManager()->RegisterFrameSinkId(
      frame_sink_id_, this, viz::ReportFirstSurfaceActivation::kNo);
  CreateDisplayAndFrameSink();
  surface_layer_->SetIsDrawable(true);
  root_layer_->SetBounds(surface_size_);

  // Shadows are solid black layers with linear gradient applied. They have the
  // same rounded corner as magnifier. And they are shifted slightly by
  // `top_shadow_height` and `bottom_shadow_height` so that they are visible.
  {
    auto top_shadow = cc::slim::SolidColorLayer::Create();
    // Layer height is calculated so that top and bottom shadows never overlap.
    // Otherwise use the corner radius since shadow not below rounded corner is
    // not visible.
    int layer_height = std::min(static_cast<int>(corner_radius + 0.5),
                                height / 2 + top_shadow_height);
    top_shadow->SetIsDrawable(true);
    top_shadow->SetBackgroundColor(SkColors::kBlack);
    top_shadow->SetBounds(gfx::Size(surface_size_.width(), layer_height));
    gfx::LinearGradient gradient;
    gradient.set_angle(90);
    gradient.AddStep(0.0f, kDarkestAlpha);
    gradient.AddStep(1.0f, kLightestAlpha);
    top_shadow->SetGradientMask(gradient);
    top_shadow->SetRoundedCorner(
        gfx::RoundedCornersF(corner_radius, corner_radius, 0, 0));
    root_layer_->AddChild(std::move(top_shadow));
  }

  {
    auto bottom_shadow = cc::slim::SolidColorLayer::Create();
    // See comment on layer height for top shadow.
    int layer_height = std::min(static_cast<int>(corner_radius + 0.5),
                                height / 2 + bottom_shadow_height);
    bottom_shadow->SetIsDrawable(true);
    bottom_shadow->SetBackgroundColor(SkColors::kBlack);
    // Inset layer horizontally by `bottom_shadow_width_reduction`.
    bottom_shadow->SetBounds(
        gfx::Size(surface_size_.width() - bottom_shadow_width_reduction * 2,
                  layer_height));
    // Place bottom shadow layer at the bottom of surface.
    bottom_shadow->SetPosition(gfx::PointF(
        bottom_shadow_width_reduction, surface_size_.height() - layer_height));
    gfx::LinearGradient gradient;
    gradient.set_angle(270);
    gradient.AddStep(0.0f, kDarkestAlpha);
    gradient.AddStep(1.0f, kLightestAlpha);
    bottom_shadow->SetGradientMask(gradient);
    bottom_shadow->SetRoundedCorner(
        gfx::RoundedCornersF(0, 0, corner_radius, corner_radius));
    root_layer_->AddChild(std::move(bottom_shadow));
  }

  rounded_corner_layer_->SetIsDrawable(true);
  rounded_corner_layer_->SetPosition(gfx::PointF(0, top_shadow_height));
  rounded_corner_layer_->SetBounds(gfx::Size(width, height));
  rounded_corner_layer_->SetRoundedCorner(gfx::RoundedCornersF(corner_radius));
  root_layer_->AddChild(rounded_corner_layer_);

  zoom_layer_->SetBounds(gfx::Size(width, height));
  zoom_layer_->SetTransformOrigin(gfx::PointF(width / 2.0f, height / 2.0f));
  zoom_layer_->SetTransform(gfx::Transform::MakeScale(zoom));

  layer_tree_->SetRoot(root_layer_);
  rounded_corner_layer_->AddChild(zoom_layer_);
  zoom_layer_->AddChild(surface_layer_);
}

MagnifierSurfaceControl::~MagnifierSurfaceControl() {
  display_private_.reset();
  if (frame_sink_id_.is_valid()) {
    GetHostFrameSinkManager()->InvalidateFrameSinkId(frame_sink_id_, this);
  }
  gpu::GpuSurfaceTracker::Get()->RemoveSurface(surface_handle_);
}

void MagnifierSurfaceControl::SetReadbackOrigin(JNIEnv* env,
                                                jfloat x,
                                                jfloat y) {
  if (readback_origin_x_ == x && readback_origin_y_ == y) {
    return;
  }
  readback_origin_x_ = x;
  readback_origin_y_ = y;
  UpdateLayers();
}

void MagnifierSurfaceControl::UpdateLayers() {
  RenderWidgetHostViewAndroid* rwhva =
      static_cast<RenderWidgetHostViewAndroid*>(
          web_contents_->GetRenderWidgetHostView());
  if (!rwhva) {
    return;
  }
  const cc::slim::SurfaceLayer* surface_layer = rwhva->GetSurfaceLayer();
  if (!surface_layer) {
    return;
  }

  std::optional<SkColor> background_color = rwhva->GetBackgroundColor();
  rounded_corner_layer_->SetBackgroundColor(
      background_color ? SkColor4f::FromColor(background_color.value())
                       : SkColors::kWhite);
  surface_layer_->SetBounds(surface_layer->bounds());
  surface_layer_->SetOldestAcceptableFallback(
      surface_layer->oldest_acceptable_fallback().value_or(viz::SurfaceId()));
  surface_layer_->SetSurfaceId(rwhva->GetCurrentSurfaceId(),
                               cc::DeadlinePolicy::UseExistingDeadline());

  surface_layer_->SetPosition(
      gfx::PointF(-readback_origin_x_, -readback_origin_y_));
}

void MagnifierSurfaceControl::ChildLocalSurfaceIdChanged(JNIEnv* env) {
  UpdateLayers();
}

void MagnifierSurfaceControl::CreateDisplayAndFrameSink() {
  ui::WindowAndroid* window_android = web_contents_->GetTopLevelNativeWindow();
  if (!window_android) {
    return;
  }

  scoped_refptr<gpu::GpuChannelHost> gpu_channel_host =
      BrowserGpuChannelHostFactory::instance()->GetGpuChannel();
  if (!gpu_channel_host) {
    return;
  }

  CompositorDependenciesAndroid::Get().TryEstablishVizConnectionIfNeeded();

  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      GetUIThreadTaskRunner({BrowserTaskType::kUserInput});

  auto root_params = viz::mojom::RootCompositorFrameSinkParams::New();

  // Create interfaces for a root CompositorFrameSink.
  mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink> sink_remote;
  root_params->compositor_frame_sink =
      sink_remote.InitWithNewEndpointAndPassReceiver();
  mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient> client_receiver =
      root_params->compositor_frame_sink_client
          .InitWithNewPipeAndPassReceiver();
  display_private_.reset();
  root_params->display_private =
      display_private_.BindNewEndpointAndPassReceiver();

  root_params->display_client = GetBoundRemote(task_runner);

  gfx::DisplayColorSpaces display_color_spaces =
      display::Screen::GetScreen()
          ->GetDisplayNearestWindow(window_android)
          .GetColorSpaces();

  viz::RendererSettings renderer_settings;
  renderer_settings.partial_swap_enabled = true;
  renderer_settings.allow_antialiasing = false;
  renderer_settings.highp_threshold_min = 2048;
  renderer_settings.requires_alpha_channel = true;
  renderer_settings.initial_screen_size = surface_size_;
  renderer_settings.color_space = display_color_spaces.GetOutputColorSpace(
      gfx::ContentColorUsage::kHDR, renderer_settings.requires_alpha_channel);

  root_params->frame_sink_id = frame_sink_id_;
  root_params->widget = surface_handle_;
  root_params->gpu_compositing = true;
  root_params->renderer_settings = renderer_settings;
  root_params->refresh_rate = window_android->GetRefreshRate();

  GetHostFrameSinkManager()->CreateRootCompositorFrameSink(
      std::move(root_params));

  display_private_->SetDisplayVisible(true);
  display_private_->Resize(surface_size_);
  display_private_->SetDisplayColorSpaces(display_color_spaces);
  display_private_->SetSupportedRefreshRates(
      window_android->GetSupportedRefreshRates());

  layer_tree_->SetFrameSink(cc::slim::FrameSink::Create(
      std::move(sink_remote), std::move(client_receiver), nullptr,
      GetUIThreadTaskRunner({BrowserTaskType::kUserInput}), nullptr,
      base::kInvalidThreadId));
  layer_tree_->SetVisible(true);
}

}  // namespace content