chromium/components/viz/service/display_embedder/skia_output_device_dcomp.cc

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

#include "components/viz/service/display_embedder/skia_output_device_dcomp.h"

#include <memory>
#include <tuple>
#include <utility>

#include "base/debug/alias.h"
#include "base/memory/scoped_refptr.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gl/dc_layer_overlay_image.h"
#include "ui/gl/dc_layer_overlay_params.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gl_utils.h"

namespace viz {

namespace {
base::TimeTicks g_last_reshape_failure = base::TimeTicks();

NOINLINE void CheckForLoopFailures() {
  const auto threshold = base::Seconds(1);
  auto now = base::TimeTicks::Now();
  if (!g_last_reshape_failure.is_null() &&
      now - g_last_reshape_failure < threshold) {
    CHECK(false);
  }
  g_last_reshape_failure = now;
}

}  // namespace

// Holds reference needed to keep overlay textures alive.
// TODO(kylechar): We can probably merge OverlayData in with
// SkiaOutputSurfaceImplOnGpu overlay data.
class SkiaOutputDeviceDComp::OverlayData {
 public:
  explicit OverlayData(
      std::unique_ptr<gpu::OverlayImageRepresentation> representation)
      : representation_(std::move(representation)) {}

  ~OverlayData() = default;
  OverlayData(OverlayData&& other) = default;
  OverlayData& operator=(OverlayData&& other) {
    // `access_` must be overwritten before `representation_`.
    access_ = std::move(other.access_);
    representation_ = std::move(other.representation_);
    return *this;
  }

  std::optional<gl::DCLayerOverlayImage> BeginOverlayAccess() {
    CHECK(representation_);
    if (!access_) {
      access_ = representation_->BeginScopedReadAccess();
      if (!access_) {
        return std::nullopt;
      }
    }
    return access_->GetDCLayerOverlayImage();
  }

  std::optional<gl::DCLayerOverlayImage> GetOverlayAccess() {
    CHECK(representation_);
    if (!access_) {
      return std::nullopt;
    }

    return access_->GetDCLayerOverlayImage();
  }

  void EndOverlayAccess() { access_.reset(); }

 private:
  std::unique_ptr<gpu::OverlayImageRepresentation> representation_;
  std::unique_ptr<gpu::OverlayImageRepresentation::ScopedReadAccess> access_;
};

SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
    gpu::SharedImageRepresentationFactory* shared_image_representation_factory,
    gpu::SharedContextState* context_state,
    scoped_refptr<gl::Presenter> presenter,
    scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
    gpu::MemoryTracker* memory_tracker,
    DidSwapBufferCompleteCallback did_swap_buffer_complete_callback)
    : SkiaOutputDevice(context_state->gr_context(),
                       context_state->graphite_context(),
                       memory_tracker,
                       std::move(did_swap_buffer_complete_callback)),
      shared_image_representation_factory_(shared_image_representation_factory),
      context_state_(context_state),
      presenter_(std::move(presenter)) {
  DCHECK(!feature_info->workarounds()
              .disable_post_sub_buffers_for_onscreen_surfaces);
  capabilities_.uses_default_gl_framebuffer = true;
  capabilities_.output_surface_origin = gfx::SurfaceOrigin::kTopLeft;
  capabilities_.number_of_buffers =
      gl::DirectCompositionRootSurfaceBufferCount();
  if (feature_info->workarounds().supports_two_yuv_hardware_overlays) {
    capabilities_.allowed_yuv_overlay_count = 2;
  }
  if (base::FeatureList::IsEnabled(
          features::kDirectCompositionUnlimitedOverlays)) {
    capabilities_.allowed_yuv_overlay_count = INT_MAX;
  }
  capabilities_.dc_support_level =
      gl::DirectCompositionTextureSupported()
          ? OutputSurface::DCSupportLevel::kDCompTexture
          : OutputSurface::DCSupportLevel::kDCLayers;
  capabilities_.supports_post_sub_buffer = true;
  capabilities_.supports_delegated_ink = presenter_->SupportsDelegatedInk();
  capabilities_.pending_swap_params.max_pending_swaps = 1;
  capabilities_.renderer_allocates_images = true;
  capabilities_.supports_viewporter = presenter_->SupportsViewporter();
  capabilities_.supports_non_backed_solid_color_overlays = true;

  DCHECK(context_state_);
  DCHECK(context_state_->gr_context() || context_state_->graphite_context());
  DCHECK(context_state_->context());
  DCHECK(presenter_);

  // SRGB
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_8888] =
      kRGBA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBX_8888] =
      kRGBA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRA_8888] =
      kRGBA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRX_8888] =
      kRGBA_8888_SkColorType;
  // HDR10
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_1010102] =
      kRGBA_1010102_SkColorType;
  // scRGB linear
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_F16] =
      kRGBA_F16_SkColorType;
}

