chromium/ui/gl/dc_layer_tree.h

// 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.

#ifndef UI_GL_DC_LAYER_TREE_H_
#define UI_GL_DC_LAYER_TREE_H_

#include <windows.h>

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

#include <memory>

#include "base/check_is_test.h"
#include "base/containers/flat_map.h"
#include "base/moving_window.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/dc_layer_overlay_params.h"
#include "ui/gl/delegated_ink_point_renderer_gpu.h"
#include "ui/gl/gl_export.h"
#include "ui/gl/hdr_metadata_helper_win.h"

namespace gfx {
namespace mojom {
class DelegatedInkPointRenderer;
}  // namespace mojom
class DelegatedInkMetadata;
}  // namespace gfx

namespace gl {

class SwapChainPresenter;

// Cache video processor and its size.
struct VideoProcessorWrapper {
  VideoProcessorWrapper();
  ~VideoProcessorWrapper();
  VideoProcessorWrapper(VideoProcessorWrapper&& other) = delete;
  VideoProcessorWrapper& operator=(VideoProcessorWrapper&& other) = delete;
  VideoProcessorWrapper(const VideoProcessorWrapper&) = delete;
  VideoProcessorWrapper& operator=(VideoProcessorWrapper& other) = delete;

  class SizeSmoother {
   public:
    SizeSmoother();
    ~SizeSmoother();
    SizeSmoother(SizeSmoother&& other) = delete;
    SizeSmoother& operator=(SizeSmoother&& other) = delete;
    SizeSmoother(const SizeSmoother& other) = delete;
    SizeSmoother& operator=(SizeSmoother& other) = delete;
    void PutSize(gfx::Size size);
    gfx::Size GetSize() const;

   private:
    base::MovingMax<int> width_;
    base::MovingMax<int> height_;
  };

  // Input and output size of video processor.
  gfx::Size video_input_size;
  gfx::Size video_output_size;

  // Max window filter for each dimension for input and output size.
  // Used to calculate required size in case there are many different
  // sizes in use.
  SizeSmoother input_size_smoother;
  SizeSmoother output_size_smoother;

  bool GetDriverSupportsVpAutoHdr() { return driver_supports_vp_auto_hdr; }
  void SetDriverSupportsVpAutoHdr(bool value) {
    driver_supports_vp_auto_hdr = value;
  }

  // The video processor is cached so SwapChains don't have to recreate it
  // whenever they're created.
  Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device;
  Microsoft::WRL::ComPtr<ID3D11VideoContext> video_context;
  Microsoft::WRL::ComPtr<ID3D11VideoProcessor> video_processor;
  Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>
      video_processor_enumerator;

 private:
  // Whether the GPU driver supports video processor auto HDR.
  bool driver_supports_vp_auto_hdr = false;
};

class SolidColorSurface;

// A resource pool that contains DComp surfaces containing solid color fills.
class SolidColorSurfacePool final {
 public:
  SolidColorSurfacePool(
      Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
      Microsoft::WRL::ComPtr<IDCompositionDevice3> dcomp_device);
  ~SolidColorSurfacePool();

  SolidColorSurfacePool(const SolidColorSurfacePool&) = delete;
  SolidColorSurfacePool& operator=(const SolidColorSurfacePool&) = delete;

  // The resulting surface only contains the opaque parts of |color| and needs
  // to be scaled by |color.fA|. Its contents are only valid until the next
  // |TrimAfterCommit| call, since surfaces can be reused (and recolored) on
  // subsequent frames.
  IDCompositionSurface* GetSolidColorSurface(const SkColor4f& color);

  // Clean up any unused resources in the pool after DComp commit.
  void TrimAfterCommit();

  // Returns the number of surfaces currently tracked by this pool.
  size_t GetNumSurfacesInPoolForTesting() const;

 private:
  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
  Microsoft::WRL::ComPtr<IDCompositionDevice3> dcomp_device_;

  // Solid color surfaces that are tracked by this pool.
  std::vector<SolidColorSurface> tracked_surfaces_;
  // Index into |tracked_surfaces_| that partitions the surfaces used this frame
  // (<num_used_this_frame_) and the surfaces free to use by subsequent
  // |GetSolidColorSurface| calls (>=num_used_this_frame_).
  size_t num_used_this_frame_ = 0;

  struct Stats {
    // The number of times |GetSolidColorSurface| was called. This represents
    // the number of solid color overlays in the frame.
    int num_surfaces_requested = 0;

    // The number of surfaces that were filled.
    int num_surfaces_recolored = 0;
  };

