// 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_SWAP_CHAIN_PRESENTER_H_
#define UI_GL_SWAP_CHAIN_PRESENTER_H_

#include <windows.h>

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

#include "base/compiler_specific.h"
#include "base/containers/circular_deque.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/power_monitor/power_monitor.h"
#include "base/time/time.h"
#include "base/win/scoped_handle.h"
#include "ui/gfx/color_space.h"
#include "ui/gl/dc_layer_overlay_image.h"
#include "ui/gl/dc_layer_overlay_params.h"
#include "ui/gl/dc_layer_tree.h"
#include "ui/gl/gl_export.h"

namespace gl {

// Get the size of the monitor on which the window handle is displayed.
gfx::Size GetMonitorSizeForWindow(HWND window);

// SwapChainPresenter holds a swap chain, direct composition visuals, and other
// associated resources for a single overlay layer.  It is updated by calling
// PresentToSwapChain(), and can update or recreate resources as necessary.
class SwapChainPresenter : public base::PowerStateObserver {
 public:
  SwapChainPresenter(DCLayerTree* layer_tree,
                     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
                     Microsoft::WRL::ComPtr<IDCompositionDevice3> dcomp_device);

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

  ~SwapChainPresenter() override;

  // Indicates the modification overlay position required to display the swap
  // chain image.
  struct OverlayPositionAdjustment {
    gfx::Size monitor_size;
  };

  // Take and present `overlay.overlay_image` to this swap chain and return a
  // new overlay image representing the video. Also returns
  // `overlay_position_adjustment` if the overlay position needs to be adjusted,
  // e.g. the handle the swap chain being resized in the full screen case.
  std::optional<DCLayerOverlayImage> PresentToSwapChain(
      DCLayerOverlayParams& overlay,
      std::optional<OverlayPositionAdjustment>& overlay_position_adjustment);

  const Microsoft::WRL::ComPtr<IDXGISwapChain3>& swap_chain() const {
    return swap_chain_;
  }

  const Microsoft::WRL::ComPtr<IUnknown>& content_for_testing() const {
    return content_;
  }

  const gfx::Size& content_size() const { return content_size_; }

  // Valid HANDLE is needed for testing to create an IDCompositionSurface with
  // `CreateSurfaceFromHandle`.
  GL_EXPORT static base::win::ScopedHandle CreateDCompSurfaceHandleForTesting();

  // This only differs from `VideoPresentationMode` because that does not
  // include MF surface proxy.
  enum class PresentationMode {
    kDecodeSwapChain,
    kVpBlt,
    kVpBltWithStagingTexture,
    kMfSurfaceProxy,
  };

  // Get the presentation mode of the last successfully presented frame.
  PresentationMode GetLastPresentationMode() const;

 private:
  // Mapped to DirectCompositonVideoPresentationMode UMA enum.  Do not remove or
  // remap existing entries!
  enum class VideoPresentationMode {
    kZeroCopyDecodeSwapChain = 0,
    kUploadAndVideoProcessorBlit = 1,
    kBindAndVideoProcessorBlit = 2,
    kMaxValue = kBindAndVideoProcessorBlit,
  };

  // This keeps track of whether the previous 30 frames used Overlays or GPU
  // composition to present.
  class PresentationHistory {
   public:
    static const int kPresentsToStore = 30;

    PresentationHistory();

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

    ~PresentationHistory();

    void AddSample(DXGI_FRAME_PRESENTATION_MODE mode);

    void Clear();
    bool Valid() const;
    int composed_count() const;

   private:
    base::circular_deque<DXGI_FRAME_PRESENTATION_MODE> presents_;
    int composed_count_ = 0;
  };

  // Ensure the swap chain is allocated and take `overlay.overlay_image` and
  // prepare the swap chain to present it.
  //
  // The backing content may not match `overlay.quad_rect` (e.g. in the case of
  // full screen) so this method returns a modified `visual_transform` and
  // `visual_clip_rect` that should be used instead of the ones on `overlay`.
  //
  // Returns true on success.
  bool SetupPresentToSwapChain(DCLayerOverlayParams& overlay);

  // Attempt to disable the desktop primary plane by expanding the video swap
  // chain to fill `monitor_size`, fully occluding any content behind it with
  // solid black.
  bool TryDisablePrimaryPlane(const gfx::Size& monitor_size,
                              const DCLayerOverlayParams& overlay);

  bool FinishPresentToSwapChain();

