Browse Source

Scene: Add the base of the serializer

Panagiotis Christopoulos Charitos 1 week ago
parent
commit
eb7fd56138
36 changed files with 624 additions and 114 deletions
  1. 18 3
      AnKi/Editor/EditorUi.cpp
  2. 1 1
      AnKi/Editor/EditorUi.h
  3. 28 0
      AnKi/Scene/Components/CameraComponent.cpp
  4. 2 0
      AnKi/Scene/Components/CameraComponent.h
  5. 24 0
      AnKi/Scene/Components/DecalComponent.cpp
  6. 2 0
      AnKi/Scene/Components/DecalComponent.h
  7. 11 0
      AnKi/Scene/Components/FogDensityComponent.cpp
  8. 2 0
      AnKi/Scene/Components/FogDensityComponent.h
  9. 9 0
      AnKi/Scene/Components/GlobalIlluminationProbeComponent.cpp
  10. 8 10
      AnKi/Scene/Components/GlobalIlluminationProbeComponent.h
  11. 19 0
      AnKi/Scene/Components/LightComponent.cpp
  12. 2 0
      AnKi/Scene/Components/LightComponent.h
  13. 8 1
      AnKi/Scene/Components/MaterialComponent.cpp
  14. 2 1
      AnKi/Scene/Components/MaterialComponent.h
  15. 6 0
      AnKi/Scene/Components/MeshComponent.cpp
  16. 4 2
      AnKi/Scene/Components/MeshComponent.h
  17. 19 13
      AnKi/Scene/Components/ParticleEmitter2Component.cpp
  18. 5 3
      AnKi/Scene/Components/ParticleEmitter2Component.h
  19. 6 0
      AnKi/Scene/Components/ReflectionProbeComponent.cpp
  20. 2 0
      AnKi/Scene/Components/ReflectionProbeComponent.h
  21. 12 6
      AnKi/Scene/Components/SceneComponent.h
  22. 20 20
      AnKi/Scene/Components/SceneComponentClasses.def.h
  23. 10 0
      AnKi/Scene/Components/ScriptComponent.cpp
  24. 3 5
      AnKi/Scene/Components/ScriptComponent.h
  25. 25 7
      AnKi/Scene/Components/SkinComponent.cpp
  26. 6 4
      AnKi/Scene/Components/SkinComponent.h
  27. 18 0
      AnKi/Scene/Components/SkyboxComponent.cpp
  28. 5 8
      AnKi/Scene/Components/SkyboxComponent.h
  29. 6 0
      AnKi/Scene/Components/TriggerComponent.cpp
  30. 3 6
      AnKi/Scene/Components/TriggerComponent.h
  31. 1 1
      AnKi/Scene/Forward.h
  32. 2 6
      AnKi/Scene/Frustum.h
  33. 27 0
      AnKi/Scene/SceneGraph.cpp
  34. 17 15
      AnKi/Scene/SceneGraph.h
  35. 2 2
      AnKi/Scene/SceneNode.cpp
  36. 289 0
      AnKi/Scene/SceneSerializer.h

+ 18 - 3
AnKi/Editor/EditorUi.cpp