SkiaOutputDeviceDComp::~SkiaOutputDeviceDComp() {
  DCHECK(presenter_->HasOneRef());
}

void SkiaOutputDeviceDComp::Present(const std::optional<gfx::Rect>& update_rect,
                                    BufferPresentedCallback feedback,
                                    OutputSurfaceFrame frame) {
  StartSwapBuffers({});

  // The |update_rect| is ignored because the SharedImage backing already
  // knows the area to be swapped.
  presenter_->Present(
      base::BindOnce(&SkiaOutputDeviceDComp::OnPresentFinished,
                     weak_ptr_factory_.GetWeakPtr(), std::move(frame), size_),
      std::move(feedback), frame.data);
}

void SkiaOutputDeviceDComp::OnPresentFinished(
    OutputSurfaceFrame frame,
    const gfx::Size& swap_size,
    gfx::SwapCompletionResult result) {
  // Remove entries from |overlays_| for textures that weren't scheduled as an
  // overlay this frame.
  if (!overlays_.empty()) {
    base::EraseIf(overlays_, [this](auto& entry) {
      const gpu::Mailbox& mailbox = entry.first;
      return !scheduled_overlay_mailboxes_.contains(mailbox);
    });
    scheduled_overlay_mailboxes_.clear();
    for (auto& [mailbox, overlay_data] : overlays_) {
      if (auto overlay_image = overlay_data.GetOverlayAccess()) {
        if (overlay_image->type() ==
            gl::DCLayerOverlayType::kDCompVisualContent) {
          Microsoft::WRL::ComPtr<IDCompositionTexture> dcomp_texture;
          if (SUCCEEDED(Microsoft::WRL::ComPtr<IUnknown>(
                            overlay_image->dcomp_visual_content())
                            .As(&dcomp_texture))) {
            // We don't want to end the read access for DComp textures since DWM
            // can read from them for potentially multiple frames.
            continue;
          }
        }

        // The remaining overlays are either DComp surfaces (which do not
        // require special synchronization) or handled by |SwapChainPresenter|
        // (which copies the image into an internal swap chain, rather than
        // having DWM read it directly).
        overlay_data.EndOverlayAccess();
      }
    }
  }

  FinishSwapBuffers(std::move(result), swap_size, std::move(frame));
}

