| Flutter Impeller |

node.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/scene/node.h"
6
7 #include <inttypes.h>
8 #include <atomic>
9 #include <memory>
10 #include <vector>
11
12 #include "flutter/fml/logging.h"
13 #include "impeller/base/strings.h"
14 #include "impeller/base/thread.h"
15 #include "impeller/base/validation.h"
16 #include "impeller/geometry/matrix.h"
17 #include "impeller/image/decompressed_image.h"
18 #include "impeller/scene/animation/animation_player.h"
19 #include "impeller/scene/importer/conversions.h"
20 #include "impeller/scene/importer/scene_flatbuffers.h"
21 #include "impeller/scene/mesh.h"
22 #include "impeller/scene/node.h"
23 #include "impeller/scene/scene_encoder.h"
24
25 namespace impeller {
26 namespace scene {
27
28 static std::atomic_uint64_t kNextNodeID = 0;
29
30 void Node::MutationLog::Append(const Entry& entry) {
31 WriterLock lock(write_mutex_);
32 dirty_ = true;
33 entries_.push_back(entry);
34 }
35
36 std::optional<std::vector<Node::MutationLog::Entry>>
37 Node::MutationLog::Flush() {
38 WriterLock lock(write_mutex_);
39 if (!dirty_) {
40 return std::nullopt;
41 }
42 dirty_ = false;
43 auto result = entries_;
44 entries_ = {};
45 return result;
46 }
47
48 std::shared_ptr<Node> Node::MakeFromFlatbuffer(
49 const fml::Mapping& ipscene_mapping,
50 Allocator& allocator) {
51 flatbuffers::Verifier verifier(ipscene_mapping.GetMapping(),
52 ipscene_mapping.GetSize());
53 if (!fb::VerifySceneBuffer(verifier)) {
54 VALIDATION_LOG << "Failed to unpack scene: Scene flatbuffer is invalid.";
55 return nullptr;
56 }
57
58 return Node::MakeFromFlatbuffer(*fb::GetScene(ipscene_mapping.GetMapping()),
59 allocator);
60 }
61
62 static std::shared_ptr<Texture> UnpackTextureFromFlatbuffer(
63 const fb::Texture* iptexture,
64 Allocator& allocator) {
65 if (iptexture == nullptr || iptexture->embedded_image() == nullptr ||
66 iptexture->embedded_image()->bytes() == nullptr) {
67 return nullptr;
68 }
69
70 auto embedded = iptexture->embedded_image();
71
72 uint8_t bytes_per_component = 0;
73 switch (embedded->component_type()) {
74 case fb::ComponentType::k8Bit:
75 bytes_per_component = 1;
76 break;
77 case fb::ComponentType::k16Bit:
78 // bytes_per_component = 2;
79 FML_LOG(WARNING) << "16 bit textures not yet supported.";
80 return nullptr;
81 }
82
83 DecompressedImage::Format format;
84 switch (embedded->component_count()) {
85 case 1:
86 format = DecompressedImage::Format::kGrey;
87 break;
88 case 3:
89 format = DecompressedImage::Format::kRGB;
90 break;
91 case 4:
92 format = DecompressedImage::Format::kRGBA;
93 break;
94 default:
95 FML_LOG(WARNING) << "Textures with " << embedded->component_count()
96 << " components are not supported." << std::endl;
97 return nullptr;
98 }
99 if (embedded->bytes()->size() != bytes_per_component *
100 embedded->component_count() *
101 embedded->width() * embedded->height()) {
102 FML_LOG(WARNING) << "Embedded texture has an unexpected size. Skipping."
103 << std::endl;
104 return nullptr;
105 }
106
107 auto image_mapping = std::make_shared<fml::NonOwnedMapping>(
108 embedded->bytes()->Data(), embedded->bytes()->size());
109 auto decompressed_image =
110 DecompressedImage(ISize(embedded->width(), embedded->height()), format,
111 image_mapping)
112 .ConvertToRGBA();
113
114 auto texture_descriptor = TextureDescriptor{};
115 texture_descriptor.storage_mode = StorageMode::kHostVisible;
116 texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
117 texture_descriptor.size = decompressed_image.GetSize();
118 // TODO(bdero): Generate mipmaps for embedded textures.
119 texture_descriptor.mip_count = 1u;
120
121 auto texture = allocator.CreateTexture(texture_descriptor);
122 if (!texture) {
123 FML_LOG(ERROR) << "Could not allocate texture.";
124 return nullptr;
125 }
126
127 auto uploaded = texture->SetContents(decompressed_image.GetAllocation());
128 if (!uploaded) {
129 FML_LOG(ERROR) << "Could not upload texture to device memory.";
130 return nullptr;
131 }
132
133 return texture;
134 }
135
136 std::shared_ptr<Node> Node::MakeFromFlatbuffer(const fb::Scene& scene,
137 Allocator& allocator) {
138 // Unpack textures.
139 std::vector<std::shared_ptr<Texture>> textures;
140 if (scene.textures()) {
141 for (const auto iptexture : *scene.textures()) {
142 // The elements of the unpacked texture array must correspond exactly with
143 // the ipscene texture array. So if a texture is empty or invalid, a
144 // nullptr is inserted as a placeholder.
145 textures.push_back(UnpackTextureFromFlatbuffer(iptexture, allocator));
146 }
147 }
148
149 auto result = std::make_shared<Node>();
150 result->SetLocalTransform(importer::ToMatrix(*scene.transform()));
151
152 if (!scene.nodes() || !scene.children()) {
153 return result; // The scene is empty.
154 }
155
156 // Initialize nodes for unpacking the entire scene.
157 std::vector<std::shared_ptr<Node>> scene_nodes;
158 scene_nodes.reserve(scene.nodes()->size());
159 for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) {
160 scene_nodes.push_back(std::make_shared<Node>());
161 }
162
163 // Connect children to the root node.
164 for (int child : *scene.children()) {
165 if (child < 0 || static_cast<size_t>(child) >= scene_nodes.size()) {
166 VALIDATION_LOG << "Scene child index out of range.";
167 continue;
168 }
169 result->AddChild(scene_nodes[child]);
170 }
171
172 // Unpack each node.
173 for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) {
174 scene_nodes[node_i]->UnpackFromFlatbuffer(*scene.nodes()->Get(node_i),
175 scene_nodes, textures, allocator);
176 }
177
178 // Unpack animations.
179 if (scene.animations()) {
180 for (const auto animation : *scene.animations()) {
181 if (auto out_animation =
182 Animation::MakeFromFlatbuffer(*animation, scene_nodes)) {
183 result->animations_.push_back(out_animation);
184 }
185 }
186 }
187
188 return result;
189 }
190
191 void Node::UnpackFromFlatbuffer(
192 const fb::Node& source_node,
193 const std::vector<std::shared_ptr<Node>>& scene_nodes,
194 const std::vector<std::shared_ptr<Texture>>& textures,
195 Allocator& allocator) {
196 name_ = source_node.name()->str();
197 SetLocalTransform(importer::ToMatrix(*source_node.transform()));
198
199 /// Meshes.
200
201 if (source_node.mesh_primitives()) {
202 Mesh mesh;
203 for (const auto* primitives : *source_node.mesh_primitives()) {
204 auto geometry = Geometry::MakeFromFlatbuffer(*primitives, allocator);
205 auto material =
206 primitives->material()
207 ? Material::MakeFromFlatbuffer(*primitives->material(), textures)
208 : Material::MakeUnlit();
209 mesh.AddPrimitive({std::move(geometry), std::move(material)});
210 }
211 SetMesh(std::move(mesh));
212 }
213
214 /// Child nodes.
215
216 if (source_node.children()) {
217 // Wire up graph connections.
218 for (int child : *source_node.children()) {
219 if (child < 0 || static_cast<size_t>(child) >= scene_nodes.size()) {
220 VALIDATION_LOG << "Node child index out of range.";
221 continue;
222 }
223 AddChild(scene_nodes[child]);
224 }
225 }
226
227 /// Skin.
228
229 if (source_node.skin()) {
230 skin_ = Skin::MakeFromFlatbuffer(*source_node.skin(), scene_nodes);
231 }
232 }
233
234 Node::Node() : name_(SPrintF("__node%" PRIu64, kNextNodeID++)){};
235
236 Node::~Node() = default;
237
238 Mesh::Mesh(Mesh&& mesh) = default;
239
240 Mesh& Mesh::operator=(Mesh&& mesh) = default;
241
242 const std::string& Node::GetName() const {
243 return name_;
244 }
245
246 void Node::SetName(const std::string& new_name) {
247 name_ = new_name;
248 }
249
250 Node* Node::GetParent() const {
251 return parent_;
252 }
253
254 std::shared_ptr<Node> Node::FindChildByName(
255 const std::string& name,
256 bool exclude_animation_players) const {
257 for (auto& child : children_) {
258 if (exclude_animation_players && child->animation_player_.has_value()) {
259 continue;
260 }
261 if (child->GetName() == name) {
262 return child;
263 }
264 if (auto found = child->FindChildByName(name)) {
265 return found;
266 }
267 }
268 return nullptr;
269 }
270
271 std::shared_ptr<Animation> Node::FindAnimationByName(
272 const std::string& name) const {
273 for (const auto& animation : animations_) {
274 if (animation->GetName() == name) {
275 return animation;
276 }
277 }
278 return nullptr;
279 }
280
281 AnimationClip* Node::AddAnimation(const std::shared_ptr<Animation>& animation) {
282 if (!animation_player_.has_value()) {
283 animation_player_ = AnimationPlayer();
284 }
285 return animation_player_->AddAnimation(animation, this);
286 }
287
288 void Node::SetLocalTransform(Matrix transform) {
289 local_transform_ = transform;
290 }
291
292 Matrix Node::GetLocalTransform() const {
293 return local_transform_;
294 }
295
296 void Node::SetGlobalTransform(Matrix transform) {
297 Matrix inverse_global_transform =
298 parent_ ? parent_->GetGlobalTransform().Invert() : Matrix();
299
300 local_transform_ = inverse_global_transform * transform;
301 }
302
303 Matrix Node::GetGlobalTransform() const {
304 if (parent_) {
305 return parent_->GetGlobalTransform() * local_transform_;
306 }
307 return local_transform_;
308 }
309
310 bool Node::AddChild(std::shared_ptr<Node> node) {
311 if (!node) {
312 VALIDATION_LOG << "Cannot add null child to node.";
313 return false;
314 }
315
316 // TODO(bdero): Figure out a better paradigm/rules for nodes with multiple
317 // parents. We should probably disallow this, make deep
318 // copying of nodes cheap and easy, add mesh instancing, etc.
319 // Today, the parent link is only used for skin posing, and so
320 // it's reasonable to not have a check and allow multi-parenting.
321 // Even still, there should still be some kind of cycle
322 // prevention/detection, ideally at the protocol level.
323 //
324 // if (node->parent_ != nullptr) {
325 // VALIDATION_LOG
326 // << "Cannot add a node as a child which already has a parent.";
327 // return false;
328 // }
329 node->parent_ = this;
330 children_.push_back(std::move(node));
331
332 return true;
333 }
334
335 std::vector<std::shared_ptr<Node>>& Node::GetChildren() {
336 return children_;
337 }
338
339 void Node::SetMesh(Mesh mesh) {
340 mesh_ = std::move(mesh);
341 }
342
343 Mesh& Node::GetMesh() {
344 return mesh_;
345 }
346
347 void Node::SetIsJoint(bool is_joint) {
348 is_joint_ = is_joint;
349 }
350
351 bool Node::IsJoint() const {
352 return is_joint_;
353 }
354
355 bool Node::Render(SceneEncoder& encoder,
356 Allocator& allocator,
357 const Matrix& parent_transform) {
358 std::optional<std::vector<MutationLog::Entry>> log = mutation_log_.Flush();
359 if (log.has_value()) {
360 for (const auto& entry : log.value()) {
361 if (auto e = std::get_if<MutationLog::SetTransformEntry>(&entry)) {
362 local_transform_ = e->transform;
363 } else if (auto e =
364 std::get_if<MutationLog::SetAnimationStateEntry>(&entry)) {
365 AnimationClip* clip =
366 animation_player_.has_value()
367 ? animation_player_->GetClip(e->animation_name)
368 : nullptr;
369 if (!clip) {
370 auto animation = FindAnimationByName(e->animation_name);
371 if (!animation) {
372 continue;
373 }
374 clip = AddAnimation(animation);
375 if (!clip) {
376 continue;
377 }
378 }
379
380 clip->SetPlaying(e->playing);
381 clip->SetLoop(e->loop);
382 clip->SetWeight(e->weight);
383 clip->SetPlaybackTimeScale(e->time_scale);
384 } else if (auto e =
385 std::get_if<MutationLog::SeekAnimationEntry>(&entry)) {
386 AnimationClip* clip =
387 animation_player_.has_value()
388 ? animation_player_->GetClip(e->animation_name)
389 : nullptr;
390 if (!clip) {
391 auto animation = FindAnimationByName(e->animation_name);
392 if (!animation) {
393 continue;
394 }
395 clip = AddAnimation(animation);
396 if (!clip) {
397 continue;
398 }
399 }
400
401 clip->Seek(SecondsF(e->time));
402 }
403 }
404 }
405
406 if (animation_player_.has_value()) {
407 animation_player_->Update();
408 }
409
410 Matrix transform = parent_transform * local_transform_;
411 mesh_.Render(encoder, transform,
412 skin_ ? skin_->GetJointsTexture(allocator) : nullptr);
413
414 for (auto& child : children_) {
415 if (!child->Render(encoder, allocator, transform)) {
416 return false;
417 }
418 }
419 return true;
420 }
421
422 void Node::AddMutation(const MutationLog::Entry& entry) {
423 mutation_log_.Append(entry);
424 }
425
426 } // namespace scene
427 } // namespace impeller
impeller::DecompressedImage
Definition: decompressed_image.h:17
impeller::scene::Node::Node
Node()
Definition: node.cc:234
impeller::scene::Node::GetLocalTransform
Matrix GetLocalTransform() const
Definition: node.cc:292
impeller::scene::Node::GetParent
Node * GetParent() const
Definition: node.cc:250
impeller::DecompressedImage::Format::kRGBA
@ kRGBA
impeller::scene::Mesh
Definition: mesh.h:21
impeller::scene::Node::FindChildByName
std::shared_ptr< Node > FindChildByName(const std::string &name, bool exclude_animation_players=false) const
Definition: node.cc:254
impeller::scene::Node::~Node
~Node()
impeller::scene::AnimationClip::SetPlaybackTimeScale
void SetPlaybackTimeScale(Scalar playback_speed)
Sets the animation playback speed. Negative values make the clip play in reverse.
Definition: animation_clip.cc:61
impeller::scene::Mesh::Mesh
Mesh()
impeller::scene::Node::SetIsJoint
void SetIsJoint(bool is_joint)
Definition: node.cc:347
impeller::DecompressedImage::Format::kGrey
@ kGrey
impeller::WriterLock
Definition: thread.h:116
impeller::PixelFormat::kR8G8B8A8UNormInt
@ kR8G8B8A8UNormInt
animation_player.h
impeller::scene::AnimationClip::SetLoop
void SetLoop(bool looping)
Definition: animation_clip.cc:53
impeller::scene::Skin::MakeFromFlatbuffer
static std::unique_ptr< Skin > MakeFromFlatbuffer(const fb::Skin &skin, const std::vector< std::shared_ptr< Node >> &scene_nodes)
Definition: skin.cc:19
impeller::scene::Node::SetMesh
void SetMesh(Mesh mesh)
Definition: node.cc:339
impeller::StorageMode::kHostVisible
@ kHostVisible
impeller::Allocator::CreateTexture
std::shared_ptr< Texture > CreateTexture(const TextureDescriptor &desc)
Definition: allocator.cc:49
validation.h
impeller::scene::AnimationClip
Definition: animation_clip.h:22
conversions.h
impeller::scene::Animation::MakeFromFlatbuffer
static std::shared_ptr< Animation > MakeFromFlatbuffer(const fb::Animation &animation, const std::vector< std::shared_ptr< Node >> &scene_nodes)
Definition: animation.cc:19
impeller::scene::Node::GetName
const std::string & GetName() const
Definition: node.cc:242
scene_encoder.h
impeller::scene::Node::GetChildren
std::vector< std::shared_ptr< Node > > & GetChildren()
Definition: node.cc:335
impeller::scene::Node::SetName
void SetName(const std::string &new_name)
Definition: node.cc:246
impeller::scene::Mesh::Render
bool Render(SceneEncoder &encoder, const Matrix &transform, const std::shared_ptr< Texture > &joints) const
Definition: mesh.cc:36
matrix.h
impeller::DecompressedImage::ConvertToRGBA
DecompressedImage ConvertToRGBA() const
Definition: decompressed_image.cc:62
impeller::SecondsF
std::chrono::duration< float > SecondsF
Definition: timing.h:13
impeller::scene::Node::FindAnimationByName
std::shared_ptr< Animation > FindAnimationByName(const std::string &name) const
Definition: node.cc:271
node.h
impeller::SPrintF
std::string SPrintF(const char *format,...)
Definition: strings.cc:12
impeller::scene::UnpackTextureFromFlatbuffer
static std::shared_ptr< Texture > UnpackTextureFromFlatbuffer(const fb::Texture *iptexture, Allocator &allocator)
Definition: node.cc:62
impeller::scene::Mesh::AddPrimitive
void AddPrimitive(Primitive mesh_)
Definition: mesh.cc:21
impeller::scene::kNextNodeID
static std::atomic_uint64_t kNextNodeID
Definition: node.cc:28
impeller::DecompressedImage::Format::kRGB
@ kRGB
impeller::Allocator
An object that allocates device memory.
Definition: allocator.h:22
impeller::scene::Node::GetMesh
Mesh & GetMesh()
Definition: node.cc:343
impeller::scene::AnimationClip::SetWeight
void SetWeight(Scalar weight)
Definition: animation_clip.cc:69
impeller::scene::Geometry::MakeFromFlatbuffer
static std::shared_ptr< Geometry > MakeFromFlatbuffer(const fb::MeshPrimitive &mesh, Allocator &allocator)
Definition: geometry.cc:49
mesh.h
impeller::scene::AnimationPlayer
Definition: animation_player.h:26
strings.h
impeller::scene::Material::MakeFromFlatbuffer
static std::unique_ptr< Material > MakeFromFlatbuffer(const fb::Material &material, const std::vector< std::shared_ptr< Texture >> &textures)
Definition: material.cc:26
impeller::Matrix::Invert
Matrix Invert() const
Definition: matrix.cc:97
impeller::scene::Node::SetGlobalTransform
void SetGlobalTransform(Matrix transform)
Definition: node.cc:296
impeller::scene::Node::AddChild
bool AddChild(std::shared_ptr< Node > child)
Definition: node.cc:310
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
decompressed_image.h
impeller::scene::Node::Render
bool Render(SceneEncoder &encoder, Allocator &allocator, const Matrix &parent_transform)
Definition: node.cc:355
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:67
impeller::scene::Node::MutationLog::Entry
std::variant< SetTransformEntry, SetAnimationStateEntry, SeekAnimationEntry > Entry
Definition: node.h:52
impeller::scene::Node::IsJoint
bool IsJoint() const
Definition: node.cc:351
impeller::scene::Node::AddAnimation
AnimationClip * AddAnimation(const std::shared_ptr< Animation > &animation)
Definition: node.cc:281
impeller::scene::Node
Definition: node.h:30
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:38
impeller::scene::Node::AddMutation
void AddMutation(const MutationLog::Entry &entry)
Definition: node.cc:422
impeller::DecompressedImage::Format
Format
Definition: decompressed_image.h:19
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:37
impeller::scene::SceneEncoder
Definition: scene_encoder.h:30
impeller::scene::Node::SetLocalTransform
void SetLocalTransform(Matrix transform)
Definition: node.cc:288
impeller::scene::AnimationClip::Seek
void Seek(SecondsF time)
Move the animation to the specified time. The given time is clamped to the animation's playback range...
Definition: animation_clip.cc:77
impeller::scene::Node::MakeFromFlatbuffer
static std::shared_ptr< Node > MakeFromFlatbuffer(const fml::Mapping &ipscene_mapping, Allocator &allocator)
Definition: node.cc:48
impeller::scene::AnimationClip::SetPlaying
void SetPlaying(bool playing)
Definition: animation_clip.cc:32
thread.h
impeller
Definition: aiks_context.cc:10
impeller::scene::Node::MutationLog::Append
void Append(const Entry &entry)
Definition: node.cc:30
impeller::scene::Mesh::operator=
Mesh & operator=(Mesh &&mesh)
impeller::scene::Material
Definition: material.h:27
impeller::scene::Node::GetGlobalTransform
Matrix GetGlobalTransform() const
Definition: node.cc:303
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::scene::importer::ToMatrix
Matrix ToMatrix(const std::vector< double > &m)
Definition: conversions.cc:15