@@ -430,6 +430,21 @@ void EditorUi::mainMenu()
 	{
 		if(ImGui::BeginMenuBar())
 		{
+			if(ImGui::Button(ICON_MDI_CONTENT_SAVE_ALL))
+			{
+				if(SceneGraph::getSingleton().saveToTextFile("./scene.ankiscene"))
+				{
+					ANKI_LOGE("Failed to save scene");
+				}
+				else
+				{
+					ANKI_LOGI("Scene saved");
+				}
+			}
+			ImGui::SetItemTooltip("Save scene");
+
+			ImGui::SameLine();
+
 			ImGui::SetNextItemWidth(ImGui::CalcTextSize("00.000").x);
 
 			if(ImGui::SliderFloat(ICON_MDI_AXIS_ARROW "&" ICON_MDI_ARROW_EXPAND_ALL " Snapping", &m_toolbox.m_scaleTranslationSnapping, 0.0, 10.0f))
@@ -481,7 +496,7 @@ void EditorUi::sceneNode(SceneNode& node)
 	{
 		switch(sceneComponentType)
 		{
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	case SceneComponentType::k##name: \
 		componentsString += ICON_MDI_##icon; \
 		break;
@@ -664,7 +679,7 @@ void EditorUi::sceneNodePropertiesWindow()
 			{
 				switch(SceneComponentType(state.m_selectedSceneComponentType))
 				{
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	case SceneComponentType::k##name: \
 		node.newComponent<name##Component>(); \
 		break;
@@ -721,7 +736,7 @@ void EditorUi::sceneNodePropertiesWindow()
 					CString icon = ICON_MDI_TOY_BRICK;
 					switch(comp.getType())
 					{
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon_) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon_, serializable) \
 	case SceneComponentType::k##name: \
 		icon = ANKI_CONCATENATE(ICON_MDI_, icon_); \
 		break;

+ 1 - 1
AnKi/Editor/EditorUi.h

@@ -15,7 +15,7 @@ namespace anki {
 
 // Forward
 class SceneNode;
-#define ANKI_DEFINE_SCENE_COMPONENT(class_, weight, sceneNodeCanHaveMany, icon) class class_##Component;
+#define ANKI_DEFINE_SCENE_COMPONENT(class_, weight, sceneNodeCanHaveMany, icon, serializable) class class_##Component;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 // A class that builds the editor UI and manipulates the scene directly.

+ 28 - 0
AnKi/Scene/Components/CameraComponent.cpp

@@ -34,4 +34,32 @@ void CameraComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	updated = m_frustum.update();
 }
 
+Error CameraComponent::serialize(SceneSerializer& serializer)
+{
+	Frustum frustum;
+
+	F32 near = m_frustum.getNear();
+	ANKI_SERIALIZE(near, 1);
+	frustum.setNear(near);
+
+	F32 far = m_frustum.getFar();
+	ANKI_SERIALIZE(far, 1);
+	frustum.setFar(far);
+
+	F32 fovX = m_frustum.getFovX();
+	ANKI_SERIALIZE(fovX, 1);
+	frustum.setFovX(fovX);
+
+	F32 fovY = m_frustum.getFovY();
+	ANKI_SERIALIZE(fovY, 1);
+	frustum.setFovY(fovY);
+
+	if(serializer.isInReadMode())
+	{
+		m_frustum = frustum;
+	}
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Components/CameraComponent.h

@@ -94,6 +94,8 @@ private:
 	Frustum m_frustum;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 /// @}
 

+ 24 - 0
AnKi/Scene/Components/DecalComponent.cpp

@@ -93,4 +93,28 @@ void DecalComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	m_gpuSceneDecal.uploadToGpuScene(gpuDecal);
 }
 
+Error DecalComponent::serialize(SceneSerializer& serializer)
+{
+	Layer& diffuse = m_layers[LayerType::kDiffuse];
+	Layer& roughnessMetalness = m_layers[LayerType::kRoughnessMetalness];
+
+	ANKI_SERIALIZE(diffuse.m_image, 1);
+	ANKI_SERIALIZE(roughnessMetalness.m_image, 1);
+	ANKI_SERIALIZE(diffuse.m_blendFactor, 1);
+	ANKI_SERIALIZE(roughnessMetalness.m_blendFactor, 1);
+
+	if(!serializer.isInWriteMode() && diffuse.m_image)
+	{
+		diffuse.m_bindlessTextureIndex = diffuse.m_image->getTexture().getOrCreateBindlessTextureIndex(TextureSubresourceDesc::all());
+	}
+
+	if(!serializer.isInWriteMode() && roughnessMetalness.m_image)
+	{
+		roughnessMetalness.m_bindlessTextureIndex =
+			roughnessMetalness.m_image->getTexture().getOrCreateBindlessTextureIndex(TextureSubresourceDesc::all());
+	}
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Components/DecalComponent.h

@@ -111,6 +111,8 @@ private:
 	void setBlendFactor(LayerType type, F32 blendFactor);
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 
 } // end namespace anki

+ 11 - 0
AnKi/Scene/Components/FogDensityComponent.cpp

@@ -51,4 +51,15 @@ void FogDensityComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	}
 }
 
+Error FogDensityComponent::serialize(SceneSerializer& serializer)
+{
+	U32 type = U32(m_type);
+	ANKI_SERIALIZE(type, 1);
+	m_type = FogDensityComponentShape(type);
+
+	ANKI_SERIALIZE(m_density, 1);
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Components/FogDensityComponent.h

@@ -73,6 +73,8 @@ private:
 	Bool m_dirty = true;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 
 } // end namespace anki

+ 9 - 0
AnKi/Scene/Components/GlobalIlluminationProbeComponent.cpp

@@ -169,4 +169,13 @@ F32 GlobalIlluminationProbeComponent::getShadowsRenderRadius() const
 	return min<F32>(getRenderRadius(), g_cvarSceneProbeShadowEffectiveDistance);
 }
 
+Error GlobalIlluminationProbeComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_cellSize, 1);
+	ANKI_SERIALIZE(m_fadeDistance, 1);
+	ANKI_SERIALIZE(m_halfSize, 1);
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 8 - 10
AnKi/Scene/Components/GlobalIlluminationProbeComponent.h

@@ -12,10 +12,7 @@
 
 namespace anki {
 
-/// @addtogroup scene
-/// @{
-
-/// Global illumination probe component. It's an axis aligned box divided into cells.
+// Global illumination probe component. It's an axis aligned box divided into cells.
 class GlobalIlluminationProbeComponent : public SceneComponent
 {
 	ANKI_SCENE_COMPONENT(GlobalIlluminationProbeComponent)
@@ -25,7 +22,7 @@ public:
 
 	~GlobalIlluminationProbeComponent();
 
-	/// Set the cell size in meters.
+	// Set the cell size in meters.
 	void setCellSize(F32 cellSize)
 	{
 		if(ANKI_EXPECT(cellSize > 0.0f) && m_cellSize != cellSize)
@@ -59,7 +56,7 @@ public:
 		return m_halfSize * 2.0f;
 	}
 
-	/// Check if any of the probe's cells need to be re-rendered.
+	// Check if any of the probe's cells need to be re-rendered.
 	Bool getCellsNeedsRefresh() const
 	{
 		return m_cellsRefreshedCount < m_totalCellCount;
@@ -71,7 +68,7 @@ public:
 		return m_cellsRefreshedCount;
 	}
 
-	/// Add to the number of texels that got refreshed this frame.
+	// Add to the number of texels that got refreshed this frame.
 	ANKI_INTERNAL void incrementRefreshedCells(U32 cellCount)
 	{
 		ANKI_ASSERT(getCellsNeedsRefresh());
@@ -80,7 +77,7 @@ public:
 		m_dirty = true;
 	}
 
-	/// The radius around the probe's center that can infuence the rendering of the env texture.
+	// The radius around the probe's center that can infuence the rendering of the env texture.
 	ANKI_INTERNAL F32 getRenderRadius() const;
 
 	ANKI_INTERNAL F32 getShadowsRenderRadius() const;
@@ -115,7 +112,7 @@ private:
 	Vec3 m_worldPos = Vec3(0.0f);
 	UVec3 m_cellCounts = UVec3(2u);
 	U32 m_totalCellCount = 8u;
-	F32 m_cellSize = 4.0f; ///< Cell size in meters.
+	F32 m_cellSize = 4.0f; // Cell size in meters.
 	F32 m_fadeDistance = 0.2f;
 
 	TexturePtr m_volTex;
@@ -130,7 +127,8 @@ private:
 	Bool m_dirty = true;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
-/// @}
 
 } // end namespace anki

+ 19 - 0
AnKi/Scene/Components/LightComponent.cpp

@@ -431,4 +431,23 @@ void LightComponent::setShadowAtlasUvViewports(ConstWeakArray<Vec4> viewports)
 	}
 }
 
+Error LightComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_type, 1);
+	ANKI_SERIALIZE(m_diffColor, 1);
+	ANKI_SERIALIZE(m_point.m_radius, 1);
+	ANKI_SERIALIZE(m_spot.m_distance, 1);
+	ANKI_SERIALIZE(m_spot.m_outerAngle, 1);
+	ANKI_SERIALIZE(m_spot.m_innerAngle, 1);
+	ANKI_SERIALIZE(m_dir.m_month, 1);
+	ANKI_SERIALIZE(m_dir.m_day, 1);
+	ANKI_SERIALIZE(m_dir.m_hour, 1);
+
+	U32 shadow = m_shadow;
+	ANKI_SERIALIZE(shadow, 1);
+	m_shadow = Bool(shadow);
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Components/LightComponent.h

@@ -221,6 +221,8 @@ private:
 	U8 m_shadowAtlasUvViewportCount : 3 = 0;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 
 } // end namespace anki

+ 8 - 1
AnKi/Scene/Components/MaterialComponent.cpp

@@ -38,7 +38,6 @@ MaterialComponent& MaterialComponent::setMaterialFilename(CString fname)
 	{
 		m_anyDirty = !m_resource || (m_resource->getUuid() != newRsrc->getUuid());
 		m_resource = std::move(newRsrc);
-		m_castsShadow = m_resource->castsShadow();
 	}
 
 	return *this;
