Browse Source

Making everything feature complete

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
51777587ae

+ 1 - 0
.gitignore

@@ -10,5 +10,6 @@
 !*.xml
 !*.md
 !*.txt
+!*.ankiprog
 !CMakeLists.txt
 build*/*

+ 185 - 0
programs/MsGeneric.ankiprog

@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<shaderProgram>
+	<mutators>
+		<mutator name="INSTANCE_COUNT" values="1 2 4 8 16 32 64"/>
+		<mutator name="LOD" values="0 1"/>
+		<mutator name="PASS" values="0 1"/>
+		<mutator name="DIFFUSE_TEX" values="0 1"/>
+		<mutator name="SPECULAR_TEX" values="0 1"/>
+		<mutator name="ROUGHNESS_TEX" values="0 1"/>
+		<mutator name="METAL_TEX" values="0 1"/>
+		<mutator name="NORMAL_TEX" values="0 1"/>
+		<mutator name="PARALLAX" values="0 1"/>
+		<mutator name="EMISSIVE_TEX" values="0 1"/>
+	</mutators>
+
+	<shaders>
+		<shader type="vert">
+			<inputs>
+				<input name="mvp" type="mat4" instanceCount="INSTANCE_COUNT"/>
+				<input name="normalMat" type="mat3" instanceCount="INSTANCE_COUNT">
+					<mutators>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="modelViewMat" type="mat4" instanceCount="INSTANCE_COUNT">
+					<mutators>
+						<mutator name="PASS" values="0"/>
+						<mutator name="PARALLAX" values="1"/>
+					</mutators>
+				</input>
+			</inputs>
+
+			<source><![CDATA[#include "shaders/MsCommonVert.glsl"
+void main() 
+{
+#if PASS == 0
+	positionUvNormalTangent(mvp, normalMat);
+	
+	#if PARALLAX
+		parallax(modelViewMat);
+	#endif
+#else
+	ANKI_WRITE_POSITION(mvp * vec4(in_position, 1.0));
+#endif
+}
+			]]>
+			</source>
+		</shader>
+
+		<shader type="frag">
+			<inputs>
+				<!-- Consts -->
+				<input name="diffColor" type="vec3" const="1">
+					<mutators>
+						<mutator name="DIFFUSE_TEX" values="0"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="specColor" type="vec3" const="1">
+					<mutators>
+						<mutator name="SPECULAR_TEX" values="0"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="roughness" type="float" const="1">
+					<mutators>
+						<mutator name="ROUGHNESS_TEX" values="0"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="metallic" type="float" const="1">
+					<mutators>
+						<mutator name="METAL_TEX" values="0"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="emission" type="vec3" const="1">
+					<mutators>
+						<mutator name="EMISSIVE_TEX" values="0"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="heightMapScale" type="float" const="1">
+					<mutators>
+						<mutator name="PARALLAX" values="1"/>
+						<mutator name="PASS" values="0"/>
+						<mutator name="LOD" values="0"/>
+					</mutators>
+				</input>
+				<input name="subsurface" type="float" const="1">
+					<mutators>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+
+				<!-- Textures -->
+				<input name="diffTex" type="sampler2D">
+					<mutators>
+						<mutator name="DIFFUSE_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="specTex" type="sampler2D">
+					<mutators>
+						<mutator name="SPECULAR_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="roughnessTex" type="sampler2D">
+					<mutators>
+						<mutator name="ROUGHNESS_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="metalTex" type="sampler2D">
+					<mutators>
+						<mutator name="METAL_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+						<mutator name="LOD" values="0"/>
+					</mutators>
+				</input>
+				<input name="normalTex" type="sampler2D">
+					<mutators>
+						<mutator name="NORMAL_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+				<input name="heightTex" type="sampler2D">
+					<mutators>
+						<mutator name="PARALLAX" values="1"/>
+						<mutator name="PASS" values="0"/>
+						<mutator name="LOD" values="0"/>
+					</mutators>
+				</input>
+				<input name="emissiveTex" type="sampler2D">
+					<mutators>
+						<mutator name="EMISSIVE_TEX" values="1"/>
+						<mutator name="PASS" values="0"/>
+					</mutators>
+				</input>
+			</inputs>
+
+			<source><![CDATA[#include "shaders/MsCommonFrag.glsl"
+void main()
+{
+#if PASS == 0
+	#if PARALLAX && LOD == 0
+		vec2 uv =  computeTextureCoordParallax(heightTex, in_uv, heightMapScale);
+	#else
+		vec2 uv = in_uv;
+	#endif
+
+	#if DIFFUSE_TEX
+		vec3 diffColor = texture(diffTex, uv).rgb;
+	#endif
+
+	#if SPECULAR_TEX
+		vec3 specColor = texture(specTex, uv).rgb;
+	#endif
+
+	#if ROUGHNESS_TEX
+		float roughness = texture(roughnessTex, uv).r;
+	#endif
+
+	#if METAL_TEX
+		float metallic = texture(metallicTex, uv).r;
+	#endif
+
+	#if NORMAL_TEX && LOD == 0
+		vec3 normal = readNormalFromTexture(normalTex, uv);
+	#else
+		vec3 normal = normalize(in_normal);
+	#endif
+
+	#if EMISSIVE_TEX
+		vec3 emission = texture(emissionTex, uv).rgb;
+	#endif
+
+	writeRts(diffColor, normal, specColor, roughness, subsurface, emission, metallic);
+#endif
+}
+			]]></source>
+		</shader>
+	</shaders>
+</shaderProgram>

+ 19 - 10
samples/simple_scene/assets/room-material.ankimtl

@@ -1,20 +1,29 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!-- This file is auto generated by ExporterMaterial.cpp -->
-<material>
-	<shaderProgram>assets/ms_diffc_specc_roughc_metalc_normal0_emisc_subsc_par0.ankiprog</shaderProgram>
+<material shaderProgram="programs/MsGeneric.ankiprog">
+	
+	<mutators>
+		<mutator name="DIFFUSE_TEX" value="0"/>
+		<mutator name="SPECULAR_TEX" value="0"/>
+		<mutator name="ROUGHNESS_TEX" value="0"/>
+		<mutator name="METAL_TEX" value="0"/>
+		<mutator name="NORMAL_TEX" value="0"/>
+		<mutator name="PARALLAX" value="0"/>
+		<mutator name="EMISSIVE_TEX" value="0"/>
+	</mutators>
 	
 	<inputs>
-		<input><shaderInput>mvp</shaderInput><builtin>MODEL_VIEW_PROJECTION_MATRIX</builtin></input>
-		<input><shaderInput>normalMat</shaderInput><builtin>NORMAL_MATRIX</builtin></input>
+		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="normalMat" builtin="NORMAL_MATRIX"/>
 		
 	
-		<input><shaderInput>diffColor</shaderInput><value>0.799988 0.635542 0.640324</value></input>
-		<input><shaderInput>specColor</shaderInput><value>0.500000 0.500000 0.500000</value></input>
-		<input><shaderInput>roughness</shaderInput><value>0.097847</value></input>
-		<input><shaderInput>metallic</shaderInput><value>0.000000</value></input>
+		<input shaderInput="diffColor" value="0.799988 0.635542 0.640324"/>
+		<input shaderInput="specColor" value="0.500000 0.500000 0.500000"/>
+		<input shaderInput="roughness" value="0.097847" />
+		<input shaderInput="metallic" value="0.000000"/>
 		
-		<input><shaderInput>emission</shaderInput><value>0.000000 0.000000 0.000000</value></input>
-		<input><shaderInput>subsurface</shaderInput><value>0.000000</value></input>
+		<input shaderInput="emission" value="0.000000 0.000000 0.000000"/>
+		<input shaderInput="subsurface" value="0.000000"/>
 		
 	</inputs>
 </material>

+ 38 - 0
sandbox/Main.cpp

@@ -59,6 +59,44 @@ Error MyApp::init(int argc, char* argv[])
 	getInput().moveCursor(Vec2(0.0));
 #endif
 
+	{
+		ShaderProgramResourcePtr prog;
+		ANKI_CHECK(getResourceManager().loadResource("programs/MsGeneric.ankiprog", prog));
+
+		Array<ShaderProgramResourceMutation, 10> mm;
+		mm[0].m_mutator = prog->tryFindMutator("INSTANCE_COUNT");
+		mm[0].m_value = 4;
+		mm[1].m_mutator = prog->tryFindMutator("LOD");
+		mm[1].m_value = 1;
+		mm[2].m_mutator = prog->tryFindMutator("PASS");
+		mm[2].m_value = 0;
+		mm[3].m_mutator = prog->tryFindMutator("DIFFUSE_TEX");
+		mm[3].m_value = 1;
+		mm[4].m_mutator = prog->tryFindMutator("SPECULAR_TEX");
+		mm[4].m_value = 0;
+		mm[5].m_mutator = prog->tryFindMutator("ROUGHNESS_TEX");
+		mm[5].m_value = 1;
+		mm[6].m_mutator = prog->tryFindMutator("METAL_TEX");
+		mm[6].m_value = 1;
+		mm[7].m_mutator = prog->tryFindMutator("NORMAL_TEX");
+		mm[7].m_value = 1;
+		mm[8].m_mutator = prog->tryFindMutator("PARALLAX");
+		mm[8].m_value = 0;
+		mm[9].m_mutator = prog->tryFindMutator("EMISSIVE_TEX");
+		mm[9].m_value = 1;
+
+		Array<ShaderProgramResourceConstantValue, 2> consts;
+		consts[0].m_variable = prog->tryFindInputVariable("specColor");
+		consts[0].m_vec3 = Vec3(1.0);
+		consts[1].m_variable = prog->tryFindInputVariable("subsurface");
+		consts[1].m_vec3 = Vec3(1.0);
+
+		const ShaderProgramResourceVariant* v;
+		prog->getOrCreateVariant(mm, consts, v);
+
+		return ErrorCode::USER_DATA;
+	}
+
 	// Load scene
 	ScriptResourcePtr script;
 	ANKI_CHECK(resources.loadResource(argv[2], script));

+ 2 - 2
shaders/MsCommonFrag.glsl

@@ -26,7 +26,7 @@ layout(location = 6) in mediump vec3 in_normalTangentSpace; // Parallax
 //
 // Output
 //
-#if COLOR
+#if PASS == 0
 layout(location = 0) out vec4 out_msRt0;
 layout(location = 1) out vec4 out_msRt1;
 layout(location = 2) out vec4 out_msRt2;
@@ -132,7 +132,7 @@ vec2 computeTextureCoordParallax(in sampler2D heightMap, in vec2 uv, in float he
 }
 
 // Write the data to FAIs
-#if COLOR
+#if PASS == 0
 void writeRts(in vec3 diffColor, // from 0 to 1
 	in vec3 normal,
 	in vec3 specularColor,

+ 13 - 20
src/anki/misc/Xml.cpp

@@ -203,7 +203,7 @@ Error XmlElement::getChildElement(const CString& name, XmlElement& out) const
 
 	if(!out)
 	{
-		ANKI_MISC_LOGE("Cannot find tag %s", &name[0]);
+		ANKI_MISC_LOGE("Cannot find tag \"%s\"", &name[0]);
 		err = ErrorCode::USER_DATA;
 	}
 
@@ -227,26 +227,19 @@ Error XmlElement::getNextSiblingElement(const CString& name, XmlElement& out) co
 
 Error XmlElement::getSiblingElementsCount(U32& out) const
 {
-	Error err = check();
-	if(!err)
-	{
-		const tinyxml2::XMLElement* el = m_el;
-
-		I count = -1;
-		do
-		{
-			el = el->NextSiblingElement(m_el->Name());
-			++count;
-		} while(el);
+	ANKI_CHECK(check());
+	const tinyxml2::XMLElement* el = m_el;
 
-		out = count;
-	}
-	else
+	I count = -1;
+	do
 	{
-		out = 0;
-	}
+		el = el->NextSiblingElement(m_el->Name());
+		++count;
+	} while(el);
 
-	return err;
+	out = count;
+
+	return ErrorCode::NONE;
 }
 
 Error XmlElement::getAttributeTextOptional(const CString& name, CString& out, Bool& attribPresent) const
@@ -257,7 +250,7 @@ Error XmlElement::getAttributeTextOptional(const CString& name, CString& out, Bo
 	if(!attrib)
 	{
 		attribPresent = false;
-		return ErrorCode::USER_DATA;
+		return ErrorCode::NONE;
 	}
 
 	attribPresent = true;
@@ -312,7 +305,7 @@ ANKI_USE_RESULT Error XmlDocument::getChildElement(const CString& name, XmlEleme
 
 	if(!out)
 	{
-		ANKI_MISC_LOGE("Cannot find tag %s", &name[0]);
+		ANKI_MISC_LOGE("Cannot find tag \"%s\"", &name[0]);
 		err = ErrorCode::USER_DATA;
 	}
 

+ 60 - 2
src/anki/misc/Xml.h

@@ -165,7 +165,7 @@ public:
 	ANKI_USE_RESULT Error getAttributeNumberOptional(const CString& name, T& out, Bool& attribPresent) const
 	{
 		DynamicArrayAuto<T> arr(m_alloc);
-		ANKI_CHECK(getAttributeIntsOptional(name, arr, attribPresent));
+		ANKI_CHECK(getAttributeNumbersOptional(name, arr, attribPresent));
 
 		if(attribPresent)
 		{
@@ -180,6 +180,44 @@ public:
 
 		return ErrorCode::NONE;
 	}
+
+	/// Get the attribute's value as a vector.
+	/// @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 getAttributeVectorOptional(const CString& name, T& out, Bool& attribPresent) const
+	{
+		DynamicArrayAuto<F32> arr(m_alloc);
+		ANKI_CHECK(getAttributeNumbersOptional(name, arr, attribPresent));
+
+		if(attribPresent)
+		{
+			if(arr.getSize() != sizeof(T) / sizeof(out[0]))
+			{
+				ANKI_MISC_LOGE("Expecting %u elements for attrib %s", sizeof(T) / sizeof(out[0]), &name[0]);
+				return ErrorCode::USER_DATA;
+			}
+
+			U count = 0;
+			for(F32 v : arr)
+			{
+				out[count++] = v;
+			}
+		}
+
+		return ErrorCode::NONE;
+	}
+
+	/// Get the attribute's value as a matrix.
+	/// @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 getAttributeMatrixOptional(const CString& name, T& out, Bool& attribPresent) const
+	{
+		return getAttributeVectorOptional(name, out, attribPresent);
+	}
 	/// @}
 
 	/// @name Get attributes
@@ -216,6 +254,26 @@ public:
 		ANKI_CHECK(getAttributeNumberOptional(name, out, found));
 		return throwAttribNotFoundError(name, found);
 	}
+
+	/// Get the attribute's value as a vector.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeVector(const CString& name, T& out) const
+	{
+		Bool found;
+		ANKI_CHECK(getAttributeVectorOptional(name, out, found));
+		return throwAttribNotFoundError(name, found);
+	}
+
+	/// Get the attribute's value as a matrix.
+	/// @param name The name of the attribute.
+	/// @param out The value of the attribute.
+	template<typename T>
+	ANKI_USE_RESULT Error getAttributeMatrix(const CString& name, T& out) const
+	{
+		return getAttributeVector(name, out);
+	}
 	/// @}
 
 private:
@@ -258,7 +316,7 @@ private:
 	{
 		if(!found)
 		{
-			ANKI_MISC_LOGE("Attribute not found %s", &attrib[0]);
+			ANKI_MISC_LOGE("Attribute not found \"%s\"", &attrib[0]);
 			return ErrorCode::USER_DATA;
 		}
 		else

+ 1 - 1
src/anki/renderer/Drawer.cpp

@@ -430,7 +430,7 @@ Error RenderableDrawer::drawSingle(DrawContext& ctx)
 
 	// Fill the crntBuild
 	F32 flod = m_r->calculateLod(sqrt(ctx.m_visibleNode->m_frustumDistanceSquared));
-	flod = min<F32>(flod, MAX_LODS - 1);
+	flod = min<F32>(flod, MAX_LOD - 1);
 
 	crntBuild.m_rc = &renderable;
 	crntBuild.m_flod = flod;

+ 1 - 1
src/anki/resource/Common.h

@@ -31,7 +31,7 @@ class ResourcePointer;
 
 /// @name Constants
 /// @{
-const U MAX_LODS = 3;
+const U MAX_LOD = 3;
 const U MAX_INSTANCES = 64;
 const U MAX_SUB_DRAWCALLS = 64;
 

+ 221 - 64
src/anki/resource/Material.cpp

@@ -43,63 +43,194 @@ Material::~Material()
 {
 	m_vars.destroy(getAllocator());
 	m_constValues.destroy(getAllocator());
+	m_mutations.destroy(getAllocator());
 }
 
 Error Material::load(const ResourceFilename& filename)
 {
 	XmlDocument doc;
 	XmlElement el;
+	Bool present = false;
 	ANKI_CHECK(openFileParseXml(filename, doc));
 
 	// <material>
 	XmlElement rootEl;
 	ANKI_CHECK(doc.getChildElement("material", rootEl));
 
-	// <shaderProgram>
-	ANKI_CHECK(rootEl.getChildElement("shaderProgram", el));
+	// shaderProgram
 	CString fname;
-	ANKI_CHECK(el.getText(fname));
+	ANKI_CHECK(rootEl.getAttributeText("shaderProgram", fname));
 	getManager().loadResource(fname, m_prog);
 
-	// <shadow>
-	ANKI_CHECK(rootEl.getChildElementOptional("shadow", el));
-	if(el)
+	// shadow
+	ANKI_CHECK(rootEl.getAttributeNumberOptional("shadow", m_shadow, present));
+	m_shadow = m_shadow != 0;
+
+	// forwardShading
+	ANKI_CHECK(rootEl.getAttributeNumberOptional("forwardShading", m_forwardShading, present));
+	m_forwardShading = m_forwardShading != 0;
+
+	// <mutators>
+	XmlElement mutatorsEl;
+	ANKI_CHECK(rootEl.getChildElementOptional("mutators", mutatorsEl));
+	if(mutatorsEl)
 	{
-		I64 num;
-		ANKI_CHECK(el.getNumber(num));
-		m_shadow = num != 0;
+		ANKI_CHECK(parseMutators(mutatorsEl));
 	}
 
-	// <levelsOfDetail>
-	ANKI_CHECK(rootEl.getChildElementOptional("levelsOfDetail", el));
+	// <inputs>
+	ANKI_CHECK(rootEl.getChildElementOptional("inputs", el));
 	if(el)
 	{
-		I64 num;
-		ANKI_CHECK(el.getNumber(num));
+		ANKI_CHECK(parseInputs(el));
+	}
+
+	return ErrorCode::NONE;
+}
+
+Error Material::parseMutators(XmlElement mutatorsEl)
+{
+	XmlElement mutatorEl;
+	ANKI_CHECK(mutatorsEl.getChildElement("mutator", mutatorEl));
+
+	U32 mutatorCount = 0;
+	mutatorEl.getSiblingElementsCount(mutatorCount);
+	++mutatorCount;
+	ANKI_ASSERT(mutatorCount > 0);
+	m_mutations.create(getAllocator(), mutatorCount);
+	mutatorCount = 0;
+
+	do
+	{
+		ShaderProgramResourceMutation& mutation = m_mutations[mutatorCount];
+
+		// name
+		CString mutatorName;
+		ANKI_CHECK(mutatorEl.getAttributeText("name", mutatorName));
+		if(mutatorName.isEmpty())
+		{
+			ANKI_RESOURCE_LOGE("Mutator name is empty");
+			return ErrorCode::USER_DATA;
+		}
+
+		if(mutatorName == "INSTANCE_COUNT" || mutatorName == "PASS" || mutatorName == "LOD")
+		{
+			ANKI_RESOURCE_LOGE("Cannot list builtin mutator \"%s\"", &mutatorName[0]);
+			return ErrorCode::USER_DATA;
+		}
+
+		// value
+		ANKI_CHECK(mutatorEl.getAttributeNumber("value", mutation.m_value));
+
+		// Find mutator
+		mutation.m_mutator = m_prog->tryFindMutator(mutatorName);
+
+		if(!mutation.m_mutator)
+		{
+			ANKI_RESOURCE_LOGE("Mutator not found in program %s", &mutatorName[0]);
+			return ErrorCode::USER_DATA;
+		}
 
-		if(num <= 0 || num > I64(MAX_LODS))
+		if(!mutation.m_mutator->valueExists(mutation.m_value))
 		{
-			ANKI_RESOURCE_LOGE("Incorrect <levelsOfDetail>: %d", num);
+			ANKI_RESOURCE_LOGE("Value %d is not part of the mutator %s", mutation.m_value, &mutatorName[0]);
 			return ErrorCode::USER_DATA;
 		}
 
-		m_lodCount = U8(num);
+		// Advance
+		++mutatorCount;
+		ANKI_CHECK(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
+	} while(mutatorEl);
+
+	// Find the builtin mutators
+	U builtinMutatorCount = 0;
+	for(const ShaderProgramResourceMutator& m : m_prog->getMutators())
+	{
+		if(m.getName() == "INSTANCE_COUNT")
+		{
+			if(!m_prog->isInstanced())
+			{
+				ANKI_RESOURCE_LOGE("Program is not instanced but has the INSTANCE_COUNT mutator");
+				return ErrorCode::USER_DATA;
+			}
+
+			if(m.getValues().getSize() != MAX_INSTANCE_GROUPS)
+			{
+				ANKI_RESOURCE_LOGE("Mutator INSTANCE_COUNT should have %u values in the program", MAX_INSTANCE_GROUPS);
+				return ErrorCode::USER_DATA;
+			}
+
+			for(U i = 0; i < MAX_INSTANCE_GROUPS; ++i)
+			{
+				if(m.getValues()[i] != (1 << i))
+				{
+					ANKI_RESOURCE_LOGE("Values of the INSTANCE_COUNT mutator in the program are not the expected");
+					return ErrorCode::USER_DATA;
+				}
+			}
+
+			m_instanceMutator = &m;
+			++builtinMutatorCount;
+		}
+		else if(m.getName() == "PASS")
+		{
+			if(m.getValues().getSize() != U(Pass::COUNT))
+			{
+				ANKI_RESOURCE_LOGE("Mutator PASS should have %u values in the program", U(Pass::COUNT));
+				return ErrorCode::USER_DATA;
+			}
+
+			for(U i = 0; i < U(Pass::COUNT); ++i)
+			{
+				if(m.getValues()[i] != I(i))
+				{
+					ANKI_RESOURCE_LOGE("Values of the PASS mutator in the program are not the expected");
+					return ErrorCode::USER_DATA;
+				}
+			}
+
+			m_passMutator = &m;
+			++builtinMutatorCount;
+		}
+		else if(m.getName() == "LOD")
+		{
+			if(m.getValues().getSize() > MAX_LOD)
+			{
+				ANKI_RESOURCE_LOGE("Mutator LOD should have at least %u values in the program", U(MAX_LOD));
+				return ErrorCode::USER_DATA;
+			}
+
+			for(U i = 0; i < m.getValues().getSize(); ++i)
+			{
+				if(m.getValues()[i] != I(i))
+				{
+					ANKI_RESOURCE_LOGE("Values of the LOD mutator in the program are not the expected");
+					return ErrorCode::USER_DATA;
+				}
+			}
+
+			m_lodMutator = &m;
+			m_lodCount = m.getValues().getSize();
+			++builtinMutatorCount;
+		}
+	}
+
+	if(m_prog->isInstanced() && !m_instanceMutator)
+	{
+		ANKI_RESOURCE_LOGE("If program is instanced then the instance mutator sHould be the INSTANCE_COUNT");
+		return ErrorCode::USER_DATA;
 	}
 
-	// <forwardShading>
-	ANKI_CHECK(rootEl.getChildElementOptional("forwardShading", el));
-	if(el)
+	if(!m_forwardShading && !m_passMutator)
 	{
-		I64 num;
-		ANKI_CHECK(el.getNumber(num));
-		m_forwardShading = num != 0;
+		ANKI_RESOURCE_LOGE("PASS mutator is required");
+		return ErrorCode::USER_DATA;
 	}
 
-	// <inputs>
-	ANKI_CHECK(rootEl.getChildElementOptional("inputs", el));
-	if(el)
+	if(m_mutations.getSize() + builtinMutatorCount != m_prog->getMutators().getSize())
 	{
-		ANKI_CHECK(parseInputs(el));
+		ANKI_RESOURCE_LOGE("Some mutatators are unacounted for");
+		return ErrorCode::USER_DATA;
 	}
 
 	return ErrorCode::NONE;
@@ -112,6 +243,12 @@ Error Material::parseInputs(XmlElement inputsEl)
 	U nonConstInputCount = 0;
 	for(const ShaderProgramResourceInputVariable& in : m_prog->getInputVariables())
 	{
+		if(!in.acceptAllMutations(m_mutations))
+		{
+			// Will not be used by this material at all
+			continue;
+		}
+
 		if(in.isConstant())
 		{
 			++constInputCount;
@@ -131,25 +268,20 @@ Error Material::parseInputs(XmlElement inputsEl)
 	while(inputEl)
 	{
 		// Get var name
-		XmlElement shaderInputEl;
-		ANKI_CHECK(inputEl.getChildElement("shaderInput", shaderInputEl));
 		CString varName;
-		ANKI_CHECK(shaderInputEl.getText(varName));
+		ANKI_CHECK(inputEl.getAttributeText("shaderInput", varName));
 
 		// Try find var
-		const ShaderProgramResourceInputVariable* foundVar = nullptr;
-		for(const ShaderProgramResourceInputVariable& in : m_prog->getInputVariables())
+		const ShaderProgramResourceInputVariable* foundVar = m_prog->tryFindInputVariable(varName);
+		if(foundVar == nullptr)
 		{
-			if(in.getName() == varName)
-			{
-				foundVar = &in;
-				break;
-			}
+			ANKI_RESOURCE_LOGE("Variable \"%s\" not found", &varName[0]);
+			return ErrorCode::USER_DATA;
 		}
 
-		if(foundVar == nullptr)
+		if(!foundVar->acceptAllMutations(m_mutations))
 		{
-			ANKI_RESOURCE_LOGE("Variable not found: %s", &varName[0]);
+			ANKI_RESOURCE_LOGE("Variable \"%s\" is not needed by the material's mutations. Don't need it");
 			return ErrorCode::USER_DATA;
 		}
 
@@ -158,25 +290,22 @@ Error Material::parseInputs(XmlElement inputsEl)
 		{
 			// Const
 
-			XmlElement valueEl;
-			ANKI_CHECK(inputEl.getChildElement("value", valueEl));
-
 			ShaderProgramResourceConstantValue& constVal = m_constValues[--constInputCount];
 			constVal.m_variable = foundVar;
 
 			switch(foundVar->getShaderVariableDataType())
 			{
 			case ShaderVariableDataType::FLOAT:
-				ANKI_CHECK(valueEl.getNumber(constVal.m_float));
+				ANKI_CHECK(inputEl.getAttributeNumber("value", constVal.m_float));
 				break;
 			case ShaderVariableDataType::VEC2:
-				ANKI_CHECK(valueEl.getVec2(constVal.m_vec2));
+				ANKI_CHECK(inputEl.getAttributeVector("value", constVal.m_vec2));
 				break;
 			case ShaderVariableDataType::VEC3:
-				ANKI_CHECK(valueEl.getVec3(constVal.m_vec3));
+				ANKI_CHECK(inputEl.getAttributeVector("value", constVal.m_vec3));
 				break;
 			case ShaderVariableDataType::VEC4:
-				ANKI_CHECK(valueEl.getVec4(constVal.m_vec4));
+				ANKI_CHECK(inputEl.getAttributeVector("value", constVal.m_vec4));
 				break;
 			default:
 				ANKI_ASSERT(0);
@@ -187,15 +316,13 @@ Error Material::parseInputs(XmlElement inputsEl)
 		{
 			// Builtin or not
 
-			XmlElement builtinEl;
-			ANKI_CHECK(inputEl.getChildElementOptional("builtin", builtinEl));
-			if(builtinEl)
+			Bool builtinPresent = false;
+			CString builtinStr;
+			ANKI_CHECK(inputEl.getAttributeTextOptional("builtin", builtinStr, builtinPresent));
+			if(builtinPresent)
 			{
 				// Builtin
 
-				CString builtinStr;
-				ANKI_CHECK(builtinEl.getText(builtinStr));
-
 				U i;
 				for(i = 0; i < BUILTIN_INFOS.getSize(); ++i)
 				{
@@ -242,28 +369,25 @@ Error Material::parseInputs(XmlElement inputsEl)
 				MaterialVariable& mtlVar = m_vars[--nonConstInputCount];
 				mtlVar.m_input = foundVar;
 
-				XmlElement valueEl;
-				ANKI_CHECK(inputEl.getChildElement("value", valueEl));
-
 				switch(foundVar->getShaderVariableDataType())
 				{
 				case ShaderVariableDataType::FLOAT:
-					ANKI_CHECK(valueEl.getNumber(mtlVar.m_float));
+					ANKI_CHECK(inputEl.getAttributeNumber("value", mtlVar.m_float));
 					break;
 				case ShaderVariableDataType::VEC2:
-					ANKI_CHECK(valueEl.getVec2(mtlVar.m_vec2));
+					ANKI_CHECK(inputEl.getAttributeVector("value", mtlVar.m_vec2));
 					break;
 				case ShaderVariableDataType::VEC3:
-					ANKI_CHECK(valueEl.getVec3(mtlVar.m_vec3));
+					ANKI_CHECK(inputEl.getAttributeVector("value", mtlVar.m_vec3));
 					break;
 				case ShaderVariableDataType::VEC4:
-					ANKI_CHECK(valueEl.getVec4(mtlVar.m_vec4));
+					ANKI_CHECK(inputEl.getAttributeVector("value", mtlVar.m_vec4));
 					break;
 				case ShaderVariableDataType::MAT3:
-					ANKI_CHECK(valueEl.getMat3(mtlVar.m_mat3));
+					ANKI_CHECK(inputEl.getAttributeMatrix("value", mtlVar.m_mat3));
 					break;
 				case ShaderVariableDataType::MAT4:
-					ANKI_CHECK(valueEl.getMat4(mtlVar.m_mat4));
+					ANKI_CHECK(inputEl.getAttributeMatrix("value", mtlVar.m_mat4));
 					break;
 				case ShaderVariableDataType::SAMPLER_2D:
 				case ShaderVariableDataType::SAMPLER_2D_ARRAY:
@@ -271,7 +395,7 @@ Error Material::parseInputs(XmlElement inputsEl)
 				case ShaderVariableDataType::SAMPLER_CUBE:
 				{
 					CString texfname;
-					ANKI_CHECK(valueEl.getText(texfname));
+					ANKI_CHECK(inputEl.getAttributeText("value", texfname));
 					getManager().loadResource(texfname, mtlVar.m_tex);
 					break;
 				}
@@ -319,9 +443,42 @@ const MaterialVariant& Material::getOrCreateVariant(const RenderingKey& key_) co
 
 	if(variant.m_variant == nullptr)
 	{
-		m_prog->getOrCreateVariant(key,
-			WeakArray<const ShaderProgramResourceConstantValue>((m_constValues.getSize()) ? &m_constValues[0] : nullptr,
-									   m_constValues.getSize()),
+		const U mutatorCount = m_mutations.getSize() + ((m_instanceMutator) ? 1 : 0) + ((m_passMutator) ? 1 : 0)
+			+ ((m_lodMutator) ? 1 : 0);
+
+		DynamicArrayAuto<ShaderProgramResourceMutation> mutations(getTempAllocator());
+		mutations.create(mutatorCount);
+		U count = m_mutations.getSize();
+		if(m_mutations.getSize())
+		{
+			memcpy(&mutations[0], &m_mutations[0], m_mutations.getSize() * sizeof(m_mutations[0]));
+		}
+
+		if(m_instanceMutator)
+		{
+			mutations[count].m_mutator = m_instanceMutator;
+			mutations[count].m_value = key.m_instanceCount;
+			++count;
+		}
+
+		if(m_passMutator)
+		{
+			mutations[count].m_mutator = m_passMutator;
+			mutations[count].m_value = I(key.m_pass);
+			++count;
+		}
+
+		if(m_lodMutator)
+		{
+			mutations[count].m_mutator = m_lodMutator;
+			mutations[count].m_value = I(key.m_lod);
+			++count;
+		}
+
+		m_prog->getOrCreateVariant(
+			WeakArray<const ShaderProgramResourceMutation>(mutations.getSize() ? &mutations[0] : nullptr, count),
+			WeakArray<const ShaderProgramResourceConstantValue>(
+				(m_constValues.getSize()) ? &m_constValues[0] : nullptr, m_constValues.getSize()),
 			variant.m_variant);
 	}
 

+ 20 - 11
src/anki/resource/Material.h

@@ -160,19 +160,20 @@ private:
 ///
 /// Material XML file format:
 /// @code
-/// <material>
-/// 	[<levelsOfDetail>N</levelsOfDetail>]
-/// 	[<shadow>0 | 1</shadow>]
-/// 	[<forwardShading>0 | 1</forwardShading>]
+/// <material
+///		[shadow="0 | 1"]
+///		[forwardShading="0 | 1"]
+///		shaderProgram="path"/>
 ///
-///		<shaderProgram>path/to/shader_program.ankiprog</shaderProgram>
+///		[<mutators>
+///			<mutator name="str" value="value"/>
+///		</mutators>]
 ///
 ///		[<inputs>
-///			<input>
-///				<shaderInput>name</shaderInput>
-///				[<value></value>] (1)
-///				[<builtin></builtin>] (2)
-///			</input>
+///			<input
+///				shaderInput="name to shaderProg input"
+///				[value="values"] (1)
+///				[builtin="name"]/> (2)
 ///		</inputs>]
 /// </material>
 /// @endcode
@@ -230,8 +231,14 @@ private:
 	Bool8 m_forwardShading = false;
 	U8 m_lodCount = 1;
 
+	const ShaderProgramResourceMutator* m_lodMutator = nullptr;
+	const ShaderProgramResourceMutator* m_passMutator = nullptr;
+	const ShaderProgramResourceMutator* m_instanceMutator = nullptr;
+
+	DynamicArray<ShaderProgramResourceMutation> m_mutations;
+
 	/// Matrix of variants.
-	mutable Array3d<MaterialVariant, U(Pass::COUNT), MAX_LODS, MAX_INSTANCE_GROUPS> m_variantMatrix;
+	mutable Array3d<MaterialVariant, U(Pass::COUNT), MAX_LOD, MAX_INSTANCE_GROUPS> m_variantMatrix;
 	mutable Mutex m_variantMatrixMtx;
 
 	DynamicArray<MaterialVariable> m_vars; ///< Non-const vars.
@@ -241,6 +248,8 @@ private:
 
 	/// Parse whatever is inside the <inputs> tag.
 	ANKI_USE_RESULT Error parseInputs(XmlElement inputsEl);
+
+	ANKI_USE_RESULT Error parseMutators(XmlElement mutatorsEl);
 };
 /// @}
 

+ 1 - 1
src/anki/resource/Model.h

@@ -124,7 +124,7 @@ public:
 private:
 	Model* m_model ANKI_DBG_NULLIFY;
 
-	Array<MeshResourcePtr, MAX_LODS> m_meshes; ///< One for each LOD
+	Array<MeshResourcePtr, MAX_LOD> m_meshes; ///< One for each LOD
 	U8 m_meshCount = 0;
 	MaterialResourcePtr m_mtl;
 

+ 1 - 1
src/anki/resource/RenderingKey.h

@@ -32,7 +32,7 @@ public:
 		, m_instanceCount(instanceCount)
 	{
 		ANKI_ASSERT(m_instanceCount <= MAX_INSTANCES && m_instanceCount != 0);
-		ANKI_ASSERT(m_lod <= MAX_LODS);
+		ANKI_ASSERT(m_lod <= MAX_LOD);
 	}
 
 	RenderingKey()

+ 113 - 72
src/anki/resource/ShaderProgramResource.cpp

@@ -166,8 +166,25 @@ ShaderProgramResource::~ShaderProgramResource()
 		alloc.deleteInstance(variant);
 	}
 
+	for(ShaderProgramResourceInputVariable& var : m_inputVars)
+	{
+		for(auto& m : var.m_mutators)
+		{
+			m.m_values.destroy(alloc);
+		}
+
+		var.m_mutators.destroy(alloc);
+		var.m_name.destroy(alloc);
+	}
 	m_inputVars.destroy(alloc);
 
+	for(ShaderProgramResourceMutator& m : m_mutators)
+	{
+		m.m_name.destroy(alloc);
+		m.m_values.destroy(alloc);
+	}
+	m_mutators.destroy(alloc);
+
 	for(String& s : m_sources)
 	{
 		s.destroy(alloc);
@@ -193,6 +210,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 		ANKI_CHECK(mutatorsEl.getChildElement("mutator", mutatorEl));
 		U32 count = 0;
 		mutatorEl.getSiblingElementsCount(count);
+		++count;
 
 		m_mutators.create(getAllocator(), count);
 		count = 0;
@@ -202,6 +220,11 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 			// Get name
 			CString name;
 			ANKI_CHECK(mutatorEl.getAttributeText("name", name));
+			if(name.isEmpty())
+			{
+				ANKI_RESOURCE_LOGE("Mutator name is empty");
+				return ErrorCode::USER_DATA;
+			}
 
 			// Check if already there
 			for(U i = 0; i < count; ++i)
@@ -215,10 +238,10 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 
 			// Get values
 			DynamicArrayAuto<ShaderProgramResourceMutatorValue> values(getTempAllocator());
-			ANKI_CHECK(mutatorEl.getAttributeNumbers("value", values));
+			ANKI_CHECK(mutatorEl.getAttributeNumbers("values", values));
 			if(values.getSize() < 1)
 			{
-				ANKI_RESOURCE_LOGE("Mutator doesn't have values %s", &name[0]);
+				ANKI_RESOURCE_LOGE("Mutator doesn't have any values %s", &name[0]);
 				return ErrorCode::USER_DATA;
 			}
 
@@ -227,7 +250,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 			{
 				if(values[i - 1] == values[i])
 				{
-					ANKI_RESOURCE_LOGE("Mutator %s has duplicate values", &name[0]);
+					ANKI_RESOURCE_LOGE("Mutator %s has duplicate values %d", &name[0], values[i]);
 					return ErrorCode::USER_DATA;
 				}
 			}
@@ -241,7 +264,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 			}
 
 			++count;
-			ANKI_ASSERT(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
+			ANKI_CHECK(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
 		} while(mutatorEl);
 	}
 
@@ -265,7 +288,8 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 			ANKI_CHECK(inputsEl.getChildElement("input", inputEl));
 
 			U32 count;
-			ANKI_CHECK(inputsEl.getSiblingElementsCount(count));
+			ANKI_CHECK(inputEl.getSiblingElementsCount(count));
+			++count;
 
 			inputVarCount += count;
 		}
@@ -283,6 +307,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 	StringListAuto constSrcList(getTempAllocator());
 	ShaderTypeBit presentShaders = ShaderTypeBit::NONE;
 	Array<CString, 5> shaderSources;
+	const ShaderProgramResourceMutator* instanceMutator = nullptr;
 	ANKI_CHECK(shadersEl.getChildElement("shader", shaderEl));
 	do
 	{
@@ -312,7 +337,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 
 		if(inputsEl)
 		{
-			ANKI_CHECK(parseInputs(inputsEl, inputVarCount, constSrcList));
+			ANKI_CHECK(parseInputs(inputsEl, inputVarCount, constSrcList, instanceMutator));
 		}
 
 		// <source>
@@ -323,6 +348,10 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 		ANKI_CHECK(shaderEl.getNextSiblingElement("shader", shaderEl));
 	} while(shaderEl);
 
+#if ANKI_EXTRA_CHECKS
+	m_instancingMutator = instanceMutator;
+#endif
+
 	ANKI_ASSERT(inputVarCount == m_inputVars.getSize());
 
 	// Sanity checks
@@ -339,10 +368,10 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 	}
 
 	// Create sources
-	StringListAuto backedConstSrc(getTempAllocator());
+	StringAuto backedConstSrc(getTempAllocator());
 	if(!constSrcList.isEmpty())
 	{
-		constSrcList.join(" ", backedConstSrc);
+		constSrcList.join("", backedConstSrc);
 	}
 
 	for(U s = 0; s < 5; ++s)
@@ -354,7 +383,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 
 			if(!constSrcList.isEmpty())
 			{
-				m_sources[s].create(getAllocator(), constSrcList);
+				m_sources[s].create(getAllocator(), backedConstSrc);
 			}
 
 			m_sources[s].append(getAllocator(), loader.getShaderSource());
@@ -364,7 +393,10 @@ Error ShaderProgramResource::load(const ResourceFilename& filename)
 	return ErrorCode::NONE;
 }
 
-Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount, StringListAuto& constsSrc)
+Error ShaderProgramResource::parseInputs(XmlElement& inputsEl,
+	U& inputVarCount,
+	StringListAuto& constsSrc,
+	const ShaderProgramResourceMutator*& instanceMutator)
 {
 	XmlElement inputEl;
 	ANKI_CHECK(inputsEl.getChildElement("input", inputEl));
@@ -376,7 +408,7 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 		// name
 		CString name;
 		ANKI_CHECK(inputEl.getAttributeText("name", name));
-		if(!name)
+		if(name.isEmpty())
 		{
 			ANKI_RESOURCE_LOGE("Input variable name is empty");
 			return ErrorCode::USER_DATA;
@@ -390,8 +422,9 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 
 		// const
 		Bool present;
-		ANKI_CHECK(inputEl.getAttributeNumberOptional("const", var.m_const, present));
-		var.m_const = var.m_const != 0;
+		U32 constant = 0;
+		ANKI_CHECK(inputEl.getAttributeNumberOptional("const", constant, present));
+		var.m_const = constant != 0;
 
 		// <mutators>
 		XmlElement mutatorsEl;
@@ -403,6 +436,7 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 
 			U32 mutatorCount;
 			ANKI_CHECK(mutatorEl.getSiblingElementsCount(mutatorCount));
+			++mutatorCount;
 			ANKI_ASSERT(mutatorCount > 0);
 			var.m_mutators.create(getAllocator(), mutatorCount);
 			mutatorCount = 0;
@@ -412,23 +446,14 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 				// name
 				CString mutatorName;
 				ANKI_CHECK(mutatorEl.getAttributeText("name", mutatorName));
-				if(!mutatorName)
+				if(mutatorName.isEmpty())
 				{
 					ANKI_RESOURCE_LOGE("Empty mutator name in input variable %s", &name[0]);
 					return ErrorCode::USER_DATA;
 				}
 
 				// Find the mutator
-				ShaderProgramResourceMutator* foundMutator = nullptr;
-				for(U i = 0; i < m_mutators.getSize(); ++i)
-				{
-					if(m_mutators[i].m_name == mutatorName)
-					{
-						foundMutator = &m_mutators[i];
-						break;
-					}
-				}
-
+				const ShaderProgramResourceMutator* foundMutator = tryFindMutator(mutatorName);
 				if(!foundMutator)
 				{
 					ANKI_RESOURCE_LOGE("Input variable %s can't link to unknown mutator %s", &name[0], &mutatorName[0]);
@@ -470,7 +495,11 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 
 				// Set var
 				var.m_mutators[mutatorCount].m_mutator = foundMutator;
-				var.m_mutators[mutatorCount].m_values.create(getAllocator(), vals);
+				var.m_mutators[mutatorCount].m_values.create(getAllocator(), vals.getSize());
+				for(U i = 0; i < vals.getSize(); ++i)
+				{
+					var.m_mutators[mutatorCount].m_values[i] = vals[i];
+				}
 				++mutatorCount;
 
 				// Advance
@@ -486,30 +515,32 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 		ANKI_CHECK(inputEl.getAttributeTextOptional("instanceCount", instanceCountTxt, found));
 		if(found)
 		{
-			if(!instanceCountTxt)
+			m_instanced = true;
+
+			if(instanceCountTxt.isEmpty())
 			{
 				ANKI_RESOURCE_LOGE("instanceCount tag is empty for input variable %s", &name[0]);
 				return ErrorCode::USER_DATA;
 			}
 
-			for(ShaderProgramResourceInputVariable::Mutator& mut : var.m_mutators)
-			{
-				if(mut.m_mutator->m_name == instanceCountTxt)
-				{
-					var.m_instancingMutator = &mut;
-					m_instanced = true;
-					break;
-				}
-			}
-
+			var.m_instancingMutator = tryFindMutator(instanceCountTxt);
 			if(!var.m_instancingMutator)
 			{
-				ANKI_RESOURCE_LOGE(
-					"Having %s as mutator for instanceCount attribute is not acceptable for input variable %s",
+				ANKI_RESOURCE_LOGE("Failed to find mutator %s, used as instanceCount attribute for input variable %s",
 					&instanceCountTxt[0],
 					&name[0]);
 				return ErrorCode::USER_DATA;
 			}
+
+			if(!instanceMutator)
+			{
+				instanceMutator = var.m_instancingMutator;
+			}
+			else if(instanceMutator != var.m_instancingMutator)
+			{
+				ANKI_RESOURCE_LOGE("All input variables should have the same instancing mutator");
+				return ErrorCode::USER_DATA;
+			}
 		}
 
 		// Sanity checks
@@ -575,7 +606,8 @@ Error ShaderProgramResource::parseInputs(XmlElement& inputsEl, U& inputVarCount,
 	return ErrorCode::NONE;
 }
 
-void ShaderProgramResource::compInputVarDefineString(const ShaderProgramResourceInputVariable& var, StringAuto& list)
+void ShaderProgramResource::compInputVarDefineString(
+	const ShaderProgramResourceInputVariable& var, StringListAuto& list)
 {
 	if(var.m_mutators.getSize() > 0)
 	{
@@ -595,7 +627,7 @@ void ShaderProgramResource::compInputVarDefineString(const ShaderProgramResource
 	}
 }
 
-U64 ShaderProgramResource::computeVariantHash(WeakArray<const ShaderProgramResourceMutatorInfo> mutations,
+U64 ShaderProgramResource::computeVariantHash(WeakArray<const ShaderProgramResourceMutation> mutations,
 	WeakArray<const ShaderProgramResourceConstantValue> constants) const
 {
 	U hash = 1;
@@ -613,17 +645,38 @@ U64 ShaderProgramResource::computeVariantHash(WeakArray<const ShaderProgramResou
 	return hash;
 }
 
-void ShaderProgramResource::getOrCreateVariant(WeakArray<const ShaderProgramResourceMutatorInfo> mutations,
+void ShaderProgramResource::getOrCreateVariant(WeakArray<const ShaderProgramResourceMutation> mutation,
 	WeakArray<const ShaderProgramResourceConstantValue> constants,
 	const ShaderProgramResourceVariant*& variant) const
 {
-	U64 hash = computeVariantHash(mutations, constants);
+	// Sanity checks
+	ANKI_ASSERT(mutation.getSize() == m_mutators.getSize());
+	ANKI_ASSERT(mutation.getSize() <= 128 && "Wrong assumption");
+	BitSet<128> mutatorPresent = {false};
+	for(const ShaderProgramResourceMutation& m : mutation)
+	{
+		(void)m;
+		ANKI_ASSERT(m.m_mutator);
+		ANKI_ASSERT(m.m_mutator->valueExists(m.m_value));
+
+		ANKI_ASSERT(&m_mutators[0] <= m.m_mutator);
+		const U idx = m.m_mutator - &m_mutators[0];
+		ANKI_ASSERT(idx < m_mutators.getSize());
+		ANKI_ASSERT(!mutatorPresent.get(idx) && "Appeared 2 times in 'mutation'");
+		mutatorPresent.set(idx);
+
+#if ANKI_EXTRA_CHECKS
+		if(m_instancingMutator == m.m_mutator)
+		{
+			ANKI_ASSERT(m.m_value > 0 && "Instancing value can't be negative");
+		}
+#endif
+	}
 
-	LockGuard<Mutex> lock(m_mtx);
+	// Compute hash
+	U64 hash = computeVariantHash(mutation, constants);
 
-	// Sanity checks
-	ANKI_ASSERT(mutations.getSize() == m_mutations.getSize());
-	// TODO
+	LockGuard<Mutex> lock(m_mtx);
 
 	auto it = m_variants.find(hash);
 	if(it != m_variants.getEnd())
@@ -634,31 +687,21 @@ void ShaderProgramResource::getOrCreateVariant(WeakArray<const ShaderProgramReso
 	{
 		// Create one
 		ShaderProgramResourceVariant* v = getAllocator().newInstance<ShaderProgramResourceVariant>();
-		initVariant(key, constants, *v);
+		initVariant(mutation, constants, *v);
 
 		m_variants.pushBack(hash, v);
 		variant = v;
 	}
 }
 
-void ShaderProgramResource::initVariant(const RenderingKey& key,
+void ShaderProgramResource::initVariant(WeakArray<const ShaderProgramResourceMutation> mutations,
 	WeakArray<const ShaderProgramResourceConstantValue> constants,
 	ShaderProgramResourceVariant& variant) const
 {
-	U instanceCount = key.m_instanceCount;
-	U lod = key.m_lod;
-	Pass pass = key.m_pass;
-
-	// Preconditions
-	if(instanceCount > 1)
-	{
-		ANKI_ASSERT(m_instanced);
-	}
-
 	variant.m_activeInputVars.unsetAll();
 	variant.m_blockInfos.create(getAllocator(), m_inputVars.getSize());
 	variant.m_texUnits.create(getAllocator(), m_inputVars.getSize());
-	if(m_inputVars)
+	if(m_inputVars.getSize() > 0)
 	{
 		memorySet<I16>(&variant.m_texUnits[0], -1, variant.m_texUnits.getSize());
 	}
@@ -673,11 +716,12 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 
 	for(const ShaderProgramResourceInputVariable& in : m_inputVars)
 	{
-		if(pass == Pass::SM && !in.m_depth)
+		if(!in.acceptAllMutations(mutations))
 		{
 			continue;
 		}
 
+		U instanceCount = 1;
 		variant.m_activeInputVars.set(in.m_idx);
 
 		// Init block info
@@ -687,7 +731,7 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 
 			// std140 rules
 			blockInfo.m_offset = variant.m_uniBlockSize;
-			blockInfo.m_arraySize = in.m_instanced ? instanceCount : 1;
+			blockInfo.m_arraySize = instanceCount;
 
 			if(in.m_dataType == ShaderVariableDataType::FLOAT)
 			{
@@ -758,9 +802,9 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 				ANKI_ASSERT(0);
 			}
 
-			if(in.m_instanced)
+			if(instanceCount > 1)
 			{
-				blockCode.pushBackSprintf(R"(%s %s_i[INSTANCE_COUNT];
+				blockCode.pushBackSprintf(R"(%s %s_i[%s];
 #if VERTEX_SHADER
 #	define %s s%_i[gl_InstanceID]
 #else
@@ -769,6 +813,7 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 )",
 					&toString(in.m_dataType)[0],
 					&in.m_name[0],
+					&in.m_instancingMutator->getName()[0],
 					&in.m_name[0],
 					&in.m_name[0]);
 			}
@@ -836,15 +881,11 @@ void ShaderProgramResource::initVariant(const RenderingKey& key,
 
 	// Write the source header
 	StringListAuto shaderHeaderSrc(getTempAllocator());
-	shaderHeaderSrc.pushBackSprintf(R"(#define INSTANCE_COUNT %u
-#define LOD %u
-#define COLOR %u
-#define DEPTH %u
-)",
-		instanceCount,
-		lod,
-		pass == Pass::MS_FS,
-		pass == Pass::SM);
+
+	for(const ShaderProgramResourceMutation& m : mutations)
+	{
+		shaderHeaderSrc.pushBackSprintf("#define %s %d\n", &m.m_mutator->getName()[0], m.m_value);
+	}
 
 	if(constsCode)
 	{

+ 111 - 43
src/anki/resource/ShaderProgramResource.h

@@ -21,6 +21,7 @@ class XmlElement;
 
 using ShaderProgramResourceMutatorValue = I32;
 
+/// The means to mutate a shader program.
 class ShaderProgramResourceMutator : public NonCopyable
 {
 	friend class ShaderProgramResource;
@@ -38,10 +39,6 @@ public:
 		return m_values;
 	}
 
-private:
-	String m_name;
-	DynamicArray<ShaderProgramResourceMutatorValue> m_values;
-
 	Bool valueExists(ShaderProgramResourceMutatorValue v) const
 	{
 		for(ShaderProgramResourceMutatorValue val : m_values)
@@ -53,9 +50,28 @@ private:
 		}
 		return false;
 	}
+
+private:
+	String m_name;
+	DynamicArray<ShaderProgramResourceMutatorValue> m_values;
+};
+
+class ShaderProgramResourceMutation
+{
+public:
+	const ShaderProgramResourceMutator* m_mutator;
+	ShaderProgramResourceMutatorValue m_value;
+	U8 _padding[sizeof(void*) - sizeof(m_value)];
+
+	ShaderProgramResourceMutation()
+	{
+		memset(this, 0, sizeof(*this));
+	}
 };
 
-/// A wrapper over the uniforms of a shader or members of the uniform block.
+static_assert(sizeof(ShaderProgramResourceMutation) == sizeof(void*) * 2, "Need it to be packed");
+
+/// A wrapper over the uniforms of a shader or constants.
 class ShaderProgramResourceInputVariable : public NonCopyable
 {
 	friend class ShaderProgramResourceVariant;
@@ -83,12 +99,24 @@ public:
 		return m_const;
 	}
 
+	Bool acceptAllMutations(const WeakArray<const ShaderProgramResourceMutation>& mutations) const
+	{
+		for(const ShaderProgramResourceMutation& m : mutations)
+		{
+			if(!acceptMutation(m))
+			{
+				return false;
+			}
+		}
+		return true;
+	}
+
 private:
 	/// Information on how this variable will be used by this mutator.
 	class Mutator
 	{
 	public:
-		ShaderProgramResourceMutator* m_mutator = nullptr;
+		const ShaderProgramResourceMutator* m_mutator = nullptr;
 		DynamicArray<ShaderProgramResourceMutatorValue> m_values;
 	};
 
@@ -96,19 +124,53 @@ private:
 	U32 m_idx;
 	ShaderVariableDataType m_dataType = ShaderVariableDataType::NONE;
 	Bool8 m_const = false;
-	Mutator* m_instancingMutator = nullptr;
+	const ShaderProgramResourceMutator* m_instancingMutator = nullptr;
 	DynamicArray<Mutator> m_mutators;
 
 	Bool isTexture() const
 	{
 		return m_dataType >= ShaderVariableDataType::SAMPLERS_FIRST
-			&& m_dataType >= ShaderVariableDataType::SAMPLERS_LAST;
+			&& m_dataType <= ShaderVariableDataType::SAMPLERS_LAST;
 	}
 
 	Bool inBlock() const
 	{
 		return !m_const && !isTexture();
 	}
+
+	const Mutator* tryFindMutator(const ShaderProgramResourceMutator& mutator) const
+	{
+		for(const Mutator& m : m_mutators)
+		{
+			if(m.m_mutator == &mutator)
+			{
+				return &m;
+			}
+		}
+
+		return nullptr;
+	}
+
+	Bool acceptMutation(const ShaderProgramResourceMutation& mutation) const
+	{
+		ANKI_ASSERT(mutation.m_mutator->valueExists(mutation.m_value));
+		const Mutator* m = tryFindMutator(*mutation.m_mutator);
+		if(m)
+		{
+			for(ShaderProgramResourceMutatorValue v : m->m_values)
+			{
+				if(mutation.m_value == v)
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+		else
+		{
+			return true;
+		}
+	}
 };
 
 /// Shader program resource variant.
@@ -195,32 +257,7 @@ public:
 	}
 };
 
-template<>
-constexpr Bool isPacked<ShaderProgramResourceConstantValue>()
-{
-	static_assert(sizeof(ShaderProgramResourceConstantValue) == sizeof(Vec4) * 2, "Need it to be packed");
-	return true;
-}
-
-class ShaderProgramResourceMutatorInfo
-{
-public:
-	const ShaderProgramResourceMutator* m_mutator;
-	ShaderProgramResourceMutatorInfo m_value;
-	U8 _padding[sizeof(void*) - sizeof(m_value)];
-
-	ShaderProgramResourceMutatorInfo()
-	{
-		memset(this, 0, sizeof(*this));
-	}
-};
-
-template<>
-constexpr Bool isPacked<ShaderProgramResourceMutatorInfo>()
-{
-	static_assert(sizeof(ShaderProgramResourceMutatorInfo) == sizeof(Vec4) * 2, "Need it to be packed");
-	return true;
-}
+static_assert(sizeof(ShaderProgramResourceConstantValue) == sizeof(Vec4) * 2, "Need it to be packed");
 
 /// Shader program resource. It defines a custom format for shader programs.
 ///
@@ -234,14 +271,14 @@ constexpr Bool isPacked<ShaderProgramResourceMutatorInfo>()
 ///		<shaders>
 ///			<shader type="vert | frag | tese | tesc"/>
 ///
-///				<inputs>
+///				[<inputs>
 ///					<input name="str" type="float | vec2 | vec3 | vec4 | mat3 | mat4 | samplerXXX"
 /// 					[instanceCount="mutator name"] [const="0 | 1"]>
-/// 					<mutators> (2)
+/// 					[<mutators> (2)
 /// 						<mutator name="variant_name" values="array of ints"/>
-/// 					</mutators>
+/// 					</mutators>]
 ///					</input>
-///				</inputs>
+///				</inputs>]
 ///
 ///				<source>
 ///					src
@@ -268,14 +305,38 @@ public:
 		return m_inputVars;
 	}
 
+	const ShaderProgramResourceInputVariable* tryFindInputVariable(CString name) const
+	{
+		for(const ShaderProgramResourceInputVariable& m : m_inputVars)
+		{
+			if(m.getName() == name)
+			{
+				return &m;
+			}
+		}
+		return nullptr;
+	}
+
 	const DynamicArray<ShaderProgramResourceMutator>& getMutators() const
 	{
 		return m_mutators;
 	}
 
+	const ShaderProgramResourceMutator* tryFindMutator(CString name) const
+	{
+		for(const ShaderProgramResourceMutator& m : m_mutators)
+		{
+			if(m.getName() == name)
+			{
+				return &m;
+			}
+		}
+		return nullptr;
+	}
+
 	/// Get or create a graphics shader program variant.
 	/// @note It's thread-safe.
-	void getOrCreateVariant(WeakArray<const ShaderProgramResourceMutatorInfo> mutations,
+	void getOrCreateVariant(WeakArray<const ShaderProgramResourceMutation> mutation,
 		WeakArray<const ShaderProgramResourceConstantValue> constants,
 		const ShaderProgramResourceVariant*& variant) const;
 
@@ -301,17 +362,24 @@ private:
 	Bool8 m_tessellation = false;
 	Bool8 m_instanced = false;
 
+#if ANKI_EXTRA_CHECKS
+	const ShaderProgramResourceMutator* m_instancingMutator = nullptr;
+#endif
+
 	/// Parse whatever is inside <inputs>
-	ANKI_USE_RESULT Error parseInputs(XmlElement& inputsEl, U& inputVarCount, StringListAuto& constsSrc);
+	ANKI_USE_RESULT Error parseInputs(XmlElement& inputsEl,
+		U& inputVarCount,
+		StringListAuto& constsSrc,
+		const ShaderProgramResourceMutator*& instanceMutator);
 
-	U64 computeVariantHash(WeakArray<const ShaderProgramResourceMutatorInfo> mutations,
+	U64 computeVariantHash(WeakArray<const ShaderProgramResourceMutation> mutations,
 		WeakArray<const ShaderProgramResourceConstantValue> constants) const;
 
-	void initVariant(WeakArray<const ShaderProgramResourceMutatorInfo> mutations,
+	void initVariant(WeakArray<const ShaderProgramResourceMutation> mutations,
 		WeakArray<const ShaderProgramResourceConstantValue> constants,
 		ShaderProgramResourceVariant& v) const;
 
-	void compInputVarDefineString(const ShaderProgramResourceInputVariable& var, StringAuto& list);
+	void compInputVarDefineString(const ShaderProgramResourceInputVariable& var, StringListAuto& list);
 };
 /// @}
 

+ 12 - 5
src/anki/util/DynamicArray.h

@@ -38,11 +38,6 @@ public:
 		return m_data[n];
 	}
 
-	operator Bool() const
-	{
-		return !isEmpty();
-	}
-
 	Iterator getBegin()
 	{
 		return m_data;
@@ -335,6 +330,18 @@ public:
 		}
 	}
 
+	template<typename Y, PtrSize S>
+	WeakArray(const Array<Y, S>& arr)
+		: Base(&arr[0], S)
+	{
+	}
+
+	template<typename Y>
+	WeakArray(const DynamicArrayBase<Y>& arr)
+		: Base((arr.getSize()) ? &arr[0] : nullptr, arr.getSize())
+	{
+	}
+
 	/// Copy.
 	WeakArray(const WeakArray& b)
 		: Base(b.m_data, b.m_size)

+ 30 - 0
src/anki/util/String.cpp

@@ -36,6 +36,21 @@ Error CString::toNumber(F32& out) const
 	return ErrorCode::NONE;
 }
 
+Error CString::toNumber(I8& out) const
+{
+	I64 i64 = 0;
+	ANKI_CHECK(toNumber(i64));
+
+	if(i64 < MIN_I8 || i64 > MAX_I8)
+	{
+		ANKI_UTIL_LOGE("Conversion failed. Our of range");
+		return ErrorCode::USER_DATA;
+	}
+
+	out = I8(i64);
+	return ErrorCode::NONE;
+}
+
 Error CString::toNumber(I64& out) const
 {
 	checkInit();
@@ -105,6 +120,21 @@ Error CString::toNumber(U32& out) const
 	return ErrorCode::NONE;
 }
 
+Error CString::toNumber(U8& out) const
+{
+	U64 i64 = 0;
+	ANKI_CHECK(toNumber(i64));
+
+	if(i64 > MAX_U8)
+	{
+		ANKI_UTIL_LOGE("Conversion failed. Our of range");
+		return ErrorCode::USER_DATA;
+	}
+
+	out = U8(i64);
+	return ErrorCode::NONE;
+}
+
 String& String::operator=(StringAuto&& b)
 {
 	m_data = std::move(b.m_data);

+ 18 - 0
src/anki/util/String.h

@@ -212,12 +212,18 @@ public:
 	/// Convert to F32.
 	ANKI_USE_RESULT Error toNumber(F32& out) const;
 
+	/// Convert to I8.
+	ANKI_USE_RESULT Error toNumber(I8& out) const;
+
 	/// Convert to I64.
 	ANKI_USE_RESULT Error toNumber(I64& out) const;
 
 	/// Convert to I32.
 	ANKI_USE_RESULT Error toNumber(I32& out) const;
 
+	/// Convert to U8.
+	ANKI_USE_RESULT Error toNumber(U8& out) const;
+
 	/// Convert to U64.
 	ANKI_USE_RESULT Error toNumber(U64& out) const;
 
@@ -539,6 +545,12 @@ public:
 		return toCString().toNumber(out);
 	}
 
+	/// Convert to I8.
+	ANKI_USE_RESULT Error toNumber(I8& out) const
+	{
+		return toCString().toNumber(out);
+	}
+
 	/// Convert to U64.
 	ANKI_USE_RESULT Error toNumber(U64& out) const
 	{
@@ -551,6 +563,12 @@ public:
 		return toCString().toNumber(out);
 	}
 
+	/// Convert to U8.
+	ANKI_USE_RESULT Error toNumber(U8& out) const
+	{
+		return toCString().toNumber(out);
+	}
+
 protected:
 	DynamicArray<Char> m_data;
 

+ 0 - 1
tools/scene/Exporter.h

@@ -111,7 +111,6 @@ public:
 	std::string m_outputDirectory;
 	std::string m_rpath;
 	std::string m_texrpath;
-	std::string m_progrpath = "programs";
 
 	bool m_flipyz = false;
 

+ 54 - 59
tools/scene/ExporterMaterial.cpp

@@ -8,21 +8,30 @@
 
 const char* MATERIAL_TEMPLATE = R"(<?xml version="1.0" encoding="UTF-8" ?>
 <!-- This file is auto generated by ExporterMaterial.cpp -->
-<material>
-	<shaderProgram>%shaderProg%</shaderProgram>
+<material shaderProgram="programs/MsGeneric.ankiprog">
+	
+	<mutators>
+		<mutator name="DIFFUSE_TEX" value="%diffTexMutator%"/>
+		<mutator name="SPECULAR_TEX" value="%specTexMutator%"/>
+		<mutator name="ROUGHNESS_TEX" value="%roughnessTexMutator%"/>
+		<mutator name="METAL_TEX" value="%metalTexMutator%"/>
+		<mutator name="NORMAL_TEX" value="%normalTexMutator%"/>
+		<mutator name="PARALLAX" value="%parallaxMutator%"/>
+		<mutator name="EMISSIVE_TEX" value="%emissiveTexMutator%"/>
+	</mutators>
 	
 	<inputs>
-		<input><shaderInput>mvp</shaderInput><builtin>MODEL_VIEW_PROJECTION_MATRIX</builtin></input>
-		<input><shaderInput>normalMat</shaderInput><builtin>NORMAL_MATRIX</builtin></input>
+		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="normalMat" builtin="NORMAL_MATRIX"/>
 		%modelViewMat%
 	
-		<input>%diff%</input>
-		<input>%spec%</input>
-		<input>%roughness%</input>
-		<input>%metallic%</input>
+		%diff%
+		%spec%
+		%roughness%
+		%metallic%
 		%normal%
-		<input>%emission%</input>
-		<input>%subsurface%</input>
+		%emission%
+		%subsurface%
 		%height%
 	</inputs>
 </material>
@@ -36,7 +45,6 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 	LOGI("Exporting material %s", name.c_str());
 
 	std::string xml = MATERIAL_TEMPLATE;
-	std::string prog_fname = "ms_";
 
 	// Diffuse texture
 	if(mtl.GetTextureCount(aiTextureType_DIFFUSE) > 0)
@@ -44,9 +52,8 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		if(mtl.GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS)
 		{
 			std::string diffTex = m_texrpath + getFilename(path.C_Str());
-			xml = replaceAllString(xml, "%diff%", "<shaderInput>diffTex</shaderInput><value>" + diffTex + "</value>");
-
-			prog_fname += "difft_";
+			xml = replaceAllString(xml, "%diff%", "<input shaderInput=\"diffTex\" value=\"" + diffTex + "\"/>");
+			xml = replaceAllString(xml, "%diffTexMutator%", "1");
 		}
 		else
 		{
@@ -60,13 +67,12 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 
 		xml = replaceAllString(xml,
 			"%diff%",
-			"<shaderInput>diffColor</shaderInput><value>" + std::to_string(diffCol[0]) + " "
-				+ std::to_string(diffCol[1])
+			"<input shaderInput=\"diffColor\" value=\"" + std::to_string(diffCol[0]) + " " + std::to_string(diffCol[1])
 				+ " "
 				+ std::to_string(diffCol[2])
-				+ "</value>");
+				+ "\"/>");
 
-		prog_fname += "diffc_";
+		xml = replaceAllString(xml, "%diffTexMutator%", "0");
 	}
 
 	// Specular color
@@ -75,9 +81,8 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		if(mtl.GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS)
 		{
 			std::string specTex = m_texrpath + getFilename(path.C_Str());
-			xml = replaceAllString(xml, "%spec%", "<shaderInput>specTex</shaderInput><value>" + specTex + "</value>");
-
-			prog_fname += "spect_";
+			xml = replaceAllString(xml, "%spec%", "<input shaderInput=\"specTex\" value=\"" + specTex + "\"/>");
+			xml = replaceAllString(xml, "%specTexMutator%", "1");
 		}
 		else
 		{
@@ -91,13 +96,12 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 
 		xml = replaceAllString(xml,
 			"%spec%",
-			"<shaderInput>specColor</shaderInput><value>" + std::to_string(specCol[0]) + " "
-				+ std::to_string(specCol[1])
+			"<input shaderInput=\"specColor\" value=\"" + std::to_string(specCol[0]) + " " + std::to_string(specCol[1])
 				+ " "
 				+ std::to_string(specCol[2])
-				+ "</value>");
+				+ "\"/>");
 
-		prog_fname += "specc_";
+		xml = replaceAllString(xml, "%specTexMutator%", "0");
 	}
 
 	// Roughness
@@ -107,9 +111,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		{
 			std::string shininessTex = m_texrpath + getFilename(path.C_Str());
 			xml = replaceAllString(
-				xml, "%roughness%", "<shaderInput>roughnessTex</shaderInput><value>" + shininessTex + "</value>");
+				xml, "%roughness%", "<input shaderInput=\"roughnessTex\" value=\"" + shininessTex + "\"/>");
 
-			prog_fname += "rought_";
+			xml = replaceAllString(xml, "%roughnessTexMutator%", "1");
 		}
 		else
 		{
@@ -130,9 +134,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		shininess = shininess / MAX_SHININESS;
 
 		xml = replaceAllString(
-			xml, "%roughness%", "<shaderInput>roughness</shaderInput><value>" + std::to_string(shininess) + "</value>");
+			xml, "%roughness%", "<input shaderInput=\"roughness\" value=\"" + std::to_string(shininess) + "\" />");
 
-		prog_fname += "roughc_";
+		xml = replaceAllString(xml, "%roughnessTexMutator%", "0");
 	}
 
 	// Metallic texture
@@ -142,9 +146,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		{
 			std::string metallicTex = m_texrpath + getFilename(path.C_Str());
 			xml = replaceAllString(
-				xml, "%metallic%", "<shaderInput>metallicTex</shaderInput><value>" + metallicTex + "</value>");
+				xml, "%metallic%", "<input shaderInput=\"metallicTex\" value=\"" + metallicTex + "\"/>");
 
-			prog_fname += "metalt_";
+			xml = replaceAllString(xml, "%metalTexMutator%", "1");
 		}
 		else
 		{
@@ -160,9 +164,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		}
 
 		xml = replaceAllString(
-			xml, "%metallic%", "<shaderInput>metallic</shaderInput><value>" + std::to_string(metallic) + "</value>");
+			xml, "%metallic%", "<input shaderInput=\"metallic\" value=\"" + std::to_string(metallic) + "\"/>");
 
-		prog_fname += "metalc_";
+		xml = replaceAllString(xml, "%metalTexMutator%", "0");
 	}
 
 	// Normal texture
@@ -171,10 +175,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		if(mtl.GetTexture(aiTextureType_NORMALS, 0, &path) == AI_SUCCESS)
 		{
 			std::string normTex = m_texrpath + getFilename(path.C_Str());
-			xml = replaceAllString(
-				xml, "%normal%", "<input><shaderInput>normalTex</shaderInput><value>" + normTex + "</value></input>");
+			xml = replaceAllString(xml, "%normal%", "<input shaderInput=\"normalTex\" value\"" + normTex + "\"/>");
 
-			prog_fname += "normalt_";
+			xml = replaceAllString(xml, "%normalTexMutator%", "1");
 		}
 		else
 		{
@@ -184,7 +187,7 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 	else
 	{
 		xml = replaceAllString(xml, "%normal%", "");
-		prog_fname += "normal0_";
+		xml = replaceAllString(xml, "%normalTexMutator%", "0");
 	}
 
 	// Emissive texture
@@ -194,9 +197,9 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 		{
 			std::string emissiveTex = m_texrpath + getFilename(path.C_Str());
 			xml = replaceAllString(
-				xml, "%emission%", "<shaderInput>emissionTex</shaderInput><value>" + emissiveTex + "</value>");
+				xml, "%emission%", "<input shaderInput=\"emissionTex\" value=\"" + emissiveTex + "\"/>");
 
-			prog_fname += "emist_";
+			xml = replaceAllString(xml, "%emissiveTexMutator%", "1");
 		}
 		else
 		{
@@ -210,13 +213,13 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 
 		xml = replaceAllString(xml,
 			"%emission%",
-			"<shaderInput>emission</shaderInput><value>" + std::to_string(emissionCol[0]) + " "
+			"<input shaderInput=\"emission\" value=\"" + std::to_string(emissionCol[0]) + " "
 				+ std::to_string(emissionCol[1])
 				+ " "
 				+ std::to_string(emissionCol[2])
-				+ "</value>");
+				+ "\"/>");
 
-		prog_fname += "emisc_";
+		xml = replaceAllString(xml, "%emissiveTexMutator%", "0");
 	}
 
 	// Subsurface
@@ -227,11 +230,8 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 			subsurface = std::stof(mtl.mAnKiProperties.at("subsurface"));
 		}
 
-		xml = replaceAllString(xml,
-			"%subsurface%",
-			"<shaderInput>subsurface</shaderInput><value>" + std::to_string(subsurface) + "</value>");
-
-		prog_fname += "subsc_";
+		xml = replaceAllString(
+			xml, "%subsurface%", "<input shaderInput=\"subsurface\" value=\"" + std::to_string(subsurface) + "\"/>");
 	}
 
 	// Height texture
@@ -242,15 +242,14 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 			std::string dispTex = m_texrpath + getFilename(path.C_Str());
 			xml = replaceAllString(xml,
 				"%height%",
-				"<input><shaderInput>heightTex</shaderInput><value>" + dispTex
-					+ "</value></input>\n"
-					  "\t\t<input><shaderInput>heightMapScale</input><value>0.05</value></input>");
+				"<input shaderInput=\"heightTex\" value=\"" + dispTex
+					+ "\"/>\n"
+					  "\t\t<input shaderInput=\"heightMapScale\" value=\"0.05\"/>");
 
-			xml = replaceAllString(xml,
-				"%modelViewMat%",
-				"<input><shaderInput>modelViewMat</shaderInput><builtin>MODEL_VIEW_MATRIX</builtin></input>");
+			xml = replaceAllString(
+				xml, "%modelViewMat%", "<input shaderInput=\"modelViewMat\" builtin=\"MODEL_VIEW_MATRIX\"/>");
 
-			prog_fname += "par1";
+			xml = replaceAllString(xml, "%parallaxMutator%", "1");
 		}
 		else
 		{
@@ -260,14 +259,10 @@ void Exporter::exportMaterial(const aiMaterial& mtl) const
 	else
 	{
 		xml = replaceAllString(xml, "%height%", "");
-
 		xml = replaceAllString(xml, "%modelViewMat%", "");
-
-		prog_fname += "par0";
+		xml = replaceAllString(xml, "%parallaxMutator%", "0");
 	}
 
-	xml = replaceAllString(xml, "%shaderProg%", m_progrpath + prog_fname + ".ankiprog");
-
 	// Replace texture extensions with .anki
 	xml = replaceAllString(xml, ".tga", ".ankitex");
 	xml = replaceAllString(xml, ".png", ".ankitex");

+ 0 - 13
tools/scene/Main.cpp

@@ -52,19 +52,6 @@ Options:
 				goto error;
 			}
 		}
-		else if(strcmp(argv[i], "-progrpath") == 0)
-		{
-			++i;
-
-			if(i < argc)
-			{
-				exporter.m_progrpath = argv[i] + std::string("/");
-			}
-			else
-			{
-				goto error;
-			}
-		}
 		else if(strcmp(argv[i], "-flipyz") == 0)
 		{
 			exporter.m_flipyz = true;