  // Stats about this pool since the last |TrimAfterCommit| call.
  Stats stats_since_last_trim_;
};

// DCLayerTree manages a tree of direct composition visuals, and associated
// swap chains for given overlay layers.
class GL_EXPORT DCLayerTree {
 public:
  using DelegatedInkRenderer = DelegatedInkPointRendererGpu;

  DCLayerTree(bool disable_nv12_dynamic_textures,
              bool disable_vp_auto_hdr,
              bool disable_vp_scaling,
              bool disable_vp_super_resolution,
              bool force_dcomp_triple_buffer_video_swap_chain,
              bool no_downscaled_overlay_promotion);

  DCLayerTree(const DCLayerTree&) = delete;
  DCLayerTree& operator=(const DCLayerTree&) = delete;

  ~DCLayerTree();

  void Initialize(HWND window,
                  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device);

  // Present overlay layers, and perform a direct composition commit if
  // necessary. Returns true if presentation and commit succeeded.
  bool CommitAndClearPendingOverlays(
      std::vector<std::unique_ptr<DCLayerOverlayParams>> overlays);

  // Called by SwapChainPresenter to initialize video processor that can handle
  // at least given input and output size.  The video processor is shared across
  // layers so the same one can be reused if it's large enough.  Returns true on
  // success.
  VideoProcessorWrapper* InitializeVideoProcessor(
      const gfx::Size& input_size,
      const gfx::Size& output_size,
      bool is_hdr_output,
      bool& video_processor_recreated);

  bool disable_nv12_dynamic_textures() const {
    return disable_nv12_dynamic_textures_;
  }

  bool disable_vp_auto_hdr() const { return disable_vp_auto_hdr_; }

  bool disable_vp_scaling() const { return disable_vp_scaling_; }

  bool disable_vp_super_resolution() const {
    return disable_vp_super_resolution_;
  }

  bool force_dcomp_triple_buffer_video_swap_chain() const {
    return force_dcomp_triple_buffer_video_swap_chain_;
  }

  bool no_downscaled_overlay_promotion() const {
    return no_downscaled_overlay_promotion_;
  }

  Microsoft::WRL::ComPtr<IDXGISwapChain1> GetLayerSwapChainForTesting(
      size_t index) const;

  void GetSwapChainVisualInfoForTesting(size_t index,
                                        gfx::Transform* transform,
                                        gfx::Point* offset,
                                        gfx::Rect* clip_rect) const;

  size_t GetSwapChainPresenterCountForTesting() const {
    CHECK_IS_TEST();
    return video_swap_chains_.size();
  }

  size_t GetDcompLayerCountForTesting() const {
    CHECK_IS_TEST();
    return visual_tree_ ? visual_tree_->GetDcompLayerCountForTesting() : 0;
  }
  IDCompositionVisual2* GetContentVisualForTesting(size_t index) const {
    CHECK_IS_TEST();
    return visual_tree_ ? visual_tree_->GetContentVisualForTesting(index)
                        : nullptr;
  }
  IDCompositionSurface* GetBackgroundColorSurfaceForTesting(
      size_t index) const {
    CHECK_IS_TEST();
    return visual_tree_
               ? visual_tree_->GetBackgroundColorSurfaceForTesting(index)
               : nullptr;
  }
  size_t GetNumSurfacesInPoolForTesting() const;
#if DCHECK_IS_ON()
  bool GetAttachedToRootFromPreviousFrameForTesting(size_t index) const;
#endif  // DCHECK_IS_ON()

  void SetFrameRate(float frame_rate);

  const std::unique_ptr<HDRMetadataHelperWin>& GetHDRMetadataHelper() {
    return hdr_metadata_helper_;
  }

  HWND window() const { return window_; }

  bool SupportsDelegatedInk();

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

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

  DelegatedInkRenderer* GetInkRendererForTesting() const {
    CHECK_IS_TEST();
    return ink_renderer_.get();
  }

  // Owns a list of |VisualSubtree|s that represent visual layers.
  class VisualTree {
   public:
    VisualTree(DCLayerTree* tree);

    VisualTree(VisualTree&&) = delete;
    VisualTree(const VisualTree&) = delete;
    VisualTree& operator=(const VisualTree&) = delete;

    ~VisualTree();
    // Given overlays, builds or updates this visual tree.
    // Returns true if commit succeeded.
    bool BuildTree(
        const std::vector<std::unique_ptr<DCLayerOverlayParams>>& overlays);

