blob: 7fe6cea064de143e258ece7366872fcdc3db8d34 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/paint/skottie_wrapper.h"
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/hash/hash.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "base/trace_event/trace_event.h"
#include "cc/paint/skottie_mru_resource_provider.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/modules/skottie/include/Skottie.h"
#include "third_party/skia/modules/skottie/include/SkottieProperty.h"
#include "third_party/skia/modules/skresources/include/SkResources.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace cc {
namespace {
// Directs logs from the skottie animation builder to //base/logging. Without
// this, errors/warnings from the animation builder get silently dropped.
class SkottieLogWriter : public skottie::Logger {
public:
void log(Level level, const char message[], const char* json) override {
static constexpr char kSkottieLogPrefix[] = "[Skottie] \"";
static constexpr char kSkottieLogSuffix[] = "\"";
switch (level) {
case Level::kWarning:
LOG(WARNING) << kSkottieLogPrefix << message << kSkottieLogSuffix;
break;
case Level::kError:
LOG(ERROR) << kSkottieLogPrefix << message << kSkottieLogSuffix;
break;
}
}
};
// Methods for converting from a skottie::<Type>PropertyValue to its
// corresponding representation in Chromium that gets circulated through the
// graphics pipeline.
class SkottiePropertyConversions {
public:
// Convert skottie library representation to Chromium representation:
static SkColor ConvertToChromium(
const skottie::ColorPropertyValue& skottie_value) {
return skottie_value;
}
static SkottieTextPropertyValue ConvertToChromium(
const skottie::TextPropertyValue& skottie_value) {
std::string text(skottie_value.fText.c_str());
return SkottieTextPropertyValue(std::move(text),
gfx::SkRectToRectF(skottie_value.fBox));
}
static SkottieTransformPropertyValue ConvertToChromium(
const skottie::TransformPropertyValue& skottie_value) {
SkottieTransformPropertyValue output = {
/*position*/ gfx::SkPointToPointF(skottie_value.fPosition)};
return output;
}
// Convert Chromium representation to skottie library representation:
static void ConvertToSkottie(const SkColor& chromium_value,
skottie::ColorPropertyValue& skottie_value_out) {
skottie_value_out = chromium_value;
}
static void ConvertToSkottie(const SkottieTextPropertyValue& chromium_value,
skottie::TextPropertyValue& skottie_value_out) {
skottie_value_out.fText.set(chromium_value.text().c_str());
skottie_value_out.fBox = gfx::RectFToSkRect(chromium_value.box());
}
static void ConvertToSkottie(
const SkottieTransformPropertyValue& chromium_value,
skottie::TransformPropertyValue& skottie_value_out) {
NOTIMPLEMENTED() << "No use case yet for modifying transform properties";
}
};
template <typename SkottiePropertyValueType,
typename SkottiePropertyHandleType,
typename ChromiumPropertyValueType>
class PropertyManager {
public:
PropertyManager() = default;
PropertyManager(const PropertyManager&) = delete;
PropertyManager& operator=(const PropertyManager&) = delete;
~PropertyManager() = default;
void OnNodeParsed(
const char node_name[],
const skottie::PropertyObserver::LazyHandle<SkottiePropertyHandleType>&
lh) {
if (!node_name)
return;
node_names_.insert(node_name);
SkottieResourceIdHash node_name_hash = HashSkottieResourceId(node_name);
auto skottie_property_handle = lh();
current_vals_as_chromium_.emplace(
node_name_hash, SkottiePropertyConversions::ConvertToChromium(
skottie_property_handle->get()));
skottie_property_handles_[node_name_hash].push_back(
std::move(skottie_property_handle));
}
void SetNewValues(
const base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType>&
new_vals_as_chromium) {
for (const auto& [node_name_hash, new_val_as_chromium] :
new_vals_as_chromium) {
auto iter = current_vals_as_chromium_.find(node_name_hash);
if (iter == current_vals_as_chromium_.end()) {
LOG(WARNING) << "Encountered unknown property node with hash: "
<< node_name_hash;
continue;
}
iter->second = new_val_as_chromium;
for (auto& skottie_handle : skottie_property_handles_[node_name_hash]) {
DCHECK(skottie_handle);
SkottiePropertyValueType current_val_as_skottie = skottie_handle->get();
SkottiePropertyConversions::ConvertToSkottie(new_val_as_chromium,
current_val_as_skottie);
skottie_handle->set(std::move(current_val_as_skottie));
}
}
}
const base::flat_set<std::string>& node_names() const { return node_names_; }
const base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType>
current_vals_as_chromium() const {
return current_vals_as_chromium_;
}
private:
base::flat_map<SkottieResourceIdHash,
std::vector<std::unique_ptr<SkottiePropertyHandleType>>>
skottie_property_handles_;
base::flat_set<std::string> node_names_;
base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType>
current_vals_as_chromium_;
};
using ColorPropertyManager = PropertyManager<skottie::ColorPropertyValue,
skottie::ColorPropertyHandle,
SkColor>;
using TextPropertyManager = PropertyManager<skottie::TextPropertyValue,
skottie::TextPropertyHandle,
SkottieTextPropertyValue>;
using TransformPropertyManager =
PropertyManager<skottie::TransformPropertyValue,
skottie::TransformPropertyHandle,
SkottieTransformPropertyValue>;
class GlobalPropertyManager final : public skottie::PropertyObserver {
public:
GlobalPropertyManager() = default;
GlobalPropertyManager(const GlobalPropertyManager&) = delete;
GlobalPropertyManager& operator=(const GlobalPropertyManager&) = delete;
~GlobalPropertyManager() override = default;
ColorPropertyManager& color_property_manager() {
return color_property_manager_;
}
TextPropertyManager& text_property_manager() {
return text_property_manager_;
}
TransformPropertyManager& transform_property_manager() {
return transform_property_manager_;
}
// skottie::PropertyObserver:
void onColorProperty(
const char node_name[],
const LazyHandle<skottie::ColorPropertyHandle>& lh) override {
color_property_manager_.OnNodeParsed(node_name, lh);
}
void onTextProperty(
const char node_name[],
const LazyHandle<skottie::TextPropertyHandle>& lh) override {
text_property_manager_.OnNodeParsed(node_name, lh);
}
void onTransformProperty(
const char node_name[],
const LazyHandle<skottie::TransformPropertyHandle>& lh) override {
transform_property_manager_.OnNodeParsed(node_name, lh);
}
private:
ColorPropertyManager color_property_manager_;
TextPropertyManager text_property_manager_;
TransformPropertyManager transform_property_manager_;
};
class MarkerStore : public skottie::MarkerObserver {
public:
MarkerStore() = default;
MarkerStore(const MarkerStore&) = delete;
MarkerStore& operator=(const MarkerStore&) = delete;
~MarkerStore() override = default;
// skottie::MarkerObserver implementation:
void onMarker(const char name[], float t0, float t1) override {
if (!name)
return;
all_markers_.push_back({std::string(name), t0, t1});
}
const std::vector<SkottieMarker>& all_markers() const { return all_markers_; }
private:
std::vector<SkottieMarker> all_markers_;
};
class SkottieWrapperImpl : public SkottieWrapper {
public:
SkottieWrapperImpl(base::span<const uint8_t> data,
std::vector<uint8_t> owned_data)
: SkottieWrapperImpl(
data,
owned_data,
// * Unretained is safe because SkottieMRUResourceProvider cannot
// outlive SkottieWrapperImpl.
// * Binding "this" in the constructor is safe because the frame
// data callback is only triggered during calls to
// |animation_->seek()|.
sk_make_sp<SkottieMRUResourceProvider>(
base::BindRepeating(
&SkottieWrapperImpl::RunCurrentFrameDataCallback,
base::Unretained(this)),
base::StringPiece(reinterpret_cast<const char*>(data.data()),
data.size()))) {}
SkottieWrapperImpl(const SkottieWrapperImpl&) = delete;
SkottieWrapperImpl& operator=(const SkottieWrapperImpl&) = delete;
// SkottieWrapper implementation:
bool is_valid() const override { return !!animation_; }
const SkottieResourceMetadataMap& GetImageAssetMetadata() const override {
return image_asset_metadata_;
}
const base::flat_set<std::string>& GetTextNodeNames() const override
LOCKS_EXCLUDED(lock_) {
base::AutoLock lock(lock_);
return property_manager_->text_property_manager().node_names();
}
SkottieTextPropertyValueMap GetCurrentTextPropertyValues() const override
LOCKS_EXCLUDED(lock_) {
base::AutoLock lock(lock_);
return property_manager_->text_property_manager()
.current_vals_as_chromium();
}
SkottieTransformPropertyValueMap GetCurrentTransformPropertyValues()
const override LOCKS_EXCLUDED(lock_) {
base::AutoLock lock(lock_);
return property_manager_->transform_property_manager()
.current_vals_as_chromium();
}
SkottieColorMap GetCurrentColorPropertyValues() const override
LOCKS_EXCLUDED(lock_) {
base::AutoLock lock(lock_);
return property_manager_->color_property_manager()
.current_vals_as_chromium();
}
const std::vector<SkottieMarker>& GetAllMarkers() const override {
return marker_store_->all_markers();
}
void Seek(float t, FrameDataCallback frame_data_cb) override
LOCKS_EXCLUDED(lock_) {
TRACE_EVENT1("cc", "SkottieWrapperImpl::Seek", "timestamp", t);
base::AutoLock lock(lock_);
// There's no need to reset |current_frame_data_cb_| to null when finished.
// The callback is guaranteed to only be invoked synchronously during calls
// to |animation_->seek/render()|, and not thereafter.
current_frame_data_cb_ = std::move(frame_data_cb);
animation_->seek(t);
}
void Draw(SkCanvas* canvas,
float t,
const SkRect& rect,
FrameDataCallback frame_data_cb,
const SkottieColorMap& color_map,
const SkottieTextPropertyValueMap& text_map) override
LOCKS_EXCLUDED(lock_) {
TRACE_EVENT1("cc", "SkottieWrapperImpl::Draw", "timestamp", t);
base::AutoLock lock(lock_);
current_frame_data_cb_ = std::move(frame_data_cb);
property_manager_->color_property_manager().SetNewValues(color_map);
property_manager_->text_property_manager().SetNewValues(text_map);
animation_->seek(t);
animation_->render(canvas, &rect);
}
float duration() const override { return animation_->duration(); }
SkSize size() const override { return animation_->size(); }
base::span<const uint8_t> raw_data() const override {
DCHECK(raw_data_.size());
return base::as_bytes(base::make_span(raw_data_.data(), raw_data_.size()));
}
uint32_t id() const override { return id_; }
private:
SkottieWrapperImpl(
base::span<const uint8_t> data,
std::vector<uint8_t> raw_data,
const sk_sp<SkottieMRUResourceProvider>& mru_resource_provider)
: property_manager_(sk_make_sp<GlobalPropertyManager>()),
marker_store_(sk_make_sp<MarkerStore>()),
animation_(
skottie::Animation::Builder()
.setLogger(sk_make_sp<SkottieLogWriter>())
.setPropertyObserver(property_manager_)
.setResourceProvider(skresources::CachingResourceProvider::Make(
mru_resource_provider))
.setMarkerObserver(marker_store_)
.make(reinterpret_cast<const char*>(data.data()), data.size())),
raw_data_(std::move(raw_data)),
id_(base::FastHash(data)),
// The underlying assumption here is that |skottie::Animation::Builder|
// loads image assets on initialization rather than doing so lazily at
// |render()| time. This is the case currently, and there will be unit
// test failures if this does not hold at some point in the future.
image_asset_metadata_(mru_resource_provider->GetImageAssetMetadata()) {}
~SkottieWrapperImpl() override = default;
FrameDataFetchResult RunCurrentFrameDataCallback(
SkottieResourceIdHash asset_id_hash,
float t,
sk_sp<SkImage>& image_out,
SkSamplingOptions& sampling_out) EXCLUSIVE_LOCKS_REQUIRED(lock_) {
lock_.AssertAcquired();
DCHECK(current_frame_data_cb_);
return current_frame_data_cb_.Run(asset_id_hash, t, image_out,
sampling_out);
}
mutable base::Lock lock_;
FrameDataCallback current_frame_data_cb_ GUARDED_BY(lock_);
sk_sp<GlobalPropertyManager> property_manager_ GUARDED_BY(lock_);
const sk_sp<MarkerStore> marker_store_;
sk_sp<skottie::Animation> animation_;
// The raw byte data is stored for serialization across OOP-R. This is only
// valid if serialization was enabled at construction.
const std::vector<uint8_t> raw_data_;
// Unique id generated for a given animation. This will be unique per
// animation file. 2 animation objects from the same source file will have the
// same value.
const uint32_t id_;
const SkottieResourceMetadataMap image_asset_metadata_;
};
} // namespace
// static
scoped_refptr<SkottieWrapper> SkottieWrapper::CreateSerializable(
std::vector<uint8_t> data) {
base::span<const uint8_t> data_span(data);
return base::WrapRefCounted(
new SkottieWrapperImpl(data_span, std::move(data)));
}
// static
scoped_refptr<SkottieWrapper> SkottieWrapper::CreateNonSerializable(
base::span<const uint8_t> data) {
return base::WrapRefCounted(
new SkottieWrapperImpl(data,
/*owned_data=*/std::vector<uint8_t>()));
}
} // namespace cc