@@ -450,4 +449,12 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	}
 }
 
+Error MaterialComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_resource, 1);
+	ANKI_SERIALIZE(m_submeshIdx, 1);
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 1
AnKi/Scene/Components/MaterialComponent.h

@@ -63,10 +63,11 @@ private:
 
 	Bool m_anyDirty : 1 = true; // A compound flag because it's too difficult to track everything
 	Bool m_movedLastFrame : 1 = true;
-	Bool m_castsShadow : 1 = false;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 
+	Error serialize(SceneSerializer& serializer) override;
+
 	void onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added) override;
 
 	Aabb computeAabb(const SceneNode& node) const;

+ 6 - 0
AnKi/Scene/Components/MeshComponent.cpp

@@ -155,4 +155,10 @@ void MeshComponent::update([[maybe_unused]] SceneComponentUpdateInfo& info, Bool
 	}
 }
 
+Error MeshComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_resource, 1);
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 4 - 2
AnKi/Scene/Components/MeshComponent.h

@@ -21,8 +21,6 @@ public:
 
 	~MeshComponent();
 
-	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
-
 	MeshComponent& setMeshFilename(CString fname);
 
 	Bool hasMeshResource() const
@@ -59,6 +57,10 @@ private:
 
 	Bool m_resourceDirty = true;
 	Bool m_gpuSceneMeshLodsReallocatedThisFrame = false;
+
+	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 
 } // end namespace anki

+ 19 - 13
AnKi/Scene/Components/ParticleEmitter2Component.cpp

@@ -172,7 +172,7 @@ ParticleEmitter2Component& ParticleEmitter2Component::setParticleEmitterFilename
 	}
 	else
 	{
-		m_particleEmitterResource = std::move(newRsrc);
+		m_resource = std::move(newRsrc);
 		m_anyDirty = true;
 	}
 
@@ -181,9 +181,9 @@ ParticleEmitter2Component& ParticleEmitter2Component::setParticleEmitterFilename
 
 CString ParticleEmitter2Component::getParticleEmitterFilename() const
 {
-	if(m_particleEmitterResource)
+	if(m_resource)
 	{
-		return m_particleEmitterResource->getFilename();
+		return m_resource->getFilename();
 	}
 	else
 	{
@@ -204,7 +204,7 @@ void ParticleEmitter2Component::onOtherComponentRemovedOrAdded(SceneComponent* o
 
 Bool ParticleEmitter2Component::isValid() const
 {
-	Bool valid = !!m_particleEmitterResource;
+	Bool valid = !!m_resource;
 
 	if(m_geomType == ParticleGeometryType::kMeshComponent)
 	{
@@ -236,22 +236,22 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 
 	if(info.m_checkForResourceUpdates) [[unlikely]]
 	{
-		if(m_particleEmitterResource->isObsolete()) [[unlikely]]
+		if(m_resource->isObsolete()) [[unlikely]]
 		{
 			ANKI_SCENE_LOGV("Particle emitter resource is obsolete. Will reload it");
 			BaseString<MemoryPoolPtrWrapper<StackMemoryPool>> fname(info.m_framePool);
-			fname = m_particleEmitterResource->getFilename();
-			ParticleEmitterResource2Ptr oldVersion = m_particleEmitterResource;
-			m_particleEmitterResource.reset(nullptr);
-			if(ResourceManager::getSingleton().loadResource(fname, m_particleEmitterResource))
+			fname = m_resource->getFilename();
+			ParticleEmitterResource2Ptr oldVersion = m_resource;
+			m_resource.reset(nullptr);
+			if(ResourceManager::getSingleton().loadResource(fname, m_resource))
 			{
 				ANKI_SCENE_LOGE("Can't update the particle resource. Ignoring the load");
-				m_particleEmitterResource = oldVersion;
+				m_resource = oldVersion;
 			}
 			else
 			{
 				m_anyDirty = true;
-				ANKI_ASSERT(!m_particleEmitterResource->isObsolete());
+				ANKI_ASSERT(!m_resource->isObsolete());
 			}
 		}
 	}
@@ -266,7 +266,7 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 	m_gpuSceneReallocationsThisFrame = true; // Difficult to properly track so set it to true and don't bother
 	m_anyDirty = false;
 	updated = true;
-	const ParticleEmitterResourceCommonProperties& commonProps = m_particleEmitterResource->getCommonProperties();
+	const ParticleEmitterResourceCommonProperties& commonProps = m_resource->getCommonProperties();
 
 	// Streams
 	for(ParticleProperty prop : EnumIterable<ParticleProperty>())
@@ -286,7 +286,7 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 	{
 		GpuSceneBuffer::getSingleton().deferredFree(m_gpuScene.m_anKiParticleEmitterProperties);
 
-		ConstWeakArray<U8> prefilled = m_particleEmitterResource->getPrefilledAnKiParticleEmitterProperties();
+		ConstWeakArray<U8> prefilled = m_resource->getPrefilledAnKiParticleEmitterProperties();
 		m_gpuScene.m_anKiParticleEmitterProperties = GpuSceneBuffer::getSingleton().allocate(prefilled.getSizeInBytes(), alignof(U32));
 
 		GpuSceneMicroPatcher::getSingleton().newCopy(m_gpuScene.m_anKiParticleEmitterProperties, prefilled.getSizeInBytes(), prefilled.getBegin());
@@ -346,4 +346,10 @@ U32 ParticleEmitter2Component::getGpuSceneMeshLodIndex(U32 submeshIdx) const
 	}
 }
 
+Error ParticleEmitter2Component::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_resource, 1);
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 5 - 3
AnKi/Scene/Components/ParticleEmitter2Component.h

@@ -42,7 +42,7 @@ public:
 
 	Bool hasParticleEmitterResource() const
 	{
-		return !!m_particleEmitterResource;
+		return !!m_resource;
 	}
 
 	ParticleEmitter2Component& setParticleGeometryType(ParticleGeometryType type)
@@ -94,7 +94,7 @@ public:
 	ANKI_INTERNAL ParticleEmitterResource2& getParticleEmitterResource() const
 	{
 		ANKI_ASSERT(isValid());
-		return *m_particleEmitterResource;
+		return *m_resource;
 	}
 
 	ANKI_INTERNAL F32 getDt() const
@@ -106,7 +106,7 @@ public:
 private:
 	class ParticleEmitterQuadGeometry;
 
-	ParticleEmitterResource2Ptr m_particleEmitterResource;
+	ParticleEmitterResource2Ptr m_resource;
 
 	MeshComponent* m_meshComponent = nullptr;
 
@@ -129,6 +129,8 @@ private:
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 
+	Error serialize(SceneSerializer& serializer) override;
+
 	void onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added) override;
 };
 

+ 6 - 0
AnKi/Scene/Components/ReflectionProbeComponent.cpp

@@ -81,4 +81,10 @@ F32 ReflectionProbeComponent::getShadowsRenderRadius() const
 	return min<F32>(getRenderRadius(), g_cvarSceneProbeShadowEffectiveDistance);
 }
 
