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

#include "impeller/display_list/dl_dispatcher.h"

#include <algorithm>
#include <cstring>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
#include <vector>

#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/aiks/color_filter.h"
#include "impeller/core/formats.h"
#include "impeller/display_list/dl_image_impeller.h"
#include "impeller/display_list/dl_vertices_geometry.h"
#include "impeller/display_list/nine_patch_converter.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/entity/contents/conical_gradient_contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/inputs/filter_input.h"
#include "impeller/entity/contents/linear_gradient_contents.h"
#include "impeller/entity/contents/radial_gradient_contents.h"
#include "impeller/entity/contents/runtime_effect_contents.h"
#include "impeller/entity/contents/scene_contents.h"
#include "impeller/entity/contents/sweep_gradient_contents.h"
#include "impeller/entity/contents/tiled_texture_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/sigma.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"

namespace impeller {

#define UNIMPLEMENTED \
  FML_DLOG(ERROR) << "Unimplemented detail in " << __FUNCTION__;

DlDispatcher::DlDispatcher() = default;

DlDispatcher::DlDispatcher(Rect cull_rect) : canvas_(cull_rect) {}

DlDispatcher::DlDispatcher(IRect cull_rect) : canvas_(cull_rect) {}

DlDispatcher::~DlDispatcher() = default;

static BlendMode ToBlendMode(flutter::DlBlendMode mode) {
  switch (mode) {
    case flutter::DlBlendMode::kClear:
      return BlendMode::kClear;
    case flutter::DlBlendMode::kSrc:
      return BlendMode::kSource;
    case flutter::DlBlendMode::kDst:
      return BlendMode::kDestination;
    case flutter::DlBlendMode::kSrcOver:
      return BlendMode::kSourceOver;
    case flutter::DlBlendMode::kDstOver:
      return BlendMode::kDestinationOver;
    case flutter::DlBlendMode::kSrcIn:
      return BlendMode::kSourceIn;
    case flutter::DlBlendMode::kDstIn:
      return BlendMode::kDestinationIn;
    case flutter::DlBlendMode::kSrcOut:
      return BlendMode::kSourceOut;
    case flutter::DlBlendMode::kDstOut:
      return BlendMode::kDestinationOut;
    case flutter::DlBlendMode::kSrcATop:
      return BlendMode::kSourceATop;
    case flutter::DlBlendMode::kDstATop:
      return BlendMode::kDestinationATop;
    case flutter::DlBlendMode::kXor:
      return BlendMode::kXor;
    case flutter::DlBlendMode::kPlus:
      return BlendMode::kPlus;
    case flutter::DlBlendMode::kModulate:
      return BlendMode::kModulate;
    case flutter::DlBlendMode::kScreen:
      return BlendMode::kScreen;
    case flutter::DlBlendMode::kOverlay:
      return BlendMode::kOverlay;
    case flutter::DlBlendMode::kDarken:
      return BlendMode::kDarken;
    case flutter::DlBlendMode::kLighten:
      return BlendMode::kLighten;
    case flutter::DlBlendMode::kColorDodge:
      return BlendMode::kColorDodge;
    case flutter::DlBlendMode::kColorBurn:
      return BlendMode::kColorBurn;
    case flutter::DlBlendMode::kHardLight:
      return BlendMode::kHardLight;
    case flutter::DlBlendMode::kSoftLight:
      return BlendMode::kSoftLight;
    case flutter::DlBlendMode::kDifference:
      return BlendMode::kDifference;
    case flutter::DlBlendMode::kExclusion:
      return BlendMode::kExclusion;
    case flutter::DlBlendMode::kMultiply:
      return BlendMode::kMultiply;
    case flutter::DlBlendMode::kHue:
      return BlendMode::kHue;
    case flutter::DlBlendMode::kSaturation:
      return BlendMode::kSaturation;
    case flutter::DlBlendMode::kColor:
      return BlendMode::kColor;
    case flutter::DlBlendMode::kLuminosity:
      return BlendMode::kLuminosity;
  }
  FML_UNREACHABLE();
}

static Entity::TileMode ToTileMode(flutter::DlTileMode tile_mode) {
  switch (tile_mode) {
    case flutter::DlTileMode::kClamp:
      return Entity::TileMode::kClamp;
    case flutter::DlTileMode::kRepeat:
      return Entity::TileMode::kRepeat;
    case flutter::DlTileMode::kMirror:
      return Entity::TileMode::kMirror;
    case flutter::DlTileMode::kDecal:
      return Entity::TileMode::kDecal;
  }
}

static impeller::SamplerDescriptor ToSamplerDescriptor(
    const flutter::DlImageSampling options) {
  impeller::SamplerDescriptor desc;
  switch (options) {
    case flutter::DlImageSampling::kNearestNeighbor:
      desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest;
      desc.label = "Nearest Sampler";
      break;
    case flutter::DlImageSampling::kLinear:
    // Impeller doesn't support cubic sampling, but linear is closer to correct
    // than nearest for this case.
    case flutter::DlImageSampling::kCubic:
      desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear;
      desc.label = "Linear Sampler";
      break;
    case flutter::DlImageSampling::kMipmapLinear:
      desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear;
      desc.mip_filter = impeller::MipFilter::kLinear;
      desc.label = "Mipmap Linear Sampler";
      break;
  }
  return desc;
}

static impeller::SamplerDescriptor ToSamplerDescriptor(
    const flutter::DlFilterMode options) {
  impeller::SamplerDescriptor desc;
  switch (options) {
    case flutter::DlFilterMode::kNearest:
      desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest;
      desc.label = "Nearest Sampler";
      break;
    case flutter::DlFilterMode::kLinear:
      desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear;
      desc.label = "Linear Sampler";
      break;
    default:
      break;
  }
  return desc;
}

static Matrix ToMatrix(const SkMatrix& m) {
  return Matrix{
      // clang-format off
      m[0], m[3], 0, m[6],
      m[1], m[4], 0, m[7],
      0,    0,    1, 0,
      m[2], m[5], 0, m[8],
      // clang-format on
  };
}

// |flutter::DlOpReceiver|
void DlDispatcher::setAntiAlias(bool aa) {
  // Nothing to do because AA is implicit.
}

// |flutter::DlOpReceiver|
void DlDispatcher::setDither(bool dither) {
  paint_.dither = dither;
}

static Paint::Style ToStyle(flutter::DlDrawStyle style) {
  switch (style) {
    case flutter::DlDrawStyle::kFill:
      return Paint::Style::kFill;
    case flutter::DlDrawStyle::kStroke:
      return Paint::Style::kStroke;
    case flutter::DlDrawStyle::kStrokeAndFill:
      UNIMPLEMENTED;
      break;
  }
  return Paint::Style::kFill;
}

// |flutter::DlOpReceiver|
void DlDispatcher::setDrawStyle(flutter::DlDrawStyle style) {
  paint_.style = ToStyle(style);
}

// |flutter::DlOpReceiver|
void DlDispatcher::setColor(flutter::DlColor color) {
  paint_.color = {
      color.getRedF(),
      color.getGreenF(),
      color.getBlueF(),
      color.getAlphaF(),
  };
}

// |flutter::DlOpReceiver|
void DlDispatcher::setStrokeWidth(SkScalar width) {
  paint_.stroke_width = width;
}

// |flutter::DlOpReceiver|
void DlDispatcher::setStrokeMiter(SkScalar limit) {
  paint_.stroke_miter = limit;
}

// |flutter::DlOpReceiver|
void DlDispatcher::setStrokeCap(flutter::DlStrokeCap cap) {
  switch (cap) {
    case flutter::DlStrokeCap::kButt:
      paint_.stroke_cap = Cap::kButt;
      break;
    case flutter::DlStrokeCap::kRound:
      paint_.stroke_cap = Cap::kRound;
      break;
    case flutter::DlStrokeCap::kSquare:
      paint_.stroke_cap = Cap::kSquare;
      break;
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) {
  switch (join) {
    case flutter::DlStrokeJoin::kMiter:
      paint_.stroke_join = Join::kMiter;
      break;
    case flutter::DlStrokeJoin::kRound:
      paint_.stroke_join = Join::kRound;
      break;
    case flutter::DlStrokeJoin::kBevel:
      paint_.stroke_join = Join::kBevel;
      break;
  }
}

static std::vector<Color> ToColors(const flutter::DlColor colors[], int count) {
  auto result = std::vector<Color>();
  if (colors == nullptr) {
    return result;
  }
  for (int i = 0; i < count; i++) {
    result.push_back(skia_conversions::ToColor(colors[i]));
  }
  return result;
}

// Convert display list colors + stops into impeller colors and stops, taking
// care to ensure that the stops always start with 0.0 and end with 1.0.
template <typename T>
static void ConvertStops(T* gradient,
                         std::vector<Color>* colors,
                         std::vector<float>* stops) {
  FML_DCHECK(gradient->stop_count() >= 2);

  auto* dl_colors = gradient->colors();
  auto* dl_stops = gradient->stops();
  if (dl_stops[0] != 0.0) {
    colors->emplace_back(skia_conversions::ToColor(dl_colors[0]));
    stops->emplace_back(0);
  }
  for (auto i = 0; i < gradient->stop_count(); i++) {
    colors->emplace_back(skia_conversions::ToColor(dl_colors[i]));
    stops->emplace_back(dl_stops[i]);
  }
  if (stops->back() != 1.0) {
    colors->emplace_back(colors->back());
    stops->emplace_back(1.0);
  }
}

static std::optional<ColorSource::Type> ToColorSourceType(
    flutter::DlColorSourceType type) {
  switch (type) {
    case flutter::DlColorSourceType::kColor:
      return ColorSource::Type::kColor;
    case flutter::DlColorSourceType::kImage:
      return ColorSource::Type::kImage;
    case flutter::DlColorSourceType::kLinearGradient:
      return ColorSource::Type::kLinearGradient;
    case flutter::DlColorSourceType::kRadialGradient:
      return ColorSource::Type::kRadialGradient;
    case flutter::DlColorSourceType::kConicalGradient:
      return ColorSource::Type::kConicalGradient;
    case flutter::DlColorSourceType::kSweepGradient:
      return ColorSource::Type::kSweepGradient;
    case flutter::DlColorSourceType::kRuntimeEffect:
      return ColorSource::Type::kRuntimeEffect;
#ifdef IMPELLER_ENABLE_3D
    case flutter::DlColorSourceType::kScene:
      return ColorSource::Type::kScene;
#endif  // IMPELLER_ENABLE_3D
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::setColorSource(const flutter::DlColorSource* source) {
  if (!source) {
    paint_.color_source = ColorSource::MakeColor();
    return;
  }

  std::optional<ColorSource::Type> type = ToColorSourceType(source->type());

  if (!type.has_value()) {
    FML_LOG(ERROR) << "Requested ColorSourceType::kUnknown";
    paint_.color_source = ColorSource::MakeColor();
    return;
  }

  switch (type.value()) {
    case ColorSource::Type::kColor: {
      const flutter::DlColorColorSource* color = source->asColor();

      paint_.color_source = ColorSource::MakeColor();
      setColor(color->color());
      FML_DCHECK(color);
      return;
    }
    case ColorSource::Type::kLinearGradient: {
      const flutter::DlLinearGradientColorSource* linear =
          source->asLinearGradient();
      FML_DCHECK(linear);
      auto start_point = skia_conversions::ToPoint(linear->start_point());
      auto end_point = skia_conversions::ToPoint(linear->end_point());
      std::vector<Color> colors;
      std::vector<float> stops;
      ConvertStops(linear, &colors, &stops);

      auto tile_mode = ToTileMode(linear->tile_mode());
      auto matrix = ToMatrix(linear->matrix());

      paint_.color_source = ColorSource::MakeLinearGradient(
          start_point, end_point, std::move(colors), std::move(stops),
          tile_mode, matrix);
      return;
    }
    case ColorSource::Type::kConicalGradient: {
      const flutter::DlConicalGradientColorSource* conical_gradient =
          source->asConicalGradient();
      FML_DCHECK(conical_gradient);
      Point center = skia_conversions::ToPoint(conical_gradient->end_center());
      SkScalar radius = conical_gradient->end_radius();
      Point focus_center =
          skia_conversions::ToPoint(conical_gradient->start_center());
      SkScalar focus_radius = conical_gradient->start_radius();
      std::vector<Color> colors;
      std::vector<float> stops;
      ConvertStops(conical_gradient, &colors, &stops);

      auto tile_mode = ToTileMode(conical_gradient->tile_mode());
      auto matrix = ToMatrix(conical_gradient->matrix());

      paint_.color_source = ColorSource::MakeConicalGradient(
          center, radius, std::move(colors), std::move(stops), focus_center,
          focus_radius, tile_mode, matrix);
      return;
    }
    case ColorSource::Type::kRadialGradient: {
      const flutter::DlRadialGradientColorSource* radialGradient =
          source->asRadialGradient();
      FML_DCHECK(radialGradient);
      auto center = skia_conversions::ToPoint(radialGradient->center());
      auto radius = radialGradient->radius();
      std::vector<Color> colors;
      std::vector<float> stops;
      ConvertStops(radialGradient, &colors, &stops);

      auto tile_mode = ToTileMode(radialGradient->tile_mode());
      auto matrix = ToMatrix(radialGradient->matrix());
      paint_.color_source =
          ColorSource::MakeRadialGradient(center, radius, std::move(colors),
                                          std::move(stops), tile_mode, matrix);
      return;
    }
    case ColorSource::Type::kSweepGradient: {
      const flutter::DlSweepGradientColorSource* sweepGradient =
          source->asSweepGradient();
      FML_DCHECK(sweepGradient);

      auto center = skia_conversions::ToPoint(sweepGradient->center());
      auto start_angle = Degrees(sweepGradient->start());
      auto end_angle = Degrees(sweepGradient->end());
      std::vector<Color> colors;
      std::vector<float> stops;
      ConvertStops(sweepGradient, &colors, &stops);

      auto tile_mode = ToTileMode(sweepGradient->tile_mode());
      auto matrix = ToMatrix(sweepGradient->matrix());
      paint_.color_source = ColorSource::MakeSweepGradient(
          center, start_angle, end_angle, std::move(colors), std::move(stops),
          tile_mode, matrix);
      return;
    }
    case ColorSource::Type::kImage: {
      const flutter::DlImageColorSource* image_color_source = source->asImage();
      FML_DCHECK(image_color_source &&
                 image_color_source->image()->impeller_texture());
      auto texture = image_color_source->image()->impeller_texture();
      auto x_tile_mode = ToTileMode(image_color_source->horizontal_tile_mode());
      auto y_tile_mode = ToTileMode(image_color_source->vertical_tile_mode());
      auto desc = ToSamplerDescriptor(image_color_source->sampling());
      auto matrix = ToMatrix(image_color_source->matrix());
      paint_.color_source = ColorSource::MakeImage(texture, x_tile_mode,
                                                   y_tile_mode, desc, matrix);
      return;
    }
    case ColorSource::Type::kRuntimeEffect: {
      const flutter::DlRuntimeEffectColorSource* runtime_effect_color_source =
          source->asRuntimeEffect();
      auto runtime_stage =
          runtime_effect_color_source->runtime_effect()->runtime_stage();
      auto uniform_data = runtime_effect_color_source->uniform_data();
      auto samplers = runtime_effect_color_source->samplers();

      std::vector<RuntimeEffectContents::TextureInput> texture_inputs;

      for (auto& sampler : samplers) {
        if (sampler == nullptr) {
          return;
        }
        auto* image = sampler->asImage();
        if (!sampler->asImage()) {
          UNIMPLEMENTED;
          return;
        }
        FML_DCHECK(image->image()->impeller_texture());
        texture_inputs.push_back({
            .sampler_descriptor = ToSamplerDescriptor(image->sampling()),
            .texture = image->image()->impeller_texture(),
        });
      }

      paint_.color_source = ColorSource::MakeRuntimeEffect(
          runtime_stage, uniform_data, texture_inputs);
      return;
    }
    case ColorSource::Type::kScene: {
#ifdef IMPELLER_ENABLE_3D
      const flutter::DlSceneColorSource* scene_color_source = source->asScene();
      std::shared_ptr<scene::Node> scene_node =
          scene_color_source->scene_node();
      Matrix camera_transform = scene_color_source->camera_matrix();

      paint_.color_source =
          ColorSource::MakeScene(scene_node, camera_transform);
#else   // IMPELLER_ENABLE_3D
      FML_LOG(ERROR) << "ColorSourceType::kScene can only be used if Impeller "
                        "Scene is enabled.";
#endif  // IMPELLER_ENABLE_3D
      return;
    }
  }
}

static std::shared_ptr<ColorFilter> ToColorFilter(
    const flutter::DlColorFilter* filter) {
  if (filter == nullptr) {
    return nullptr;
  }
  switch (filter->type()) {
    case flutter::DlColorFilterType::kBlend: {
      auto dl_blend = filter->asBlend();
      auto blend_mode = ToBlendMode(dl_blend->mode());
      auto color = skia_conversions::ToColor(dl_blend->color());
      return ColorFilter::MakeBlend(blend_mode, color);
    }
    case flutter::DlColorFilterType::kMatrix: {
      const flutter::DlMatrixColorFilter* dl_matrix = filter->asMatrix();
      impeller::ColorMatrix color_matrix;
      dl_matrix->get_matrix(color_matrix.array);
      return ColorFilter::MakeMatrix(color_matrix);
    }
    case flutter::DlColorFilterType::kSrgbToLinearGamma:
      return ColorFilter::MakeSrgbToLinear();
    case flutter::DlColorFilterType::kLinearToSrgbGamma:
      return ColorFilter::MakeLinearToSrgb();
  }
  return nullptr;
}

// |flutter::DlOpReceiver|
void DlDispatcher::setColorFilter(const flutter::DlColorFilter* filter) {
  // Needs https://github.com/flutter/flutter/issues/95434
  paint_.color_filter = ToColorFilter(filter);
}

// |flutter::DlOpReceiver|
void DlDispatcher::setInvertColors(bool invert) {
  paint_.invert_colors = invert;
}

// |flutter::DlOpReceiver|
void DlDispatcher::setBlendMode(flutter::DlBlendMode dl_mode) {
  paint_.blend_mode = ToBlendMode(dl_mode);
}

// |flutter::DlOpReceiver|
void DlDispatcher::setPathEffect(const flutter::DlPathEffect* effect) {
  // Needs https://github.com/flutter/flutter/issues/95434
  UNIMPLEMENTED;
}

static FilterContents::BlurStyle ToBlurStyle(flutter::DlBlurStyle blur_style) {
  switch (blur_style) {
    case flutter::DlBlurStyle::kNormal:
      return FilterContents::BlurStyle::kNormal;
    case flutter::DlBlurStyle::kSolid:
      return FilterContents::BlurStyle::kSolid;
    case flutter::DlBlurStyle::kOuter:
      return FilterContents::BlurStyle::kOuter;
    case flutter::DlBlurStyle::kInner:
      return FilterContents::BlurStyle::kInner;
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) {
  // Needs https://github.com/flutter/flutter/issues/95434
  if (filter == nullptr) {
    paint_.mask_blur_descriptor = std::nullopt;
    return;
  }
  switch (filter->type()) {
    case flutter::DlMaskFilterType::kBlur: {
      auto blur = filter->asBlur();

      paint_.mask_blur_descriptor = {
          .style = ToBlurStyle(blur->style()),
          .sigma = Sigma(blur->sigma()),
      };
      break;
    }
  }
}

static Paint::ImageFilterProc ToImageFilterProc(
    const flutter::DlImageFilter* filter) {
  if (filter == nullptr) {
    return nullptr;
  }

  switch (filter->type()) {
    case flutter::DlImageFilterType::kBlur: {
      auto blur = filter->asBlur();
      auto sigma_x = Sigma(blur->sigma_x());
      auto sigma_y = Sigma(blur->sigma_y());
      auto tile_mode = ToTileMode(blur->tile_mode());

      return [sigma_x, sigma_y, tile_mode](const FilterInput::Ref& input,
                                           const Matrix& effect_transform,
                                           bool is_subpass) {
        return FilterContents::MakeGaussianBlur(
            input, sigma_x, sigma_y, FilterContents::BlurStyle::kNormal,
            tile_mode, effect_transform);
      };

      break;
    }
    case flutter::DlImageFilterType::kDilate: {
      auto dilate = filter->asDilate();
      FML_DCHECK(dilate);
      if (dilate->radius_x() < 0 || dilate->radius_y() < 0) {
        return nullptr;
      }
      auto radius_x = Radius(dilate->radius_x());
      auto radius_y = Radius(dilate->radius_y());
      return [radius_x, radius_y](FilterInput::Ref input,
                                  const Matrix& effect_transform,
                                  bool is_subpass) {
        return FilterContents::MakeMorphology(
            std::move(input), radius_x, radius_y,
            FilterContents::MorphType::kDilate, effect_transform);
      };
      break;
    }
    case flutter::DlImageFilterType::kErode: {
      auto erode = filter->asErode();
      FML_DCHECK(erode);
      if (erode->radius_x() < 0 || erode->radius_y() < 0) {
        return nullptr;
      }
      auto radius_x = Radius(erode->radius_x());
      auto radius_y = Radius(erode->radius_y());
      return [radius_x, radius_y](FilterInput::Ref input,
                                  const Matrix& effect_transform,
                                  bool is_subpass) {
        return FilterContents::MakeMorphology(
            std::move(input), radius_x, radius_y,
            FilterContents::MorphType::kErode, effect_transform);
      };
      break;
    }
    case flutter::DlImageFilterType::kMatrix: {
      auto matrix_filter = filter->asMatrix();
      FML_DCHECK(matrix_filter);
      auto matrix = ToMatrix(matrix_filter->matrix());
      auto desc = ToSamplerDescriptor(matrix_filter->sampling());
      return [matrix, desc](FilterInput::Ref input,
                            const Matrix& effect_transform, bool is_subpass) {
        return FilterContents::MakeMatrixFilter(std::move(input), matrix, desc,
                                                effect_transform, is_subpass);
      };
      break;
    }
    case flutter::DlImageFilterType::kCompose: {
      auto compose = filter->asCompose();
      FML_DCHECK(compose);
      auto outer = compose->outer();
      auto inner = compose->inner();
      auto outer_proc = ToImageFilterProc(outer.get());
      auto inner_proc = ToImageFilterProc(inner.get());
      if (!outer_proc) {
        return inner_proc;
      }
      if (!inner_proc) {
        return outer_proc;
      }
      FML_DCHECK(outer_proc && inner_proc);
      return [outer_filter = outer_proc, inner_filter = inner_proc](
                 FilterInput::Ref input, const Matrix& effect_transform,
                 bool is_subpass) {
        auto contents =
            inner_filter(std::move(input), effect_transform, is_subpass);
        contents = outer_filter(FilterInput::Make(contents), effect_transform,
                                is_subpass);
        return contents;
      };
      break;
    }
    case flutter::DlImageFilterType::kColorFilter: {
      auto color_filter_image_filter = filter->asColorFilter();
      FML_DCHECK(color_filter_image_filter);
      auto color_filter =
          ToColorFilter(color_filter_image_filter->color_filter().get());
      if (!color_filter) {
        return nullptr;
      }
      return [filter = color_filter](FilterInput::Ref input,
                                     const Matrix& effect_transform,
                                     bool is_subpass) {
        // When color filters are used as image filters, set the color filter's
        // "absorb opacity" flag to false. For image filters, the snapshot
        // opacity needs to be deferred until the result of the filter chain is
        // being blended with the layer.
        return filter->WrapWithGPUColorFilter(std::move(input), false);
      };
      break;
    }
    case flutter::DlImageFilterType::kLocalMatrix: {
      auto local_matrix_filter = filter->asLocalMatrix();
      FML_DCHECK(local_matrix_filter);
      auto internal_filter = local_matrix_filter->image_filter();
      FML_DCHECK(internal_filter);

      auto image_filter_proc = ToImageFilterProc(internal_filter.get());
      if (!image_filter_proc) {
        return nullptr;
      }

      auto matrix = ToMatrix(local_matrix_filter->matrix());

      return [matrix, filter_proc = image_filter_proc](
                 FilterInput::Ref input, const Matrix& effect_transform,
                 bool is_subpass) {
        std::shared_ptr<FilterContents> filter =
            filter_proc(std::move(input), effect_transform, is_subpass);
        return FilterContents::MakeLocalMatrixFilter(FilterInput::Make(filter),
                                                     matrix);
      };
      break;
    }
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::setImageFilter(const flutter::DlImageFilter* filter) {
  paint_.image_filter = ToImageFilterProc(filter);
}

// |flutter::DlOpReceiver|
void DlDispatcher::save() {
  canvas_.Save();
}

// |flutter::DlOpReceiver|
void DlDispatcher::saveLayer(const SkRect* bounds,
                             const flutter::SaveLayerOptions options,
                             const flutter::DlImageFilter* backdrop) {
  auto paint = options.renders_with_attributes() ? paint_ : Paint{};
  canvas_.SaveLayer(paint, skia_conversions::ToRect(bounds),
                    ToImageFilterProc(backdrop));
}

// |flutter::DlOpReceiver|
void DlDispatcher::restore() {
  canvas_.Restore();
}

// |flutter::DlOpReceiver|
void DlDispatcher::translate(SkScalar tx, SkScalar ty) {
  canvas_.Translate({tx, ty, 0.0});
}

// |flutter::DlOpReceiver|
void DlDispatcher::scale(SkScalar sx, SkScalar sy) {
  canvas_.Scale({sx, sy, 1.0});
}

// |flutter::DlOpReceiver|
void DlDispatcher::rotate(SkScalar degrees) {
  canvas_.Rotate(Degrees{degrees});
}

// |flutter::DlOpReceiver|
void DlDispatcher::skew(SkScalar sx, SkScalar sy) {
  canvas_.Skew(sx, sy);
}

// |flutter::DlOpReceiver|
void DlDispatcher::transform2DAffine(SkScalar mxx,
                                     SkScalar mxy,
                                     SkScalar mxt,
                                     SkScalar myx,
                                     SkScalar myy,
                                     SkScalar myt) {
  // clang-format off
  transformFullPerspective(
    mxx, mxy,  0, mxt,
    myx, myy,  0, myt,
    0  ,   0,  1,   0,
    0  ,   0,  0,   1
  );
  // clang-format on
}

// |flutter::DlOpReceiver|
void DlDispatcher::transformFullPerspective(SkScalar mxx,
                                            SkScalar mxy,
                                            SkScalar mxz,
                                            SkScalar mxt,
                                            SkScalar myx,
                                            SkScalar myy,
                                            SkScalar myz,
                                            SkScalar myt,
                                            SkScalar mzx,
                                            SkScalar mzy,
                                            SkScalar mzz,
                                            SkScalar mzt,
                                            SkScalar mwx,
                                            SkScalar mwy,
                                            SkScalar mwz,
                                            SkScalar mwt) {
  // The order of arguments is row-major but Impeller matrices are
  // column-major.
  // clang-format off
  auto xformation = Matrix{
    mxx, myx, mzx, mwx,
    mxy, myy, mzy, mwy,
    mxz, myz, mzz, mwz,
    mxt, myt, mzt, mwt
  };
  // clang-format on
  canvas_.Transform(xformation);
}

// |flutter::DlOpReceiver|
void DlDispatcher::transformReset() {
  canvas_.ResetTransform();
  canvas_.Transform(initial_matrix_);
}

static Entity::ClipOperation ToClipOperation(
    flutter::DlCanvas::ClipOp clip_op) {
  switch (clip_op) {
    case flutter::DlCanvas::ClipOp::kDifference:
      return Entity::ClipOperation::kDifference;
    case flutter::DlCanvas::ClipOp::kIntersect:
      return Entity::ClipOperation::kIntersect;
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) {
  canvas_.ClipRect(skia_conversions::ToRect(rect), ToClipOperation(clip_op));
}

// |flutter::DlOpReceiver|
void DlDispatcher::clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) {
  if (rrect.isSimple()) {
    canvas_.ClipRRect(skia_conversions::ToRect(rrect.rect()),
                      rrect.getSimpleRadii().fX, ToClipOperation(clip_op));
  } else {
    canvas_.ClipPath(skia_conversions::ToPath(rrect), ToClipOperation(clip_op));
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) {
  canvas_.ClipPath(skia_conversions::ToPath(path), ToClipOperation(clip_op));
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawColor(flutter::DlColor color,
                             flutter::DlBlendMode dl_mode) {
  Paint paint;
  paint.color = skia_conversions::ToColor(color);
  paint.blend_mode = ToBlendMode(dl_mode);
  canvas_.DrawPaint(paint);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawPaint() {
  canvas_.DrawPaint(paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) {
  auto path =
      PathBuilder{}
          .AddLine(skia_conversions::ToPoint(p0), skia_conversions::ToPoint(p1))
          .SetConvexity(Convexity::kConvex)
          .TakePath();
  Paint paint = paint_;
  paint.style = Paint::Style::kStroke;
  canvas_.DrawPath(path, paint);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawRect(const SkRect& rect) {
  canvas_.DrawRect(skia_conversions::ToRect(rect), paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawOval(const SkRect& bounds) {
  if (bounds.width() == bounds.height()) {
    canvas_.DrawCircle(skia_conversions::ToPoint(bounds.center()),
                       bounds.width() * 0.5, paint_);
  } else {
    auto path = PathBuilder{}
                    .AddOval(skia_conversions::ToRect(bounds))
                    .SetConvexity(Convexity::kConvex)
                    .TakePath();
    canvas_.DrawPath(path, paint_);
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawCircle(const SkPoint& center, SkScalar radius) {
  canvas_.DrawCircle(skia_conversions::ToPoint(center), radius, paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawRRect(const SkRRect& rrect) {
  if (rrect.isSimple()) {
    canvas_.DrawRRect(skia_conversions::ToRect(rrect.rect()),
                      rrect.getSimpleRadii().fX, paint_);
  } else {
    canvas_.DrawPath(skia_conversions::ToPath(rrect), paint_);
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawDRRect(const SkRRect& outer, const SkRRect& inner) {
  PathBuilder builder;
  builder.AddPath(skia_conversions::ToPath(outer));
  builder.AddPath(skia_conversions::ToPath(inner));
  canvas_.DrawPath(builder.TakePath(FillType::kOdd), paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawPath(const SkPath& path) {
  SkRect rect;
  SkRRect rrect;
  SkRect oval;
  if (path.isRect(&rect)) {
    canvas_.DrawRect(skia_conversions::ToRect(rect), paint_);
  } else if (path.isRRect(&rrect) && rrect.isSimple()) {
    canvas_.DrawRRect(skia_conversions::ToRect(rrect.rect()),
                      rrect.getSimpleRadii().fX, paint_);
  } else if (path.isOval(&oval) && oval.width() == oval.height()) {
    canvas_.DrawCircle(skia_conversions::ToPoint(oval.center()),
                       oval.width() * 0.5, paint_);
  } else {
    canvas_.DrawPath(skia_conversions::ToPath(path), paint_);
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawArc(const SkRect& oval_bounds,
                           SkScalar start_degrees,
                           SkScalar sweep_degrees,
                           bool use_center) {
  PathBuilder builder;
  builder.AddArc(skia_conversions::ToRect(oval_bounds), Degrees(start_degrees),
                 Degrees(sweep_degrees), use_center);
  canvas_.DrawPath(builder.TakePath(), paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawPoints(PointMode mode,
                              uint32_t count,
                              const SkPoint points[]) {
  Paint paint = paint_;
  paint.style = Paint::Style::kStroke;
  switch (mode) {
    case flutter::DlCanvas::PointMode::kPoints: {
      // Cap::kButt is also treated as a square.
      auto point_style = paint.stroke_cap == Cap::kRound ? PointStyle::kRound
                                                         : PointStyle::kSquare;
      auto radius = paint.stroke_width;
      if (radius > 0) {
        radius /= 2.0;
      }
      canvas_.DrawPoints(skia_conversions::ToPoints(points, count), radius,
                         paint, point_style);
    } break;
    case flutter::DlCanvas::PointMode::kLines:
      for (uint32_t i = 1; i < count; i += 2) {
        Point p0 = skia_conversions::ToPoint(points[i - 1]);
        Point p1 = skia_conversions::ToPoint(points[i]);
        auto path = PathBuilder{}.AddLine(p0, p1).TakePath();
        canvas_.DrawPath(path, paint);
      }
      break;
    case flutter::DlCanvas::PointMode::kPolygon:
      if (count > 1) {
        Point p0 = skia_conversions::ToPoint(points[0]);
        for (uint32_t i = 1; i < count; i++) {
          Point p1 = skia_conversions::ToPoint(points[i]);
          auto path = PathBuilder{}.AddLine(p0, p1).TakePath();
          canvas_.DrawPath(path, paint);
          p0 = p1;
        }
      }
      break;
  }
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawVertices(const flutter::DlVertices* vertices,
                                flutter::DlBlendMode dl_mode) {
  canvas_.DrawVertices(DlVerticesGeometry::MakeVertices(vertices),
                       ToBlendMode(dl_mode), paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawImage(const sk_sp<flutter::DlImage> image,
                             const SkPoint point,
                             flutter::DlImageSampling sampling,
                             bool render_with_attributes) {
  if (!image) {
    return;
  }

  auto texture = image->impeller_texture();
  if (!texture) {
    return;
  }

  const auto size = texture->GetSize();
  const auto src = SkRect::MakeWH(size.width, size.height);
  const auto dest =
      SkRect::MakeXYWH(point.fX, point.fY, size.width, size.height);

  drawImageRect(image,                      // image
                src,                        // source rect
                dest,                       // destination rect
                sampling,                   // sampling options
                render_with_attributes,     // render with attributes
                SrcRectConstraint::kStrict  // constraint
  );
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawImageRect(
    const sk_sp<flutter::DlImage> image,
    const SkRect& src,
    const SkRect& dst,
    flutter::DlImageSampling sampling,
    bool render_with_attributes,
    SrcRectConstraint constraint = SrcRectConstraint::kFast) {
  canvas_.DrawImageRect(
      std::make_shared<Image>(image->impeller_texture()),  // image
      skia_conversions::ToRect(src),                       // source rect
      skia_conversions::ToRect(dst),                       // destination rect
      render_with_attributes ? paint_ : Paint(),           // paint
      ToSamplerDescriptor(sampling)                        // sampling
  );
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawImageNine(const sk_sp<flutter::DlImage> image,
                                 const SkIRect& center,
                                 const SkRect& dst,
                                 flutter::DlFilterMode filter,
                                 bool render_with_attributes) {
  NinePatchConverter converter = {};
  converter.DrawNinePatch(
      std::make_shared<Image>(image->impeller_texture()),
      Rect::MakeLTRB(center.fLeft, center.fTop, center.fRight, center.fBottom),
      skia_conversions::ToRect(dst), ToSamplerDescriptor(filter), &canvas_,
      &paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawAtlas(const sk_sp<flutter::DlImage> atlas,
                             const SkRSXform xform[],
                             const SkRect tex[],
                             const flutter::DlColor colors[],
                             int count,
                             flutter::DlBlendMode mode,
                             flutter::DlImageSampling sampling,
                             const SkRect* cull_rect,
                             bool render_with_attributes) {
  canvas_.DrawAtlas(std::make_shared<Image>(atlas->impeller_texture()),
                    skia_conversions::ToRSXForms(xform, count),
                    skia_conversions::ToRects(tex, count),
                    ToColors(colors, count), ToBlendMode(mode),
                    ToSamplerDescriptor(sampling),
                    skia_conversions::ToRect(cull_rect), paint_);
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawDisplayList(
    const sk_sp<flutter::DisplayList> display_list,
    SkScalar opacity) {
  // Save all values that must remain untouched after the operation.
  Paint saved_paint = paint_;
  Matrix saved_initial_matrix = initial_matrix_;
  int restore_count = canvas_.GetSaveCount();

  // The display list may alter the clip, which must be restored to the current
  // clip at the end of playback.
  canvas_.Save();

  // Establish a new baseline for interpreting the new DL.
  // Matrix and clip are left untouched, the current
  // transform is saved as the new base matrix, and paint
  // values are reset to defaults.
  initial_matrix_ = canvas_.GetCurrentTransformation();
  paint_ = Paint();

  // Handle passed opacity in the most brute-force way by using
  // a SaveLayer. If the display_list is able to inherit the
  // opacity, this could also be handled by modulating all of its
  // attribute settings (for example, color), by the indicated
  // opacity.
  if (opacity < SK_Scalar1) {
    Paint save_paint;
    save_paint.color = Color(0, 0, 0, opacity);
    canvas_.SaveLayer(save_paint);
  }

  // TODO(131445): Remove this restriction if we can correctly cull with
  // perspective transforms.
  if (display_list->has_rtree() && !initial_matrix_.HasPerspective()) {
    // The canvas remembers the screen-space culling bounds clipped by
    // the surface and the history of clip calls. DisplayList can cull
    // the ops based on a rectangle expressed in its "destination bounds"
    // so we need the canvas to transform those into the current local
    // coordinate space into which the DisplayList will be rendered.
    auto cull_bounds = canvas_.GetCurrentLocalCullingBounds();
    if (cull_bounds.has_value()) {
      Rect cull_rect = cull_bounds.value();
      display_list->Dispatch(
          *this, SkRect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(),
                                  cull_rect.GetRight(), cull_rect.GetBottom()));
    } else {
      display_list->Dispatch(*this);
    }
  } else {
    display_list->Dispatch(*this);
  }

  // Restore all saved state back to what it was before we interpreted
  // the display_list
  canvas_.RestoreToCount(restore_count);
  initial_matrix_ = saved_initial_matrix;
  paint_ = saved_paint;
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
                                SkScalar x,
                                SkScalar y) {
  const auto text_frame = TextFrameFromTextBlob(blob);
  if (paint_.style == Paint::Style::kStroke) {
    auto path = skia_conversions::PathDataFromTextBlob(blob);
    auto bounds = text_frame.GetBounds();
    if (!bounds.has_value()) {
      return;
    }
    canvas_.Save();
    canvas_.Translate({x + bounds->origin.x, y + bounds->origin.y, 0.0});
    canvas_.DrawPath(path, paint_);
    canvas_.Restore();
    return;
  }

  canvas_.DrawTextFrame(text_frame,             //
                        impeller::Point{x, y},  //
                        paint_                  //
  );
}

// |flutter::DlOpReceiver|
void DlDispatcher::drawShadow(const SkPath& path,
                              const flutter::DlColor color,
                              const SkScalar elevation,
                              bool transparent_occluder,
                              SkScalar dpr) {
  Color spot_color = skia_conversions::ToColor(color);
  spot_color.alpha *= 0.25;

  // Compute the spot color -- ported from SkShadowUtils::ComputeTonalColors.
  {
    Scalar max =
        std::max(std::max(spot_color.red, spot_color.green), spot_color.blue);
    Scalar min =
        std::min(std::min(spot_color.red, spot_color.green), spot_color.blue);
    Scalar luminance = (min + max) * 0.5;

    Scalar alpha_adjust =
        (2.6f + (-2.66667f + 1.06667f * spot_color.alpha) * spot_color.alpha) *
        spot_color.alpha;
    Scalar color_alpha =
        (3.544762f + (-4.891428f + 2.3466f * luminance) * luminance) *
        luminance;
    color_alpha = std::clamp(alpha_adjust * color_alpha, 0.0f, 1.0f);

    Scalar greyscale_alpha =
        std::clamp(spot_color.alpha * (1 - 0.4f * luminance), 0.0f, 1.0f);

    Scalar color_scale = color_alpha * (1 - greyscale_alpha);
    Scalar tonal_alpha = color_scale + greyscale_alpha;
    Scalar unpremul_scale = tonal_alpha != 0 ? color_scale / tonal_alpha : 0;
    spot_color = Color(unpremul_scale * spot_color.red,
                       unpremul_scale * spot_color.green,
                       unpremul_scale * spot_color.blue, tonal_alpha);
  }

  Vector3 light_position(0, -1, 1);
  Scalar occluder_z = dpr * elevation;

  constexpr Scalar kLightRadius = 800 / 600;  // Light radius / light height

  Paint paint;
  paint.style = Paint::Style::kFill;
  paint.color = spot_color;
  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
      .style = FilterContents::BlurStyle::kNormal,
      .sigma = Radius{kLightRadius * occluder_z /
                      canvas_.GetCurrentTransformation().GetScale().y},
  };

  canvas_.Save();
  canvas_.PreConcat(
      Matrix::MakeTranslation(Vector2(0, -occluder_z * light_position.y)));

  SkRect rect;
  SkRRect rrect;
  SkRect oval;
  if (path.isRect(&rect)) {
    canvas_.DrawRect(skia_conversions::ToRect(rect), paint);
  } else if (path.isRRect(&rrect) && rrect.isSimple()) {
    canvas_.DrawRRect(skia_conversions::ToRect(rrect.rect()),
                      rrect.getSimpleRadii().fX, paint);
  } else if (path.isOval(&oval) && oval.width() == oval.height()) {
    canvas_.DrawCircle(skia_conversions::ToPoint(oval.center()),
                       oval.width() * 0.5, paint);
  } else {
    canvas_.DrawPath(skia_conversions::ToPath(path), paint);
  }

  canvas_.Restore();
}

Picture DlDispatcher::EndRecordingAsPicture() {
  TRACE_EVENT0("impeller", "DisplayListDispatcher::EndRecordingAsPicture");
  return canvas_.EndRecordingAsPicture();
}

}  // namespace impeller