void SkiaOutputDeviceDComp::ScheduleOverlays(
    SkiaOutputSurface::OverlayList overlays) {
  for (auto& dc_layer : overlays) {
    // This is not necessarily an error, DCLayerTree can succeed with any
    // combination of overlay image and background color. However, it's wasteful
    // to have an overlay with no image or background color.
    if (dc_layer.mailbox.IsZero() && !dc_layer.color.has_value()) {
      // This can happen when |PrepareRenderPassOverlay| encounters a bypass
      // quad that is skipped.
      continue;
    }

    if (dc_layer.is_solid_color) {
      CHECK(dc_layer.color.has_value());
      CHECK(dc_layer.mailbox.IsZero());
    }

    auto params = std::make_unique<gl::DCLayerOverlayParams>();

    const gpu::Mailbox& mailbox = dc_layer.mailbox;
    if (!mailbox.IsZero()) {
      std::optional<gl::DCLayerOverlayImage> overlay_image =
          BeginOverlayAccess(mailbox);
      if (!overlay_image) {
        DLOG(ERROR) << "Failed to ProduceOverlay or GetDCLayerOverlayImage";
        continue;
      }
      params->overlay_image = std::move(overlay_image);
    }

    params->background_color = dc_layer.color;
    params->z_order = dc_layer.plane_z_order;

    // SwapChainPresenter uses the size of the overlay's resource in pixels to
    // calculate its swap chain size. `uv_rect` maps the portion of
    // `resource_size_in_pixels` that will be displayed.
    params->content_rect = gfx::ScaleRect(
        dc_layer.uv_rect, dc_layer.resource_size_in_pixels.width(),
        dc_layer.resource_size_in_pixels.height());

    params->quad_rect = gfx::ToRoundedRect(dc_layer.display_rect);
    CHECK(absl::holds_alternative<gfx::Transform>(dc_layer.transform));
    params->transform = absl::get<gfx::Transform>(dc_layer.transform);
    params->clip_rect = dc_layer.clip_rect;
    params->opacity = dc_layer.opacity;
    params->rounded_corner_bounds = dc_layer.rounded_corners;
    params->nearest_neighbor_filter = dc_layer.nearest_neighbor_filter;
    params->aggregated_layer_id = dc_layer.aggregated_layer_id;

    params->video_params.protected_video_type = dc_layer.protected_video_type;
    params->video_params.color_space = dc_layer.color_space;
    params->video_params.hdr_metadata = dc_layer.hdr_metadata;
    params->video_params.possible_video_fullscreen_letterboxing =
        dc_layer.possible_video_fullscreen_letterboxing;

    // Schedule DC layer overlay to be presented at next SwapBuffers().
    presenter_->ScheduleDCLayer(std::move(params));
    scheduled_overlay_mailboxes_.insert(mailbox);
  }
}

std::optional<gl::DCLayerOverlayImage>
SkiaOutputDeviceDComp::BeginOverlayAccess(const gpu::Mailbox& mailbox) {
  auto it = overlays_.find(mailbox);
  if (it != overlays_.end())
    return it->second.BeginOverlayAccess();

  auto overlay = shared_image_representation_factory_->ProduceOverlay(mailbox);
  if (!overlay)
    return std::nullopt;

  TRACE_EVENT2("gpu", "SkiaOutputDeviceDComp::BeginOverlayAccess",
               "debug_label", overlay->debug_label(), "image_size",
               overlay->size().ToString());

  std::tie(it, std::ignore) = overlays_.emplace(mailbox, std::move(overlay));
  return it->second.BeginOverlayAccess();
}

bool SkiaOutputDeviceDComp::Reshape(const ReshapeParams& params) {
  DCHECK_EQ(params.transform, gfx::OVERLAY_TRANSFORM_NONE);

  auto size = params.GfxSize();

  // DCompPresenter calls SetWindowPos on resize, so we call it to reflect the
  // newly allocated root surface.
  // Note, we could inline SetWindowPos here, but we need access to the HWND.
  if (!presenter_->Resize(size, params.device_scale_factor, params.color_space,
                          /*has_alpha=*/!params.image_info.isOpaque())) {
    CheckForLoopFailures();
    // To prevent tail call, so we can see the stack.
    base::debug::Alias(nullptr);
    return false;
  }

  size_ = size;

  return true;
}

SkSurface* SkiaOutputDeviceDComp::BeginPaint(
    std::vector<GrBackendSemaphore>* end_semaphores) {
  NOTIMPLEMENTED();
  return nullptr;
}

void SkiaOutputDeviceDComp::EndPaint() {
  NOTIMPLEMENTED();
}

}  // namespace viz