blob: aad7fc67199e701c42ca4731194682f36a8cd8e0 [file] [log] [blame]
// 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/scene/node.h"
#include <inttypes.h>
#include <atomic>
#include <memory>
#include <vector>
#include "flutter/fml/logging.h"
#include "impeller/base/strings.h"
#include "impeller/base/thread.h"
#include "impeller/base/validation.h"
#include "impeller/geometry/matrix.h"
#include "impeller/scene/animation/animation_player.h"
#include "impeller/scene/importer/conversions.h"
#include "impeller/scene/importer/scene_flatbuffers.h"
#include "impeller/scene/mesh.h"
#include "impeller/scene/node.h"
#include "impeller/scene/scene_encoder.h"
namespace impeller {
namespace scene {
static std::atomic_uint64_t kNextNodeID = 0;
void Node::MutationLog::Append(const Entry& entry) {
WriterLock lock(write_mutex_);
dirty_ = true;
entries_.push_back(entry);
}
std::optional<std::vector<Node::MutationLog::Entry>>
Node::MutationLog::Flush() {
WriterLock lock(write_mutex_);
if (!dirty_) {
return std::nullopt;
}
dirty_ = false;
auto result = entries_;
entries_ = {};
return result;
}
std::shared_ptr<Node> Node::MakeFromFlatbuffer(
const fml::Mapping& ipscene_mapping,
Allocator& allocator) {
flatbuffers::Verifier verifier(ipscene_mapping.GetMapping(),
ipscene_mapping.GetSize());
if (!fb::VerifySceneBuffer(verifier)) {
VALIDATION_LOG << "Failed to unpack scene: Scene flatbuffer is invalid.";
return nullptr;
}
return Node::MakeFromFlatbuffer(*fb::GetScene(ipscene_mapping.GetMapping()),
allocator);
}
static std::shared_ptr<Texture> UnpackTextureFromFlatbuffer(
const fb::Texture* iptexture,
Allocator& allocator) {
if (iptexture == nullptr || iptexture->embedded_image() == nullptr ||
iptexture->embedded_image()->bytes() == nullptr) {
return nullptr;
}
auto embedded = iptexture->embedded_image();
uint8_t bytes_per_component = 0;
switch (embedded->component_type()) {
case fb::ComponentType::k8Bit:
bytes_per_component = 1;
break;
case fb::ComponentType::k16Bit:
// bytes_per_component = 2;
FML_LOG(WARNING) << "16 bit textures not yet supported.";
return nullptr;
}
switch (embedded->component_count()) {
case 4:
// RGBA.
break;
case 1:
case 3:
default:
FML_LOG(WARNING) << "Textures with " << embedded->component_count()
<< " components are not supported." << std::endl;
return nullptr;
}
if (embedded->bytes()->size() != bytes_per_component *
embedded->component_count() *
embedded->width() * embedded->height()) {
FML_LOG(WARNING) << "Embedded texture has an unexpected size. Skipping."
<< std::endl;
return nullptr;
}
auto image_mapping = std::make_shared<fml::NonOwnedMapping>(
embedded->bytes()->Data(), embedded->bytes()->size());
auto texture_descriptor = TextureDescriptor{};
texture_descriptor.storage_mode = StorageMode::kHostVisible;
texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
texture_descriptor.size = ISize(embedded->width(), embedded->height());
// TODO(bdero): Generate mipmaps for embedded textures.
texture_descriptor.mip_count = 1u;
auto texture = allocator.CreateTexture(texture_descriptor);
if (!texture) {
FML_LOG(ERROR) << "Could not allocate texture.";
return nullptr;
}
auto uploaded = texture->SetContents(image_mapping);
if (!uploaded) {
FML_LOG(ERROR) << "Could not upload texture to device memory.";
return nullptr;
}
return texture;
}
std::shared_ptr<Node> Node::MakeFromFlatbuffer(const fb::Scene& scene,
Allocator& allocator) {
// Unpack textures.
std::vector<std::shared_ptr<Texture>> textures;
if (scene.textures()) {
for (const auto iptexture : *scene.textures()) {
// The elements of the unpacked texture array must correspond exactly with
// the ipscene texture array. So if a texture is empty or invalid, a
// nullptr is inserted as a placeholder.
textures.push_back(UnpackTextureFromFlatbuffer(iptexture, allocator));
}
}
auto result = std::make_shared<Node>();
result->SetLocalTransform(importer::ToMatrix(*scene.transform()));
if (!scene.nodes() || !scene.children()) {
return result; // The scene is empty.
}
// Initialize nodes for unpacking the entire scene.
std::vector<std::shared_ptr<Node>> scene_nodes;
scene_nodes.reserve(scene.nodes()->size());
for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) {
scene_nodes.push_back(std::make_shared<Node>());
}
// Connect children to the root node.
for (int child : *scene.children()) {
if (child < 0 || static_cast<size_t>(child) >= scene_nodes.size()) {
VALIDATION_LOG << "Scene child index out of range.";
continue;
}
result->AddChild(scene_nodes[child]);
}
// Unpack each node.
for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) {
scene_nodes[node_i]->UnpackFromFlatbuffer(*scene.nodes()->Get(node_i),
scene_nodes, textures, allocator);
}
// Unpack animations.
if (scene.animations()) {
for (const auto animation : *scene.animations()) {
if (auto out_animation =
Animation::MakeFromFlatbuffer(*animation, scene_nodes)) {
result->animations_.push_back(out_animation);
}
}
}
return result;
}
void Node::UnpackFromFlatbuffer(
const fb::Node& source_node,
const std::vector<std::shared_ptr<Node>>& scene_nodes,
const std::vector<std::shared_ptr<Texture>>& textures,
Allocator& allocator) {
name_ = source_node.name()->str();
SetLocalTransform(importer::ToMatrix(*source_node.transform()));
/// Meshes.
if (source_node.mesh_primitives()) {
Mesh mesh;
for (const auto* primitives : *source_node.mesh_primitives()) {
auto geometry = Geometry::MakeFromFlatbuffer(*primitives, allocator);
auto material =
primitives->material()
? Material::MakeFromFlatbuffer(*primitives->material(), textures)
: Material::MakeUnlit();
mesh.AddPrimitive({std::move(geometry), std::move(material)});
}
SetMesh(std::move(mesh));
}
/// Child nodes.
if (source_node.children()) {
// Wire up graph connections.
for (int child : *source_node.children()) {
if (child < 0 || static_cast<size_t>(child) >= scene_nodes.size()) {
VALIDATION_LOG << "Node child index out of range.";
continue;
}
AddChild(scene_nodes[child]);
}
}
/// Skin.
if (source_node.skin()) {
skin_ = Skin::MakeFromFlatbuffer(*source_node.skin(), scene_nodes);
}
}
Node::Node() : name_(SPrintF("__node%" PRIu64, kNextNodeID++)){};
Node::~Node() = default;
Mesh::Mesh(Mesh&& mesh) = default;
Mesh& Mesh::operator=(Mesh&& mesh) = default;
const std::string& Node::GetName() const {
return name_;
}
void Node::SetName(const std::string& new_name) {
name_ = new_name;
}
Node* Node::GetParent() const {
return parent_;
}
std::shared_ptr<Node> Node::FindChildByName(
const std::string& name,
bool exclude_animation_players) const {
for (auto& child : children_) {
if (exclude_animation_players && child->animation_player_.has_value()) {
continue;
}
if (child->GetName() == name) {
return child;
}
if (auto found = child->FindChildByName(name)) {
return found;
}
}
return nullptr;
}
std::shared_ptr<Animation> Node::FindAnimationByName(
const std::string& name) const {
for (const auto& animation : animations_) {
if (animation->GetName() == name) {
return animation;
}
}
return nullptr;
}
AnimationClip* Node::AddAnimation(const std::shared_ptr<Animation>& animation) {
if (!animation_player_.has_value()) {
animation_player_ = AnimationPlayer();
}
return animation_player_->AddAnimation(animation, this);
}
void Node::SetLocalTransform(Matrix transform) {
local_transform_ = transform;
}
Matrix Node::GetLocalTransform() const {
return local_transform_;
}
void Node::SetGlobalTransform(Matrix transform) {
Matrix inverse_global_transform =
parent_ ? parent_->GetGlobalTransform().Invert() : Matrix();
local_transform_ = inverse_global_transform * transform;
}
Matrix Node::GetGlobalTransform() const {
if (parent_) {
return parent_->GetGlobalTransform() * local_transform_;
}
return local_transform_;
}
bool Node::AddChild(std::shared_ptr<Node> node) {
if (!node) {
VALIDATION_LOG << "Cannot add null child to node.";
return false;
}
// TODO(bdero): Figure out a better paradigm/rules for nodes with multiple
// parents. We should probably disallow this, make deep
// copying of nodes cheap and easy, add mesh instancing, etc.
// Today, the parent link is only used for skin posing, and so
// it's reasonable to not have a check and allow multi-parenting.
// Even still, there should still be some kind of cycle
// prevention/detection, ideally at the protocol level.
//
// if (node->parent_ != nullptr) {
// VALIDATION_LOG
// << "Cannot add a node as a child which already has a parent.";
// return false;
// }
node->parent_ = this;
children_.push_back(std::move(node));
return true;
}
std::vector<std::shared_ptr<Node>>& Node::GetChildren() {
return children_;
}
void Node::SetMesh(Mesh mesh) {
mesh_ = std::move(mesh);
}
Mesh& Node::GetMesh() {
return mesh_;
}
void Node::SetIsJoint(bool is_joint) {
is_joint_ = is_joint;
}
bool Node::IsJoint() const {
return is_joint_;
}
bool Node::Render(SceneEncoder& encoder,
Allocator& allocator,
const Matrix& parent_transform) {
std::optional<std::vector<MutationLog::Entry>> log = mutation_log_.Flush();
if (log.has_value()) {
for (const auto& entry : log.value()) {
if (auto e = std::get_if<MutationLog::SetTransformEntry>(&entry)) {
local_transform_ = e->transform;
} else if (auto e =
std::get_if<MutationLog::SetAnimationStateEntry>(&entry)) {
AnimationClip* clip =
animation_player_.has_value()
? animation_player_->GetClip(e->animation_name)
: nullptr;
if (!clip) {
auto animation = FindAnimationByName(e->animation_name);
if (!animation) {
continue;
}
clip = AddAnimation(animation);
if (!clip) {
continue;
}
}
clip->SetPlaying(e->playing);
clip->SetLoop(e->loop);
clip->SetWeight(e->weight);
clip->SetPlaybackTimeScale(e->time_scale);
} else if (auto e =
std::get_if<MutationLog::SeekAnimationEntry>(&entry)) {
AnimationClip* clip =
animation_player_.has_value()
? animation_player_->GetClip(e->animation_name)
: nullptr;
if (!clip) {
auto animation = FindAnimationByName(e->animation_name);
if (!animation) {
continue;
}
clip = AddAnimation(animation);
if (!clip) {
continue;
}
}
clip->Seek(SecondsF(e->time));
}
}
}
if (animation_player_.has_value()) {
animation_player_->Update();
}
Matrix transform = parent_transform * local_transform_;
mesh_.Render(encoder, transform,
skin_ ? skin_->GetJointsTexture(allocator) : nullptr);
for (auto& child : children_) {
if (!child->Render(encoder, allocator, transform)) {
return false;
}
}
return true;
}
void Node::AddMutation(const MutationLog::Entry& entry) {
mutation_log_.Append(entry);
}
} // namespace scene
} // namespace impeller