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

#pragma once

#include <memory>
#include <optional>
#include <unordered_map>

#include "flutter/fml/build_config.h"
#include "flutter/fml/hash_combine.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/capabilities.h"
#include "impeller/renderer/pipeline.h"
#include "impeller/scene/scene_context.h"

#ifdef IMPELLER_DEBUG
#include "impeller/entity/checkerboard.frag.h"
#include "impeller/entity/checkerboard.vert.h"
#endif  // IMPELLER_DEBUG

#include "impeller/entity/blend.frag.h"
#include "impeller/entity/blend.vert.h"
#include "impeller/entity/border_mask_blur.frag.h"
#include "impeller/entity/border_mask_blur.vert.h"
#include "impeller/entity/clip.frag.h"
#include "impeller/entity/clip.vert.h"
#include "impeller/entity/color_matrix_color_filter.frag.h"
#include "impeller/entity/color_matrix_color_filter.vert.h"
#include "impeller/entity/conical_gradient_fill.frag.h"
#include "impeller/entity/glyph_atlas.frag.h"
#include "impeller/entity/glyph_atlas.vert.h"
#include "impeller/entity/glyph_atlas_color.frag.h"
#include "impeller/entity/gradient_fill.vert.h"
#include "impeller/entity/linear_gradient_fill.frag.h"
#include "impeller/entity/linear_to_srgb_filter.frag.h"
#include "impeller/entity/linear_to_srgb_filter.vert.h"
#include "impeller/entity/morphology_filter.frag.h"
#include "impeller/entity/morphology_filter.vert.h"
#include "impeller/entity/points.comp.h"
#include "impeller/entity/porter_duff_blend.frag.h"
#include "impeller/entity/radial_gradient_fill.frag.h"
#include "impeller/entity/rrect_blur.frag.h"
#include "impeller/entity/rrect_blur.vert.h"
#include "impeller/entity/solid_fill.frag.h"
#include "impeller/entity/solid_fill.vert.h"
#include "impeller/entity/srgb_to_linear_filter.frag.h"
#include "impeller/entity/srgb_to_linear_filter.vert.h"
#include "impeller/entity/sweep_gradient_fill.frag.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/entity/texture_fill_external.frag.h"
#include "impeller/entity/tiled_texture_fill.frag.h"
#include "impeller/entity/uv.comp.h"
#include "impeller/entity/vertices.frag.h"
#include "impeller/entity/yuv_to_rgb_filter.frag.h"
#include "impeller/entity/yuv_to_rgb_filter.vert.h"

#include "impeller/entity/gaussian_blur.vert.h"
#include "impeller/entity/gaussian_blur_alpha_decal.frag.h"
#include "impeller/entity/gaussian_blur_alpha_nodecal.frag.h"
#include "impeller/entity/gaussian_blur_noalpha_decal.frag.h"
#include "impeller/entity/gaussian_blur_noalpha_nodecal.frag.h"

#include "impeller/entity/position_color.vert.h"

#include "impeller/typographer/glyph_atlas.h"

#include "impeller/entity/conical_gradient_ssbo_fill.frag.h"
#include "impeller/entity/linear_gradient_ssbo_fill.frag.h"
#include "impeller/entity/radial_gradient_ssbo_fill.frag.h"
#include "impeller/entity/sweep_gradient_ssbo_fill.frag.h"

#include "impeller/entity/advanced_blend.vert.h"
#include "impeller/entity/advanced_blend_color.frag.h"
#include "impeller/entity/advanced_blend_colorburn.frag.h"
#include "impeller/entity/advanced_blend_colordodge.frag.h"
#include "impeller/entity/advanced_blend_darken.frag.h"
#include "impeller/entity/advanced_blend_difference.frag.h"
#include "impeller/entity/advanced_blend_exclusion.frag.h"
#include "impeller/entity/advanced_blend_hardlight.frag.h"
#include "impeller/entity/advanced_blend_hue.frag.h"
#include "impeller/entity/advanced_blend_lighten.frag.h"
#include "impeller/entity/advanced_blend_luminosity.frag.h"
#include "impeller/entity/advanced_blend_multiply.frag.h"
#include "impeller/entity/advanced_blend_overlay.frag.h"
#include "impeller/entity/advanced_blend_saturation.frag.h"
#include "impeller/entity/advanced_blend_screen.frag.h"
#include "impeller/entity/advanced_blend_softlight.frag.h"

