Browse Source

GPU particles, Editor: Create the editor UI

Panagiotis Christopoulos Charitos 1 month ago
parent
commit
5b01f14ffa

+ 23 - 0
AnKi/Editor/EditorUi.cpp

@@ -89,6 +89,10 @@ void EditorUi::listDir(const std::filesystem::path& rootPath, const std::filesys
 			{
 				file.m_type = AssetFileType::kMesh;
 			}
+			else if(extension == ".ankipart")
+			{
+				file.m_type = AssetFileType::kParticleEmitter;
+			}
 
 			if(file.m_type != AssetFileType::kNone)
 			{
@@ -188,6 +192,14 @@ void EditorUi::draw(UiCanvas& canvas)
 		m_imageViewer.drawWindow(canvas, initialPos, initialSize, 0);
 	}
 
+	{
+		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
+		const Vec2 initialSize = Vec2(800.0f, 600.0f);
+		const Vec2 initialPos = (viewportSize - initialSize) / 2.0f;
+
+		m_particlesEditor.drawWindow(canvas, initialPos, initialSize, 0);
+	}
+
 	ImGui::End();
 
 	ImGui::PopStyleVar();
@@ -1298,6 +1310,17 @@ void EditorUi::assetsWindow()
 											m_imageViewer.m_open = true;
 										}
 									}
+									else if(file.m_type == AssetFileType::kParticleEmitter)
+									{
+										ImGui::PushFont(nullptr, cellWidth - 1.0f);
+										if(ImGui::Button(ICON_MDI_CREATION, Vec2(cellWidth)))
+										{
+											ParticleEmitterResource2Ptr rsrc;
+											ANKI_CHECKF(ResourceManager::getSingleton().loadResource(file.m_filename, rsrc));
+											m_particlesEditor.open(*rsrc);
+										}
+										ImGui::PopFont();
+									}
 									ImGui::PopID();
 
 									ImGui::TextWrapped("%s", file.m_basename.cstr());

+ 10 - 11
AnKi/Editor/EditorUi.h

@@ -7,6 +7,7 @@
 
 #include <AnKi/Ui.h>
 #include <AnKi/Editor/ImageViewerUi.h>
+#include <AnKi/Editor/ParticleEditorUi.h>
 #include <filesystem>
 
 namespace anki {
@@ -16,20 +17,17 @@ class SceneNode;
 #define ANKI_DEFINE_SCENE_COMPONENT(class_, weight, sceneNodeCanHaveMany, icon) class class_##Component;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
-/// @addtogroup editor
-/// @{
-
-/// A class that builds the editor UI and manipulates the scene directly.
+// A class that builds the editor UI and manipulates the scene directly.
 class EditorUi
 {
 public:
-	EditorUi();
+	Bool m_quit = false;
 
-	~EditorUi();
+	Bool m_mouseHoveredOverAnyWindow = false; // Mouse is over one of the editor windows.
 
-	Bool m_quit = false;
+	EditorUi();
 
-	Bool m_mouseHoveredOverAnyWindow = false; ///< Mouse is over one of the editor windows.
+	~EditorUi();
 
 	void draw(UiCanvas& canvas);
 
@@ -44,7 +42,8 @@ private:
 		kTexture,
 		kMaterial,
 		kMesh,
-		kLua
+		kLua,
+		kParticleEmitter
 	};
 
 	class AssetFile
@@ -88,6 +87,7 @@ private:
 	ImageResourcePtr m_meshIcon;
 
 	ImageViewerUi m_imageViewer;
+	ParticleEditorUi m_particlesEditor;
 
 	class
 	{
@@ -138,7 +138,7 @@ private:
 
 		ImGuiTextFilter m_fileFilter;
 
-		I32 m_cellSize = 8; ///< Icon size
+		I32 m_cellSize = 8; // Icon size
 	} m_assetsWindow;
 
 	class
@@ -176,6 +176,5 @@ private:
 	static void gatherAssets(DynamicArray<AssetPath>& paths);
 	void loadImageToCache(CString fname, ImageResourcePtr& img);
 };
-/// @}
 
 } // end namespace anki

+ 0 - 4
AnKi/Editor/ImageViewerUi.h

@@ -9,9 +9,6 @@
 
 namespace anki {
 
-/// @addtogroup editor
-/// @{
-
 class ImageViewerUi
 {
 public:
@@ -35,6 +32,5 @@ private:
 
 	U32 m_imageUuid = 0;
 };
-/// @}
 
 } // end namespace anki

