Browse Source

Work on the new shader program design

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
fa002881f5

+ 1 - 1
docs/doxyfile

@@ -330,7 +330,7 @@ IDL_PROPERTY_SUPPORT   = YES
 # all members of a group must be documented explicitly.
 # The default value is: NO.
 
-DISTRIBUTE_GROUP_DOC   = NO
+DISTRIBUTE_GROUP_DOC   = YES
 
 # Set the SUBGROUPING tag to YES to allow class member groups of the same type
 # (for instance a group of public functions) to be put as a subgroup of that

+ 2 - 2
src/anki/misc/ConfigSet.cpp

@@ -151,7 +151,7 @@ Error ConfigSet::loadFromFile(CString filename)
 		{
 			if(option.m_type == 1)
 			{
-				ANKI_CHECK(el.getF64(option.m_fVal));
+				ANKI_CHECK(el.getNumber(option.m_fVal));
 			}
 			else
 			{
@@ -251,7 +251,7 @@ Error ConfigSet::setFromCommandLineArguments(U cmdLineArgsCount, char* cmdLineAr
 			else
 			{
 				CString val(arg);
-				ANKI_CHECK(val.toF64(option->m_fVal));
+				ANKI_CHECK(val.toNumber(option->m_fVal));
 			}
 		}
 	}

+ 36 - 97
src/anki/misc/Xml.cpp

@@ -4,7 +4,6 @@
 // http://www.anki3d.org/LICENSE
 
 #include <anki/misc/Xml.h>
-#include <anki/util/StringList.h>
 #include <anki/util/File.h>
 #include <anki/util/Logger.h>
 
@@ -37,96 +36,10 @@ Error XmlElement::getText(CString& out) const
 	return err;
 }
 