    void GetSwapChainVisualInfoForTesting(size_t index,
                                          gfx::Transform* transform,
                                          gfx::Point* offset,
                                          gfx::Rect* clip_rect) const;
    size_t GetDcompLayerCountForTesting() const {
      CHECK_IS_TEST();
      return visual_subtrees_.size();
    }
    IDCompositionVisual2* GetContentVisualForTesting(size_t index) const {
      CHECK_IS_TEST();
      return visual_subtrees_[index]->content_visual();
    }
    IDCompositionSurface* GetBackgroundColorSurfaceForTesting(
        size_t index) const {
      CHECK_IS_TEST();
      return visual_subtrees_[index]->background_color_surface_for_testing();
    }
#if DCHECK_IS_ON()
    bool GetAttachedToRootFromPreviousFrameForTesting(size_t index) const {
      CHECK_IS_TEST();
      return visual_subtrees_[index]
          ->GetAttachedToRootFromPreviousFrameForTesting();
    }
#endif  // DCHECK_IS_ON()
    // Maps the visual content to its corresponding subtree index.
    // This is used to find matching subtrees from the previous frame
    // that can be reused in the current frame.
    // It's safe to use raw pointers here since we have a ComPtr to the visual
    // content in the visual subtrees list for the previous frame.
    using VisualSubtreeMap = base::flat_map<raw_ptr<IUnknown>, size_t>;
    // Owns a subtree of DComp visual that apply clip, offset, etc. and contains
    // some content at its leaf.
    // This class keeps track about what properties are currently set on the
    // visuals.
    class VisualSubtree {
     public:
      VisualSubtree();
      ~VisualSubtree();
      VisualSubtree(VisualSubtree&& other) = delete;
      VisualSubtree& operator=(VisualSubtree&& other) = delete;
      VisualSubtree(const VisualSubtree&) = delete;
      VisualSubtree& operator=(VisualSubtree& other) = delete;

      // Returns true if something was changed.
      bool Update(
          IDCompositionDevice3* dcomp_device,
          Microsoft::WRL::ComPtr<IUnknown> dcomp_visual_content,
          uint64_t dcomp_surface_serial,
          const gfx::Size& image_size,
          const gfx::RectF& content_rect,
          Microsoft::WRL::ComPtr<IDCompositionSurface> background_color_surface,
          const SkColor4f& background_color,
          const gfx::Rect& quad_rect,
          bool nearest_neighbor_filter,
          const gfx::Transform& quad_to_root_transform,
          const gfx::RRectF& rounded_corner_bounds,
          float opacity,
          const std::optional<gfx::Rect>& clip_rect_in_root,
          bool allow_antialiasing);

      IDCompositionVisual2* container_visual() const {
        return clip_visual_.Get();
      }
      IDCompositionVisual2* content_visual() const {
        return content_visual_.Get();
      }
      IUnknown* dcomp_visual_content() const {
        return dcomp_visual_content_.Get();
      }
      IDCompositionSurface* background_color_surface_for_testing() const {
        CHECK_IS_TEST();
        return background_color_surface_.Get();
      }
      void GetSwapChainVisualInfoForTesting(gfx::Transform* transform,
                                            gfx::Point* offset,
                                            gfx::Rect* clip_rect) const;
#if DCHECK_IS_ON()
      bool GetAttachedToRootFromPreviousFrameForTesting() const {
        CHECK_IS_TEST();
        return attached_to_root_from_previous_frame_;
      }
#endif  // DCHECK_IS_ON()

      int z_order() const { return z_order_; }
      void set_z_order(int z_order) { z_order_ = z_order; }

     private:
#if DCHECK_IS_ON()
      friend class VisualTree;
#endif  // DCHECK_IS_ON()
      // The root of this subtree. In root space and contains the clip rect and
      // controls subtree opacity.
      Microsoft::WRL::ComPtr<IDCompositionVisual2> clip_visual_;
      // In root space and contains the rounded rectangle clip. This is separate
      // from |clip_visual_| since an overlay layer can have both a rectangular
      // and a rounded rectangular clip rects.
      Microsoft::WRL::ComPtr<IDCompositionVisual2> rounded_corners_visual_;
      // The child of |clip_visual_|, transforms its children from quad to root
      // space. This visual exists because |offset_| is in quad space, so it
      // must be affected by |transform_|. They cannot be on the same visual
      // since |IDCompositionVisual::SetTransform| and
      // |IDCompositionVisual::SetOffset[XY]| are applied in the opposite order
      // than we want.
      Microsoft::WRL::ComPtr<IDCompositionVisual2> transform_visual_;
      // A child of |transform_visual_|. In quad space, holds
      // |dcomp_visual_content_|. Visually, this is behind |content_visual_|.
      Microsoft::WRL::ComPtr<IDCompositionVisual2> background_color_visual_;
      // A child of |transform_visual_|. In quad space, holds
      // |dcomp_visual_content_|.
      Microsoft::WRL::ComPtr<IDCompositionVisual2> content_visual_;