+Error ReflectionProbeComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_halfSize, 1);
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Components/ReflectionProbeComponent.h

@@ -77,6 +77,8 @@ private:
 	Bool m_reflectionNeedsRefresh = true;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
 /// @}
 

+ 12 - 6
AnKi/Scene/Components/SceneComponent.h

@@ -6,6 +6,7 @@
 #pragma once
 
 #include <AnKi/Scene/Common.h>
+#include <AnKi/Scene/SceneSerializer.h>
 #include <AnKi/Util/Functions.h>
 #include <AnKi/Util/BitMask.h>
 #include <AnKi/Util/Enum.h>
@@ -15,7 +16,7 @@ namespace anki {
 
 enum class SceneComponentType : U8
 {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) k##name,
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) k##name,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 	kCount,
@@ -27,7 +28,7 @@ enum class SceneComponentTypeMask : U32
 {
 	kNone = 0,
 
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) k##name = 1 << U32(SceneComponentType::k##name),
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) k##name = 1 << U32(SceneComponentType::k##name),
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(SceneComponentTypeMask)
@@ -35,7 +36,7 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(SceneComponentTypeMask)
 class SceneComponentType2
 {
 public:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	static constexpr SceneComponentType k##name##Component = SceneComponentType::k##name;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
@@ -48,14 +49,14 @@ private:
 
 // Component names
 inline Array<const Char*, U32(SceneComponentType::kCount)> kSceneComponentTypeName = {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) ANKI_STRINGIZE(name)
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) ANKI_STRINGIZE(name)
 #define ANKI_SCENE_COMPONENT_SEPARATOR ,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 
 // Just a flag per component
 inline Array<Bool, U32(SceneComponentType::kCount)> kSceneComponentSceneNodeCanHaveMany = {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) sceneNodeCanHaveMany
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) sceneNodeCanHaveMany
 #define ANKI_SCENE_COMPONENT_SEPARATOR ,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
@@ -158,6 +159,11 @@ public:
 		return m_timestamp == GlobalFrameIndex::getSingleton().m_value;
 	}
 
+	virtual Error serialize([[maybe_unused]] SceneSerializer& serializer)
+	{
+		return Error::kNone;
+	}
+
 protected:
 	U32 regenerateUuid();
 
@@ -220,7 +226,7 @@ private:
 	U32 m_type : 8 = 0; ///< Cache the type ID.
 
 	static constexpr Array<F32, U32(SceneComponentType::kCount)> m_updateOrderWeights = {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) weight
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) weight
 #define ANKI_SCENE_COMPONENT_SEPARATOR ,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 	};

+ 20 - 20
AnKi/Scene/Components/SceneComponentClasses.def.h

@@ -7,52 +7,52 @@
 #	define ANKI_SCENE_COMPONENT_SEPARATOR
 #endif
 
-// ANKI_DEFINE_SCENE_COMPONENT(className, weight, sceneNodeCanHaveMany, icon)
+// ANKI_DEFINE_SCENE_COMPONENT(className, weight, sceneNodeCanHaveMany, icon, serializable)
 
-ANKI_DEFINE_SCENE_COMPONENT(Script, 0.0f, true, LANGUAGE_LUA)
+ANKI_DEFINE_SCENE_COMPONENT(Script, 0.0f, true, LANGUAGE_LUA, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Body, 10.0f, false, CUBE_SEND)
+ANKI_DEFINE_SCENE_COMPONENT(Body, 10.0f, false, CUBE_SEND, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(PlayerController, 10.0f, false, HUMAN)
+ANKI_DEFINE_SCENE_COMPONENT(PlayerController, 10.0f, false, HUMAN, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Move, 30.0f, false, AXIS_ARROW)
+ANKI_DEFINE_SCENE_COMPONENT(Move, 30.0f, false, AXIS_ARROW, false)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Skin, 30.0f, false, BONE)
+ANKI_DEFINE_SCENE_COMPONENT(Skin, 30.0f, false, BONE, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Joint, 35.0f, true, CONNECTION)
+ANKI_DEFINE_SCENE_COMPONENT(Joint, 35.0f, true, CONNECTION, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Trigger, 40.0f, true, LIGHT_SWITCH_OFF)
+ANKI_DEFINE_SCENE_COMPONENT(Trigger, 40.0f, true, LIGHT_SWITCH_OFF, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f, false, VECTOR_POLYGON)
+ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f, false, VECTOR_POLYGON, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter2, 60.0f, false, CREATION)
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter2, 60.0f, false, CREATION, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f, true, TEXTURE_BOX)
+ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f, true, TEXTURE_BOX, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f, false, LIQUID_SPOT)
+ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f, false, LIQUID_SPOT, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Camera, 100.0f, false, CAMERA)
+ANKI_DEFINE_SCENE_COMPONENT(Camera, 100.0f, false, CAMERA, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(FogDensity, 100.0f, false, CLOUD)
+ANKI_DEFINE_SCENE_COMPONENT(FogDensity, 100.0f, false, CLOUD, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(GlobalIlluminationProbe, 100.0f, false, SPHERE)
+ANKI_DEFINE_SCENE_COMPONENT(GlobalIlluminationProbe, 100.0f, false, SPHERE, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(ReflectionProbe, 100.0f, false, SPHERE)
+ANKI_DEFINE_SCENE_COMPONENT(ReflectionProbe, 100.0f, false, SPHERE, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Skybox, 100.0f, false, EARTH)
+ANKI_DEFINE_SCENE_COMPONENT(Skybox, 100.0f, false, EARTH, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Ui, 100.0f, true, WINDOW_RESTORE)
+ANKI_DEFINE_SCENE_COMPONENT(Ui, 100.0f, true, WINDOW_RESTORE, false)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(LensFlare, 100.0f, true, FLARE)
+ANKI_DEFINE_SCENE_COMPONENT(LensFlare, 100.0f, true, FLARE, true)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Light, 100.0f, false, LIGHTBULB)
+ANKI_DEFINE_SCENE_COMPONENT(Light, 100.0f, false, LIGHTBULB, true)
 
 #undef ANKI_DEFINE_SCENE_COMPONENT
 #undef ANKI_SCENE_COMPONENT_SEPARATOR

+ 10 - 0
AnKi/Scene/Components/ScriptComponent.cpp

@@ -147,4 +147,14 @@ void ScriptComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	updated = true;
 }
 
+Error ScriptComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_resource, 1);
+	ANKI_SERIALIZE(m_text, 1);
+
+	// TODO Serialize environments
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 3 - 5
AnKi/Scene/Components/ScriptComponent.h

