// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_CONTENT_CONTEXT_H_
#define FLUTTER_IMPELLER_ENTITY_CONTENTS_CONTENT_CONTEXT_H_

#include <initializer_list>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>

#include "flutter/fml/logging.h"
#include "flutter/fml/status_or.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/core/host_buffer.h"
#include "impeller/entity/contents/text_shadow_cache.h"
#include "impeller/geometry/color.h"
#include "impeller/renderer/capabilities.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/pipeline.h"
#include "impeller/renderer/pipeline_descriptor.h"
#include "impeller/renderer/render_target.h"
#include "impeller/typographer/lazy_glyph_atlas.h"
#include "impeller/typographer/typographer_context.h"

namespace impeller {
/// Pipeline state configuration.
///
/// Each unique combination of these options requires a different pipeline state
/// object to be built. This struct is used as a key for the per-pipeline
/// variant cache.
///
/// When adding fields to this key, reliant features should take care to limit
/// the combinatorical explosion of variations. A sufficiently complicated
/// Flutter application may easily require building hundreds of PSOs in total,
/// but they shouldn't require e.g. 10s of thousands.
struct ContentContextOptions {
  enum class StencilMode : uint8_t {
    /// Turn the stencil test off. Used when drawing without stencil-then-cover.
    kIgnore,

    // Operations used for stencil-then-cover.

    /// Draw the stencil for the NonZero fill path rule.
    ///
    /// The stencil ref should always be 0 on commands using this mode.
    kStencilNonZeroFill,
    /// Draw the stencil for the EvenOdd fill path rule.
    ///
    /// The stencil ref should always be 0 on commands using this mode.
    kStencilEvenOddFill,
    /// Draw a stencil which always increments once for everything in the vertex
    /// coverage regardless of triangle overlap.
    ///
    /// The stencil ref should always be 0 on commands using this mode.
    kStencilIncrementAll,
    /// Used for draw calls which fill in the stenciled area. Intended to be
    /// used after `kStencilNonZeroFill` or `kStencilEvenOddFill` is used to set
    /// up the stencil buffer. Also cleans up the stencil buffer by resetting
    /// everything to zero.
    ///
    /// The stencil ref should always be 0 on commands using this mode.
    kCoverCompare,
    /// The opposite of `kCoverCompare`. Used for draw calls which fill in the
    /// non-stenciled area (intersection clips). Intended to be used after
    /// `kStencilNonZeroFill` or `kStencilEvenOddFill` is used to set up the
    /// stencil buffer. Also cleans up the stencil buffer by resetting
    /// everything to zero.
    ///
    /// The stencil ref should always be 0 on commands using this mode.
    kCoverCompareInverted,
  };

  SampleCount sample_count = SampleCount::kCount1;
  BlendMode blend_mode = BlendMode::kSrcOver;
  CompareFunction depth_compare = CompareFunction::kAlways;
  StencilMode stencil_mode = ContentContextOptions::StencilMode::kIgnore;
  PrimitiveType primitive_type = PrimitiveType::kTriangle;
  PixelFormat color_attachment_pixel_format = PixelFormat::kUnknown;
  bool has_depth_stencil_attachments = true;
  bool depth_write_enabled = false;
  bool is_for_rrect_blur_clear = false;

  constexpr uint64_t ToKey() const {
    static_assert(sizeof(sample_count) == 1);
    static_assert(sizeof(blend_mode) == 1);
    static_assert(sizeof(sample_count) == 1);
    static_assert(sizeof(depth_compare) == 1);
    static_assert(sizeof(stencil_mode) == 1);
    static_assert(sizeof(primitive_type) == 1);
    static_assert(sizeof(color_attachment_pixel_format) == 1);

    return (is_for_rrect_blur_clear ? 1llu : 0llu) << 0 |
           (0) << 1 |  // // Unused, previously wireframe.
           (has_depth_stencil_attachments ? 1llu : 0llu) << 2 |
           (depth_write_enabled ? 1llu : 0llu) << 3 |
           // enums
           static_cast<uint64_t>(color_attachment_pixel_format) << 8 |
           static_cast<uint64_t>(primitive_type) << 16 |
           static_cast<uint64_t>(stencil_mode) << 24 |
           static_cast<uint64_t>(depth_compare) << 32 |
           static_cast<uint64_t>(blend_mode) << 40 |
           static_cast<uint64_t>(sample_count) << 48;
  }

  void ApplyToPipelineDescriptor(PipelineDescriptor& desc) const;
};

enum ConicalKind {
  kConical,
  kRadial,
  kStrip,
  kStripAndRadial,
};

class Tessellator;
class RenderTargetCache;

class ContentContext {
 public:
  explicit ContentContext(
      std::shared_ptr<Context> context,
      std::shared_ptr<TypographerContext> typographer_context,
      std::shared_ptr<RenderTargetAllocator> render_target_allocator = nullptr);

  ~ContentContext();

  bool IsValid() const;

  Tessellator& GetTessellator() const;