+ 274 - 0
AnKi/Editor/ParticleEditorUi.cpp

@@ -0,0 +1,274 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Editor/ParticleEditorUi.h>
+#include <AnKi/Resource/ResourceFilesystem.h>
+#include <AnKi/Util/Filesystem.h>
+#include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
+
+namespace anki {
+
+void ParticleEditorUi::open(const ParticleEmitterResource2& resource)
+{
+	if(m_programs.getSize() == 0)
+	{
+		gatherParticlePrograms();
+	}
+
+	rebuildCache(resource);
+	m_open = true;
+}
+
+void ParticleEditorUi::drawWindow([[maybe_unused]] UiCanvas& canvas, Vec2 initialPos, Vec2 initialSize, ImGuiWindowFlags windowFlags)
+{
+	if(!m_open)
+	{
+		return;
+	}
+
+	if(m_programs.getSize() == 0)
+	{
+		gatherParticlePrograms();
+	}
+
+	if(ImGui::GetFrameCount() > 1)
+	{
+		ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
+	}
+
+	// Do the window now
+	Bool cacheDirty = false;
+	if(ImGui::Begin("Particle Editor", &m_open, windowFlags))
+	{
+		if(ImGui::BeginChild("Toolbox", Vec2(0.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY,
+							 ImGuiWindowFlags_None))
+		{
+			if(ImGui::Button(ICON_MDI_CONTENT_SAVE " Save"))
+			{
+				ANKI_LOGI("TODO");
+			}
+			ImGui::SameLine();
+			ImGui::SetNextItemWidth(-1.0f);
+			ImGui::Separator();
+			ImGui::EndChild();
+		}
+
+		if(ImGui::BeginChild("Content", Vec2(-1.0f, -1.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, windowFlags))
+		{
+			ImGui::SeparatorText("Common Properties");
+
+			// <shaderProgram>
+			if(ImGui::BeginCombo("Program", (m_currentlySelectedProgram.getLength()) ? m_currentlySelectedProgram.cstr() : nullptr,
+								 ImGuiComboFlags_HeightLarge))
+			{
+				for(U32 i = 0; i < m_programs.getSize(); ++i)
+				{
+					const Bool isSelected = (m_programs[i].m_name == m_currentlySelectedProgram);
+					if(ImGui::Selectable(m_programs[i].m_name.cstr(), isSelected))
+					{
+						cacheDirty = cacheDirty || m_currentlySelectedProgram != m_programs[i].m_name;
+						m_currentlySelectedProgram = m_programs[i].m_name;
+					}
+
+					if(isSelected)
+					{
+						ImGui::SetItemDefaultFocus();
+					}
+				}
+				ImGui::EndCombo();
+			}
+
+			// <particleCount>
+			I32 particleCount = I32(m_commonProps.m_particleCount);
+			if(ImGui::InputInt("Particle Count", &particleCount))
+			{
+				m_commonProps.m_particleCount = U32(max(0, particleCount));
+			}
+
+			// <emissionPeriod>
+			if(ImGui::InputFloat("Emission Period", &m_commonProps.m_emissionPeriod, 0.01f))
+			{
+				m_commonProps.m_emissionPeriod = max(0.0f, m_commonProps.m_emissionPeriod);
+			}
+
+			// <particlesPerEmission>
+			I32 particlesPerEmission = I32(m_commonProps.m_particlesPerEmission);
+			if(ImGui::InputInt("Particles Per Emission", &particlesPerEmission))
+			{
+				m_commonProps.m_particlesPerEmission = U32(clamp(particlesPerEmission, 0, particleCount));
+			}
+
+			ImGui::SeparatorText("Other Properties");
+
+			// Other props
+			for(Prop& prop : m_otherProps)
+			{
+				[[maybe_unused]] Bool valueChanged = false;
+				switch(prop.m_type)
+				{
+				case ShaderVariableDataType::kU32:
+					valueChanged = ImGui::InputInt(prop.m_name.cstr(), &prop.m_I32);
+					prop.m_I32 = max(prop.m_I32, 0);
+					break;
+				case ShaderVariableDataType::kUVec2:
+					valueChanged = ImGui::InputInt2(prop.m_name.cstr(), &prop.m_I32);
+					prop.m_IVec2 = prop.m_IVec2.max(0);
+					break;
+				case ShaderVariableDataType::kUVec3:
+					valueChanged = ImGui::InputInt3(prop.m_name.cstr(), &prop.m_I32);
+					prop.m_IVec3 = prop.m_IVec3.max(0);
+					break;
+				case ShaderVariableDataType::kUVec4:
+					valueChanged = ImGui::InputInt4(prop.m_name.cstr(), &prop.m_I32);
+					prop.m_IVec4 = prop.m_IVec4.max(0);
+					break;
+				case ShaderVariableDataType::kF32:
+					valueChanged = ImGui::InputFloat(prop.m_name.cstr(), &prop.m_F32, 1.0f);
+					break;
+				case ShaderVariableDataType::kVec2:
+					valueChanged = ImGui::InputFloat2(prop.m_name.cstr(), &prop.m_F32);
+					break;
+				case ShaderVariableDataType::kVec3:
+					valueChanged = ImGui::InputFloat3(prop.m_name.cstr(), &prop.m_F32);
+					break;
+				case ShaderVariableDataType::kVec4:
+					valueChanged = ImGui::InputFloat4(prop.m_name.cstr(), &prop.m_F32);
+					break;
+				default:
+					ANKI_ASSERT(!"TODO");
+				}
+			}
+		}
+		ImGui::EndChild();
+	}
+	ImGui::End();
+
+	if(cacheDirty)
+	{
+		rebuildCache(m_currentlySelectedProgram);
+	}
+}
+
+void ParticleEditorUi::gatherParticlePrograms()
+{
+	ResourceFilesystem::getSingleton().iterateAllFilenames([this](CString filename) {
+		String ext;
+		getFilepathExtension(filename, ext);
+		const CString extension = "ankiprogbin";
+		if(ext == extension)
+		{
+			ResourceFilePtr file;
+			ShaderBinary* binary = nullptr;
+			if(ResourceFilesystem::getSingleton().openFile(filename, file)
+			   || deserializeShaderBinaryFromAnyFile(*file, binary, DefaultMemoryPool::getSingleton()))
+			{
+				ANKI_LOGE("Failed to load particles file. Ignoring this file: %s", filename.cstr());
+			}
+			else
+			{
+				for(const auto& strct : binary->m_structs)
+				{
+					if(CString(strct.m_name.getBegin()).find("AnKiParticleEmitterProperties") == 0)
+					{
+						String basename;
+						getFilepathFilename(filename, basename);
+						ANKI_ASSERT(basename.getLength() > extension.getLength() + 1);
+						basename = String(basename.getBegin(), basename.getBegin() + basename.getLength() - extension.getLength() - 1);
+
+						ParticleProgram& prog = *m_programs.emplaceBack();
+						prog.m_filename = filename;
+						prog.m_name = basename;
+						prog.m_props.resize(strct.m_members.getSize());
+
+						for(U32 i = 0; i < prog.m_props.getSize(); ++i)
+						{
+							prog.m_props[i].m_name = strct.m_members[i].m_name.getBegin();
+							prog.m_props[i].m_type = strct.m_members[i].m_type;
+
+							switch(prog.m_props[i].m_type)
+							{
+#define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) \
+	case ShaderVariableDataType::k##type: \
+	{ \
+		memcpy(&prog.m_props[i].m_F32, strct.m_members[i].m_defaultValues.getBegin(), sizeof(type)); \
+		break; \
+	}
+#include <AnKi/Gr/ShaderVariableDataType.def.h>
+							default:
+								ANKI_ASSERT(0);
+								break;
+							}
+						}
+					}
+				}
+
+				DefaultMemoryPool::getSingleton().free(binary);
+			}
+		}
+		return FunctorContinue::kContinue;
+	});
+}
+
+void ParticleEditorUi::rebuildCache(const ParticleEmitterResource2& rsrc)
+{
+	m_commonProps = rsrc.getCommonProperties();
+
+	m_currentlySelectedProgram = rsrc.getShaderProgramResource().getFilename();
+	getFilepathFilename(m_currentlySelectedProgram, m_currentlySelectedProgram);
+	const CString extension = "ankiprogbin";
+	m_currentlySelectedProgram = String(m_currentlySelectedProgram.getBegin(),
+										m_currentlySelectedProgram.getBegin() + m_currentlySelectedProgram.getLength() - (extension.getLength() + 1));
+
+	m_otherProps.resize(rsrc.getOtherProperties().getSize());
+	for(U32 i = 0; i < m_otherProps.getSize(); ++i)
+	{
+		m_otherProps[i].m_name = rsrc.getOtherProperties()[i].getName();
+		m_otherProps[i].m_type = rsrc.getOtherProperties()[i].getDataType();
+
+		switch(m_otherProps[i].m_type)
+		{
+#define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) \
+	case ShaderVariableDataType::k##type: \
+	{ \
+		m_otherProps[i].m_##type = rsrc.getOtherProperties()[i].getValue<type>(); \
+		break; \
+	}
+#include <AnKi/Gr/ShaderVariableDataType.def.h>
+
+		default:
+			ANKI_ASSERT(0);
+			break;
+		}
+	}
+}
+
+void ParticleEditorUi::rebuildCache(CString particleProgramName)
+{
+	const ParticleProgram* foundProg = nullptr;
+	for(const ParticleProgram& prog : m_programs)
+	{
+		if(particleProgramName == prog.m_name)
+		{
+			foundProg = &prog;
+			break;
+		}
+	}
+
+	ANKI_ASSERT(foundProg);
+
+	m_commonProps = {};
+	m_currentlySelectedProgram = particleProgramName;
+
+	m_otherProps.resize(foundProg->m_props.getSize());
+	for(U32 i = 0; i < m_otherProps.getSize(); ++i)
+	{
+		m_otherProps[i].m_name = foundProg->m_props[i].m_name;
+		m_otherProps[i].m_type = foundProg->m_props[i].m_type;
+		m_otherProps[i].m_Mat4 = foundProg->m_props[i].m_Mat4;
+	}
+}
+
+} // end namespace anki

+ 80 - 0
AnKi/Editor/ParticleEditorUi.h

@@ -0,0 +1,80 @@
+// 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/Ui.h>
+#include <AnKi/Resource/ParticleEmitterResource2.h>
+
+namespace anki {
+
+class ParticleEditorUi
+{
+public:
+	void open(const ParticleEmitterResource2& resource);
+
+	void openNew();
+
+	void drawWindow(UiCanvas& canvas, Vec2 initialPos, Vec2 initialSize, ImGuiWindowFlags windowFlags = 0);
+
+private:
+	class Prop
+	{
+	public:
+		union
+		{
+#define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) type ANKI_CONCATENATE(m_, type);
+#include <AnKi/Gr/ShaderVariableDataType.def.h>
+		};
+
+		String m_name;
+		ShaderVariableDataType m_type = ShaderVariableDataType::kNone;
+
+		Prop()
+		{
+			m_Mat4 = Mat4(0.0f);
+		}
+
+		Prop(const Prop& b)
+		{
+			*this = b;
+		}
+
+		Prop& operator=(const Prop& b)
+		{
+			m_name = b.m_name;
+			m_type = b.m_type;
+			m_Mat4 = b.m_Mat4;
+			return *this;
+		}
+	};
+
+	class ParticleProgram
+	{
+	public:
+		String m_filename;
+		String m_name;
+		DynamicArray<Prop> m_props;
+	};
+
+	Bool m_open = false;
+
+	DynamicArray<ParticleProgram> m_programs;
+
+	// Cache begin. The UI will manipulate this cache because the resource is immutable
+	String m_currentlySelectedProgram;
+	ParticleEmitterResourceCommonProperties m_commonProps = {};
+	DynamicArray<Prop> m_otherProps;
+	// Cache end
+
+	// Look at the filesystem and get the programs that are for particles
+	void gatherParticlePrograms();
+
+	void rebuildCache(const ParticleEmitterResource2& resource);
+
+	void rebuildCache(CString particleProgramName);
+};
+
+} // end namespace anki

+ 25 - 0
AnKi/Resource/ParticleEmitterResource2.cpp

@@ -134,6 +134,7 @@ Error ParticleEmitterResource2::parseShaderProgram(XmlElement shaderProgramEl, B
 		}
 
 		m_prefilledAnKiParticleEmitterProperties.resize(propsStruct->m_size, 0_U8);
+		m_otherProps.resize(propsStruct->m_members.getSize());
 
 		for(U32 i = 0; i < propsStruct->m_members.getSize(); ++i)
 		{
@@ -142,6 +143,25 @@ Error ParticleEmitterResource2::parseShaderProgram(XmlElement shaderProgramEl, B
 
 			ANKI_ASSERT(member.m_offset + memberSize <= propsStruct->m_size);
 			memcpy(m_prefilledAnKiParticleEmitterProperties.getBegin() + member.m_offset, member.m_defaultValues.getBegin(), memberSize);
+
+			// Initialize the property with the default values
+			ParticleEmitterResourceProperty& prop = m_otherProps[i];
+			prop.m_offsetInAnKiParticleEmitterProperties = member.m_offset;
+			prop.m_name = member.m_name.getBegin();
+			prop.m_dataType = member.m_type;
+			switch(prop.m_dataType)
+			{
+#define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) \
+	case ShaderVariableDataType::k##type: \
+	{ \
+		ANKI_ASSERT(sizeof(prop.m_##type) == memberSize); \
+		memcpy(&prop.m_##type, member.m_defaultValues.getBegin(), memberSize); \
+		break; \
+	}
+#include <AnKi/Gr/ShaderVariableDataType.def.h>
+			default:
+				ANKI_ASSERT(0);
+			}
 		}
 	}
 
@@ -224,6 +244,7 @@ Error ParticleEmitterResource2::parseInput(XmlElement inputEl)
 
 	// Find the member
 	const ShaderBinaryStructMember* foundMember = nullptr;
+	U32 foundMemberIdx = kMaxU32;
 	for(U32 i = 0; i < propsStruct->m_members.getSize(); ++i)
 	{
 		const ShaderBinaryStructMember& member = propsStruct->m_members[i];
@@ -231,6 +252,7 @@ Error ParticleEmitterResource2::parseInput(XmlElement inputEl)
 		if(memberName == varName)
 		{
 			foundMember = &member;
+			foundMemberIdx = i;
 			break;
 		}
 	}
@@ -244,6 +266,8 @@ Error ParticleEmitterResource2::parseInput(XmlElement inputEl)
 	const U32 memberSize = getShaderVariableDataTypeInfo(foundMember->m_type).m_size;
 	const U32 memberOffset = foundMember->m_offset;
 
+	ParticleEmitterResourceProperty& prop = m_otherProps[foundMemberIdx];
+
 	// Set the value
 	switch(foundMember->m_type)
 	{
@@ -255,6 +279,7 @@ Error ParticleEmitterResource2::parseInput(XmlElement inputEl)
 		ANKI_ASSERT(memberOffset + memberSize <= m_prefilledAnKiParticleEmitterProperties.getSize()); \
 		ANKI_ASSERT(memberSize == sizeof(arr)); \
 		memcpy(m_prefilledAnKiParticleEmitterProperties.getBegin() + memberOffset, arr.getBegin(), memberSize); \
+		memcpy(&prop.m_##type, arr.getBegin(), memberSize); \
 		break; \
 	}
 #include <AnKi/Gr/ShaderVariableDataType.def.h>

+ 25 - 0
AnKi/Resource/ParticleEmitterResource2.h

@@ -15,7 +15,14 @@ class ShaderProgramResourceVariantInitInfo;
 
 class ParticleEmitterResourceProperty
 {
+	friend class ParticleEmitterResource2;
+
 public:
+	ParticleEmitterResourceProperty()
+	{
+		zeroMemory(m_Mat4);
+	}
+
 	ParticleEmitterResourceProperty(const ParticleEmitterResourceProperty&) = delete; // Non-copyable
 
 	ParticleEmitterResourceProperty& operator=(const ParticleEmitterResourceProperty&) = delete; // Non-copyable
@@ -28,6 +35,12 @@ public:
 	template<typename T>
 	const T& getValue() const;
 
+	ShaderVariableDataType getDataType() const
+	{
+		ANKI_ASSERT(m_dataType != ShaderVariableDataType::kNone);
+		return m_dataType;
+	}
+
 private:
 	ResourceString m_name;
 	U32 m_offsetInAnKiParticleEmitterProperties = kMaxU32;
@@ -97,6 +110,11 @@ public:
 		return m_commonProps;
 	}
 
+	ConstWeakArray<ParticleEmitterResourceProperty> getOtherProperties() const
+	{
+		return m_otherProps;
+	}
+
 	ConstWeakArray<U8> getPrefilledAnKiParticleEmitterProperties() const
 	{
 		return m_prefilledAnKiParticleEmitterProperties;
@@ -107,6 +125,11 @@ public:
 		return *m_grProg;
 	}
 
+	const ShaderProgramResource& getShaderProgramResource() const
+	{
+		return *m_prog;
+	}
+
 private:
 	ResourceDynamicArray<U8> m_prefilledAnKiParticleEmitterProperties;
 
@@ -115,6 +138,8 @@ private:
 
 	ParticleEmitterResourceCommonProperties m_commonProps;
 
+	ResourceDynamicArray<ParticleEmitterResourceProperty> m_otherProps;
+
 	Error parseShaderProgram(XmlElement shaderProgramEl, Bool async);
 
 	Error parseMutators(XmlElement mutatorsEl, ShaderProgramResourceVariantInitInfo& variantInitInfo);

+ 6 - 3
AnKi/Resource/ResourceFilesystem.h

@@ -102,16 +102,19 @@ public:
 
 	/// Iterate all the filenames from all paths provided.
 	template<typename TFunc>
-	Error iterateAllFilenames(TFunc func) const
+	void iterateAllFilenames(TFunc func) const
 	{
 		for(const Path& path : m_paths)
 		{
 			for(const ResourceString& fname : path.m_files)
 			{
-				ANKI_CHECK(func(fname.toCString()));
+				const FunctorContinue cont_ = func(fname.toCString());
+				if(cont_ == FunctorContinue::kStop)
+				{
+					break;
+				}
 			}
 		}
-		return Error::kNone;
 	}
 
 	/// Iterate paths in the DataPaths CVar

+ 13 - 6
AnKi/Resource/ShaderProgramResourceSystem.cpp

@@ -160,21 +160,28 @@ Error ShaderProgramResourceSystem::createRayTracingPrograms(ResourceDynamicArray
 
 	ResourceDynamicArray<Lib> libs;
 
-	const Error err = ResourceFilesystem::getSingleton().iterateAllFilenames([&](CString filename) -> Error {
+	Error err = Error::kNone;
+	ResourceFilesystem::getSingleton().iterateAllFilenames([&](CString filename) {
 		// Check file extension
 		String extension;
 		getFilepathExtension(filename, extension);
 		const Char binExtension[] = "ankiprogbin";
 		if(extension.getLength() != sizeof(binExtension) - 1 || extension != binExtension)
 		{
-			return Error::kNone;
+			return FunctorContinue::kContinue;
 		}
 
 		// Get the binary
 		ResourceFilePtr file;
-		ANKI_CHECK(ResourceFilesystem::getSingleton().openFile(filename, file));
+		if((err = ResourceFilesystem::getSingleton().openFile(filename, file)))
+		{
+			return FunctorContinue::kStop;
+		}
 		ShaderBinary* binary;
-		ANKI_CHECK(deserializeShaderBinaryFromAnyFile(*file, binary, ResourceMemoryPool::getSingleton()));
+		if((err = deserializeShaderBinaryFromAnyFile(*file, binary, ResourceMemoryPool::getSingleton())))
+		{
+			return FunctorContinue::kStop;
+		}
 
 		class Dummy
 		{
@@ -189,7 +196,7 @@ Error ShaderProgramResourceSystem::createRayTracingPrograms(ResourceDynamicArray
 
 		if(!(binary->m_shaderTypes & ShaderTypeBit::kAllRayTracing))
 		{
-			return Error::kNone;
+			return FunctorContinue::kContinue;
 		}
 
 		// Create the program name
@@ -323,7 +330,7 @@ Error ShaderProgramResourceSystem::createRayTracingPrograms(ResourceDynamicArray
 			}
 		} // iterate techniques
 
-		return Error::kNone;
+		return FunctorContinue::kContinue;
 	}); // For all RT filenames
 	ANKI_CHECK(err);
 

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

@@ -211,6 +211,7 @@ void MaterialComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 					m_gpuSceneRenderableAabbForward.uploadToGpuScene(gpuVolume);
 					break;
 				case RenderingTechnique::kRtMaterialFetch:
+				case RenderingTechnique::kRtShadow:
 					m_gpuSceneRenderableAabbRt.uploadToGpuScene(gpuVolume);
 					break;
 				default:

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

@@ -31,12 +31,12 @@ ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f, false, VECTOR_POLYGON)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
-ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter2, 60.0f, false, FOUNTAIN)
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter2, 60.0f, false, CREATION)
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f, true, TEXTURE_BOX)
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f, false, FOUNTAIN)
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f, false, CREATION)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f, false, LIQUID_SPOT)
 ANKI_SCENE_COMPONENT_SEPARATOR

+ 1 - 0
AnKi/Util/String.h

@@ -490,6 +490,7 @@ public:
 	/// Append using a range. Copies the range of [first, oneAfterLast)
 	BaseString& append(ConstIterator first, ConstIterator oneAfterLast)
 	{
+		ANKI_ASSERT(oneAfterLast >= first);
 		const PtrSize len = oneAfterLast - first;
 		appendInternal(first, len);
 		return *this;