@@ -11,10 +11,7 @@
 
 namespace anki {
 
-/// @addtogroup scene
-/// @{
-
-/// Component of scripts. It can point to a resource with the script code or have the script code embedded to it.
+// Component of scripts. It can point to a resource with the script code or have the script code embedded to it.
 class ScriptComponent : public SceneComponent
 {
 	ANKI_SCENE_COMPONENT(ScriptComponent)
@@ -53,7 +50,8 @@ private:
 	Array<ScriptEnvironment*, 2> m_environments = {};
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
-/// @}
 
 } // end namespace anki

+ 25 - 7
AnKi/Scene/Components/SkinComponent.cpp

@@ -31,8 +31,8 @@ SkinComponent& SkinComponent::setSkeletonFilename(CString fname)
 	}
 	else
 	{
-		m_resourceDirty = !m_skeleton || (m_skeleton->getUuid() != newRsrc->getUuid());
-		m_skeleton = std::move(newRsrc);
+		m_resourceDirty = !m_resource || (m_resource->getUuid() != newRsrc->getUuid());
+		m_resource = std::move(newRsrc);
 	}
 
 	return *this;
@@ -40,7 +40,7 @@ SkinComponent& SkinComponent::setSkeletonFilename(CString fname)
 
 CString SkinComponent::getSkeletonFilename() const
 {
-	return (m_skeleton) ? m_skeleton->getFilename() : "*Error*";
+	return (m_resource) ? m_resource->getFilename() : "*Error*";
 }
 
 void SkinComponent::playAnimation(U32 trackIdx, AnimationResourcePtr anim, const AnimationPlayInfo& info)
@@ -95,7 +95,7 @@ void SkinComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	if(resourceDirty) [[unlikely]]
 	{
 		// Create
-		const U32 boneCount = m_skeleton->getBones().getSize();
+		const U32 boneCount = m_resource->getBones().getSize();
 		m_boneTrfs[0].resize(boneCount, Mat3x4::getIdentity());
 		m_boneTrfs[1].resize(boneCount, Mat3x4::getIdentity());
 		m_animationTrfs.resize(boneCount, Trf{Vec3(0.0f), Quat::getIdentity(), 1.0f});
@@ -143,7 +143,7 @@ void SkinComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 		for(U32 i = 0; i < track.m_anim->getChannels().getSize(); ++i)
 		{
 			const AnimationChannel& channel = track.m_anim->getChannels()[i];
-			const Bone* bone = m_skeleton->tryFindBone(channel.m_name.toCString());
+			const Bone* bone = m_resource->tryFindBone(channel.m_name.toCString());
 			if(!bone)
 			{
 				ANKI_SCENE_LOGW("Animation is referencing unknown bone \"%s\"", &channel.m_name[0]);
@@ -204,14 +204,14 @@ void SkinComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 		m_crntBoneTrfs = m_crntBoneTrfs ^ 1;
 
 		// Walk the bone hierarchy to add additional transforms
-		visitBones(m_skeleton->getRootBone(), Mat3x4::getIdentity(), bonesAnimated, minExtend, maxExtend);
+		visitBones(m_resource->getRootBone(), Mat3x4::getIdentity(), bonesAnimated, minExtend, maxExtend);
 
 		const Vec4 e(kEpsilonf, kEpsilonf, kEpsilonf, 0.0f);
 		m_boneBoundingVolume.setMin(minExtend - e);
 		m_boneBoundingVolume.setMax(maxExtend + e);
 
 		// Update the GPU scene
-		const U32 boneCount = m_skeleton->getBones().getSize();
+		const U32 boneCount = m_resource->getBones().getSize();
 		DynamicArray<Mat3x4, MemoryPoolPtrWrapper<StackMemoryPool>> trfs(info.m_framePool);
 		trfs.resize(boneCount * 2);
 		for(U32 i = 0; i < boneCount; ++i)
@@ -258,4 +258,22 @@ void SkinComponent::visitBones(const Bone& bone, const Mat3x4& parentTrf, const
 	}
 }
 
+Error SkinComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_resource, 1);
+
+	for(Track& track : m_tracks)
+	{
+		ANKI_SERIALIZE(track.m_anim, 1);
+		ANKI_SERIALIZE(track.m_absoluteStartTime, 1);
+		ANKI_SERIALIZE(track.m_relativeTimePassed, 1);
+		ANKI_SERIALIZE(track.m_blendInTime, 1);
+		ANKI_SERIALIZE(track.m_blendOutTime, 1);
+		ANKI_SERIALIZE(track.m_repeatTimes, 1);
+		ANKI_SERIALIZE(track.m_animationSpeedScale, 1);
+	}
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 6 - 4
AnKi/Scene/Components/SkinComponent.h

@@ -53,14 +53,14 @@ public:
 
 	Bool hasSkeletonResource() const
 	{
-		return !!m_skeleton;
+		return !!m_resource;
 	}
 
 	void playAnimation(U32 track, AnimationResourcePtr anim, const AnimationPlayInfo& info);
 
 	Bool isValid() const
 	{
-		return m_skeleton.isCreated();
+		return m_resource.isCreated();
 	}
 
 	ANKI_INTERNAL ConstWeakArray<Mat3x4> getBoneTransforms() const
@@ -78,7 +78,7 @@ public:
 	ANKI_INTERNAL const SkeletonResourcePtr& getSkeleronResource() const
 	{
 		ANKI_ASSERT(isValid());
-		return m_skeleton;
+		return m_resource;
 	}
 
 	ANKI_INTERNAL const Aabb& getBoneBoundingVolumeLocalSpace() const
@@ -120,7 +120,7 @@ private:
 		F32 m_scale;
 	};
 
-	SkeletonResourcePtr m_skeleton;
+	SkeletonResourcePtr m_resource;
 	Array<SceneDynamicArray<Mat3x4>, 2> m_boneTrfs;
 	SceneDynamicArray<Trf> m_animationTrfs;
 	Aabb m_boneBoundingVolume = Aabb(Vec3(-1.0f), Vec3(1.0f));