  // clang-format off
  PipelineRef GetBlendColorBurnPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendColorDodgePipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendColorPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendDarkenPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendDifferencePipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendExclusionPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendHardLightPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendHuePipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendLightenPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendLuminosityPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendMultiplyPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendOverlayPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendSaturationPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendScreenPipeline(ContentContextOptions opts) const;
  PipelineRef GetBlendSoftLightPipeline(ContentContextOptions opts) const;
  PipelineRef GetBorderMaskBlurPipeline(ContentContextOptions opts) const;
  PipelineRef GetCirclePipeline(ContentContextOptions opts) const;
  PipelineRef GetClearBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetClipPipeline(ContentContextOptions opts) const;
  PipelineRef GetColorMatrixColorFilterPipeline(ContentContextOptions opts) const;
  PipelineRef GetConicalGradientFillPipeline(ContentContextOptions opts, ConicalKind kind) const;
  PipelineRef GetConicalGradientSSBOFillPipeline(ContentContextOptions opts, ConicalKind kind) const;
  PipelineRef GetConicalGradientUniformFillPipeline(ContentContextOptions opts, ConicalKind kind) const;
  PipelineRef GetDestinationATopBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetDestinationBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetDestinationInBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetDestinationOutBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetDestinationOverBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetDownsamplePipeline(ContentContextOptions opts) const;
  PipelineRef GetDrawShadowVerticesPipeline(ContentContextOptions opts) const;
  PipelineRef GetDownsampleBoundedPipeline(ContentContextOptions opts) const;
  PipelineRef GetDrawVerticesUberPipeline(BlendMode blend_mode, ContentContextOptions opts) const;
  PipelineRef GetFastGradientPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendColorBurnPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendColorDodgePipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendColorPipeline( ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendDarkenPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendDifferencePipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendExclusionPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendHardLightPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendHuePipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendLightenPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendLuminosityPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendMultiplyPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendOverlayPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendSaturationPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendScreenPipeline(ContentContextOptions opts) const;
  PipelineRef GetFramebufferBlendSoftLightPipeline(ContentContextOptions opts) const;
  PipelineRef GetGaussianBlurPipeline(ContentContextOptions opts) const;
  PipelineRef GetGlyphAtlasPipeline(ContentContextOptions opts) const;
  PipelineRef GetLinePipeline(ContentContextOptions opts) const;
  PipelineRef GetLinearGradientFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetLinearGradientSSBOFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetLinearGradientUniformFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetLinearToSrgbFilterPipeline(ContentContextOptions opts) const;
  PipelineRef GetModulateBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetMorphologyFilterPipeline(ContentContextOptions opts) const;
  PipelineRef GetPlusBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetPorterDuffPipeline(BlendMode mode, ContentContextOptions opts) const;
  PipelineRef GetRadialGradientFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetRadialGradientSSBOFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetRadialGradientUniformFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetRRectBlurPipeline(ContentContextOptions opts) const;
  PipelineRef GetRSuperellipseBlurPipeline(ContentContextOptions opts) const;
  PipelineRef GetScreenBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSolidFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetSourceATopBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSourceBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSourceInBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSourceOutBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSourceOverBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetSrgbToLinearFilterPipeline(ContentContextOptions opts) const;
  PipelineRef GetSweepGradientFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetSweepGradientSSBOFillPipeline( ContentContextOptions opts) const;
  PipelineRef GetSweepGradientUniformFillPipeline(ContentContextOptions opts) const;
  PipelineRef GetTexturePipeline(ContentContextOptions opts) const;
  PipelineRef GetTextureStrictSrcPipeline(ContentContextOptions opts) const;
  PipelineRef GetTiledTexturePipeline(ContentContextOptions opts) const;
  PipelineRef GetXorBlendPipeline(ContentContextOptions opts) const;
  PipelineRef GetYUVToRGBFilterPipeline(ContentContextOptions opts) const;
#ifdef IMPELLER_ENABLE_OPENGLES
#if !defined(FML_OS_EMSCRIPTEN)
  PipelineRef GetTiledTextureExternalPipeline(ContentContextOptions opts) const;
  PipelineRef GetTiledTextureUvExternalPipeline(ContentContextOptions opts) const;
#endif
  PipelineRef GetDownsampleTextureGlesPipeline(ContentContextOptions opts) const;
#endif  // IMPELLER_ENABLE_OPENGLES
  // clang-format on

  // An empty 1x1 texture for binding drawVertices/drawAtlas or other cases
  // that don't always have a texture (due to blending).
  std::shared_ptr<Texture> GetEmptyTexture() const;

  std::shared_ptr<Context> GetContext() const;

  const Capabilities& GetDeviceCapabilities() const;

  using SubpassCallback =
      std::function<bool(const ContentContext&, RenderPass&)>;

  /// @brief  Creates a new texture of size `texture_size` and calls
  ///         `subpass_callback` with a `RenderPass` for drawing to the texture.
  fml::StatusOr<RenderTarget> MakeSubpass(
      std::string_view label,
      ISize texture_size,
      const std::shared_ptr<CommandBuffer>& command_buffer,
      const SubpassCallback& subpass_callback,
      bool msaa_enabled = true,
      bool depth_stencil_enabled = false,
      int32_t mip_count = 1) const;

  /// Makes a subpass that will render to `subpass_target`.
  fml::StatusOr<RenderTarget> MakeSubpass(
      std::string_view label,
      const RenderTarget& subpass_target,
      const std::shared_ptr<CommandBuffer>& command_buffer,
      const SubpassCallback& subpass_callback) const;

  const std::shared_ptr<LazyGlyphAtlas>& GetLazyGlyphAtlas() const {
    return lazy_glyph_atlas_;
  }

  const std::shared_ptr<RenderTargetAllocator>& GetRenderTargetCache() const {
    return render_target_cache_;
  }

  /// RuntimeEffect pipelines must be obtained via this method to avoid
  /// re-creating them every frame.
  ///
  /// The unique_entrypoint_name comes from RuntimeEffect::GetEntrypoint.
  /// Impellerc generates a unique entrypoint name for runtime effect shaders
  /// based on the input file name and shader stage.
  ///
  /// The create_callback is synchronously invoked exactly once if a cached
  /// pipeline is not found.
  PipelineRef GetCachedRuntimeEffectPipeline(
      const std::string& unique_entrypoint_name,
      const ContentContextOptions& options,
      const std::function<std::shared_ptr<Pipeline<PipelineDescriptor>>()>&
          create_callback) const;

  /// Used by hot reload/hot restart to clear a cached pipeline from
  /// GetCachedRuntimeEffectPipeline.
  void ClearCachedRuntimeEffectPipeline(
      const std::string& unique_entrypoint_name) const;

  /// @brief Retrieve the current host buffer for transient storage of indexes
  ///        used for indexed draws.
  ///
  /// This may or may not return the same value as `GetTransientsDataBuffer`
  /// depending on the backend.
  ///
  /// This is only safe to use from the raster threads. Other threads should
  /// allocate their own device buffers.
  HostBuffer& GetTransientsIndexesBuffer() const {
    return *indexes_host_buffer_;
  }

  /// @brief Retrieve the current host buffer for transient storage of other
  ///        non-index data.
  ///
  /// This is only safe to use from the raster threads. Other threads should
  /// allocate their own device buffers.
  HostBuffer& GetTransientsDataBuffer() const { return *data_host_buffer_; }

  /// @brief Resets the transients buffers held onto by the content context.
  void ResetTransientsBuffers();

  TextShadowCache& GetTextShadowCache() const { return *text_shadow_cache_; }

 protected:
  // Visible for testing.
  void SetTransientsIndexesBuffer(std::shared_ptr<HostBuffer> host_buffer) {
    indexes_host_buffer_ = std::move(host_buffer);
  }

  // Visible for testing.
  void SetTransientsDataBuffer(std::shared_ptr<HostBuffer> host_buffer) {
    data_host_buffer_ = std::move(host_buffer);
  }

 private:
  std::shared_ptr<Context> context_;
  std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;

  /// Run backend specific additional setup and create common shader variants.
  ///
  /// This bootstrap is intended to improve the performance of several
  /// first frame benchmarks that are tracked in the flutter device lab.
  /// The workload includes initializing commonly used but not default
  /// shader variants, as well as forcing driver initialization.
  void InitializeCommonlyUsedShadersIfNeeded() const;

  struct RuntimeEffectPipelineKey {
    std::string unique_entrypoint_name;
    ContentContextOptions options;

    struct Hash {
      std::size_t operator()(const RuntimeEffectPipelineKey& key) const {
        return fml::HashCombine(key.unique_entrypoint_name,
                                key.options.ToKey());
      }
    };

    struct Equal {
      inline bool operator()(const RuntimeEffectPipelineKey& lhs,
                             const RuntimeEffectPipelineKey& rhs) const {
        return lhs.unique_entrypoint_name == rhs.unique_entrypoint_name &&
               lhs.options.ToKey() == rhs.options.ToKey();
      }
    };
  };

  mutable std::unordered_map<RuntimeEffectPipelineKey,
                             std::shared_ptr<Pipeline<PipelineDescriptor>>,
                             RuntimeEffectPipelineKey::Hash,
                             RuntimeEffectPipelineKey::Equal>
      runtime_effect_pipelines_;

  struct Pipelines;
  std::unique_ptr<Pipelines> pipelines_;

  bool is_valid_ = false;
  std::shared_ptr<Tessellator> tessellator_;
  std::shared_ptr<RenderTargetAllocator> render_target_cache_;
  std::shared_ptr<HostBuffer> data_host_buffer_;
  std::shared_ptr<HostBuffer> indexes_host_buffer_;
  std::shared_ptr<Texture> empty_texture_;
  std::unique_ptr<TextShadowCache> text_shadow_cache_;

  ContentContext(const ContentContext&) = delete;

  ContentContext& operator=(const ContentContext&) = delete;
};

}  // namespace impeller

#endif  // FLUTTER_IMPELLER_ENTITY_CONTENTS_CONTENT_CONTEXT_H_
