// 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 #include #include #include #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> Node::MutationLog::Flush() { WriterLock lock(write_mutex_); if (!dirty_) { return std::nullopt; } dirty_ = false; auto result = entries_; entries_ = {}; return result; } std::shared_ptr 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 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( 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::MakeFromFlatbuffer(const fb::Scene& scene, Allocator& allocator) { // Unpack textures. std::vector> 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(); 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> 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()); } // Connect children to the root node. for (int child : *scene.children()) { if (child < 0 || static_cast(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>& scene_nodes, const std::vector>& 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(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::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 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) { 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) { 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>& 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> log = mutation_log_.Flush(); if (log.has_value()) { for (const auto& entry : log.value()) { if (auto e = std::get_if(&entry)) { local_transform_ = e->transform; } else if (auto e = std::get_if(&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(&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