      // The content to be placed at a leaf of the visual subtree. Either an
      // IDCompositionSurface or an IDXGISwapChain.
      Microsoft::WRL::ComPtr<IUnknown> dcomp_visual_content_;
      // |dcomp_surface_serial_| is associated with |dcomp_visual_content_| of
      // IDCompositionSurface type. New value indicates that dcomp surface data
      // is updated.
      uint64_t dcomp_surface_serial_ = 0;

      // The portion of |dcomp_visual_content_| to display. This area will be
      // mapped to |quad_rect_|'s bounds.
      gfx::RectF content_rect_;

      // The surface for the background color fill to be placed at a leaf of the
      // visual subtree. Since |SolidColorSurfacePool::GetSolidColorSurface|
      // returns a surface that is opaque, |background_color_visual_|'s opacity
      // will be set to |background_color_.fA|. Must be present if
      // |background_color_| is non-transparent. Must be re-updated from
      // |SolidColorSurfacePool::GetSolidColorSurface| every frame it is
      // present.
      Microsoft::WRL::ComPtr<IDCompositionSurface> background_color_surface_;

      // The color of |background_color_surface_|.
      SkColor4f background_color_;

      // The bounds which contain this overlay. When mapped by |transform_|,
      // this is the bounds of the overlay in root space.
      gfx::Rect quad_rect_;

      // Whether or not to use nearest-neighbor filtering to scale
      // |dcomp_visual_content_|. This is applied to |transform_visual_| since
      // both it and |content_visual_| can scale the content.
      bool nearest_neighbor_filter_ = false;

      // Transform from quad space to root space.
      gfx::Transform quad_to_root_transform_;

      // Clip rect in root space.
      std::optional<gfx::Rect> clip_rect_in_root_;

      // Rounded corner clip in root space
      gfx::RRectF rounded_corner_bounds_;

      // The opacity of the entire visual subtree
      float opacity_ = 1.0;

      // The size of overlay image in |dcomp_visual_content_| which is in
      // pixels.
      gfx::Size image_size_;

      // If false, force |transform_visual_| to use the hard border mode.
      bool allow_antialiasing_ = true;

      // The order relative to the root surface. Positive values means the
      // visual appears in front of the root surface (i.e. overlay) and negative
      // values means the visual appears below the root surface (i.e. underlay).
      int z_order_ = 0;

#if DCHECK_IS_ON()
      // True if the subtree is reused from the previous frame and keeps its
      // attachment to the root from the previous frame. Used for testing.
      bool attached_to_root_from_previous_frame_ = false;
#endif  // DCHECK_IS_ON()
    };

   private:
    // This function is called as part of |BuildTreeOptimized|.
    // For each given overlay:
    // 1. Populate visual subtree map with visual content.
    // 2. Find the matching subtree from the previous frame. The subtree matches
    // if it owns identical visual content. If the match is found:
    //    2.1. Updates |overlay_index_to_reused_subtree| with the
    //    index to the matching subtree.
    //    2.2. Updates |subtree_index_to_overlay| with the overlay index the
    //    previous frame subtree is matched to.
    // Returns populated visual subtree map.
    VisualSubtreeMap BuildMapAndAssignMatchingSubtrees(
        const std::vector<std::unique_ptr<DCLayerOverlayParams>>& overlays,
        std::vector<std::unique_ptr<VisualSubtree>>& visual_subtrees,
        std::vector<std::optional<size_t>>& overlay_index_to_reused_subtree,
        std::vector<std::optional<size_t>>& subtree_index_to_overlay);

    // This function is called as part of |BuildTreeOptimized|.
    // For each overlay that has no match attempts to find unused subtree of
    // the previous frame to be reused in the current frame. If such a subtree
    // is identified:
    // 1. Updates |overlay_index_to_reused_subtree| with the
    //    index to the found subtree.
    // 2. Updates |subtree_index_to_overlay| with the overlay index the
    //    found subtree is assigned to.
    // Returns previous frame subtree first unused index.
    size_t ReuseUnmatchedSubtrees(
        std::vector<std::unique_ptr<VisualSubtree>>& new_visual_subtrees,
        std::vector<std::optional<size_t>>& overlay_index_to_reused_subtree,
        std::vector<std::optional<size_t>>& subtree_index_to_overlay);

