DALi Version 2.2.4
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-scene3d / utc-Dali-Gltf2Loader.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // Enable debug log for test coverage
19 #define DEBUG_ENABLED 1
20
21 #include <dali-test-suite-utils.h>
22 #include <string_view>
23 #include "dali-scene3d/public-api/loader/gltf2-loader.h"
24 #include "dali-scene3d/public-api/loader/load-result.h"
25 #include "dali-scene3d/public-api/loader/resource-bundle.h"
26 #include "dali-scene3d/public-api/loader/scene-definition.h"
27 #include "dali-scene3d/public-api/loader/shader-definition-factory.h"
28
29 using namespace Dali;
30 using namespace Dali::Scene3D::Loader;
31
32 #define DALI_TEST_THROW(expression, exception, predicate) \
33   {                                                       \
34     bool daliTestThrowSuccess__ = false;                  \
35     try                                                   \
36     {                                                     \
37       do                                                  \
38       {                                                   \
39         expression;                                       \
40       } while(0);                                         \
41       printf("No exception was thrown.\n");               \
42     }                                                     \
43     catch(std::decay<exception>::type & ex)               \
44     {                                                     \
45       daliTestThrowSuccess__ = predicate(ex);             \
46     }                                                     \
47     catch(...)                                            \
48     {                                                     \
49       printf("Wrong type of exception thrown.\n");        \
50     }                                                     \
51     DALI_TEST_CHECK(daliTestThrowSuccess__);              \
52   }
53
54 namespace
55 {
56 struct Context
57 {
58   ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {
59     return TEST_RESOURCE_DIR "/";
60   };
61
62   ResourceBundle  resources;
63   SceneDefinition scene;
64
65   std::vector<AnimationDefinition>      animations;
66   std::vector<AnimationGroupDefinition> animationGroups;
67   std::vector<CameraParameters>         cameras;
68   std::vector<LightParameters>          lights;
69
70   LoadResult loadResult{
71     resources,
72     scene,
73     animations,
74     animationGroups,
75     cameras,
76     lights};
77 };
78
79 struct ExceptionMessageStartsWith
80 {
81   const std::string_view expected;
82
83   bool operator()(const std::runtime_error& e)
84   {
85     const bool success = (0 == strncmp(e.what(), expected.data(), expected.size()));
86     if(!success)
87     {
88       printf("Expected: %s, got: %s.\n", expected.data(), e.what());
89     }
90     return success;
91   }
92 };
93
94 } // namespace
95
96 int UtcDaliGltfLoaderFailedToLoad(void)
97 {
98   Context ctx;
99
100   ShaderDefinitionFactory sdf;
101   sdf.SetResources(ctx.resources);
102
103   DALI_TEST_THROW(LoadGltfScene("non-existent.gltf", sdf, ctx.loadResult),
104                   std::runtime_error,
105                   ExceptionMessageStartsWith{"Failed to load"});
106
107   DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size());
108   DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount());
109
110   DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size());
111   DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size());
112   DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size());
113   DALI_TEST_EQUAL(0, ctx.resources.mShaders.size());
114   DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size());
115
116   DALI_TEST_EQUAL(0, ctx.cameras.size());
117   DALI_TEST_EQUAL(0, ctx.lights.size());
118   DALI_TEST_EQUAL(0, ctx.animations.size());
119   DALI_TEST_EQUAL(0, ctx.animationGroups.size());
120
121   END_TEST;
122 }
123
124 int UtcDaliGltfLoaderFailedToParse(void)
125 {
126   Context ctx;
127
128   ShaderDefinitionFactory sdf;
129   sdf.SetResources(ctx.resources);
130
131   DALI_TEST_THROW(LoadGltfScene(TEST_RESOURCE_DIR "/invalid.gltf", sdf, ctx.loadResult),
132                   std::runtime_error,
133                   ExceptionMessageStartsWith{"Failed to parse"});
134
135   DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size());
136   DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount());
137
138   DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size());
139   DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size());
140   DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size());
141   DALI_TEST_EQUAL(0, ctx.resources.mShaders.size());
142   DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size());
143
144   DALI_TEST_EQUAL(0, ctx.cameras.size());
145   DALI_TEST_EQUAL(0, ctx.lights.size());
146   DALI_TEST_EQUAL(0, ctx.animations.size());
147   DALI_TEST_EQUAL(0, ctx.animationGroups.size());
148
149   END_TEST;
150 }
151
152 int UtcDaliGltfLoaderSuccess1(void)
153 {
154   Context ctx;
155
156   ShaderDefinitionFactory sdf;
157   sdf.SetResources(ctx.resources);
158
159   LoadGltfScene(TEST_RESOURCE_DIR "/AnimatedCube.gltf", sdf, ctx.loadResult);
160
161   DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
162   DALI_TEST_EQUAL(6u, ctx.scene.GetNodeCount());
163
164   // Default envmap is used
165   DALI_TEST_EQUAL(1u, ctx.resources.mEnvironmentMaps.size());
166
167   auto& materials = ctx.resources.mMaterials;
168   DALI_TEST_EQUAL(2u, materials.size());
169   const MaterialDefinition materialGroundTruth[]{
170     {MaterialDefinition::ALBEDO | MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION |
171        MaterialDefinition::NORMAL | MaterialDefinition::SPECULAR | MaterialDefinition::SPECULAR_COLOR |
172        (0x80 << MaterialDefinition::ALPHA_CUTOFF_SHIFT),
173      0,
174      Color::WHITE,
175      1.f,
176      0.f,
177      Vector4(1.000, 0.766, 0.336, 1.0),
178      1.f,
179      1.f,
180      Vector3(0.2, 0.1, 0.0),
181      0.0f,
182      0.5f,
183      Vector3(0, 0, 1),
184      true,
185      false,
186      true,
187      false,
188      {
189        {MaterialDefinition::ALBEDO,
190         {"AnimatedCube_BaseColor.png",
191          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
192        {MaterialDefinition::NORMAL,
193         {"AnimatedCube_BaseColor.png",
194          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
195        {MaterialDefinition::OCCLUSION,
196         {"AnimatedCube_BaseColor.png",
197          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
198        {MaterialDefinition::EMISSIVE,
199         {"AnimatedCube_BaseColor.png",
200          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
201        {MaterialDefinition::SPECULAR,
202         {"AnimatedCube_BaseColor.png",
203          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
204        {MaterialDefinition::SPECULAR_COLOR,
205         {"AnimatedCube_BaseColor.png",
206          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
207      }},
208     {MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS |
209        MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION |
210        MaterialDefinition::NORMAL | MaterialDefinition::GLTF_CHANNELS,
211      0,
212      Color::WHITE,
213      1.f,
214      0.f,
215      Vector4(1.000, 0.766, 0.336, 1.0),
216      1.f,
217      1.f,
218      Vector3(0.2, 0.1, 0.0),
219      0.04f,
220      1.0f,
221      Vector3::ONE,
222      true,
223      true,
224      true,
225      false,
226      {
227        {MaterialDefinition::ALBEDO,
228         {"AnimatedCube_BaseColor.png",
229          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
230        {MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS | MaterialDefinition::GLTF_CHANNELS,
231         {"AnimatedCube_MetallicRoughness.png",
232          SamplerFlags::Encode(FilterMode::NEAREST_MIPMAP_LINEAR, FilterMode::NEAREST, WrapMode::CLAMP_TO_EDGE, WrapMode::MIRRORED_REPEAT)}},
233        {MaterialDefinition::NORMAL,
234         {"AnimatedCube_BaseColor.png",
235          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
236        {MaterialDefinition::OCCLUSION,
237         {"AnimatedCube_BaseColor.png",
238          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
239        {MaterialDefinition::EMISSIVE,
240         {"AnimatedCube_BaseColor.png",
241          SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT)}},
242      }},
243   };
244
245   auto iMaterial = materials.begin();
246   for(auto& m : materialGroundTruth)
247   {
248     printf("material %ld\n", iMaterial - materials.begin());
249     auto& md = iMaterial->first;
250     DALI_TEST_EQUAL(md.mFlags, m.mFlags);
251     DALI_TEST_EQUAL(md.mEnvironmentIdx, m.mEnvironmentIdx);
252     DALI_TEST_EQUAL(md.mColor, m.mColor);
253     DALI_TEST_EQUAL(md.mMetallic, m.mMetallic);
254     DALI_TEST_EQUAL(md.mRoughness, m.mRoughness);
255     DALI_TEST_EQUAL(md.mBaseColorFactor, m.mBaseColorFactor);
256     DALI_TEST_EQUAL(md.mNormalScale, m.mNormalScale);
257     DALI_TEST_EQUAL(md.mOcclusionStrength, m.mOcclusionStrength);
258     DALI_TEST_EQUAL(md.mEmissiveFactor, m.mEmissiveFactor);
259     DALI_TEST_EQUAL(md.mDielectricSpecular, m.mDielectricSpecular);
260     DALI_TEST_EQUAL(md.mSpecularFactor, m.mSpecularFactor);
261     DALI_TEST_EQUAL(md.mSpecularColorFactor, m.mSpecularColorFactor);
262     DALI_TEST_EQUAL(md.mNeedAlbedoTexture, m.mNeedAlbedoTexture);
263     DALI_TEST_EQUAL(md.mNeedMetallicRoughnessTexture, m.mNeedMetallicRoughnessTexture);
264     DALI_TEST_EQUAL(md.mNeedNormalTexture, m.mNeedNormalTexture);
265
266     DALI_TEST_EQUAL(md.mTextureStages.size(), m.mTextureStages.size());
267     auto iTexture = md.mTextureStages.begin();
268     for(auto& ts : m.mTextureStages)
269     {
270       printf("texture %ld\n", iTexture - md.mTextureStages.begin());
271       DALI_TEST_EQUAL(iTexture->mSemantic, ts.mSemantic);
272       DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, ts.mTexture.mImageUri);
273       DALI_TEST_EQUAL(uint32_t(iTexture->mTexture.mSamplerFlags), uint32_t(ts.mTexture.mSamplerFlags)); // don't interpret it as a character
274       ++iTexture;
275     }
276     ++iMaterial;
277   }
278
279   auto& meshes = ctx.resources.mMeshes;
280   DALI_TEST_EQUAL(2u, meshes.size());
281
282   using Blob     = MeshDefinition::Blob;
283   using Accessor = MeshDefinition::Accessor;
284   const MeshDefinition meshGroundTruth[]{
285     {
286       0,
287       Geometry::TRIANGLES,
288       "AnimatedCube.bin",
289       Accessor{Blob{0, 0}, {}},
290       Accessor{Blob{0, 0}, {}},
291       Accessor{Blob{0, 0}, {}},
292       Accessor{Blob{0, 0}, {}},
293       Accessor{Blob{0, 0}, {}},
294       Accessor{Blob{0, 0}, {}},
295     },
296     {
297       0,
298       Geometry::TRIANGLES,
299       "AnimatedCube.bin",
300       Accessor{Blob{0, 0}, {}},
301       Accessor{Blob{0, 0}, {}},
302       Accessor{Blob{0, 0}, {}},
303       Accessor{Blob{0, 0}, {}},
304       Accessor{Blob{0, 0}, {}},
305       Accessor{Blob{0, 0}, {}},
306     },
307   };
308
309   auto iMesh = meshes.begin();
310   for(auto& m : meshGroundTruth)
311   {
312     printf("mesh %ld\n", iMesh - meshes.begin());
313
314     auto& md = iMesh->first;
315     DALI_TEST_EQUAL(md.mFlags, m.mFlags);
316     DALI_TEST_EQUAL(md.mPrimitiveType, m.mPrimitiveType);
317     for(auto mp : {
318           &MeshDefinition::mIndices,
319           &MeshDefinition::mPositions,
320           &MeshDefinition::mNormals,
321           &MeshDefinition::mTexCoords,
322           &MeshDefinition::mColors,
323           &MeshDefinition::mTangents,
324           &MeshDefinition::mJoints0,
325           &MeshDefinition::mWeights0})
326     {
327       DALI_TEST_EQUAL((md.*mp).IsDefined(), (m.*mp).IsDefined());
328       DALI_TEST_EQUAL((md.*mp).mBlob.IsDefined(), (m.*mp).mBlob.IsDefined());
329     }
330
331     DALI_TEST_EQUAL(md.mBlendShapeHeader.IsDefined(), m.mBlendShapeHeader.IsDefined());
332
333     ++iMesh;
334   }
335
336   DALI_TEST_EQUAL(2u, ctx.resources.mShaders.size());
337   DALI_TEST_EQUAL(0u, ctx.resources.mSkeletons.size());
338
339   DALI_TEST_EQUAL(3u, ctx.cameras.size());
340   DALI_TEST_EQUAL(0u, ctx.lights.size());
341   DALI_TEST_EQUAL(1u, ctx.animations.size());
342   DALI_TEST_EQUAL(0u, ctx.animationGroups.size());
343
344   END_TEST;
345 }
346
347 int UtcDaliGltfLoaderSuccessShort(void)
348 {
349   TestApplication app;
350
351   const std::string resourcePath = TEST_RESOURCE_DIR "/";
352   auto              pathProvider = [resourcePath](ResourceType::Value) {
353     return resourcePath;
354   };
355
356   Customization::Choices choices;
357   for(auto modelName : {
358         "2CylinderEngine",
359         "AnimatedMorphCube",
360         "AnimatedMorphSphere",
361         "AnimatedTriangle",
362         "BoxAnimated",
363         "CesiumMan",
364         "CesiumMilkTruck",
365         "EnvironmentTest",
366         "MetalRoughSpheres",
367         "MorphPrimitivesTest",
368         "MRendererTest",
369         "SimpleSparseAccessor",
370         "AnimatedCube",
371       })
372   {
373     Context ctx;
374
375     ShaderDefinitionFactory sdf;
376
377     auto& resources = ctx.resources;
378     resources.mEnvironmentMaps.push_back({});
379
380     sdf.SetResources(resources);
381
382     printf("%s\n", modelName);
383     LoadGltfScene(resourcePath + modelName + ".gltf", sdf, ctx.loadResult);
384     DALI_TEST_CHECK(ctx.scene.GetNodeCount() > 0);
385
386     auto& scene = ctx.scene;
387     for(auto iRoot : scene.GetRoots())
388     {
389       struct Visitor : NodeDefinition::IVisitor
390       {
391         struct ResourceReceiver : IResourceReceiver
392         {
393           std::vector<bool> mCounts;
394
395           void Register(ResourceType::Value type, Index id) override
396           {
397             if(type == ResourceType::Mesh)
398             {
399               mCounts[id] = true;
400             }
401           }
402         } receiver;
403
404         void Start(NodeDefinition& n) override
405         {
406           for(auto& renderable : n.mRenderables)
407           {
408             renderable->RegisterResources(receiver);
409           }
410         }
411
412         void Finish(NodeDefinition& n) override
413         {
414         }
415       } visitor;
416       visitor.receiver.mCounts.resize(resources.mMeshes.size(), false);
417
418       scene.Visit(iRoot, choices, visitor);
419       for(uint32_t i0 = 0, i1 = resources.mMeshes.size(); i0 < i1; ++i0)
420       {
421         if(visitor.receiver.mCounts[i0])
422         {
423           auto raw = resources.mMeshes[i0].first.LoadRaw(resourcePath);
424           DALI_TEST_CHECK(!raw.mAttribs.empty());
425
426           resources.mMeshes[i0].second = resources.mMeshes[i0].first.Load(std::move(raw));
427           DALI_TEST_CHECK(resources.mMeshes[i0].second.geometry);
428         }
429       }
430     }
431   }
432
433   END_TEST;
434 }
435
436 int UtcDaliGltfLoaderMRendererTest(void)
437 {
438   Context ctx;
439
440   ShaderDefinitionFactory sdf;
441   sdf.SetResources(ctx.resources);
442   auto& resources = ctx.resources;
443
444   LoadGltfScene(TEST_RESOURCE_DIR "/MRendererTest.gltf", sdf, ctx.loadResult);
445
446   auto& scene = ctx.scene;
447   auto& roots = scene.GetRoots();
448   DALI_TEST_EQUAL(roots.size(), 1u);
449   DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "RootNode");
450   DALI_TEST_EQUAL(scene.GetNode(roots[0])->mScale, Vector3(1.0f, 1.0f, 1.0f));
451
452   DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
453
454   ViewProjection viewProjection;
455   Transforms     xforms{
456     MatrixStack{},
457     viewProjection};
458   NodeDefinition::CreateParams nodeParams{
459     resources,
460     xforms,
461   };
462
463   Customization::Choices choices;
464
465   TestApplication app;
466
467   Actor root = Actor::New();
468   SetActorCentered(root);
469   for(auto iRoot : roots)
470   {
471     auto resourceRefs = resources.CreateRefCounter();
472     scene.CountResourceRefs(iRoot, choices, resourceRefs);
473     resources.CountEnvironmentReferences(resourceRefs);
474     resources.LoadResources(resourceRefs, ctx.pathProvider);
475     if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
476     {
477       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
478       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
479       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
480       root.Add(actor);
481     }
482   }
483
484   DALI_TEST_EQUAL(root.GetChildCount(), 1u);
485   Actor child = root.GetChildAt(0);
486
487   DALI_TEST_EQUAL(child.GetProperty(Actor::Property::NAME).Get<std::string>(), "RootNode");
488   DALI_TEST_EQUAL(child.GetProperty(Actor::Property::SCALE).Get<Vector3>(), Vector3(1.0f, 1.0f, 1.0f));
489   DALI_TEST_EQUAL(child.GetRendererCount(), 1u);
490   DALI_TEST_EQUAL(child.GetRendererAt(0).GetTextures().GetTextureCount(), 4u);
491
492   END_TEST;
493 }