  // Upload given YUV buffers to an NV12 texture that can be used to create
  // video processor input view.  Returns nullptr on failure.
  UNSAFE_BUFFER_USAGE Microsoft::WRL::ComPtr<ID3D11Texture2D> UploadVideoImage(
      const gfx::Size& size,
      base::span<const uint8_t> shm_video_pixmap,
      size_t stride);

  // Releases resources that might hold indirect references to the swap chain.
  void ReleaseSwapChainResources();

  // Recreate swap chain using given size.  Use preferred YUV format if
  // |use_yuv_swap_chain| is true, or BGRA otherwise.  Sets flags based on
  // |protected_video_type|. Returns true on success.
  bool ReallocateSwapChain(const gfx::Size& swap_chain_size,
                           DXGI_FORMAT swap_chain_format,
                           gfx::ProtectedVideoType protected_video_type);

  // Returns DXGI format that swap chain uses.
  // This changes over time based on stats recorded in |presentation_history|.
  DXGI_FORMAT GetSwapChainFormat(gfx::ProtectedVideoType protected_video_type,
                                 bool use_hdr_swap_chain,
                                 bool use_p010_for_sdr_swap_chain,
                                 const gfx::ColorSpace& input_color_space);

  // Perform a blit using video processor from given input texture to swap chain
  // backbuffer. |input_texture| is the input texture (array), and |input_level|
  // is the index of the texture in the texture array. |content_rect| is the
  // sub-rectangle of the input texture that should be blitted to swap chain,
  // and |src_color_space| is the color space of the video.
  bool VideoProcessorBlt(
      Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture,
      UINT input_level,
      const gfx::Rect& content_rect,
      const gfx::ColorSpace& src_color_space,
      std::optional<DXGI_HDR_METADATA_HDR10> stream_hdr_metadata,
      bool use_vp_auto_hdr);

  // Returns optimal swap chain size for given layer.
  gfx::Size CalculateSwapChainSize(const DCLayerOverlayParams& params) const;

  // Try presenting to a decode swap chain based on various conditions such as
  // global state (e.g. finch, NV12 support), texture flags, and transform.
  // Returns true on success. See PresentToDecodeSwapChain() for more info.
  bool TryPresentToDecodeSwapChain(
      Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
      unsigned array_slice,
      const gfx::ColorSpace& color_space,
      const gfx::Rect& content_rect,
      const gfx::Size& swap_chain_size,
      DXGI_FORMAT swap_chain_format,
      const gfx::Transform& transform_to_root);

  // Present to a decode swap chain created from compatible video decoder
  // buffers using given |texture|.
  // Use |dest_size| for destination size and |target_rect| for target rectangle
  // if valid. Otherwise, |swap_chain_size| would be used instead.
  // Returns true on success.
  bool PresentToDecodeSwapChain(Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
                                unsigned array_slice,
                                const gfx::ColorSpace& color_space,
                                const gfx::Rect& content_rect,
                                const gfx::Size& swap_chain_size);

  // Records presentation statistics in UMA and traces (for pixel tests) for the
  // current swap chain which could either be a regular flip swap chain or a
  // decode swap chain.
  void RecordPresentationStatistics();

  // base::PowerStateObserver
  void OnBatteryPowerStatusChange(
      PowerStateObserver::BatteryPowerStatus battery_power_status) override;

  // If connected with a power source, let the Intel video processor to do
  // the upscaling because it produces better results.
  bool ShouldUseVideoProcessorScaling();

  // This is called when a new swap chain is created, or when a new frame
  // rate is received.
  void SetSwapChainPresentDuration();

  // Returns swap chain media for either |swap_chain_| or |decode_swap_chain_|,
  // whichever is currently used.
  Microsoft::WRL::ComPtr<IDXGISwapChainMedia> GetSwapChainMedia() const;

  // Present the Direct Composition surface from MediaFoundationRenderer.
  bool PresentDCOMPSurface(DCLayerOverlayParams& overlay);

  // Release resources related to `PresentDCOMPSurface()`.
  void ReleaseDCOMPSurfaceResourcesIfNeeded();

  bool RevertSwapChainToSDR(
      Microsoft::WRL::ComPtr<ID3D11VideoDevice1> video_device,
      Microsoft::WRL::ComPtr<ID3D11VideoProcessor> video_processor,
      Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>
          video_processor_enumerator,
      Microsoft::WRL::ComPtr<ID3D11VideoContext1> context1,
      const gfx::ColorSpace& input_color_space);

  // Returns `true` if the last successfully presented frame was a Media
  // Foundation surface proxy.
  bool IsMediaFoundationSurfaceProxy() const;