@@ -137,6 +137,8 @@ private:
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 
+	Error serialize(SceneSerializer& serializer) override;
+
 	void visitBones(const Bone& bone, const Mat3x4& parentTrf, const BitSet<128, U8>& bonesAnimated, Vec4& minExtend, Vec4& maxExtend);
 };
 

+ 18 - 0
AnKi/Scene/Components/SkyboxComponent.cpp

@@ -39,4 +39,22 @@ void SkyboxComponent::update([[maybe_unused]] SceneComponentUpdateInfo& info, Bo
 	updated = false;
 }
 
+Error SkyboxComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_type, 1);
+	ANKI_SERIALIZE(m_color, 1);
+	ANKI_SERIALIZE(m_image, 1);
+	ANKI_SERIALIZE(m_imageScale, 1);
+	ANKI_SERIALIZE(m_imageBias, 1);
+	ANKI_SERIALIZE(m_fog.m_minDensity, 1);
+	ANKI_SERIALIZE(m_fog.m_maxDensity, 1);
+	ANKI_SERIALIZE(m_fog.m_heightOfMinDensity, 1);
+	ANKI_SERIALIZE(m_fog.m_heightOfMaxDensity, 1);
+	ANKI_SERIALIZE(m_fog.m_scatteringCoeff, 1);
+	ANKI_SERIALIZE(m_fog.m_absorptionCoeff, 1);
+	ANKI_SERIALIZE(m_fog.m_diffuseColor, 1);
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 5 - 8
AnKi/Scene/Components/SkyboxComponent.h

@@ -14,10 +14,6 @@ namespace anki {
 // Forward
 class SkyboxQueueElement;
 
-/// @addtogroup scene
-/// @{
-
-/// @memberof SkyboxComponent
 enum class SkyboxType : U8
 {
 	kSolidColor,
@@ -25,7 +21,7 @@ enum class SkyboxType : U8
 	kGenerated
 };
 
-/// Skybox config.
+// Skybox config.
 class SkyboxComponent : public SceneComponent
 {
 	ANKI_SCENE_COMPONENT(SkyboxComponent)
@@ -170,15 +166,16 @@ private:
 	public:
 		F32 m_minDensity = 0.0f;
 		F32 m_maxDensity = 0.9f;
-		F32 m_heightOfMinDensity = 20.0f; ///< The height (meters) where fog density is max.
-		F32 m_heightOfMaxDensity = 0.0f; ///< The height (meters) where fog density is the min value.
+		F32 m_heightOfMinDensity = 20.0f; // The height (meters) where fog density is max.
+		F32 m_heightOfMaxDensity = 0.0f; // The height (meters) where fog density is the min value.
 		F32 m_scatteringCoeff = 0.01f;
 		F32 m_absorptionCoeff = 0.02f;
 		Vec3 m_diffuseColor = Vec3(1.0f);
 	} m_fog;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
-/// @}
 
 } // end namespace anki

+ 6 - 0
AnKi/Scene/Components/TriggerComponent.cpp

@@ -146,4 +146,10 @@ void TriggerComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 	m_resetExit = true;
 }
 
+Error TriggerComponent::serialize(SceneSerializer& serializer)
+{
+	ANKI_SERIALIZE(m_type, 1);
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 3 - 6
AnKi/Scene/Components/TriggerComponent.h

@@ -10,10 +10,6 @@
 
 namespace anki {
 
-/// @addtogroup scene
-/// @{
-
-/// @memberof TriggerComponent
 enum class TriggerComponentShapeType
 {
 	kSphere,
@@ -21,7 +17,7 @@ enum class TriggerComponentShapeType
 	kCount
 };
 
-/// Trigger component.
+// Trigger component
 class TriggerComponent : public SceneComponent
 {
 	ANKI_SCENE_COMPONENT(TriggerComponent)
@@ -60,7 +56,8 @@ private:
 	static MyPhysicsTriggerCallbacks m_callbacks;
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
+
+	Error serialize(SceneSerializer& serializer) override;
 };
-/// @}
 
 } // end namespace anki

+ 1 - 1
AnKi/Scene/Forward.h

@@ -9,7 +9,7 @@ namespace anki {
 
 // Components
 class SceneComponent;
-#define ANKI_DEFINE_SCENE_COMPONENT(name, updateOrder, sceneNodeCanHaveMany, icon) class name##Component;
+#define ANKI_DEFINE_SCENE_COMPONENT(name, updateOrder, sceneNodeCanHaveMany, icon, serializable) class name##Component;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 // Nodes

+ 2 - 6
AnKi/Scene/Frustum.h

@@ -12,10 +12,7 @@
 
 namespace anki {
 
-/// @addtogroup scene
-/// @{
-
-/// A helper class that represents a frustum.
+// A helper class that represents a frustum.
 class Frustum
 {
 public:
@@ -184,7 +181,7 @@ public:
 
 	Bool update();
 
-	/// Get the precalculated rotations of each of the 6 frustums of an omnidirectional source (eg a point light).
+	// Get the precalculated rotations of each of the 6 frustums of an omnidirectional source (eg a point light).
 	static const Array<Mat3x4, 6>& getOmnidirectionalFrustumRotations()
 	{
 		return m_omnidirectionalRotations;
@@ -243,6 +240,5 @@ private:
 		return m_shapeDirty || m_worldTransformDirty;
 	}
 };
-/// @}
 
 } // end namespace anki

+ 27 - 0
AnKi/Scene/SceneGraph.cpp

@@ -556,4 +556,31 @@ void SceneGraph::sceneNodeChangedName(SceneNode& node, CString oldName)
 	m_nodesRenamed.emplaceBack(std::pair(&node, oldName));
 }
 
+Error SceneGraph::saveToTextFile(CString filename)
+{
+	File file;
+	ANKI_CHECK(file.open(filename, FileOpenFlag::kWrite));
+
+	TextSceneSerializer serializer(&file, true);
+
+	U32 version = kSceneBinaryVersion;
+	ANKI_SERIALIZE(version, 1);
+
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
+	if(serializable) \
+	{ \
+		U32 name##Count = m_componentArrays.get##name##s().getSize(); \
+		ANKI_SERIALIZE(name##Count, 1); \
+		for(SceneComponent & comp : m_componentArrays.get##name##s()) \
+		{ \
+			U32 uuid = comp.getUuid(); \
+			ANKI_SERIALIZE(uuid, 1); \
+			ANKI_CHECK(comp.serialize(serializer)); \
+		} \
+	}
+#include <AnKi/Scene/Components/SceneComponentClasses.def.h>
+
+	return Error::kNone;
+}
+
 } // end namespace anki

+ 17 - 15
AnKi/Scene/SceneGraph.h

@@ -17,9 +17,6 @@
 
 namespace anki {
 
-/// @addtogroup scene
-/// @{
-
 ANKI_CVAR(NumericCVar<F32>, Scene, ProbeEffectiveDistance, 256.0f, 1.0f, kMaxF32, "How far various probes can render")
 ANKI_CVAR(NumericCVar<F32>, Scene, ProbeShadowEffectiveDistance, 32.0f, 1.0f, kMaxF32, "How far to render shadows for the various probes")
 
@@ -38,19 +35,23 @@ ANKI_CVAR(NumericCVar<U32>, Scene, MinGpuSceneRenderables, 10 * 1024, 8, 100 * 1
 class SceneComponentArrays
 {
 public:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	SceneBlockArray<name##Component>& get##name##s() \
+	{ \
+		return m_##name##Array; \
+	} \
+	const SceneBlockArray<name##Component>& get##name##s() const \
 	{ \
 		return m_##name##Array; \
 	}
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 private:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) SceneBlockArray<name##Component> m_##name##Array;
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) SceneBlockArray<name##Component> m_##name##Array;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 
-/// The scene graph that  all the scene entities
+// The scene graph that  all the scene entities
 class SceneGraph : public MakeSingleton<SceneGraph>
 {
 	template<typename>
@@ -94,13 +95,13 @@ public:
 
 	void update(Second prevUpdateTime, Second crntTime);
 
-	/// @note Thread-safe against itself. Can be called by SceneNode::update
+	// Note: Thread-safe against itself. Can be called by SceneNode::update
 	SceneNode* tryFindSceneNode(const CString& name);
 
-	/// @note Thread-safe against itself. Can be called by SceneNode::update
+	// Note: Thread-safe against itself. Can be called by SceneNode::update
 	SceneNode& findSceneNode(const CString& name);
 
-	/// Iterate the scene nodes using a lambda
+	// Iterate the scene nodes using a lambda
 	template<typename Func>
 	void visitNodes(Func func)
 	{
@@ -113,8 +114,8 @@ public:
 		}
 	}
 
-	/// Create a new SceneNode
-	/// @note Thread-safe against itself. Can be called by SceneNode::update
+	// Create a new SceneNode
+	// Note: Thread-safe against itself. Can be called by SceneNode::update
 	template<typename TNode>
 	TNode* newSceneNode(CString name)
 	{
@@ -134,8 +135,8 @@ public:
 		return m_sceneMax;
 	}
 
-	/// Get a unique UUID.
-	/// @note It's thread-safe.
+	// Get a unique UUID.
+	// Note: It's thread-safe.
 	U32 getNewUuid()
 	{
 		return m_nodesUuid.fetchAdd(1);
@@ -169,6 +170,8 @@ public:
 		m_checkForResourceUpdates = enable;
 	}
 
+	Error saveToTextFile(CString filename);
+
 private:
 	class UpdateSceneNodesCtx;
 
@@ -181,7 +184,7 @@ private:
 		}
 	} m_initMemPoolDummy;
 
-	static constexpr U32 kForceSetSceneBoundsFrameCount = 60 * 2; ///< Re-set the scene bounds after 2".
+	static constexpr U32 kForceSetSceneBoundsFrameCount = 60 * 2; // Re-set the scene bounds after 2".
 
 	mutable StackMemoryPool m_framePool;
 
@@ -221,6 +224,5 @@ private:
 
 	void sceneNodeChangedName(SceneNode& node, CString oldName);
 };