-Error XmlElement::getI64(I64& out) const
-{
-	Error err = check();
-
-	if(!err)
-	{
-		const char* txt = m_el->GetText();
-		if(txt != nullptr)
-		{
-			err = CString(txt).toI64(out);
-		}
-		else
-		{
-			ANKI_MISC_LOGE("Failed to return int. Element: %s", m_el->Value());
-			err = ErrorCode::USER_DATA;
-		}
-	}
-
-	return err;
-}
-
-Error XmlElement::getF64(F64& out) const
-{
-	Error err = check();
-
-	if(!err)
-	{
-		const char* txt = m_el->GetText();
-		if(txt != nullptr)
-		{
-			err = CString(txt).toF64(out);
-		}
-		else
-		{
-			ANKI_MISC_LOGE("Failed to return float. Element: %s", m_el->Value());
-			err = ErrorCode::USER_DATA;
-		}
-	}
-
-	return err;
-}
-
-Error XmlElement::getFloats(DynamicArrayAuto<F64>& out) const
-{
-	Error err = check();
-
-	const char* txt;
-	if(!err)
-	{
-		txt = m_el->GetText();
-		if(txt == nullptr)
-		{
-			err = ErrorCode::USER_DATA;
-		}
-	}
-
-	StringList list;
-	if(!err)
-	{
-		list.splitString(m_alloc, txt, ' ');
-	}
-
-	out = DynamicArrayAuto<F64>(m_alloc);
-
-	if(!err)
-	{
-		out.create(list.getSize());
-	}
-
-	auto it = list.getBegin();
-	for(U i = 0; i < out.getSize() && !err; i++)
-	{
-		err = it->toF64(out[i]);
-		++it;
-	}
-
-	if(err)
-	{
-		ANKI_MISC_LOGE("Failed to return floats. Element: %s", m_el->Value());
-	}
-
-	list.destroy(m_alloc);
-
-	return err;
-}
-
 Error XmlElement::getMat3(Mat3& out) const
 {
-	DynamicArrayAuto<F64> arr(m_alloc);
-	Error err = getFloats(arr);
+	DynamicArrayAuto<F32> arr(m_alloc);
+	Error err = getNumbers(arr);
 
 	if(!err && arr.getSize() != 9)
 	{
@@ -152,8 +65,8 @@ Error XmlElement::getMat3(Mat3& out) const
 
 Error XmlElement::getMat4(Mat4& out) const
 {
-	DynamicArrayAuto<F64> arr(m_alloc);
-	Error err = getFloats(arr);
+	DynamicArrayAuto<F32> arr(m_alloc);
+	Error err = getNumbers(arr);
 
 	if(!err && arr.getSize() != 16)
 	{
@@ -179,8 +92,8 @@ Error XmlElement::getMat4(Mat4& out) const
 
 Error XmlElement::getVec2(Vec2& out) const
 {
-	DynamicArrayAuto<F64> arr(m_alloc);
-	Error err = getFloats(arr);
+	DynamicArrayAuto<F32> arr(m_alloc);
+	Error err = getNumbers(arr);
 
 	if(!err && arr.getSize() != 2)
 	{
@@ -206,8 +119,8 @@ Error XmlElement::getVec2(Vec2& out) const
 
 Error XmlElement::getVec3(Vec3& out) const
 {
-	DynamicArrayAuto<F64> arr(m_alloc);
-	Error err = getFloats(arr);
+	DynamicArrayAuto<F32> arr(m_alloc);
+	Error err = getNumbers(arr);
 
 	if(!err && arr.getSize() != 3)
 	{
@@ -233,8 +146,8 @@ Error XmlElement::getVec3(Vec3& out) const
 
 Error XmlElement::getVec4(Vec4& out) const
 {
-	DynamicArrayAuto<F64> arr(m_alloc);
-	Error err = getFloats(arr);
+	DynamicArrayAuto<F32> arr(m_alloc);
+	Error err = getNumbers(arr);
 
 	if(!err && arr.getSize() != 4)
 	{
@@ -336,6 +249,32 @@ Error XmlElement::getSiblingElementsCount(U32& out) const
 	return err;
 }
 
+Error XmlElement::getAttributeTextOptional(const CString& name, CString& out, Bool& attribPresent) const
+{
+	ANKI_CHECK(check());
+
+	const tinyxml2::XMLAttribute* attrib = m_el->FindAttribute(&name[0]);
+	if(!attrib)
+	{
+		attribPresent = false;
+		return ErrorCode::USER_DATA;
+	}
+
+	attribPresent = true;
+
+	const char* value = attrib->Value();
+	if(value)
+	{
+		out = value;
+	}
+	else
+	{
+		out = CString();
+	}
+
+	return ErrorCode::NONE;
+}
+
 CString XmlDocument::XML_HEADER = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
 
 Error XmlDocument::loadFile(const CString& filename, GenericMemoryPoolAllocator<U8> alloc)

+ 169 - 14
src/anki/misc/Xml.h

@@ -8,6 +8,7 @@
 #include <anki/misc/Common.h>
 #include <anki/util/String.h>
 #include <anki/util/DynamicArray.h>
+#include <anki/util/StringList.h>
 #include <anki/Math.h>
 #include <tinyxml2.h>
 #if !ANKI_TINYXML2
@@ -57,26 +58,46 @@ public:
 		return *this;
 	}
 
-	/// Return the text inside a tag
+	/// Return the text inside a tag. May return empty string.
 	ANKI_USE_RESULT Error getText(CString& out) const;
 
-	/// Return the text inside as an int
-	ANKI_USE_RESULT Error getI64(I64& out) const;
-
-	/// Return the text inside as a float
-	ANKI_USE_RESULT Error getF64(F64& out) const;
-
-	/// Return the text inside as a float
-	ANKI_USE_RESULT Error getF32(F32& out) const
+	/// Return the text inside as a number.
+	template<typename T>
+	ANKI_USE_RESULT Error getNumber(T& out) const
 	{
-		F64 outd;
-		ANKI_CHECK(getF64(outd));
-		out = outd;
+		ANKI_CHECK(check());
+
+		const char* txt = m_el->GetText();
+		if(txt != nullptr)
+		{
+			ANKI_CHECK(CString(txt).toNumber(out));
+		}
+		else
+		{
+			ANKI_MISC_LOGE("Failed to return number. Element: %s", m_el->Value());
+			return ErrorCode::USER_DATA;
+		}
+
 		return ErrorCode::NONE;
 	}
 
-	/// Get a number of floats
-	ANKI_USE_RESULT Error getFloats(DynamicArrayAuto<F64>& out) const;
+	/// Get a number of numbers.
+	template<typename T>
+	ANKI_USE_RESULT Error getNumbers(DynamicArrayAuto<T>& out) const
+	{
+		CString txt;
+		ANKI_CHECK(getText(txt));
+
+		if(txt)
+		{
+			return parseNumbers(txt, out);
+		}
+		else
+		{
+			out = DynamicArrayAuto<T>(m_alloc);
+			return ErrorCode::NONE;
+		}
+	}
 
 	/// Return the text inside as a Mat4
 	ANKI_USE_RESULT Error getMat4(Mat4& out) const;
@@ -106,11 +127,145 @@ public:
 	/// @note The sibling elements share the same name.
 	ANKI_USE_RESULT Error getSiblingElementsCount(U32& out) const;
 
+	/// @name Get attributes optional
+	/// @{
+
+	/// Get value of a string attribute. May return empty string.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	/// @param attribPresent True if the attribute exists. If it doesn't the @a out is undefined.
+	ANKI_USE_RESULT Error getAttributeTextOptional(const CString& name, CString& out, Bool& attribPresent) const;
+
+	/// Get the attribute's value as a series of numbers.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	/// @param attribPresent True if the attribute exists. If it doesn't the @a out is undefined.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeNumbersOptional(
+		const CString& name, DynamicArrayAuto<T>& out, Bool& attribPresent) const
+	{
+		CString txtVal;
+		ANKI_CHECK(getAttributeTextOptional(name, txtVal, attribPresent));
+
+		if(txtVal && attribPresent)
+		{
+			return parseNumbers(txtVal, out);
+		}
+		else
+		{
+			return ErrorCode::NONE;
+		}
+	}
+
+	/// Get the attribute's value as a number.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	/// @param attribPresent True if the attribute exists. If it doesn't the @a out is undefined.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeNumberOptional(const CString& name, T& out, Bool& attribPresent) const
+	{
+		DynamicArrayAuto<T> arr(m_alloc);
+		ANKI_CHECK(getAttributeIntsOptional(name, arr, attribPresent));
+
+		if(attribPresent)
+		{
+			if(arr.getSize() != 1)
+			{
+				ANKI_MISC_LOGE("Expecting one element for attrib %s", &name[0]);
+				return ErrorCode::USER_DATA;
+			}
+
+			out = arr[0];
+		}
+
+		return ErrorCode::NONE;
+	}
+	/// @}
+
+	/// @name Get attributes
+	/// @{
+
+	/// Get value of a string attribute. May return empty string.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	ANKI_USE_RESULT Error getAttributeText(const CString& name, CString& out) const
+	{
+		Bool found;
+		ANKI_CHECK(getAttributeTextOptional(name, out, found));
+		return throwAttribNotFoundError(name, found);
+	}
+
+	/// Get the attribute's value as a series of numbers.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeNumbers(const CString& name, DynamicArrayAuto<T>& out) const
+	{
+		Bool found;
+		ANKI_CHECK(getAttributeNumbersOptional(name, out, found));
+		return throwAttribNotFoundError(name, found);
+	}
+
+	/// Get the attribute's value as a number.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeNumber(const CString& name, T& out) const
+	{
+		Bool found;
+		ANKI_CHECK(getAttributeNumberOptional(name, out, found));
+		return throwAttribNotFoundError(name, found);
+	}
+	/// @}
+
 private:
 	const tinyxml2::XMLElement* m_el;
 	GenericMemoryPoolAllocator<U8> m_alloc;
 
 	ANKI_USE_RESULT Error check() const;
+
+	template<typename T>
+	ANKI_USE_RESULT Error parseNumbers(CString txt, DynamicArrayAuto<T>& out) const
+	{
+		ANKI_ASSERT(txt);
+		ANKI_ASSERT(m_el);
+
+		StringListAuto list(m_alloc);
+		list.splitString(txt, ' ');
+
+		out = DynamicArrayAuto<T>(m_alloc);
+		out.create(list.getSize());
+
+		Error err = ErrorCode::NONE;
+		auto it = list.getBegin();
+		auto end = list.getEnd();
+		U i = 0;
+		while(it != end && !err)
+		{
+			err = it->toNumber(out[i++]);
+			++it;
+		}
+
+		if(err)
+		{
+			ANKI_MISC_LOGE("Failed to parse floats. Element: %s", m_el->Value());
+		}
+
+		return err;
+	}
+
+	ANKI_USE_RESULT Error throwAttribNotFoundError(CString attrib, Bool found) const
+	{
+		if(!found)
+		{
+			ANKI_MISC_LOGE("Attribute not found %s", &attrib[0]);
+			return ErrorCode::USER_DATA;
+		}
+		else
+		{
+			return ErrorCode::NONE;
+		}
+	}
 };
 
 /// XML document

+ 5 - 5
src/anki/resource/Animation.cpp

@@ -49,7 +49,7 @@ Error Animation::load(const ResourceFilename& filename)
 	ANKI_CHECK(rootel.getChildElementOptional("repeat", repel));
 	if(repel)
 	{
-		ANKI_CHECK(repel.getI64(tmp));
+		ANKI_CHECK(repel.getNumber(tmp));
 		m_repeat = tmp;
 	}
 	else
@@ -103,7 +103,7 @@ Error Animation::load(const ResourceFilename& filename)
 
 				// <time>
 				ANKI_CHECK(keyEl.getChildElement("time", el));
-				ANKI_CHECK(el.getF64(ftmp));
+				ANKI_CHECK(el.getNumber(ftmp));
 				key.m_time = ftmp;
 				m_startTime = std::min(m_startTime, key.m_time);
 				maxTime = std::max(maxTime, key.m_time);
@@ -140,7 +140,7 @@ Error Animation::load(const ResourceFilename& filename)
 
 				// <time>
 				ANKI_CHECK(keyEl.getChildElement("time", el));
-				ANKI_CHECK(el.getF64(ftmp));
+				ANKI_CHECK(el.getNumber(ftmp));
 				key.m_time = ftmp;
 				m_startTime = std::min(m_startTime, key.m_time);
 				maxTime = std::max(maxTime, key.m_time);
@@ -179,14 +179,14 @@ Error Animation::load(const ResourceFilename& filename)
 
 				// <time>
 				ANKI_CHECK(keyEl.getChildElement("time", el));
-				ANKI_CHECK(el.getF64(ftmp));
+				ANKI_CHECK(el.getNumber(ftmp));
 				key.m_time = ftmp;
 				m_startTime = std::min(m_startTime, key.m_time);
 				maxTime = std::max(maxTime, key.m_time);
 
 				// <value>
 				ANKI_CHECK(keyEl.getChildElement("value", el));
-				ANKI_CHECK(el.getF64(ftmp));
+				ANKI_CHECK(el.getNumber(ftmp));
 				key.m_value = ftmp;
 
 				// Check ident

+ 1 - 1
src/anki/resource/CollisionResource.cpp

@@ -34,7 +34,7 @@ Error CollisionResource::load(const ResourceFilename& filename)
 	if(type == "sphere")
 	{
 		F64 tmp;
-		ANKI_CHECK(valEl.getF64(tmp));
+		ANKI_CHECK(valEl.getNumber(tmp));
 		m_physicsShape = physics.newInstance<PhysicsSphere>(csInit, tmp);
 	}
 	else if(type == "box")

+ 5 - 5
src/anki/resource/Material.cpp

@@ -66,7 +66,7 @@ Error Material::load(const ResourceFilename& filename)
 	if(el)
 	{
 		I64 num;
-		ANKI_CHECK(el.getI64(num));
+		ANKI_CHECK(el.getNumber(num));
 		m_shadow = num != 0;
 	}
 
@@ -75,7 +75,7 @@ Error Material::load(const ResourceFilename& filename)
 	if(el)
 	{
 		I64 num;
-		ANKI_CHECK(el.getI64(num));
+		ANKI_CHECK(el.getNumber(num));
 
 		if(num <= 0 || num > I64(MAX_LODS))
 		{
@@ -91,7 +91,7 @@ Error Material::load(const ResourceFilename& filename)
 	if(el)
 	{
 		I64 num;
-		ANKI_CHECK(el.getI64(num));
+		ANKI_CHECK(el.getNumber(num));
 		m_forwardShading = num != 0;
 	}
 
@@ -167,7 +167,7 @@ Error Material::parseInputs(XmlElement inputsEl)
 			switch(foundVar->getShaderVariableDataType())
 			{
 			case ShaderVariableDataType::FLOAT:
-				ANKI_CHECK(valueEl.getF32(constVal.m_float));
+				ANKI_CHECK(valueEl.getNumber(constVal.m_float));
 				break;
 			case ShaderVariableDataType::VEC2:
 				ANKI_CHECK(valueEl.getVec2(constVal.m_vec2));
@@ -248,7 +248,7 @@ Error Material::parseInputs(XmlElement inputsEl)
 				switch(foundVar->getShaderVariableDataType())
 				{
 				case ShaderVariableDataType::FLOAT:
-					ANKI_CHECK(valueEl.getF32(mtlVar.m_float));
+					ANKI_CHECK(valueEl.getNumber(mtlVar.m_float));
 					break;
 				case ShaderVariableDataType::VEC2:
 					ANKI_CHECK(valueEl.getVec2(mtlVar.m_vec2));

+ 2 - 2
src/anki/resource/ParticleEmitterResource.cpp

@@ -40,7 +40,7 @@ static ANKI_USE_RESULT Error xmlF32(const XmlElement& el_, const CString& str, F
 	}
 
 	F64 tmp;
-	err = el.getF64(tmp);
+	err = el.getNumber(tmp);
 	if(!err)
 	{
 		out = tmp;
@@ -61,7 +61,7 @@ static ANKI_USE_RESULT Error xmlU32(const XmlElement& el_, const CString& str, U
 	}
 
 	I64 tmp;
-	err = el.getI64(tmp);
+	err = el.getNumber(tmp);
 	if(!err)
 	{
 		out = static_cast<U32>(tmp);

+ 42 - 6
src/anki/resource/ShaderProgramResource.cpp

@@ -185,6 +185,42 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 	XmlElement rootEl;
 	ANKI_CHECK(doc.getChildElement("shaderProgram", rootEl));
 
+	// <mutators>
+	XmlElement mutatorsEl;
+	ANKI_CHECK(rootEl.getChildElementOptional("mutators", mutatorsEl));
+	if(mutatorsEl)
+	{
+		XmlElement mutatorEl;
+		ANKI_CHECK(mutatorsEl.getChildElement("mutator", mutatorEl));
+		U32 count = 0;
+		mutatorEl.getSiblingElementsCount(count);
+
+		m_mutators.create(getAllocator(), count);
+		count = 0;
+
+		do
+		{
+			// Get name
+			CString name;
+			ANKI_CHECK(mutatorEl.getAttributeText("name", name));
+
+			// Check if already there
+			for(U i = 0; i < count; ++i)
+			{
+				if(m_mutators[i].m_name == name)
+				{
+					ANKI_RESOURCE_LOGE("Mutator aleady present %s", &name[0]);
+					return ErrorCode::USER_DATA;
+				}
+			}
+
+			// Get values
+			// DynamicArrayAuto<I64>
+
+			ANKI_ASSERT(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
+		} while(mutatorEl);
+	}
+
 	// <shaders>
 	XmlElement shadersEl;
 	ANKI_CHECK(rootEl.getChildElement("shaders", shadersEl));
@@ -226,7 +262,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 				if(el)
 				{
 					I64 num;
-					ANKI_CHECK(el.getI64(num));
+					ANKI_CHECK(el.getNumber(num));
 					depth = num != 0;
 				}
 
@@ -235,7 +271,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 				if(el)
 				{
 					I64 num;
-					ANKI_CHECK(el.getI64(num));
+					ANKI_CHECK(el.getNumber(num));
 					if(num)
 					{
 						StringAuto str(getTempAllocator());
@@ -371,7 +407,7 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 		ANKI_CHECK(inputEl.getChildElementOptional("depth", el));
 		if(el)
 		{
-			ANKI_CHECK(el.getI64(num));
+			ANKI_CHECK(el.getNumber(num));
 			var.m_depth = num != 0;
 		}
 		else
@@ -383,7 +419,7 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 		ANKI_CHECK(inputEl.getChildElementOptional("const", el));
 		if(el)
 		{
-			ANKI_CHECK(el.getI64(num));
+			ANKI_CHECK(el.getNumber(num));
 			var.m_const = num != 0;
 		}
 		else
@@ -395,7 +431,7 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 		ANKI_CHECK(inputEl.getChildElementOptional("instanced", el));
 		if(el)
 		{
-			ANKI_CHECK(el.getI64(num));
+			ANKI_CHECK(el.getNumber(num));
 			var.m_instanced = num != 0;
 
 			if(var.m_instanced)
@@ -746,4 +782,4 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 		shaders[ShaderType::FRAGMENT]);
 }
 
-} // end namespace anki
+} // end namespace anki

+ 46 - 10
src/anki/resource/ShaderProgramResource.h

@@ -19,6 +19,28 @@ class XmlElement;
 /// @addtogroup resource
 /// @{
 
+class ShaderProgramResourceMutator : public NonCopyable
+{
+	friend class ShaderProgramResource;
+
+public:
+	const CString& getName() const
+	{
+		ANKI_ASSERT(m_name);
+		return m_name;
+	}
+
+	const DynamicArray<I32>& getValues() const
+	{
+		ANKI_ASSERT(m_values.getSize() > 0);
+		return m_values;
+	}
+
+private:
+	CString m_name;
+	DynamicArray<I32> m_values;
+};
+
 /// A wrapper over the uniforms of a shader or members of the uniform block.
 class ShaderProgramResourceInputVariable : public NonCopyable
 {
@@ -48,6 +70,13 @@ public:
 	}
 
 private:
+	class Mutator
+	{
+	public:
+		ShaderProgramResourceMutator* m_mutator;
+		DynamicArray<I32> m_values;
+	};
+
 	CString m_name;
 	U32 m_idx;
 	ShaderVariableDataType m_dataType = ShaderVariableDataType::NONE;
@@ -55,6 +84,8 @@ private:
 	Bool8 m_depth = true;
 	Bool8 m_instanced = false;
 
+	DynamicArray<Mutator> m_mutators;
+
 	Bool isTexture() const
 	{
 		return m_dataType >= ShaderVariableDataType::SAMPLERS_FIRST
@@ -173,17 +204,19 @@ constexpr Bool isPacked<ShaderProgramResourceConstantValue>()
 /// XML file format:
 /// @code
 /// <shaderProgram>
+/// 	[<mutators> (1)
+/// 		<mutator name="str" values="array of ints"/>
+/// 	</mutators>]
+///
 ///		<shaders>
-///			<shader>
-///				<type>vert | frag | tese | tesc</type>
+///			<shader type="vert | frag | tese | tesc"/>
 ///
 ///				<inputs>
-///					<input>
-///						<name>str</name>
-///						<type>float | vec2 | vec3 | vec4 | mat3 | mat4</type>
-///						[<depth>0 | 1*</depth>]
-///						[<const>0* | 1</const>]
-///						[instanced>0* | 1</instanced>]
+///					<input name="str" type="float | vec2 | vec3 | vec4 | mat3 | mat4 | samplerXXX"
+/// 					[instanceCount="mutator name"] [const="0 | 1"]>
+/// 					<mutators> (2)
+/// 						<mutator name="variant_name" values="array of ints"/>
+/// 					</mutators>
 ///					</input>
 ///				</inputs>
 ///
@@ -194,7 +227,9 @@ constexpr Bool isPacked<ShaderProgramResourceConstantValue>()
 ///		</shaders>
 /// </shaderProgram>
 /// @endcode
-/// *: The default values.
+/// (1): This is a variant list. It contains the means to permutate the program.
+/// (2): This lists a subset of mutators and out of these variants a subset of their values. The input variable will
+///      become active only on those mutators. Mutators not listed are implicitly added with all their values.
 class ShaderProgramResource : public ResourceObject
 {
 public:
@@ -229,6 +264,7 @@ public:
 private:
 	DynamicArray<ShaderProgramResourceInputVariable> m_inputVars;
 	DynamicArray<char> m_inputVarsNames;
+	DynamicArray<ShaderProgramResourceMutator> m_mutators;
 
 	Array<String, 5> m_sources;
 
@@ -250,4 +286,4 @@ private:
 };
 /// @}
 
-} // end namespace anki
+} // end namespace anki

+ 1 - 1
src/anki/resource/TextureAtlas.cpp

@@ -49,7 +49,7 @@ Error TextureAtlas::load(const ResourceFilename& filename)
 	//
 	ANKI_CHECK(rootel.getChildElement("subTextureMargin", el));
 	I64 margin = 0;
-	ANKI_CHECK(el.getI64(margin));
+	ANKI_CHECK(el.getNumber(margin));
 	if(margin >= I(m_tex->getWidth()) || margin >= I(m_tex->getHeight()) || margin < 0)
 	{
 		ANKI_RESOURCE_LOGE("Too big margin %d", U(margin));

+ 67 - 16
src/anki/util/String.cpp

@@ -12,46 +12,97 @@
 namespace anki
 {
 
-Error CString::toF64(F64& out) const
+Error CString::toNumber(F64& out) const
 {
 	checkInit();
-	Error err = ErrorCode::NONE;
+	errno = 0;
 	out = std::strtod(m_ptr, nullptr);
 
-	if(out == HUGE_VAL)
+	if(errno)
 	{
+		errno = 0;
 		ANKI_UTIL_LOGE("Conversion failed");
-		err = ErrorCode::USER_DATA;
+		return ErrorCode::USER_DATA;
 	}
 
-	return err;
+	return ErrorCode::NONE;
 }
 
-Error CString::toF32(F32& out) const
+Error CString::toNumber(F32& out) const
 {
 	F64 d;
-	Error err = toF64(d);
-	if(!err)
+	ANKI_CHECK(toNumber(d));
+	out = d;
+	return ErrorCode::NONE;
+}
+
+Error CString::toNumber(I64& out) const
+{
+	checkInit();
+	errno = 0;
+	static_assert(sizeof(long long) == sizeof(I64), "See file");
+	out = std::strtoll(m_ptr, nullptr, 10);
+
+	if(errno)
 	{
-		out = d;
+		errno = 0;
+		ANKI_UTIL_LOGE("Conversion failed");
+		return ErrorCode::USER_DATA;
 	}
 
-	return err;
+	return ErrorCode::NONE;
 }
 
-Error CString::toI64(I64& out) const
+Error CString::toNumber(I32& out) const
 {
 	checkInit();
-	Error err = ErrorCode::NONE;
-	out = std::strtoll(m_ptr, nullptr, 10);
+	errno = 0;
+	long long i = std::strtoll(m_ptr, nullptr, 10);
+
+	if(errno || i < MIN_I32 || i > MAX_I32)
+	{
+		errno = 0;
+		ANKI_UTIL_LOGE("Conversion failed");
+		return ErrorCode::USER_DATA;
+	}
+
+	out = I32(i);
+
+	return ErrorCode::NONE;
+}
+
+Error CString::toNumber(U64& out) const
+{
+	checkInit();
+	errno = 0;
+	static_assert(sizeof(unsigned long long) == sizeof(U64), "See file");
+	out = std::strtoull(m_ptr, nullptr, 10);
+
+	if(errno)
+	{
+		errno = 0;
+		ANKI_UTIL_LOGE("Conversion failed");
+		return ErrorCode::USER_DATA;
+	}
+
+	return ErrorCode::NONE;
+}
+
+Error CString::toNumber(U32& out) const
+{
+	checkInit();
+	errno = 0;
+	unsigned long long i = std::strtoull(m_ptr, nullptr, 10);
 
-	if(out == LLONG_MAX || out == LLONG_MIN)
+	if(errno || i > MAX_U32)
 	{
+		errno = 0;
 		ANKI_UTIL_LOGE("Conversion failed");
-		err = ErrorCode::USER_DATA;
+		return ErrorCode::USER_DATA;
 	}
 
-	return err;
+	out = U32(i);
+	return ErrorCode::NONE;
 }
 
 String& String::operator=(StringAuto&& b)

+ 36 - 9
src/anki/util/String.h

@@ -207,13 +207,22 @@ public:
 	}
 
 	/// Convert to F64.
-	ANKI_USE_RESULT Error toF64(F64& out) const;
+	ANKI_USE_RESULT Error toNumber(F64& out) const;
 
 	/// Convert to F32.
-	ANKI_USE_RESULT Error toF32(F32& out) const;
+	ANKI_USE_RESULT Error toNumber(F32& out) const;
 
 	/// Convert to I64.
-	ANKI_USE_RESULT Error toI64(I64& out) const;
+	ANKI_USE_RESULT Error toNumber(I64& out) const;
+
+	/// Convert to I32.
+	ANKI_USE_RESULT Error toNumber(I32& out) const;
+
+	/// Convert to U64.
+	ANKI_USE_RESULT Error toNumber(U64& out) const;
+
+	/// Convert to U32.
+	ANKI_USE_RESULT Error toNumber(U32& out) const;
 
 private:
 	const Char* m_ptr = nullptr;
@@ -507,21 +516,39 @@ public:
 	void toString(Allocator alloc, TNumber number);
 
 	/// Convert to F64.
-	ANKI_USE_RESULT Error toF64(F64& out) const
+	ANKI_USE_RESULT Error toNumber(F64& out) const
 	{
-		return toCString().toF64(out);
+		return toCString().toNumber(out);
 	}
 
 	/// Convert to F32.
-	ANKI_USE_RESULT Error toF32(F32& out) const
+	ANKI_USE_RESULT Error toNumber(F32& out) const
 	{
-		return toCString().toF32(out);
+		return toCString().toNumber(out);
 	}
 
 	/// Convert to I64.
-	ANKI_USE_RESULT Error toI64(I64& out) const
+	ANKI_USE_RESULT Error toNumber(I64& out) const
+	{
+		return toCString().toNumber(out);
+	}
+
+	/// Convert to I32.
+	ANKI_USE_RESULT Error toNumber(I32& out) const
+	{
+		return toCString().toNumber(out);
+	}
+
+	/// Convert to U64.
+	ANKI_USE_RESULT Error toNumber(U64& out) const
+	{
+		return toCString().toNumber(out);
+	}
+
+	/// Convert to U32.
+	ANKI_USE_RESULT Error toNumber(U32& out) const
 	{
-		return toCString().toI64(out);
+		return toCString().toNumber(out);
 	}
 
 protected:

+ 9 - 1
tools/format_source.sh

@@ -7,11 +7,19 @@ filecount=${#files[@]}
 count=0
 for f in ${files[@]}
 do
+	# Run it in parallel
 	echo -ne Formatting ${count}/${filecount}\\r
-	./thirdparty/bin/clang-format -sort-includes=false -i ${f}
+	./thirdparty/bin/clang-format -sort-includes=false -i ${f} &
 	count=$((${count}+1))
+
+	# Throttle the parallel commands
+	if !((count % 8)); then
+		wait
+	fi
 done
 
+wait
+
 echo Done! Formatted ${filecount} files