    // This function is called as part of |BuildTreeOptimized|.
    // Detaches unused subtrees of the previous frame from root starting with
    // |first_prev_frame_subtree_unused_index| returned from
    // |ReuseUnmatchedSubtrees|.
    // Updates |prev_subtree_is_attached_to_root| accordingly.
    // Returns true if commit is needed.
    bool DetachUnusedSubtreesFromRoot(
        size_t first_prev_frame_subtree_unused_index,
        std::vector<bool>& prev_subtree_is_attached_to_root);

    // This function is called as part of |BuildTreeOptimized|.
    // Removes reused subtrees of the previous frame from the root that need to
    // be repositioned in the current frame.
    // Updates |prev_subtree_is_attached_to_root| accordingly.
    // Returns true if commit is needed.
    bool DetachReusedSubtreesThatNeedRepositioningFromRoot(
        const std::vector<std::unique_ptr<VisualSubtree>>& new_visual_subtrees,
        const std::vector<std::optional<size_t>>&
            overlay_index_to_reused_subtree,
        const std::vector<std::optional<size_t>>& subtree_index_to_overlay,
        std::vector<bool>& prev_subtree_is_attached_to_root);

    // Detaches given subtree from the root.
    void DetachSubtreeFromRoot(VisualSubtree* subtree);

    // Tree that owns `this`.
    const raw_ptr<DCLayerTree> dc_layer_tree_ = nullptr;
    // List of DCOMP visual subtrees for previous frame.
    std::vector<std::unique_ptr<VisualSubtree>> visual_subtrees_;
    VisualSubtreeMap subtree_map_;
  };

 private:
  const bool disable_nv12_dynamic_textures_;
  const bool disable_vp_auto_hdr_;
  const bool disable_vp_scaling_;
  const bool disable_vp_super_resolution_;
  const bool force_dcomp_triple_buffer_video_swap_chain_;
  const bool no_downscaled_overlay_promotion_;

  HWND window_;
  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
  Microsoft::WRL::ComPtr<IDCompositionDevice3> dcomp_device_;
  Microsoft::WRL::ComPtr<IDCompositionTarget> dcomp_target_;

  // Resource pool which owns surfaces for solid color overlays. This is needed
  // since there is no way to procedurally fill a DComp visual.
  std::unique_ptr<SolidColorSurfacePool> solid_color_surface_pool_;

  // Store the largest video processor for SDR and HDR content
  // to avoid problems in (http://crbug.com/1121061) and
  // (http://crbug.com/1472975).
  VideoProcessorWrapper video_processor_wrapper_sdr_;
  VideoProcessorWrapper video_processor_wrapper_hdr_;

  // Current video processor input and output colorspace.
  gfx::ColorSpace video_input_color_space_;
  gfx::ColorSpace video_output_color_space_;

  // Root direct composition visual for window dcomp target.
  Microsoft::WRL::ComPtr<IDCompositionVisual2> dcomp_root_visual_;

  // List of swap chain presenters for previous frame.
  std::vector<std::unique_ptr<SwapChainPresenter>> video_swap_chains_;

  // A tree that owns all DCOMP visuals for overlays along with attributes
  // required to build DCOMP tree. It's updated for each frame.
  std::unique_ptr<VisualTree> visual_tree_;

  // Number of frames per second.
  float frame_rate_ = 0.f;

  // dealing with hdr metadata
  std::unique_ptr<HDRMetadataHelperWin> hdr_metadata_helper_;

  // Renderer for drawing delegated ink trails using OS APIs. This is created
  // when the DCLayerTree is created, but can only be queried to check if the
  // platform supports delegated ink trails. It will be initialized via the
  // call to MakeDelegatedInkOverlay when DCLayerTree has received a
  // delegated_ink_metadata_ and CommitAndClearPendingOverlays is underway.
  std::unique_ptr<DelegatedInkRenderer> ink_renderer_;

  // Cache the metadata received by the DCLayerTree until it is time to
  // CommitAndClearPendingOverlays. At that point the metadata will be moved
  // to DelegatedInkPointRendererGPU.
  std::unique_ptr<gfx::DelegatedInkMetadata> pending_delegated_ink_metadata_;
};

}  // namespace gl

#endif  // UI_GL_DC_LAYER_TREE_H_