#include "impeller/entity/framebuffer_blend.vert.h"
#include "impeller/entity/framebuffer_blend_color.frag.h"
#include "impeller/entity/framebuffer_blend_colorburn.frag.h"
#include "impeller/entity/framebuffer_blend_colordodge.frag.h"
#include "impeller/entity/framebuffer_blend_darken.frag.h"
#include "impeller/entity/framebuffer_blend_difference.frag.h"
#include "impeller/entity/framebuffer_blend_exclusion.frag.h"
#include "impeller/entity/framebuffer_blend_hardlight.frag.h"
#include "impeller/entity/framebuffer_blend_hue.frag.h"
#include "impeller/entity/framebuffer_blend_lighten.frag.h"
#include "impeller/entity/framebuffer_blend_luminosity.frag.h"
#include "impeller/entity/framebuffer_blend_multiply.frag.h"
#include "impeller/entity/framebuffer_blend_overlay.frag.h"
#include "impeller/entity/framebuffer_blend_saturation.frag.h"
#include "impeller/entity/framebuffer_blend_screen.frag.h"
#include "impeller/entity/framebuffer_blend_softlight.frag.h"

namespace impeller {

#ifdef IMPELLER_DEBUG
using CheckerboardPipeline =
    RenderPipelineT<CheckerboardVertexShader, CheckerboardFragmentShader>;
#endif  // IMPELLER_DEBUG

using LinearGradientFillPipeline =
    RenderPipelineT<GradientFillVertexShader, LinearGradientFillFragmentShader>;
using SolidFillPipeline =
    RenderPipelineT<SolidFillVertexShader, SolidFillFragmentShader>;
using RadialGradientFillPipeline =
    RenderPipelineT<GradientFillVertexShader, RadialGradientFillFragmentShader>;
using ConicalGradientFillPipeline =
    RenderPipelineT<GradientFillVertexShader,
                    ConicalGradientFillFragmentShader>;
using SweepGradientFillPipeline =
    RenderPipelineT<GradientFillVertexShader, SweepGradientFillFragmentShader>;
using LinearGradientSSBOFillPipeline =
    RenderPipelineT<GradientFillVertexShader,
                    LinearGradientSsboFillFragmentShader>;
using ConicalGradientSSBOFillPipeline =
    RenderPipelineT<GradientFillVertexShader,
                    ConicalGradientSsboFillFragmentShader>;
using RadialGradientSSBOFillPipeline =
    RenderPipelineT<GradientFillVertexShader,
                    RadialGradientSsboFillFragmentShader>;
using SweepGradientSSBOFillPipeline =
    RenderPipelineT<GradientFillVertexShader,
                    SweepGradientSsboFillFragmentShader>;
using RRectBlurPipeline =
    RenderPipelineT<RrectBlurVertexShader, RrectBlurFragmentShader>;
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
using TexturePipeline =
    RenderPipelineT<TextureFillVertexShader, TextureFillFragmentShader>;
using TextureExternalPipeline =
    RenderPipelineT<TextureFillVertexShader, TextureFillExternalFragmentShader>;
using PositionUVPipeline =
    RenderPipelineT<TextureFillVertexShader, TiledTextureFillFragmentShader>;
using TiledTexturePipeline =
    RenderPipelineT<TextureFillVertexShader, TiledTextureFillFragmentShader>;
using GaussianBlurAlphaDecalPipeline =
    RenderPipelineT<GaussianBlurVertexShader,
                    GaussianBlurAlphaDecalFragmentShader>;
using GaussianBlurAlphaPipeline =
    RenderPipelineT<GaussianBlurVertexShader,
                    GaussianBlurAlphaNodecalFragmentShader>;
using GaussianBlurDecalPipeline =
    RenderPipelineT<GaussianBlurVertexShader,
                    GaussianBlurNoalphaDecalFragmentShader>;
using GaussianBlurPipeline =
    RenderPipelineT<GaussianBlurVertexShader,
                    GaussianBlurNoalphaNodecalFragmentShader>;
using BorderMaskBlurPipeline =
    RenderPipelineT<BorderMaskBlurVertexShader, BorderMaskBlurFragmentShader>;
using MorphologyFilterPipeline =
    RenderPipelineT<MorphologyFilterVertexShader,
                    MorphologyFilterFragmentShader>;
using ColorMatrixColorFilterPipeline =
    RenderPipelineT<ColorMatrixColorFilterVertexShader,
                    ColorMatrixColorFilterFragmentShader>;
using LinearToSrgbFilterPipeline =
    RenderPipelineT<LinearToSrgbFilterVertexShader,
                    LinearToSrgbFilterFragmentShader>;
using SrgbToLinearFilterPipeline =
    RenderPipelineT<SrgbToLinearFilterVertexShader,
                    SrgbToLinearFilterFragmentShader>;
using GlyphAtlasPipeline =
    RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
using GlyphAtlasColorPipeline =
    RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasColorFragmentShader>;
using PorterDuffBlendPipeline =
    RenderPipelineT<BlendVertexShader, PorterDuffBlendFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
// to redirect writing to the stencil instead of color attachments.
using ClipPipeline = RenderPipelineT<ClipVertexShader, ClipFragmentShader>;

using GeometryColorPipeline =
    RenderPipelineT<PositionColorVertexShader, VerticesFragmentShader>;
using YUVToRGBFilterPipeline =
    RenderPipelineT<YuvToRgbFilterVertexShader, YuvToRgbFilterFragmentShader>;

// Advanced blends
using BlendColorPipeline = RenderPipelineT<AdvancedBlendVertexShader,
                                           AdvancedBlendColorFragmentShader>;
using BlendColorBurnPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendColorburnFragmentShader>;
using BlendColorDodgePipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendColordodgeFragmentShader>;
using BlendDarkenPipeline = RenderPipelineT<AdvancedBlendVertexShader,
                                            AdvancedBlendDarkenFragmentShader>;
using BlendDifferencePipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendDifferenceFragmentShader>;
using BlendExclusionPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendExclusionFragmentShader>;
using BlendHardLightPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendHardlightFragmentShader>;
using BlendHuePipeline =
    RenderPipelineT<AdvancedBlendVertexShader, AdvancedBlendHueFragmentShader>;
using BlendLightenPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendLightenFragmentShader>;
using BlendLuminosityPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendLuminosityFragmentShader>;
using BlendMultiplyPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendMultiplyFragmentShader>;
using BlendOverlayPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendOverlayFragmentShader>;
using BlendSaturationPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendSaturationFragmentShader>;
using BlendScreenPipeline = RenderPipelineT<AdvancedBlendVertexShader,
                                            AdvancedBlendScreenFragmentShader>;
using BlendSoftLightPipeline =
    RenderPipelineT<AdvancedBlendVertexShader,
                    AdvancedBlendSoftlightFragmentShader>;
// Framebuffer Advanced Blends
using FramebufferBlendColorPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendColorFragmentShader>;
using FramebufferBlendColorBurnPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendColorburnFragmentShader>;
using FramebufferBlendColorDodgePipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendColordodgeFragmentShader>;
using FramebufferBlendDarkenPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendDarkenFragmentShader>;
using FramebufferBlendDifferencePipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendDifferenceFragmentShader>;
using FramebufferBlendExclusionPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendExclusionFragmentShader>;
using FramebufferBlendHardLightPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendHardlightFragmentShader>;
using FramebufferBlendHuePipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendHueFragmentShader>;
using FramebufferBlendLightenPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendLightenFragmentShader>;
using FramebufferBlendLuminosityPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendLuminosityFragmentShader>;
using FramebufferBlendMultiplyPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendMultiplyFragmentShader>;
using FramebufferBlendOverlayPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendOverlayFragmentShader>;
using FramebufferBlendSaturationPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendSaturationFragmentShader>;
using FramebufferBlendScreenPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendScreenFragmentShader>;
using FramebufferBlendSoftLightPipeline =
    RenderPipelineT<FramebufferBlendVertexShader,
                    FramebufferBlendSoftlightFragmentShader>;

/// Geometry Pipelines
using PointsComputeShaderPipeline = ComputePipelineBuilder<PointsComputeShader>;
using UvComputeShaderPipeline = ComputePipelineBuilder<UvComputeShader>;

/// 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 {
  SampleCount sample_count = SampleCount::kCount1;
  BlendMode blend_mode = BlendMode::kSourceOver;
  CompareFunction stencil_compare = CompareFunction::kEqual;
  StencilOperation stencil_operation = StencilOperation::kKeep;
  PrimitiveType primitive_type = PrimitiveType::kTriangle;
  PixelFormat color_attachment_pixel_format = PixelFormat::kUnknown;
  bool has_stencil_attachment = true;
  bool wireframe = false;

