chromium/ui/gl/delegated_ink_point_renderer_gpu.h

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

#ifndef UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_
#define UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_

#include <dcomp.h>
#include <wrl/client.h>

#include <optional>
#include <vector>

#include "base/check_is_test.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "ui/gfx/delegated_ink_metadata.h"
#include "ui/gfx/delegated_ink_point.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/mojom/delegated_ink_point_renderer.mojom.h"
#include "ui/gl/dc_layer_overlay_params.h"
#include "ui/gl/gl_export.h"

namespace gl {

namespace {
struct DelegatedInkPointCompare {
  bool operator()(const gfx::DelegatedInkPoint& lhs,
                  const gfx::DelegatedInkPoint& rhs) const {
    return lhs.timestamp() < rhs.timestamp();
  }
};
}  // namespace

// On construction, this class will create a new visual for the visual tree with
// an IDCompositionDelegatedInk object as the contents. This will be added as
// a child of the root surface visual in the tree, and the trail will be drawn
// to it. It is a child of the root surface visual because this visual contains
// the swapchain, and there will be no transforms applied to the delegated ink
// visual this way.
// For more information about the design of this class and using the OS APIs,
// view the design doc here: https://aka.ms/GPUBackedDesignDoc
class GL_EXPORT DelegatedInkPointRendererGpu
    : public gfx::mojom::DelegatedInkPointRenderer {
 public:
  DelegatedInkPointRendererGpu();
  ~DelegatedInkPointRendererGpu() override;

  using DelegatedInkPointTokenMap = base::flat_map<gfx::DelegatedInkPoint,
                                                   std::optional<unsigned int>,
                                                   DelegatedInkPointCompare>;

  void InitMessagePipeline(
      mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
          pending_receiver);

  bool DelegatedInkIsSupported(
      const Microsoft::WRL::ComPtr<IDCompositionDevice2>& dcomp_device) const;

  void SetDelegatedInkTrailStartPoint(
      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);

  void StoreDelegatedInkPoint(const gfx::DelegatedInkPoint& point) override;

  void ResetPrediction() override;

  gfx::DelegatedInkMetadata* MetadataForTesting() const {
    CHECK_IS_TEST();
    return metadata_.get();
  }

  // Creates an overlay for the current frame's Delegated Ink Metadata.
  // If the Delegated Ink Renderer can not be initialized, it returns a
  // nullptr. The ink trail will synchronize its updates with |root_swap_chain|
  // if present, otherwise it will synchronize with DComp commit.
  std::unique_ptr<DCLayerOverlayParams> MakeDelegatedInkOverlay(
      IDCompositionDevice2* dcomp_device2,
      IDXGISwapChain1* root_swap_chain,
      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);

  uint64_t InkTrailTokenCountForTesting() const;

  uint64_t DelegatedInkPointPointerIdCountForTesting() const {
    CHECK_IS_TEST();
    return delegated_ink_points_.size();
  }

  bool CheckForPointerIdForTesting(int32_t pointer_id) const;

  const DelegatedInkPointTokenMap& DelegatedInkPointsForTesting(
      int32_t pointer_id) {
    CHECK_IS_TEST();
    DCHECK(delegated_ink_points_.find(pointer_id) !=
           delegated_ink_points_.end());
    return delegated_ink_points_[pointer_id];
  }

  bool WaitForNewTrailToDrawForTesting() const {
    CHECK_IS_TEST();
    return wait_for_new_trail_to_draw_;
  }

  std::vector<gfx::DelegatedInkPoint> PointstoBeDrawnForTesting() const {
    CHECK_IS_TEST();
    return points_to_be_drawn_;
  }

  uint64_t GetMaximumNumberOfPointerIdsForTesting() const;

  void InitializeForTesting(IDCompositionDevice2* dcomp_device2);

  // This function is called after Delegated Ink's points are submitted to be
  // drawn on screen, and fires a histogram with the time between points' event
  // creation and the points' draw submission to the OS.
  void ReportPointsDrawn();

 private:
  bool Initialize(IDCompositionDevice2* dcomp_device2,
                  IDXGISwapChain1* root_swap_chain);

  void EraseExcessPointerIds();

  std::optional<int32_t> GetPointerIdForMetadata();

  void DrawSavedTrailPoints();

  bool DrawDelegatedInkPoint(const gfx::DelegatedInkPoint& point);

  // The delegated ink trail object that the ink trail is drawn on. This is the
  // content of the ink visual.
  Microsoft::WRL::ComPtr<IDCompositionDelegatedInkTrail> delegated_ink_trail_;

  // The swap chain whose updates |delegated_ink_trail_| is synchronized with.
  // If null, |delegated_ink_trail_| is synchronized with DComp commit.
  raw_ptr<IDXGISwapChain1> swap_chain_ = nullptr;

  Microsoft::WRL::ComPtr<IDCompositionInkTrailDevice> ink_trail_device_;

  // The most recent metadata received. The metadata marks the last point of
  // the app rendered stroke, which corresponds to the first point of the
  // delegated ink trail that will be drawn.
  std::unique_ptr<gfx::DelegatedInkMetadata> metadata_;

  // A base::flat_map of all the points that have arrived in
  // StoreDelegatedInkPoint() with a timestamp greater than or equal to that of
  // |metadata_|, where the key is the DelegatedInkPoint that was received, and
  // the value is an optional token. The value will be null until the
  // DelegatedInkPoint is added to the trail, at which point the value is the
  // token that was returned by AddTrailPoints. The elements of the flat_map are
  // sorted by the timestamp of the DelegatedInkPoint. All points are stored in
  // here until we receive a |metadata_|, then any DelegatedInkPoints that have
  // a timestamp earlier than |metadata_|'s are removed, and any new
  // DelegatedInkPoints that may arrive with an earlier timestamp are ignored.
  // Then as each new |metadata| arrives in SetDelegatedInkTrailStartPoint(),
  // we remove any old elements with earlier timestamps, up to and including the
  // element that matches the DelegatedInkMetadata.
  base::flat_map<int32_t, DelegatedInkPointTokenMap> delegated_ink_points_;

  // Cached pointer id of the most recently drawn trail.
  std::optional<int32_t> pointer_id_;

  // Flag to know if new DelegatedInkPoints that arrive should be drawn
  // immediately or if they should wait for a new trail to be started. Set to
  // true when ResetPrediction is called, as this tells us that something about
  // the pointerevents in the browser process changed. When that happens, we
  // should continue to remove points for any metadata that arrive with
  // timestamps that match DelegatedInkPoints that arrived before the
  // ResetPrediction call. However, once a metadata arrives with a timestamp
  // after the ResetPrediction, then we know that inking should continue, so a
  // new trail is started and we try to draw everything in
  // |delegated_ink_points_|.
  bool wait_for_new_trail_to_draw_ = true;

  // Set to true when DelegatedInkPointRendererGPU has been (re)initialized.
  // This can happen due to the first metadata being received, or change in
  // root surface swap chain or DirectCompositionDevice.
  bool force_new_ink_trail_ = false;

  mojo::Receiver<gfx::mojom::DelegatedInkPointRenderer> receiver_{this};

  // Holds the timestamp of points added to the Delegated Ink's Trail to measure
  // the time between point creation and draw operation called.
  std::vector<gfx::DelegatedInkPoint> points_to_be_drawn_;

  // The timestamp in which a Delegated Ink point that matches the metadata was
  // first painted.
  std::optional<base::TimeTicks> metadata_paint_time_;
};

}  // namespace gl

#endif  // UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_