  // The Direct Composition surface handle from MediaFoundationRenderer.
  HANDLE dcomp_surface_handle_ = INVALID_HANDLE_VALUE;

  // If set, represents the pending rect meant to be passed to
  // `DCOMPSurfaceProxy::SetRect` when we finalize the current commit. This is
  // needed because the full screen optimization can possibly adjust this rect,
  // but still only want to call `SetRect` once per frame.
  std::optional<gfx::Rect> pending_dcomp_surface_rect_in_window_;

  // Layer tree instance that owns this swap chain presenter.
  raw_ptr<DCLayerTree> layer_tree_ = nullptr;

  // Current size of swap chain.
  gfx::Size swap_chain_size_;

  // Current buffer count of swap chain.
  const UINT swap_chain_buffer_count_;

  // Current swap chain format.
  DXGI_FORMAT swap_chain_format_ = DXGI_FORMAT_B8G8R8A8_UNORM;

  // Last time tick when switching to BGRA8888 format.
  base::TimeTicks switched_to_BGRA8888_time_tick_;

  // Whether the swap chain was reallocated, and next present will be the first.
  bool first_present_ = false;

  // Whether the current swap chain is presenting protected video, software
  // or hardware protection.
  gfx::ProtectedVideoType swap_chain_protected_video_type_ =
      gfx::ProtectedVideoType::kClear;

  // Presentation history to track if swap chain was composited or used hardware
  // overlays.
  PresentationHistory presentation_history_;

  // Whether creating a YUV swap chain failed.
  bool failed_to_create_yuv_swapchain_ = false;

  // Set to true when PresentToDecodeSwapChain fails for the first time after
  // which we won't attempt to use decode swap chain again.
  bool failed_to_present_decode_swapchain_ = false;

  // The swap chain content, sometimes a IDCompositionSurface. This is updated
  // during |PresentToSwapChain| and copied to VisualSubtree owned by
  // DCLayerTree and set as the content of the content visual when the subtree
  // is updated.
  Microsoft::WRL::ComPtr<IUnknown> content_;
  // Size of the swap chain or dcomp surface assigned to |content_|.
  gfx::Size content_size_;

  // Overlay image that was presented in the last frame.
  std::optional<DCLayerOverlayImage> last_overlay_image_;

  // NV12 staging texture used for software decoded YUV buffers.  Mapped to CPU
  // for copying from YUV buffers.  Texture usage is DYNAMIC or STAGING.
  Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_texture_;
  // Used to copy from staging texture with usage STAGING for workarounds.
  Microsoft::WRL::ComPtr<ID3D11Texture2D> copy_texture_;
  gfx::Size staging_texture_size_;

  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
  Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> dcomp_device_;
  Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain_;

  // Video processor output view created from swap chain back buffer.  Must be
  // cached for performance reasons.
  Microsoft::WRL::ComPtr<ID3D11VideoProcessorOutputView> output_view_;

  Microsoft::WRL::ComPtr<IDXGIResource> decode_resource_;
  Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain_;
  Microsoft::WRL::ComPtr<IUnknown> decode_surface_;

  std::optional<uint32_t> pending_swap_buffer_;

  bool is_on_battery_power_;

  bool enable_vp_auto_hdr_ = false;
  bool enable_vp_super_resolution_ = false;

  UINT gpu_vendor_id_ = 0;

  // Cache key for DirectCompositionColorSpaceOverlaySupported(). Keyed on
  // (format, color_space, output), invalidated when any changes.
  struct ColorSpaceSupportedKey {
    ColorSpaceSupportedKey(DXGI_FORMAT format,
                           DXGI_COLOR_SPACE_TYPE color_space,
                           IDXGIOutput* dxgi_output);
    ColorSpaceSupportedKey(const ColorSpaceSupportedKey&);
    ~ColorSpaceSupportedKey();
    bool operator==(const ColorSpaceSupportedKey& other) const;
    DXGI_FORMAT format;
    DXGI_COLOR_SPACE_TYPE color_space;
    // Holds a reference to the IDXGIOutput so the object remains alive while
    // this cache entry exists. Without this, the output could be freed and its
    // address recycled for a new IDXGIOutput, causing a false cache hit on
    // pointer comparison.
    Microsoft::WRL::ComPtr<IDXGIOutput> output;
  };
  // Cache for the result of DirectCompositionColorSpaceOverlaySupported().
  // Avoids querying the driver for color space overlay support every frame.
  std::optional<std::pair<ColorSpaceSupportedKey, bool>>
      color_space_supported_cache_;
};

}  // namespace gl

#endif  // UI_GL_SWAP_CHAIN_PRESENTER_H_
