blob: 7b867f92b259845531d844fb78add61927a3b0a9 [file] [log] [blame]
// 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"
#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) ((void)0)
#endif
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;
}
LOG("------\n");
for (const auto & l : scene_layers)
{
// Spinel renders front to back.
spn_layer_id layer_id = layer_count - 1 - l.layer_id;
LOG("layer_id:%u l.layer_id:%u fill_color=%08x fill_opacity=%g\n",
layer_id,
l.layer_id,
l.fill_color,
l.fill_opacity);
{
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));
LOG(" %u raster_id:%u\n", layer_id, print.raster_index);
}
}
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);
};
}