  struct Hash {
    constexpr std::size_t operator()(const ContentContextOptions& o) const {
      return fml::HashCombine(o.sample_count, o.blend_mode, o.stencil_compare,
                              o.stencil_operation, o.primitive_type,
                              o.color_attachment_pixel_format,
                              o.has_stencil_attachment, o.wireframe);
    }
  };

  struct Equal {
    constexpr bool operator()(const ContentContextOptions& lhs,
                              const ContentContextOptions& rhs) const {
      return lhs.sample_count == rhs.sample_count &&
             lhs.blend_mode == rhs.blend_mode &&
             lhs.stencil_compare == rhs.stencil_compare &&
             lhs.stencil_operation == rhs.stencil_operation &&
             lhs.primitive_type == rhs.primitive_type &&
             lhs.color_attachment_pixel_format ==
                 rhs.color_attachment_pixel_format &&
             lhs.has_stencil_attachment == rhs.has_stencil_attachment &&
             lhs.wireframe == rhs.wireframe;
    }
  };

  void ApplyToPipelineDescriptor(PipelineDescriptor& desc) const;
};

class Tessellator;

class ContentContext {
 public:
  explicit ContentContext(std::shared_ptr<Context> context);

  ~ContentContext();

  bool IsValid() const;

  std::shared_ptr<scene::SceneContext> GetSceneContext() const;

