text: // Copyright 2020 The Fuchsia 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 "svg_scene_demo_image.h"
  
  #include <utility>
  
  #include "spinel/ext/color/color.h"
  #include "spinel/ext/transform_stack/transform_stack.h"
  #include "spinel/spinel.h"
  #include "spinel/spinel_assert.h"
  #include "spinel/spinel_opcodes.h"
  #include "tests/common/spinel/spinel_path_sink.h"
  #include "tests/common/svg/svg_utils.h"
  #include "tests/common/utils.h"
  
  using FrameTransformFunc = SvgSceneDemoImage::FrameTransformFunc;
  
  //
  // SvgSceneDemoImage::Parent contains the shared state between
  // all SvgSceneDemoImage instances. In this case, this corresponds
  // to the SVG scene paths and Spinel builder handles.
  //
  class SvgSceneDemoImage::Parent {
   public:
    Parent(const SvgScene & scene, spn_context_t context, FrameTransformFunc transform_func)
        : scene_(scene), context_(context), transform_func_(transform_func)
    {
      // BUG: spn_context_retain() doesn't do anything, while spn_context_release()
      //      destroys the context immediately.
      //spn(context_retain(context));
  
      spn(path_builder_create(context, &path_builder_));
      spn(raster_builder_create(context, &raster_builder_));
    }
  
    ~Parent()
    {
      resetPaths();
  
      if (path_builder_)
        spn(path_builder_release(path_builder_));
      if (raster_builder_)
        spn(raster_builder_release(raster_builder_));
  
      // Don't release context here, see bug described in constructor.
      // spn(context_release(context_));
    }
  
   private:
    friend class SvgSceneDemoImage;
  
    void
    resetPaths()
    {
      spn(path_release(context_, paths_.data(), static_cast<uint32_t>(paths_.size())));
      paths_.clear();
    }
  
    const std::vector<spn_path_t> &
    getPaths()
    {
      // Regenerate paths if needed.
      if (scene_.ensureUpdated() || paths_.empty())
        {
          resetPaths();
  
          // Decode all paths into spn_path_t handles.
          SpinelPathSink spinel_paths(context_, path_builder_);
  
          const auto & svgs = scene_.unique_svgs();
          for (const SvgScene::Path & path : scene_.unique_paths())
            svg_decode_path(svgs[path.svg_index], path.path_id, nullptr, &spinel_paths);
  
          paths_ = spinel_paths.release();
        }
  
      return paths_;
    }
  
    const SvgScene &        scene_;
    spn_context_t           context_;
    FrameTransformFunc      transform_func_;
    spn_path_builder_t      path_builder_;
    spn_raster_builder_t    raster_builder_;
    std::vector<spn_path_t> paths_;
  };
  
  //
  // SvgSceneDemoImage implementation.
  //
  
  SvgSceneDemoImage::SvgSceneDemoImage(SvgSceneDemoImage::Parent * parent,
                                       uint32_t                    clip_width,
                                       uint32_t                    clip_height)
      : parent_(parent), context_(parent_->context_)
  {
    spn(composition_create(context_, &composition_));
  
    uint32_t clip[4] = { 0, 0, clip_width, clip_height };
    spn(composition_set_clip(composition_, clip));
  
    spn(styling_create(context_, &styling_, 4096, 16000));
  }
  
  SvgSceneDemoImage::~SvgSceneDemoImage()
  {
    if (styling_)
      {
        spn(styling_unseal(styling_));
        spn(styling_release(styling_));
        styling_ = nullptr;
      }
  
    if (composition_)
      {
        spn(composition_unseal(composition_));
        spn(composition_release(composition_));
        composition_ = nullptr;
      }
  
    resetRasters();
  }
  
  void
  SvgSceneDemoImage::resetRasters()
  {
    spn(raster_release(context_, rasters_.data(), static_cast<uint32_t>(rasters_.size())));
    rasters_.clear();
  }
  
  void
  SvgSceneDemoImage::resetLayers()
  {
    if (styling_)
      {
        spn(styling_unseal(styling_));
        spn(styling_reset(styling_));
      }
    if (composition_)
      {
        spn(composition_unseal(composition_));
        spn(composition_reset(composition_));
      }
  }
  
  void
  SvgSceneDemoImage::setup(uint32_t frame_counter)
  {
    //
    // Setup rasters
    //
    spn_transform_t transform = parent_->transform_func_(frame_counter);
  
    const auto &                    scene_rasters = parent_->scene_.unique_rasters();
    const std::vector<spn_path_t> & spinel_paths  = parent_->getPaths();
  
    if (!scene_rasters.empty())
      {
        rasters_.reserve(scene_rasters.size());
  
        transform_stack * ts = transform_stack_create(2);
  
        // Scaling to Spinel's sub-pixel space first.
        transform_stack_push_scale(ts, 32., 32.);
  
        transform_stack_push_matrix(ts,
                                    transform.sx,
                                    transform.shx,
                                    transform.tx,
                                    transform.shy,
                                    transform.sy,
                                    transform.ty,
                                    transform.w0,
                                    transform.w1,
                                    1.);
        transform_stack_concat(ts);
  
        spn_raster_builder_t raster_builder = parent_->raster_builder_;
  
        for (const SvgScene::Raster & raster : scene_rasters)
          {
            static const struct spn_clip raster_clips[] = { { 0., 0., FLT_MAX, FLT_MAX } };
            spn_raster_t                 handle;
  
            transform_stack_push_affine(ts,
                                        (float)raster.transform.sx,
                                        (float)raster.transform.shx,
                                        (float)raster.transform.tx,
                                        (float)raster.transform.shy,
                                        (float)raster.transform.sy,
                                        (float)raster.transform.ty);
            transform_stack_concat(ts);
  
            spn(raster_builder_begin(raster_builder));
  
            spn(raster_builder_add(raster_builder,
                                   &spinel_paths[raster.path_index],
                                   nullptr,  // transform_weakrefs
                                   (const spn_transform_t *)transform_stack_top_transform(ts),
                                   nullptr,  // clip_weakrefs
                                   raster_clips,
                                   1));
  
            spn(raster_builder_end(raster_builder, &handle));
  
            rasters_.push_back(handle);
  
            transform_stack_drop(ts);
          }
  
        transform_stack_release(ts);
      }
  
    //
    // Setup layers
    //
    bool is_srgb = false;
  
    // First thing to do is setup the composition.
    const auto & scene_layers = parent_->scene_.layers();
  
    uint32_t layer_count = static_cast<uint32_t>(scene_layers.size());
  
    // Create top-level styling group
    spn_group_id group_id;
    spn(styling_group_alloc(styling_, &group_id));
  
    // This is the root group.
    spn(styling_group_parents(styling_, group_id, 0, nullptr));
  
    // the range of the root group is maximal [0,layer_count)
    spn(styling_group_range_lo(styling_, group_id, 0));
    spn(styling_group_range_hi(styling_, group_id, layer_count - 1));
  
    {
      spn_styling_cmd_t * cmds;
      spn(styling_group_enter(styling_, group_id, 1, &cmds));
      cmds[0] = SPN_STYLING_OPCODE_COLOR_ACC_ZERO;
    }
  
    {
      spn_styling_cmd_t * cmds;
      spn(styling_group_leave(styling_, group_id, 4, &cmds));
      const float background[4] = { 1., 1., 1., 1. };
      spn_styling_background_over_encoder(cmds, background);
      cmds[3] = SPN_STYLING_OPCODE_COLOR_ACC_STORE_TO_SURFACE;
    }
  
    for (const auto & l : scene_layers)
      {
        // Spinel renders front to back.
        spn_layer_id layer_id = layer_count - 1 - l.layer_id;
  
        {
          float rgba[4];
  
          color_rgb32_to_rgba_f32(rgba, l.fill_color, l.fill_opacity);
          if (is_srgb)
            color_srgb_to_linear_rgb_f32(rgba);
  
          color_premultiply_rgba_f32(rgba);
  
          spn_styling_cmd_t * cmds;
          spn(styling_group_layer(styling_, group_id, layer_id, 5, &cmds));
          cmds[0] =
            l.fill_even_odd ? SPN_STYLING_OPCODE_COVER_EVENODD : SPN_STYLING_OPCODE_COVER_NONZERO;
          spn_styling_layer_fill_rgba_encoder(cmds + 1, rgba);
          cmds[4] = SPN_STYLING_OPCODE_BLEND_OVER;
        }
  
        for (const auto & print : l.prints)
          {
            const spn_txty_t txty = {
              .tx = print.tx,
              .ty = print.ty,
            };
            spn(composition_place(composition_, &rasters_[print.raster_index], &layer_id, &txty, 1));
          }
      }
  
    spn(composition_seal(composition_));
    spn(styling_seal(styling_));
  }
  
  void
  SvgSceneDemoImage::render(void * submit_ext, uint32_t width, uint32_t height)
  {
    const spn_render_submit_t submit = {
      .ext         = submit_ext,
      .styling     = styling_,
      .composition = composition_,
      .clip        = { 0, 0, width, height },
    };
    spn(render(context_, &submit));
  
    // Discard rasters now.
    resetRasters();
  }
  
  void
  SvgSceneDemoImage::flush()
  {
    resetLayers();
  }
  
  // static
  DemoImage::Factory
  SvgSceneDemoImage::makeFactory(const SvgScene & scene, FrameTransformFunc transform_func)
  {
    // Create a lambda that creates the parent instance on the first call.
    std::shared_ptr<SvgSceneDemoImage::Parent> parent;
  
    return [parent, transform_func, &scene](const DemoImage::Config & config) mutable {
      if (!parent.get())
        parent = std::make_shared<SvgSceneDemoImage::Parent>(scene, config.context, transform_func);
  
      return std::make_unique<SvgSceneDemoImage>(parent.get(),
                                                 config.surface_width,
                                                 config.surface_height);
    };
  }
  