// 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 "external_view_embedder.h"
#include <algorithm>
#include <cstdint>

#include "flutter/fml/trace_event.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/GrRecordingContext.h"

namespace flutter_runner {
namespace {

void AttachClipTransformChild(
    FlatlandConnection* flatland,
    ExternalViewEmbedder::ClipTransform* parent_clip_transform,
    const fuchsia::ui::composition::TransformId& child_transform_id) {
  flatland->flatland()->AddChild(parent_clip_transform->transform_id,
                                 child_transform_id);
  parent_clip_transform->children.push_back(child_transform_id);
}

void DetachClipTransformChildren(
    FlatlandConnection* flatland,
    ExternalViewEmbedder::ClipTransform* clip_transform) {
  for (auto& child : clip_transform->children) {
    flatland->flatland()->RemoveChild(clip_transform->transform_id, child);
  }
  clip_transform->children.clear();
}

}  // namespace

ExternalViewEmbedder::ExternalViewEmbedder(
    fuchsia::ui::views::ViewCreationToken view_creation_token,
    fuchsia::ui::views::ViewIdentityOnCreation view_identity,
    fuchsia::ui::composition::ViewBoundProtocols view_protocols,
    fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
        parent_viewport_watcher_request,
    std::shared_ptr<FlatlandConnection> flatland,
    std::shared_ptr<SurfaceProducer> surface_producer,
    bool intercept_all_input)
    : flatland_(flatland), surface_producer_(surface_producer) {
  flatland_->flatland()->CreateView2(
      std::move(view_creation_token), std::move(view_identity),
      std::move(view_protocols), std::move(parent_viewport_watcher_request));

  root_transform_id_ = flatland_->NextTransformId();
  flatland_->flatland()->CreateTransform(root_transform_id_);
  flatland_->flatland()->SetRootTransform(root_transform_id_);

  if (intercept_all_input) {
    input_interceptor_transform_ = flatland_->NextTransformId();
    flatland_->flatland()->CreateTransform(*input_interceptor_transform_);
    flatland_->flatland()->SetInfiniteHitRegion(
        *input_interceptor_transform_,
        fuchsia::ui::composition::HitTestInteraction::SEMANTICALLY_INVISIBLE);

    flatland_->flatland()->AddChild(root_transform_id_,
                                    *input_interceptor_transform_);
    child_transforms_.emplace_back(*input_interceptor_transform_);
  }
}

ExternalViewEmbedder::~ExternalViewEmbedder() = default;

flutter::DlCanvas* ExternalViewEmbedder::GetRootCanvas() {
  auto found = frame_layers_.find(kRootLayerId);
  if (found == frame_layers_.end()) {
    FML_LOG(WARNING)
        << "No root canvas could be found. This is extremely unlikely and "
           "indicates that the external view embedder did not receive the "
           "notification to begin the frame.";
    return nullptr;
  }

  return found->second.canvas_spy->GetSpyingCanvas();
}

void ExternalViewEmbedder::PrerollCompositeEmbeddedView(
    int64_t view_id,
    std::unique_ptr<flutter::EmbeddedViewParams> params) {
  zx_handle_t handle = static_cast<zx_handle_t>(view_id);
  FML_CHECK(frame_layers_.count(handle) == 0);

  frame_layers_.emplace(std::make_pair(
      EmbedderLayerId{handle},
      EmbedderLayer(frame_size_, *params, flutter::RTreeFactory())));
  frame_composition_order_.push_back(handle);
}

flutter::DlCanvas* ExternalViewEmbedder::CompositeEmbeddedView(
    int64_t view_id) {
  zx_handle_t handle = static_cast<zx_handle_t>(view_id);
  auto found = frame_layers_.find(handle);
  FML_CHECK(found != frame_layers_.end());

  return found->second.canvas_spy->GetSpyingCanvas();
}

flutter::PostPrerollResult ExternalViewEmbedder::PostPrerollAction(
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  return flutter::PostPrerollResult::kSuccess;
}

void ExternalViewEmbedder::BeginFrame(
    SkISize frame_size,
    GrDirectContext* context,
    double device_pixel_ratio,
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  TRACE_EVENT0("flutter", "ExternalViewEmbedder::BeginFrame");

  // Reset for new frame.
  Reset();
  frame_size_ = frame_size;
  frame_dpr_ = device_pixel_ratio;

  // Create the root layer.
  frame_layers_.emplace(std::make_pair(
      kRootLayerId,
      EmbedderLayer(frame_size, std::nullopt, flutter::RTreeFactory())));
  frame_composition_order_.push_back(kRootLayerId);
}

void ExternalViewEmbedder::EndFrame(
    bool should_resubmit_frame,
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  TRACE_EVENT0("flutter", "ExternalViewEmbedder::EndFrame");
}

void ExternalViewEmbedder::SubmitFrame(
    GrDirectContext* context,
    const std::shared_ptr<impeller::AiksContext>& aiks_context,
    std::unique_ptr<flutter::SurfaceFrame> frame) {
  TRACE_EVENT0("flutter", "ExternalViewEmbedder::SubmitFrame");
  std::vector<std::unique_ptr<SurfaceProducerSurface>> frame_surfaces;
  std::unordered_map<EmbedderLayerId, size_t> frame_surface_indices;

  // Create surfaces for the frame and associate them with layer IDs.
  {
    TRACE_EVENT0("flutter", "CreateSurfaces");

    for (const auto& layer : frame_layers_) {
      if (!layer.second.canvas_spy->DidDrawIntoCanvas()) {
        continue;
      }

      auto surface =
          surface_producer_->ProduceSurface(layer.second.surface_size);
      if (!surface) {
        const std::string layer_id_str =
            layer.first.has_value() ? std::to_string(layer.first.value())
                                    : "Background";
        FML_LOG(ERROR) << "Failed to create surface for layer " << layer_id_str
                       << "; size (" << layer.second.surface_size.width()
                       << ", " << layer.second.surface_size.height() << ")";
        FML_DCHECK(false);
        continue;
      }

      // If we receive an unitialized surface, we need to first create flatland
      // resource.
      if (surface->GetImageId() == 0) {
        auto image_id = flatland_->NextContentId().value;
        const auto& size = surface->GetSize();
        fuchsia::ui::composition::ImageProperties image_properties;
        image_properties.set_size({static_cast<uint32_t>(size.width()),
                                   static_cast<uint32_t>(size.height())});
        flatland_->flatland()->CreateImage(
            {image_id}, surface->GetBufferCollectionImportToken(), 0,
            std::move(image_properties));

        surface->SetImageId(image_id);
        surface->SetReleaseImageCallback([flatland = flatland_, image_id]() {
          flatland->flatland()->ReleaseImage({image_id});
        });
      }

      // Enqueue fences for the next present.
      flatland_->EnqueueAcquireFence(surface->GetAcquireFence());
      flatland_->EnqueueReleaseFence(surface->GetReleaseFence());

      frame_surface_indices.emplace(
          std::make_pair(layer.first, frame_surfaces.size()));
      frame_surfaces.emplace_back(std::move(surface));
    }
  }

  // Finish recording SkPictures.
  {
    TRACE_EVENT0("flutter", "FinishRecordingPictures");

    for (const auto& surface_index : frame_surface_indices) {
      const auto& layer = frame_layers_.find(surface_index.first);
      FML_CHECK(layer != frame_layers_.end());
      layer->second.picture =
          layer->second.recorder->finishRecordingAsPicture();
      FML_CHECK(layer->second.picture != nullptr);
    }
  }

  // Submit layers and platform views to Scenic in composition order.
  {
    TRACE_EVENT0("flutter", "SubmitLayers");

    // First re-scale everything according to the DPR.
    const float inv_dpr = 1.0f / frame_dpr_;
    flatland_->flatland()->SetScale(root_transform_id_, {inv_dpr, inv_dpr});

    size_t layer_index = 0;
    for (const auto& layer_id : frame_composition_order_) {
      const auto& layer = frame_layers_.find(layer_id);
      FML_CHECK(layer != frame_layers_.end());

      // Draw the PlatformView associated with each layer first.
      if (layer_id.has_value()) {
        FML_CHECK(layer->second.embedded_view_params.has_value());
        auto& view_params = layer->second.embedded_view_params.value();

        // Get the View structure corresponding to the platform view.
        auto found = views_.find(layer_id.value());
        FML_CHECK(found != views_.end())
            << "No View for layer_id = " << layer_id.value()
            << ". This typically indicates that the Dart code in "
               "Fuchsia's fuchsia_scenic_flutter library failed to create "
               "the platform view, leading to a crash later down the road in "
               "the Flutter Engine code that tries to find that platform view. "
               "Check the Flutter Framework for changes to PlatformView that "
               "might have caused a regression.";
        auto& viewport = found->second;

        // Compute mutators, and size for the platform view.
        const ViewMutators view_mutators =
            ParseMutatorStack(view_params.mutatorsStack());
        const SkSize view_size = view_params.sizePoints();

        // Verify that we're unpacking the mutators' transform matrix correctly.
        // Use built-in get method for SkMatrix to get values, see:
        // https://source.corp.google.com/piper///depot/google3/third_party/skia/HEAD/include/core/SkMatrix.h;l=391
        for (int index = 0; index < 9; index++) {
          const SkScalar mutators_transform_value =
              view_mutators.total_transform.get(index);
          const SkScalar params_transform_value =
              view_params.transformMatrix().get(index);
          if (!SkScalarNearlyEqual(mutators_transform_value,
                                   params_transform_value, 0.0005f)) {
            FML_LOG(ERROR)
                << "Assertion failed: view_mutators.total_transform[" << index
                << "] (" << mutators_transform_value
                << ") != view_params.transformMatrix()[" << index << "] ("
                << params_transform_value
                << "). This likely means there is a bug with the "
                << "logic for parsing embedded views' transform matrices.";
          }
        }

        if (viewport.pending_create_viewport_callback) {
          if (view_size.fWidth && view_size.fHeight) {
            viewport.pending_create_viewport_callback(
                view_size, viewport.pending_occlusion_hint);
            viewport.size = view_size;
            viewport.occlusion_hint = viewport.pending_occlusion_hint;
          } else {
            FML_DLOG(WARNING)
                << "Failed to create viewport because width or height is zero.";
          }
        }

        // Set transform for the viewport.
        if (view_mutators.transform != viewport.mutators.transform) {
          flatland_->flatland()->SetTranslation(
              viewport.transform_id,
              {static_cast<int32_t>(view_mutators.transform.getTranslateX()),
               static_cast<int32_t>(view_mutators.transform.getTranslateY())});
          flatland_->flatland()->SetScale(
              viewport.transform_id, {view_mutators.transform.getScaleX(),
                                      view_mutators.transform.getScaleY()});
          viewport.mutators.transform = view_mutators.transform;
        }

        // Set clip regions.
        if (view_mutators.clips != viewport.mutators.clips) {
          // Expand the clip_transforms array to fit any new transforms.
          while (viewport.clip_transforms.size() < view_mutators.clips.size()) {
            ClipTransform clip_transform;
            clip_transform.transform_id = flatland_->NextTransformId();
            flatland_->flatland()->CreateTransform(clip_transform.transform_id);
            viewport.clip_transforms.emplace_back(std::move(clip_transform));
          }
          FML_CHECK(viewport.clip_transforms.size() >=
                    view_mutators.clips.size());

          // Adjust and re-parent all clip transforms.
          for (auto& clip_transform : viewport.clip_transforms) {
            DetachClipTransformChildren(flatland_.get(), &clip_transform);
          }

          for (size_t c = 0; c < view_mutators.clips.size(); c++) {
            const SkMatrix& clip_matrix = view_mutators.clips[c].transform;
            const SkRect& clip_rect = view_mutators.clips[c].rect;

            flatland_->flatland()->SetTranslation(
                viewport.clip_transforms[c].transform_id,
                {static_cast<int32_t>(clip_matrix.getTranslateX()),
                 static_cast<int32_t>(clip_matrix.getTranslateY())});
            flatland_->flatland()->SetScale(
                viewport.clip_transforms[c].transform_id,
                {clip_matrix.getScaleX(), clip_matrix.getScaleY()});
            fuchsia::math::Rect rect = {
                static_cast<int32_t>(clip_rect.x()),
                static_cast<int32_t>(clip_rect.y()),
                static_cast<int32_t>(clip_rect.width()),
                static_cast<int32_t>(clip_rect.height())};
            flatland_->flatland()->SetClipBoundary(
                viewport.clip_transforms[c].transform_id,
                std::make_unique<fuchsia::math::Rect>(std::move(rect)));

            const auto child_transform_id =
                c != (view_mutators.clips.size() - 1)
                    ? viewport.clip_transforms[c + 1].transform_id
                    : viewport.transform_id;
            AttachClipTransformChild(flatland_.get(),
                                     &(viewport.clip_transforms[c]),
                                     child_transform_id);
          }
          viewport.mutators.clips = view_mutators.clips;
        }

        // Set opacity.
        if (view_mutators.opacity != viewport.mutators.opacity) {
          flatland_->flatland()->SetOpacity(viewport.transform_id,
                                            view_mutators.opacity);
          viewport.mutators.opacity = view_mutators.opacity;
        }

        // Set size and occlusion hint.
        if (view_size != viewport.size ||
            viewport.pending_occlusion_hint != viewport.occlusion_hint) {
          fuchsia::ui::composition::ViewportProperties properties;
          properties.set_logical_size(
              {static_cast<uint32_t>(view_size.fWidth),
               static_cast<uint32_t>(view_size.fHeight)});
          properties.set_inset(
              {static_cast<int32_t>(viewport.pending_occlusion_hint.fTop),
               static_cast<int32_t>(viewport.pending_occlusion_hint.fRight),
               static_cast<int32_t>(viewport.pending_occlusion_hint.fBottom),
               static_cast<int32_t>(viewport.pending_occlusion_hint.fLeft)});
          flatland_->flatland()->SetViewportProperties(viewport.viewport_id,
                                                       std::move(properties));
          viewport.size = view_size;
          viewport.occlusion_hint = viewport.pending_occlusion_hint;
        }

        // Attach the View to the main scene graph.
        const auto main_child_transform =
            viewport.mutators.clips.empty()
                ? viewport.transform_id
                : viewport.clip_transforms[0].transform_id;
        flatland_->flatland()->AddChild(root_transform_id_,
                                        main_child_transform);
        child_transforms_.emplace_back(main_child_transform);
      }

      // Acquire the surface associated with the layer.
      SurfaceProducerSurface* surface_for_layer = nullptr;
      if (layer->second.canvas_spy->DidDrawIntoCanvas()) {
        const auto& surface_index = frame_surface_indices.find(layer_id);
        if (surface_index != frame_surface_indices.end()) {
          FML_CHECK(surface_index->second < frame_surfaces.size());
          surface_for_layer = frame_surfaces[surface_index->second].get();
          FML_CHECK(surface_for_layer != nullptr);
        } else {
          const std::string layer_id_str =
              layer_id.has_value() ? std::to_string(layer_id.value())
                                   : "Background";
          FML_LOG(ERROR) << "Missing surface for layer " << layer_id_str
                         << "; skipping scene graph add of layer.";
          FML_DCHECK(false);
        }
      }

      // Draw the layer if we acquired a surface for it successfully.
      if (surface_for_layer != nullptr) {
        // Create a new layer if needed for the surface.
        FML_CHECK(layer_index <= layers_.size());
        if (layer_index == layers_.size()) {
          Layer new_layer{.transform_id = flatland_->NextTransformId()};
          flatland_->flatland()->CreateTransform(new_layer.transform_id);
          layers_.emplace_back(std::move(new_layer));
        }

        // Update the image content and set size.
        flatland_->flatland()->SetContent(layers_[layer_index].transform_id,
                                          {surface_for_layer->GetImageId()});
        flatland_->flatland()->SetImageDestinationSize(
            {surface_for_layer->GetImageId()},
            {static_cast<uint32_t>(surface_for_layer->GetSize().width()),
             static_cast<uint32_t>(surface_for_layer->GetSize().height())});

        // Flutter Embedder lacks an API to detect if a layer has alpha or not.
        // For now, we assume any layer beyond the first has alpha.
        flatland_->flatland()->SetImageBlendingFunction(
            {surface_for_layer->GetImageId()},
            layer_index == 0 ? fuchsia::ui::composition::BlendMode::SRC
                             : fuchsia::ui::composition::BlendMode::SRC_OVER);

        // Set hit regions for this layer; these hit regions correspond to the
        // portions of the layer on which skia drew content.
        {
          FML_CHECK(layer->second.rtree);
          std::list<SkRect> intersection_rects =
              layer->second.rtree->searchNonOverlappingDrawnRects(
                  SkRect::Make(layer->second.surface_size));

          std::vector<fuchsia::ui::composition::HitRegion> hit_regions;
          for (const SkRect& rect : intersection_rects) {
            hit_regions.emplace_back();
            auto& new_hit_region = hit_regions.back();
            new_hit_region.region.x = rect.x();
            new_hit_region.region.y = rect.y();
            new_hit_region.region.width = rect.width();
            new_hit_region.region.height = rect.height();
            new_hit_region.hit_test =
                fuchsia::ui::composition::HitTestInteraction::DEFAULT;
          }

          flatland_->flatland()->SetHitRegions(
              layers_[layer_index].transform_id, std::move(hit_regions));
        }

        // Attach the Layer to the main scene graph.
        flatland_->flatland()->AddChild(root_transform_id_,
                                        layers_[layer_index].transform_id);
        child_transforms_.emplace_back(layers_[layer_index].transform_id);
      }

      // Reset for the next pass:
      layer_index++;
    }

    // Set up the input interceptor at the top of the scene, if applicable. It
    // will capture all input, and any unwanted input will be reinjected into
    // embedded views.
    if (input_interceptor_transform_.has_value()) {
      flatland_->flatland()->AddChild(root_transform_id_,
                                      *input_interceptor_transform_);
      child_transforms_.emplace_back(*input_interceptor_transform_);
    }
  }

  // Present the session to Scenic, along with surface acquire/release fences.
  {
    TRACE_EVENT0("flutter", "SessionPresent");

    flatland_->Present();
  }

  // Render the recorded SkPictures into the surfaces.
  {
    TRACE_EVENT0("flutter", "RasterizeSurfaces");

    for (const auto& surface_index : frame_surface_indices) {
      TRACE_EVENT0("flutter", "RasterizeSurface");

      FML_CHECK(surface_index.second < frame_surfaces.size());
      SurfaceProducerSurface* surface =
          frame_surfaces[surface_index.second].get();
      FML_CHECK(surface != nullptr);

      sk_sp<SkSurface> sk_surface = surface->GetSkiaSurface();
      FML_CHECK(sk_surface != nullptr);
      FML_CHECK(SkISize::Make(sk_surface->width(), sk_surface->height()) ==
                frame_size_);
      SkCanvas* canvas = sk_surface->getCanvas();
      FML_CHECK(canvas != nullptr);

      const auto& layer = frame_layers_.find(surface_index.first);
      FML_CHECK(layer != frame_layers_.end());

      canvas->setMatrix(SkMatrix::I());
      canvas->clear(SK_ColorTRANSPARENT);
      canvas->drawPicture(layer->second.picture);
      if (GrDirectContext* direct_context =
              GrAsDirectContext(canvas->recordingContext())) {
        direct_context->flushAndSubmit();
      }
    }
  }

  // Flush deferred Skia work and inform Scenic that render targets are ready.
  {
    TRACE_EVENT0("flutter", "PresentSurfaces");

    surface_producer_->SubmitSurfaces(std::move(frame_surfaces));
  }

  // Submit the underlying render-backend-specific frame for processing.
  frame->Submit();
}

void ExternalViewEmbedder::CreateView(int64_t view_id,
                                      ViewCallback on_view_created,
                                      ViewCreatedCallback on_view_bound) {
  FML_CHECK(views_.find(view_id) == views_.end());

  const auto transform_id = flatland_->NextTransformId();
  const auto viewport_id = flatland_->NextContentId();
  View new_view = {.transform_id = transform_id, .viewport_id = viewport_id};
  flatland_->flatland()->CreateTransform(new_view.transform_id);
  fuchsia::ui::composition::ChildViewWatcherHandle child_view_watcher;
  new_view.pending_create_viewport_callback =
      [this, transform_id, viewport_id, view_id,
       child_view_watcher_request = child_view_watcher.NewRequest()](
          const SkSize& size, const SkRect& inset) mutable {
        fuchsia::ui::composition::ViewportProperties properties;
        properties.set_logical_size({static_cast<uint32_t>(size.fWidth),
                                     static_cast<uint32_t>(size.fHeight)});
        properties.set_inset({static_cast<int32_t>(inset.fTop),
                              static_cast<int32_t>(inset.fRight),
                              static_cast<int32_t>(inset.fBottom),
                              static_cast<int32_t>(inset.fLeft)});
        flatland_->flatland()->CreateViewport(
            viewport_id, {zx::channel((zx_handle_t)view_id)},
            std::move(properties), std::move(child_view_watcher_request));
        flatland_->flatland()->SetContent(transform_id, viewport_id);
      };

  on_view_created();
  on_view_bound(new_view.viewport_id, std::move(child_view_watcher));
  views_.emplace(std::make_pair(view_id, std::move(new_view)));
}

void ExternalViewEmbedder::DestroyView(int64_t view_id,
                                       ViewIdCallback on_view_unbound) {
  auto view = views_.find(view_id);
  FML_CHECK(view != views_.end());

  auto viewport_id = view->second.viewport_id;
  auto transform_id = view->second.transform_id;
  auto& clip_transforms = view->second.clip_transforms;
  if (!view->second.pending_create_viewport_callback) {
    flatland_->flatland()->ReleaseViewport(viewport_id, [](auto) {});
  }
  auto itr = std::find_if(
      child_transforms_.begin(), child_transforms_.end(),
      [transform_id,
       &clip_transforms](fuchsia::ui::composition::TransformId id) {
        return id.value == transform_id.value ||
               (!clip_transforms.empty() &&
                (id.value == clip_transforms[0].transform_id.value));
      });
  if (itr != child_transforms_.end()) {
    flatland_->flatland()->RemoveChild(root_transform_id_, *itr);
    child_transforms_.erase(itr);
  }
  for (auto& clip_transform : clip_transforms) {
    DetachClipTransformChildren(flatland_.get(), &clip_transform);
  }
  flatland_->flatland()->ReleaseTransform(transform_id);
  for (auto& clip_transform : clip_transforms) {
    flatland_->flatland()->ReleaseTransform(clip_transform.transform_id);
  }

  views_.erase(view);
  on_view_unbound(viewport_id);
}

void ExternalViewEmbedder::SetViewProperties(int64_t view_id,
                                             const SkRect& occlusion_hint,
                                             bool hit_testable,
                                             bool focusable) {
  auto found = views_.find(view_id);
  FML_CHECK(found != views_.end());

  // Note that pending_create_viewport_callback might not have run at this
  // point.
  auto& viewport = found->second;
  viewport.pending_occlusion_hint = occlusion_hint;
}

void ExternalViewEmbedder::Reset() {
  frame_layers_.clear();
  frame_composition_order_.clear();
  frame_size_ = SkISize::Make(0, 0);
  frame_dpr_ = 1.f;

  // Clear all children from root.
  for (const auto& transform : child_transforms_) {
    flatland_->flatland()->RemoveChild(root_transform_id_, transform);
  }
  child_transforms_.clear();

  // Clear images on all layers so they aren't cached unnecessarily.
  for (const auto& layer : layers_) {
    flatland_->flatland()->SetContent(layer.transform_id, {0});
  }
}

ExternalViewEmbedder::ViewMutators ExternalViewEmbedder::ParseMutatorStack(
    const flutter::MutatorsStack& mutators_stack) {
  ViewMutators mutators;
  SkMatrix total_transform = SkMatrix::I();
  SkMatrix transform_accumulator = SkMatrix::I();

  for (auto i = mutators_stack.Begin(); i != mutators_stack.End(); ++i) {
    const auto& mutator = *i;
    switch (mutator->GetType()) {
      case flutter::MutatorType::kOpacity: {
        mutators.opacity *= std::clamp(mutator->GetAlphaFloat(), 0.f, 1.f);
      } break;
      case flutter::MutatorType::kTransform: {
        total_transform.preConcat(mutator->GetMatrix());
        transform_accumulator.preConcat(mutator->GetMatrix());
      } break;
      case flutter::MutatorType::kClipRect: {
        mutators.clips.emplace_back(TransformedClip{
            .transform = transform_accumulator,
            .rect = mutator->GetRect(),
        });
        transform_accumulator = SkMatrix::I();
      } break;
      case flutter::MutatorType::kClipRRect: {
        mutators.clips.emplace_back(TransformedClip{
            .transform = transform_accumulator,
            .rect = mutator->GetRRect().getBounds(),
        });
        transform_accumulator = SkMatrix::I();
      } break;
      case flutter::MutatorType::kClipPath: {
        mutators.clips.emplace_back(TransformedClip{
            .transform = transform_accumulator,
            .rect = mutator->GetPath().getBounds(),
        });
        transform_accumulator = SkMatrix::I();
      } break;
      default: {
        break;
      }
    }
  }
  mutators.total_transform = total_transform;
  mutators.transform = transform_accumulator;
  mutators.opacity = std::clamp(mutators.opacity, 0.f, 1.f);

  return mutators;
}

}  // namespace flutter_runner