  std::shared_ptr<Tessellator> GetTessellator() const;

#ifdef IMPELLER_DEBUG
  std::shared_ptr<Pipeline<PipelineDescriptor>> GetCheckerboardPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(checkerboard_pipelines_, opts);
  }
#endif  // IMPELLER_DEBUG

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetLinearGradientFillPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(linear_gradient_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetLinearGradientSSBOFillPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsSSBO());
    return GetPipeline(linear_gradient_ssbo_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetRadialGradientSSBOFillPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsSSBO());
    return GetPipeline(radial_gradient_ssbo_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetConicalGradientSSBOFillPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsSSBO());
    return GetPipeline(conical_gradient_ssbo_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetSweepGradientSSBOFillPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsSSBO());
    return GetPipeline(sweep_gradient_ssbo_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetRadialGradientFillPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(radial_gradient_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetConicalGradientFillPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(conical_gradient_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetRRectBlurPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(rrect_blur_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetSweepGradientFillPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(sweep_gradient_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetSolidFillPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(solid_fill_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(texture_blend_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetTexturePipeline(
      ContentContextOptions opts) const {
    return GetPipeline(texture_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetTextureExternalPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(texture_external_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetPositionUVPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(position_uv_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetTiledTexturePipeline(
      ContentContextOptions opts) const {
    return GetPipeline(tiled_texture_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetGaussianBlurAlphaDecalPipeline(ContentContextOptions opts) const {
    return GetPipeline(gaussian_blur_alpha_decal_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGaussianBlurAlphaPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(gaussian_blur_alpha_nodecal_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGaussianBlurDecalPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(gaussian_blur_noalpha_decal_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGaussianBlurPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(gaussian_blur_noalpha_nodecal_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBorderMaskBlurPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(border_mask_blur_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetMorphologyFilterPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(morphology_filter_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetColorMatrixColorFilterPipeline(ContentContextOptions opts) const {
    return GetPipeline(color_matrix_color_filter_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetLinearToSrgbFilterPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(linear_to_srgb_filter_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetSrgbToLinearFilterPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(srgb_to_linear_filter_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetClipPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(clip_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(glyph_atlas_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasColorPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(glyph_atlas_color_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetGeometryColorPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(geometry_color_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetYUVToRGBFilterPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(yuv_to_rgb_filter_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetPorterDuffBlendPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(porter_duff_blend_pipelines_, opts);
  }

  // Advanced blends.

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_color_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorBurnPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_colorburn_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorDodgePipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_colordodge_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendDarkenPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_darken_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendDifferencePipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_difference_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendExclusionPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_exclusion_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendHardLightPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_hardlight_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendHuePipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_hue_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendLightenPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_lighten_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendLuminosityPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_luminosity_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendMultiplyPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_multiply_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendOverlayPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_overlay_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendSaturationPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_saturation_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendScreenPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_screen_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendSoftLightPipeline(
      ContentContextOptions opts) const {
    return GetPipeline(blend_softlight_pipelines_, opts);
  }

  // Framebuffer Advanced Blends
  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendColorPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_color_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendColorBurnPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_colorburn_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendColorDodgePipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_colordodge_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendDarkenPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_darken_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendDifferencePipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_difference_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendExclusionPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_exclusion_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendHardLightPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_hardlight_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>> GetFramebufferBlendHuePipeline(
      ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_hue_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendLightenPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_lighten_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendLuminosityPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_luminosity_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendMultiplyPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_multiply_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendOverlayPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_overlay_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendSaturationPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_saturation_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendScreenPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_screen_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<PipelineDescriptor>>
  GetFramebufferBlendSoftLightPipeline(ContentContextOptions opts) const {
    FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch());
    return GetPipeline(framebuffer_blend_softlight_pipelines_, opts);
  }

  std::shared_ptr<Pipeline<ComputePipelineDescriptor>> GetPointComputePipeline()
      const {
    FML_DCHECK(GetDeviceCapabilities().SupportsCompute());
    return point_field_compute_pipelines_;
  }

  std::shared_ptr<Pipeline<ComputePipelineDescriptor>> GetUvComputePipeline()
      const {
    FML_DCHECK(GetDeviceCapabilities().SupportsCompute());
    return uv_compute_pipelines_;
  }

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

  const Capabilities& GetDeviceCapabilities() const;

  void SetWireframe(bool wireframe);

  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.
  std::shared_ptr<Texture> MakeSubpass(const std::string& label,
                                       ISize texture_size,
                                       const SubpassCallback& subpass_callback,
                                       bool msaa_enabled = true) const;

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

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

  template <class T>
  using Variants = std::unordered_map<ContentContextOptions,
                                      std::unique_ptr<T>,
                                      ContentContextOptions::Hash,
                                      ContentContextOptions::Equal>;

  // These are mutable because while the prototypes are created eagerly, any
  // variants requested from that are lazily created and cached in the variants
  // map.

#ifdef IMPELLER_DEBUG
  mutable Variants<CheckerboardPipeline> checkerboard_pipelines_;
#endif  // IMPELLER_DEBUG

  mutable Variants<SolidFillPipeline> solid_fill_pipelines_;
  mutable Variants<LinearGradientFillPipeline> linear_gradient_fill_pipelines_;
  mutable Variants<RadialGradientFillPipeline> radial_gradient_fill_pipelines_;
  mutable Variants<ConicalGradientFillPipeline>
      conical_gradient_fill_pipelines_;
  mutable Variants<SweepGradientFillPipeline> sweep_gradient_fill_pipelines_;
  mutable Variants<LinearGradientSSBOFillPipeline>
      linear_gradient_ssbo_fill_pipelines_;
  mutable Variants<RadialGradientSSBOFillPipeline>
      radial_gradient_ssbo_fill_pipelines_;
  mutable Variants<ConicalGradientSSBOFillPipeline>
      conical_gradient_ssbo_fill_pipelines_;
  mutable Variants<SweepGradientSSBOFillPipeline>
      sweep_gradient_ssbo_fill_pipelines_;
  mutable Variants<RRectBlurPipeline> rrect_blur_pipelines_;
  mutable Variants<BlendPipeline> texture_blend_pipelines_;
  mutable Variants<TexturePipeline> texture_pipelines_;
  mutable Variants<TextureExternalPipeline> texture_external_pipelines_;
  mutable Variants<PositionUVPipeline> position_uv_pipelines_;
  mutable Variants<TiledTexturePipeline> tiled_texture_pipelines_;
  mutable Variants<GaussianBlurAlphaDecalPipeline>
      gaussian_blur_alpha_decal_pipelines_;
  mutable Variants<GaussianBlurAlphaPipeline>
      gaussian_blur_alpha_nodecal_pipelines_;
  mutable Variants<GaussianBlurDecalPipeline>
      gaussian_blur_noalpha_decal_pipelines_;
  mutable Variants<GaussianBlurPipeline>
      gaussian_blur_noalpha_nodecal_pipelines_;
  mutable Variants<BorderMaskBlurPipeline> border_mask_blur_pipelines_;
  mutable Variants<MorphologyFilterPipeline> morphology_filter_pipelines_;
  mutable Variants<ColorMatrixColorFilterPipeline>
      color_matrix_color_filter_pipelines_;
  mutable Variants<LinearToSrgbFilterPipeline> linear_to_srgb_filter_pipelines_;
  mutable Variants<SrgbToLinearFilterPipeline> srgb_to_linear_filter_pipelines_;
  mutable Variants<ClipPipeline> clip_pipelines_;
  mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
  mutable Variants<GlyphAtlasColorPipeline> glyph_atlas_color_pipelines_;
  mutable Variants<GeometryColorPipeline> geometry_color_pipelines_;
  mutable Variants<YUVToRGBFilterPipeline> yuv_to_rgb_filter_pipelines_;
  mutable Variants<PorterDuffBlendPipeline> porter_duff_blend_pipelines_;
  // Advanced blends.
  mutable Variants<BlendColorPipeline> blend_color_pipelines_;
  mutable Variants<BlendColorBurnPipeline> blend_colorburn_pipelines_;
  mutable Variants<BlendColorDodgePipeline> blend_colordodge_pipelines_;
  mutable Variants<BlendDarkenPipeline> blend_darken_pipelines_;
  mutable Variants<BlendDifferencePipeline> blend_difference_pipelines_;
  mutable Variants<BlendExclusionPipeline> blend_exclusion_pipelines_;
  mutable Variants<BlendHardLightPipeline> blend_hardlight_pipelines_;
  mutable Variants<BlendHuePipeline> blend_hue_pipelines_;
  mutable Variants<BlendLightenPipeline> blend_lighten_pipelines_;
  mutable Variants<BlendLuminosityPipeline> blend_luminosity_pipelines_;
  mutable Variants<BlendMultiplyPipeline> blend_multiply_pipelines_;
  mutable Variants<BlendOverlayPipeline> blend_overlay_pipelines_;
  mutable Variants<BlendSaturationPipeline> blend_saturation_pipelines_;
  mutable Variants<BlendScreenPipeline> blend_screen_pipelines_;
  mutable Variants<BlendSoftLightPipeline> blend_softlight_pipelines_;
  // Framebuffer Advanced blends.
  mutable Variants<FramebufferBlendColorPipeline>
      framebuffer_blend_color_pipelines_;
  mutable Variants<FramebufferBlendColorBurnPipeline>
      framebuffer_blend_colorburn_pipelines_;
  mutable Variants<FramebufferBlendColorDodgePipeline>
      framebuffer_blend_colordodge_pipelines_;
  mutable Variants<FramebufferBlendDarkenPipeline>
      framebuffer_blend_darken_pipelines_;
  mutable Variants<FramebufferBlendDifferencePipeline>
      framebuffer_blend_difference_pipelines_;
  mutable Variants<FramebufferBlendExclusionPipeline>
      framebuffer_blend_exclusion_pipelines_;
  mutable Variants<FramebufferBlendHardLightPipeline>
      framebuffer_blend_hardlight_pipelines_;
  mutable Variants<FramebufferBlendHuePipeline>
      framebuffer_blend_hue_pipelines_;
  mutable Variants<FramebufferBlendLightenPipeline>
      framebuffer_blend_lighten_pipelines_;
  mutable Variants<FramebufferBlendLuminosityPipeline>
      framebuffer_blend_luminosity_pipelines_;
  mutable Variants<FramebufferBlendMultiplyPipeline>
      framebuffer_blend_multiply_pipelines_;
  mutable Variants<FramebufferBlendOverlayPipeline>
      framebuffer_blend_overlay_pipelines_;
  mutable Variants<FramebufferBlendSaturationPipeline>
      framebuffer_blend_saturation_pipelines_;
  mutable Variants<FramebufferBlendScreenPipeline>
      framebuffer_blend_screen_pipelines_;
  mutable Variants<FramebufferBlendSoftLightPipeline>
      framebuffer_blend_softlight_pipelines_;
  mutable std::shared_ptr<Pipeline<ComputePipelineDescriptor>>
      point_field_compute_pipelines_;
  mutable std::shared_ptr<Pipeline<ComputePipelineDescriptor>>
      uv_compute_pipelines_;
  // The values for the default context options must be cached on
  // initial creation. In the presence of wide gamut and platform views,
  // it is possible that secondary surfaces will have a different default
  // pixel format, which would cause the prototype check in GetPipeline
  // below to fail.
  ContentContextOptions default_options_;

  template <class TypedPipeline>
  std::shared_ptr<Pipeline<PipelineDescriptor>> GetPipeline(
      Variants<TypedPipeline>& container,
      ContentContextOptions opts) const {
    if (!IsValid()) {
      return nullptr;
    }

    if (wireframe_) {
      opts.wireframe = true;
    }

    if (auto found = container.find(opts); found != container.end()) {
      return found->second->WaitAndGet();
    }

    auto prototype = container.find(default_options_);

    // The prototype must always be initialized in the constructor.
    FML_CHECK(prototype != container.end());

    auto pipeline = prototype->second->WaitAndGet();
    if (!pipeline) {
      return nullptr;
    }

    auto variant_future = pipeline->CreateVariant(
        [&opts, variants_count = container.size()](PipelineDescriptor& desc) {
          opts.ApplyToPipelineDescriptor(desc);
          desc.SetLabel(
              SPrintF("%s V#%zu", desc.GetLabel().c_str(), variants_count));
        });
    auto variant = std::make_unique<TypedPipeline>(std::move(variant_future));
    auto variant_pipeline = variant->WaitAndGet();
    container[opts] = std::move(variant);
    return variant_pipeline;
  }

  bool is_valid_ = false;
  std::shared_ptr<Tessellator> tessellator_;
  std::shared_ptr<scene::SceneContext> scene_context_;
  bool wireframe_ = false;

  FML_DISALLOW_COPY_AND_ASSIGN(ContentContext);
};

}  // namespace impeller