-/// @}
 
 } // end namespace anki

+ 2 - 2
AnKi/Scene/SceneNode.cpp

@@ -31,7 +31,7 @@
 namespace anki {
 
 // Specialize newComponent(). Do that first
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	template<> \
 	name##Component* SceneNode::newComponent<name##Component>() \
 	{ \
@@ -68,7 +68,7 @@ SceneNode::~SceneNode()
 
 		switch(comp->getType())
 		{
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, sceneNodeCanHaveMany, icon, serializable) \
 	case SceneComponentType::k##name: \
 		SceneGraph::getSingleton().getComponentArrays().get##name##s().erase(comp->getArrayIndex()); \
 		break;

+ 289 - 0
AnKi/Scene/SceneSerializer.h

@@ -0,0 +1,289 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Scene/Common.h>
+#include <AnKi/Resource/ResourceManager.h>
+
+namespace anki {
+
+constexpr U32 kSceneBinaryVersion = 1;
+
+#define ANKI_SERIALIZE(var, version) \
+	{ \
+		static_assert(version <= kSceneBinaryVersion); \
+		ANKI_CHECK(serializer.serialize(#var, version, false, var)); \
+	}
+
+#define ANKI_SERIALIZE_ALIAS(varName, var, version) \
+	{ \
+		static_assert(version <= kSceneBinaryVersion); \
+		ANKI_CHECK(serializer.serialize(varName, version, false, var)); \
+	}
+
+#define ANKI_SERIALIZE_DEPRECATED(var, version) \
+	{ \
+		static_assert(version <= kSceneBinaryVersion); \
+		ANKI_CHECK(serializer.serialize(#var, version, true, var)); \
+	}
+
+// Interface class for scene serialization
+class SceneSerializer
+{
+public:
+	SceneSerializer(Bool writingMode)
+		: m_writeMode(writingMode)
+	{
+	}
+
+	virtual Error write(CString name, ConstWeakArray<U32> values) = 0;
+	virtual Error read(CString name, WeakArray<U32> values) = 0;
+
+	virtual Error write(CString name, ConstWeakArray<I32> values) = 0;
+	virtual Error read(CString name, WeakArray<I32> values) = 0;
+
+	virtual Error write(CString name, ConstWeakArray<F32> values) = 0;
+	virtual Error read(CString name, WeakArray<F32> values) = 0;
+
+	virtual Error write(CString name, CString value) = 0;
+	virtual Error read(CString name, SceneString& value) = 0;
+
+	virtual Error write(CString name, ConstWeakArray<U8> byteArray) = 0;
+	virtual Error read(CString name, SceneDynamicArray<U8>& byteArray, U32& arraySize) = 0;
+
+	// For resources
+	template<typename T>
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, ResourcePtr<T>& rsrc)
+	{
+		SceneString filename;
+
+		if(m_writeMode && rsrc)
+		{
+			filename = rsrc->getFilename();
+		}
+
+		ANKI_CHECK(serializeInternal(varName, varVersion, varDeprecated, filename));
+
+		if(!m_writeMode && filename.getLength())
+		{
+			ANKI_CHECK(ResourceManager::getSingleton().loadResource(filename, rsrc));
+		}
+
+		return Error::kNone;
+	}
+
+	// For regular arithmetic
+	template<typename T>
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, T& varValue) requires(std::is_arithmetic_v<T>)
+	{
+		WeakArray<T> arr(&varValue, 1);
+		return serializeInternal(varName, varVersion, varDeprecated, arr);
+	}
+
+	// For regular arithmetic
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, F64& varValue)
+	{
+		F32 val = F32(varValue);
+		WeakArray<F32> arr(&val, 1);
+		ANKI_CHECK(serializeInternal(varName, varVersion, varDeprecated, arr));
+		varValue = val;
+		return Error::kNone;
+	}
+
+	// Vector 3
+	template<typename T>
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, TVec<T, 3>& varValue)
+	{
+		WeakArray<T> arr(&varValue[0], 3);
+		return serializeInternal(varName, varVersion, varDeprecated, arr);
+	}
+
+	// Vector 4
+	template<typename T>
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, TVec<T, 4>& varValue)
+	{
+		WeakArray<T> arr(&varValue[0], 4);
+		return serializeInternal(varName, varVersion, varDeprecated, arr);
+	}
+
+	// Enums
+	template<typename T>
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, T& varValue) requires(std::is_enum_v<T>)
+	{
+		static_assert(sizeof(T) <= sizeof(U32));
+		U32 val = U32(varValue);
+		WeakArray<U32> arr(&val, 1);
+		ANKI_CHECK(serializeInternal(varName, varVersion, varDeprecated, arr));
+		varValue = T(val);
+		return Error::kNone;
+	}
+
+	// Strings
+	Error serialize(String varName, U32 varVersion, Bool varDeprecated, SceneString& varValue)
+	{
+		return serializeInternal(varName, varVersion, varDeprecated, varValue);
+	}
+
+	Bool isInWriteMode() const
+	{
+		return m_writeMode;
+	}
+
+	Bool isInReadMode() const
+	{
+		return !m_writeMode;
+	}
+
+public:
+	Bool m_writeMode = false;
+	U32 m_crntBinaryVersion = kMaxU32;
+
+	template<typename T>
+	Error serializeInternal(CString varName, U32 varVersion, Bool varDeprecated, T& varValue)
+	{
+		ANKI_ASSERT(varVersion <= kSceneBinaryVersion);
+
+		if(m_writeMode)
+		{
+			// Always writing in the latest version so skip deprecated vars
+			const Bool skip = varDeprecated;
+
+			if(!skip)
+			{
+				return write(varName, varValue);
+			}
+		}
+		else
+		{
+			// Var is newer than the binary we are reading, skip it
+			Bool skip = varVersion >= m_crntBinaryVersion;
+
+			// Var is depracated and binary is newer, skip it
+			skip = skip || (varDeprecated && m_crntBinaryVersion > varVersion);
+
+			if(!skip)
+			{
+				return read(varName, varValue);
+			}
+		}
+
+		return Error::kNone;
+	}
+};
+
+// Serialize in a custom text format
+class TextSceneSerializer : public SceneSerializer
+{
+public:
+	TextSceneSerializer(File* file, Bool writingMode)
+		: SceneSerializer(writingMode)
+		, m_file(*file)
+	{
+	}
+
+	Error write(CString name, ConstWeakArray<U32> values) final
+	{
+		if(values.getSize() == 1)
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %u\n", name.cstr(), values[0]));
+		}
+		else
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %u ", name.cstr(), values[0]));
+			for(U32 i = 1; i < values.getSize(); ++i)
+			{
+				ANKI_CHECK(m_file.writeTextf((i < values.getSize() - 1) ? "%u " : "%u\n", values[i]));
+			}
+		}
+		return Error::kNone;
+	}
+
+	Error read([[maybe_unused]] CString name, [[maybe_unused]] WeakArray<U32> values) final
+	{
+		ANKI_ASSERT(!"TODO");
+		return Error::kNone;
+	}
+
+	Error write(CString name, ConstWeakArray<I32> values) final
+	{
+		if(values.getSize() == 1)
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %d\n", name.cstr(), values[0]));
+		}
+		else
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %d ", name.cstr(), values[0]));
+			for(U32 i = 1; i < values.getSize(); ++i)
+			{
+				ANKI_CHECK(m_file.writeTextf((i < values.getSize() - 1) ? "%d " : "%d\n", values[i]));
+			}
+		}
+		return Error::kNone;
+	}
+
+	Error read([[maybe_unused]] CString name, [[maybe_unused]] WeakArray<I32> values) final
+	{
+		ANKI_ASSERT(!"TODO");
+		return Error::kNone;
+	}
+
+	Error write(CString name, ConstWeakArray<F32> values) final
+	{
+		if(values.getSize() == 1)
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %f\n", name.cstr(), values[0]));
+		}
+		else
+		{
+			ANKI_CHECK(m_file.writeTextf("%s %f ", name.cstr(), values[0]));
+			for(U32 i = 1; i < values.getSize(); ++i)
+			{
+				ANKI_CHECK(m_file.writeTextf((i < values.getSize() - 1) ? "%f " : "%f\n", values[i]));
+			}
+		}
+		return Error::kNone;
+	}
+
+	Error read([[maybe_unused]] CString name, [[maybe_unused]] WeakArray<F32> values)
+	{
+		ANKI_ASSERT(!"TODO");
+		return Error::kNone;
+	}
+
+	Error write(CString name, CString value) final
+	{
+		return m_file.writeTextf("%s %s\n", name.cstr(), value.cstr());
+	}
+
+	Error read([[maybe_unused]] CString name, [[maybe_unused]] SceneString& value) final
+	{
+		ANKI_ASSERT(!"TODO");
+		return Error::kNone;
+	}
+
+	Error write(CString name, ConstWeakArray<U8> byteArray) final
+	{
+		ANKI_CHECK(m_file.writeTextf("%s %u ", name.cstr(), byteArray.getSize()));
+
+		for(U32 i = 0; i < byteArray.getSize(); ++i)
+		{
+			ANKI_CHECK(m_file.writeTextf((i < byteArray.getSize() - 1) ? "%u " : "%u\n", byteArray[i]));
+		}
+
+		return Error::kNone;
+	}
+
+	Error read([[maybe_unused]] CString name, [[maybe_unused]] SceneDynamicArray<U8>& byteArray, [[maybe_unused]] U32& arraySize) final
+	{
+		ANKI_ASSERT(!"TODO");
+		return Error::kNone;
+	}
+
+private:
+	File& m_file;
+};
+
+} // end namespace anki