| // 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 |