// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
// All rights reserved.
// Code licensed under the BSD License.
// http://www.anki3d.org/LICENSE

#include <anki/resource/MaterialResource.h>
#include <anki/resource/ResourceManager.h>
#include <anki/resource/TextureResource.h>
#include <anki/util/Xml.h>

namespace anki
{

static const Array<CString, U32(BuiltinMutatorId::COUNT)> BUILTIN_MUTATOR_NAMES = {
	{"NONE", "ANKI_INSTANCE_COUNT", "ANKI_PASS", "ANKI_LOD", "ANKI_BONES", "ANKI_VELOCITY"}};

class BuiltinVarInfo
{
public:
	const char* m_name;
	ShaderVariableDataType m_type;
	Bool m_instanced;
};

static const Array<BuiltinVarInfo, U(BuiltinMaterialVariableId::COUNT)> BUILTIN_INFOS = {
	{{"NONE", ShaderVariableDataType::NONE, false},
		{"m_ankiMvp", ShaderVariableDataType::MAT4, true},
		{"m_ankiPreviousMvp", ShaderVariableDataType::MAT4, true},
		{"m_ankiModelMatrix", ShaderVariableDataType::MAT4, true},
		{"m_ankiViewMatrix", ShaderVariableDataType::MAT4, false},
		{"m_ankiProjectionMatrix", ShaderVariableDataType::MAT4, false},
		{"m_ankiModelViewMatrix", ShaderVariableDataType::MAT4, true},
		{"m_ankiViewProjectionMatrix", ShaderVariableDataType::MAT4, false},
		{"m_ankiNormalMatrix", ShaderVariableDataType::MAT3, true},
		{"m_ankiRotationMatrix", ShaderVariableDataType::MAT3, true},
		{"m_ankiCameraRotationMatrix", ShaderVariableDataType::MAT3, false},
		{"m_ankiCameraPosition", ShaderVariableDataType::VEC3, false},
		{"u_ankiGlobalSampler", ShaderVariableDataType::SAMPLER, false}}};

static ANKI_USE_RESULT Error checkBuiltin(
	CString name, ShaderVariableDataType dataType, Bool instanced, BuiltinMaterialVariableId& outId)
{
	outId = BuiltinMaterialVariableId::NONE;

	for(BuiltinMaterialVariableId id : EnumIterable<BuiltinMaterialVariableId>())
	{
		if(id == BuiltinMaterialVariableId::NONE)
		{
			continue;
		}

		if(BUILTIN_INFOS[id].m_name == name)
		{
			outId = id;

			if(BUILTIN_INFOS[id].m_type != dataType)
			{
				ANKI_RESOURCE_LOGE("Incorect type for builtin: %s", name.cstr());
				return Error::USER_DATA;
			}

			if(instanced && !BUILTIN_INFOS[id].m_instanced)
			{
				ANKI_RESOURCE_LOGE("Variable %s be instanced: %s",
					(BUILTIN_INFOS[id].m_instanced) ? "should" : "shouldn't",
					name.cstr());
				return Error::USER_DATA;
			}

			break;
		}
	}

	if(outId == BuiltinMaterialVariableId::NONE && (name.find("m_anki") == 0 || name.find("u_anki") == 0))
	{
		ANKI_RESOURCE_LOGE("Unknown builtin var: %s", name.cstr());
		return Error::USER_DATA;
	}

	return Error::NONE;
}

MaterialVariable::MaterialVariable()
{
	m_mat4 = Mat4::getZero();
	m_mat4(3, 3) = NO_VALUE; // Add a random value
}

MaterialVariable::~MaterialVariable()
{
}

MaterialResource::MaterialResource(ResourceManager* manager)
	: ResourceObject(manager)
{
}

MaterialResource::~MaterialResource()
{
	for(Pass p : EnumIterable<Pass>())
	{
		for(U32 l = 0; l < MAX_LOD_COUNT; ++l)
		{
			for(U32 inst = 0; inst < MAX_INSTANCE_GROUPS; ++inst)
			{
				for(U32 skinned = 0; skinned <= 1; ++skinned)
				{
					for(U32 vel = 0; vel <= 1; ++vel)
					{
						MaterialVariant& variant = m_variantMatrix[p][l][inst][skinned][vel];
						variant.m_blockInfos.destroy(getAllocator());
						variant.m_opaqueBindings.destroy(getAllocator());
					}
				}
			}
		}
	}

	for(MaterialVariable& var : m_vars)
	{
		var.m_name.destroy(getAllocator());
	}
	m_vars.destroy(getAllocator());

	m_nonBuiltinsMutation.destroy(getAllocator());
}

Error MaterialResource::load(const ResourceFilename& filename, Bool async)
{
	XmlDocument doc;
	XmlElement el;
	Bool present = false;
	ANKI_CHECK(openFileParseXml(filename, doc));

	// <material>
	XmlElement rootEl;
	ANKI_CHECK(doc.getChildElement("material", rootEl));

	// shaderProgram
	CString fname;
	ANKI_CHECK(rootEl.getAttributeText("shaderProgram", fname));
	ANKI_CHECK(getManager().loadResource(fname, m_prog, async));

	// Good time to create the vars
	ANKI_CHECK(createVars());

	// 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;

	// <mutation>
	XmlElement mutatorsEl;
	ANKI_CHECK(rootEl.getChildElementOptional("mutation", mutatorsEl));
	if(mutatorsEl)
	{
		ANKI_CHECK(parseMutators(mutatorsEl));
	}

	// The rest of the mutators
	ANKI_CHECK(findBuiltinMutators());

	// <inputs>
	ANKI_CHECK(rootEl.getChildElementOptional("inputs", el));
	if(el)
	{
		ANKI_CHECK(parseInputs(el, async));
	}

	return Error::NONE;
}

Error MaterialResource::parseMutators(XmlElement mutatorsEl)
{
	XmlElement mutatorEl;
	ANKI_CHECK(mutatorsEl.getChildElement("mutator", mutatorEl));

	//
	// Process the non-builtin mutators
	//
	U32 mutatorCount = 0;
	ANKI_CHECK(mutatorEl.getSiblingElementsCount(mutatorCount));
	++mutatorCount;
	ANKI_ASSERT(mutatorCount > 0);
	m_nonBuiltinsMutation.create(getAllocator(), mutatorCount);
	mutatorCount = 0;

	do
	{
		SubMutation& smutation = m_nonBuiltinsMutation[mutatorCount];

		// name
		CString mutatorName;
		ANKI_CHECK(mutatorEl.getAttributeText("name", mutatorName));
		if(mutatorName.isEmpty())
		{
			ANKI_RESOURCE_LOGE("Mutator name is empty");
			return Error::USER_DATA;
		}

		for(BuiltinMutatorId id : EnumIterable<BuiltinMutatorId>())
		{
			if(id == BuiltinMutatorId::NONE)
			{
				continue;
			}

			if(mutatorName == BUILTIN_MUTATOR_NAMES[id])
			{
				ANKI_RESOURCE_LOGE("Cannot list builtin mutator: %s", mutatorName.cstr());
				return Error::USER_DATA;
			}
		}

		if(mutatorName.find("ANKI_") == 0)
		{
			ANKI_RESOURCE_LOGE("Mutators can't start with ANKI_: %s", mutatorName.cstr());
			return Error::USER_DATA;
		}

		// value
		ANKI_CHECK(mutatorEl.getAttributeNumber("value", smutation.m_value));

		// Find mutator
		smutation.m_mutator = m_prog->tryFindMutator(mutatorName);

		if(!smutation.m_mutator)
		{
			ANKI_RESOURCE_LOGE("Mutator not found in program %s", &mutatorName[0]);
			return Error::USER_DATA;
		}

		if(!smutation.m_mutator->valueExists(smutation.m_value))
		{
			ANKI_RESOURCE_LOGE("Value %d is not part of the mutator %s", smutation.m_value, &mutatorName[0]);
			return Error::USER_DATA;
		}

		// Advance
		++mutatorCount;
		ANKI_CHECK(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
	} while(mutatorEl);

	ANKI_ASSERT(mutatorCount == m_nonBuiltinsMutation.getSize());

	return Error::NONE;
}

Error MaterialResource::findBuiltinMutators()
{
	// INSTANCE_COUNT
	U builtinMutatorCount = 0;

	m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT] =
		m_prog->tryFindMutator(BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::INSTANCE_COUNT]);
	if(m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT])
	{
		if(m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT]->m_values.getSize() != MAX_INSTANCE_GROUPS)
		{
			ANKI_RESOURCE_LOGE("Mutator %s should have %u values in the program",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::INSTANCE_COUNT].cstr(),
				MAX_INSTANCE_GROUPS);
			return Error::USER_DATA;
		}

		for(U32 i = 0; i < MAX_INSTANCE_GROUPS; ++i)
		{
			if(m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT]->m_values[i] != (1 << i))
			{
				ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
					BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::INSTANCE_COUNT].cstr());
				return Error::USER_DATA;
			}
		}

		++builtinMutatorCount;
	}

	// PASS
	m_builtinMutators[BuiltinMutatorId::PASS] = m_prog->tryFindMutator(BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS]);
	if(m_builtinMutators[BuiltinMutatorId::PASS] && m_forwardShading)
	{
		ANKI_RESOURCE_LOGE(
			"Mutator is not required for forward shading: %s", BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS].cstr());
		return Error::USER_DATA;
	}
	else if(!m_builtinMutators[BuiltinMutatorId::PASS] && !m_forwardShading)
	{
		ANKI_RESOURCE_LOGE(
			"Mutator is required for opaque shading: %s", BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS].cstr());
		return Error::USER_DATA;
	}

	if(m_builtinMutators[BuiltinMutatorId::PASS])
	{
		if(m_builtinMutators[BuiltinMutatorId::PASS]->m_values.getSize() != U32(Pass::COUNT) - 1)
		{
			ANKI_RESOURCE_LOGE("Mutator %s should have %u values in the program",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS].cstr(),
				U32(Pass::COUNT) - 1);
			return Error::USER_DATA;
		}

		U32 count = 0;
		for(Pass p : EnumIterable<Pass>())
		{
			if(p == Pass::FS)
			{
				continue;
			}

			if(m_builtinMutators[BuiltinMutatorId::PASS]->m_values[count++] != I(p))
			{
				ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
					BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS].cstr());
				return Error::USER_DATA;
			}
		}

		++builtinMutatorCount;
	}

	if(!m_forwardShading && !m_builtinMutators[BuiltinMutatorId::PASS])
	{
		ANKI_RESOURCE_LOGE("%s mutator is required", BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::PASS].cstr());
		return Error::USER_DATA;
	}

	// LOD
	m_builtinMutators[BuiltinMutatorId::LOD] = m_prog->tryFindMutator(BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::LOD]);
	if(m_builtinMutators[BuiltinMutatorId::LOD])
	{
		if(m_builtinMutators[BuiltinMutatorId::LOD]->m_values.getSize() > MAX_LOD_COUNT)
		{
			ANKI_RESOURCE_LOGE("Mutator %s should have at least %u values in the program",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::LOD].cstr(),
				U32(MAX_LOD_COUNT));
			return Error::USER_DATA;
		}

		for(U32 i = 0; i < m_builtinMutators[BuiltinMutatorId::LOD]->m_values.getSize(); ++i)
		{
			if(m_builtinMutators[BuiltinMutatorId::LOD]->m_values[i] != I(i))
			{
				ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
					BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::LOD].cstr());
				return Error::USER_DATA;
			}
		}

		m_lodCount = U8(m_builtinMutators[BuiltinMutatorId::LOD]->m_values.getSize());
		++builtinMutatorCount;
	}

	// BONES
	m_builtinMutators[BuiltinMutatorId::BONES] = m_prog->tryFindMutator(BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::BONES]);
	if(m_builtinMutators[BuiltinMutatorId::BONES])
	{
		if(m_builtinMutators[BuiltinMutatorId::BONES]->m_values.getSize() != 2)
		{
			ANKI_RESOURCE_LOGE("Mutator %s should have 2 values in the program",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::BONES].cstr());
			return Error::USER_DATA;
		}

		for(U32 i = 0; i < m_builtinMutators[BuiltinMutatorId::BONES]->m_values.getSize(); ++i)
		{
			if(m_builtinMutators[BuiltinMutatorId::BONES]->m_values[i] != I(i))
			{
				ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
					BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::BONES].cstr());
				return Error::USER_DATA;
			}
		}

		++builtinMutatorCount;

		// Find the binding of the transforms
		const ShaderProgramBinary& binary = m_prog->getBinary();
		for(const ShaderProgramBinaryBlock& block : binary.m_storageBlocks)
		{
			if(block.m_name.getBegin() == CString("b_ankiBoneTransforms"))
			{
				if(block.m_set != m_descriptorSetIdx)
				{
					ANKI_RESOURCE_LOGE("The set of u_ankiBoneTransforms should be %u", m_descriptorSetIdx);
					return Error::USER_DATA;
				}

				m_boneTrfsBinding = block.m_binding;
				break;
			}
		}

		if(m_boneTrfsBinding == MAX_U32)
		{
			ANKI_RESOURCE_LOGE("The program is using the %s mutator but b_ankiBoneTransforms was not found",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::BONES].cstr());
			return Error::NONE;
		}
	}

	// VELOCITY
	m_builtinMutators[BuiltinMutatorId::VELOCITY] =
		m_prog->tryFindMutator(BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::VELOCITY]);
	if(m_builtinMutators[BuiltinMutatorId::VELOCITY])
	{
		if(m_builtinMutators[BuiltinMutatorId::VELOCITY]->m_values.getSize() != 2)
		{
			ANKI_RESOURCE_LOGE("Mutator %s should have 2 values in the program",
				BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::VELOCITY].cstr());
			return Error::USER_DATA;
		}

		for(U32 i = 0; i < m_builtinMutators[BuiltinMutatorId::VELOCITY]->m_values.getSize(); ++i)
		{
			if(m_builtinMutators[BuiltinMutatorId::VELOCITY]->m_values[i] != I(i))
			{
				ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
					BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::VELOCITY].cstr());
				return Error::USER_DATA;
			}
		}

		++builtinMutatorCount;
	}

	if(m_nonBuiltinsMutation.getSize() + builtinMutatorCount != m_prog->getMutators().getSize())
	{
		ANKI_RESOURCE_LOGE("Some mutatators are unacounted for");
		return Error::USER_DATA;
	}

	return Error::NONE;
}

Error MaterialResource::parseVariable(CString fullVarName, Bool& instanced, U32& idx, CString& name)
{
	idx = 0;

	if(fullVarName.find("u_ankiPerDraw") != CString::NPOS)
	{
		instanced = false;
	}
	else if(fullVarName.find("u_ankiPerInstance") != CString::NPOS)
	{
		instanced = true;
	}
	else
	{
		ANKI_RESOURCE_LOGE("Wrong variable name: %s", fullVarName.cstr());
		return Error::USER_DATA;
	}

	const PtrSize leftBracket = fullVarName.find("[");
	const PtrSize rightBracket = fullVarName.find("]");

	if(instanced)
	{
		const Bool correct =
			(leftBracket == CString::NPOS && rightBracket == CString::NPOS)
			|| (leftBracket != CString::NPOS && rightBracket != CString::NPOS && rightBracket > leftBracket);
		if(!correct)
		{
			ANKI_RESOURCE_LOGE("Wrong variable name: %s", fullVarName.cstr());
			return Error::USER_DATA;
		}

		if(leftBracket != CString::NPOS)
		{
			Array<char, 8> idxStr = {};
			for(PtrSize i = leftBracket + 1; i < rightBracket; ++i)
			{
				idxStr[i - (leftBracket + 1)] = fullVarName[i];
			}

			ANKI_CHECK(CString(idxStr.getBegin()).toNumber(idx));
		}
		else
		{
			idx = 0;
		}
	}
	else
	{
		if(leftBracket != CString::NPOS || rightBracket != CString::NPOS)
		{
			ANKI_RESOURCE_LOGE("Can't support non instanced array variables: %s", fullVarName.cstr());
			return Error::USER_DATA;
		}
	}

	const PtrSize dot = fullVarName.find(".");
	if(dot == CString::NPOS)
	{
		ANKI_RESOURCE_LOGE("Wrong variable name: %s", fullVarName.cstr());
		return Error::USER_DATA;
	}

	name = fullVarName.getBegin() + dot + 1;

	return Error::NONE;
}

Error MaterialResource::createVars()
{
	const ShaderProgramBinary& binary = m_prog->getBinary();

	// Create the uniform vars
	U32 descriptorSet = MAX_U32;
	U32 maxDescriptorSet = 0;
	for(const ShaderProgramBinaryBlock& block : binary.m_uniformBlocks)
	{
		maxDescriptorSet = max(maxDescriptorSet, block.m_set);

		if(block.m_name.getBegin() != CString("b_ankiMaterial"))
		{
			continue;
		}

		descriptorSet = block.m_set;
		m_uboIdx = U32(&block - binary.m_uniformBlocks.getBegin());
		m_uboBinding = block.m_binding;

		for(const ShaderProgramBinaryVariable& var : block.m_variables)
		{
			Bool instanced;
			U32 idx;
			CString name;
			ANKI_CHECK(parseVariable(var.m_name.getBegin(), instanced, idx, name));
			ANKI_ASSERT(name.getLength() > 0 && (instanced || idx == 0));

			if(idx > 0)
			{
				if(idx > MAX_INSTANCES)
				{
					ANKI_RESOURCE_LOGE("Array variable exceeds the instance count: %s", var.m_name.getBegin());
					return Error::USER_DATA;
				}

				if(idx == 1)
				{
					// Find the idx==0
					MaterialVariable* other = tryFindVariable(name);
					ANKI_ASSERT(other);
					ANKI_ASSERT(other->m_indexInBinary2ndElement == MAX_U32);
					other->m_indexInBinary2ndElement = U32(&var - block.m_variables.getBegin());
				}

				// Skip var
				continue;
			}

			const MaterialVariable* other = tryFindVariable(name);
			if(other)
			{
				ANKI_RESOURCE_LOGE("Variable found twice: %s", name.cstr());
				return Error::USER_DATA;
			}

			MaterialVariable& in = *m_vars.emplaceBack(getAllocator());
			in.m_name.create(getAllocator(), name);
			in.m_index = m_vars.getSize() - 1;
			in.m_indexInBinary = U32(&var - block.m_variables.getBegin());
			in.m_constant = false;
			in.m_instanced = instanced;
			in.m_dataType = var.m_type;

			// Check if it's builtin
			ANKI_CHECK(checkBuiltin(name, in.m_dataType, instanced, in.m_builtin));
		}
	}

	if(descriptorSet == MAX_U32)
	{
		ANKI_RESOURCE_LOGE("The b_ankiMaterial UBO is missing");
		return Error::USER_DATA;
	}

	// Continue with the opaque if it's a material shader program
	for(const ShaderProgramBinaryOpaque& o : binary.m_opaques)
	{
		maxDescriptorSet = max(maxDescriptorSet, o.m_set);

		if(o.m_set != descriptorSet)
		{
			continue;
		}

		MaterialVariable& in = *m_vars.emplaceBack(getAllocator());
		in.m_name.create(getAllocator(), o.m_name.getBegin());
		in.m_index = m_vars.getSize() - 1;
		in.m_indexInBinary = U32(&o - binary.m_opaques.getBegin());
		in.m_constant = false;
		in.m_instanced = false;
		in.m_dataType = o.m_type;

		// Check if it's builtin
		ANKI_CHECK(checkBuiltin(in.m_name, in.m_dataType, false, in.m_builtin));
	}

	if(descriptorSet != maxDescriptorSet)
	{
		ANKI_RESOURCE_LOGE("All bindings of a material shader should be in the highest descriptor set");
		return Error::USER_DATA;
	}

	m_descriptorSetIdx = U8(descriptorSet);

	// Consts
	for(const ShaderProgramResourceConstant& c : m_prog->getConstants())
	{
		MaterialVariable& in = *m_vars.emplaceBack(getAllocator());
		in.m_name.create(getAllocator(), c.m_name);
		in.m_index = m_vars.getSize() - 1;
		in.m_constant = true;
		in.m_instanced = false;
		in.m_dataType = c.m_dataType;
	}

	return Error::NONE;
}

Error MaterialResource::parseInputs(XmlElement inputsEl, Bool async)
{
	// Connect the input variables
	XmlElement inputEl;
	ANKI_CHECK(inputsEl.getChildElementOptional("input", inputEl));
	while(inputEl)
	{
		// Get var name
		CString varName;
		ANKI_CHECK(inputEl.getAttributeText("shaderVar", varName));

		// Try find var
		MaterialVariable* foundVar = tryFindVariable(varName);

		if(foundVar == nullptr)
		{
			ANKI_RESOURCE_LOGE("Variable \"%s\" not found", varName.cstr());
			return Error::USER_DATA;
		}

		if(foundVar->m_builtin != BuiltinMaterialVariableId::NONE)
		{
			ANKI_RESOURCE_LOGE("Shouldn't list builtin vars: %s", varName.cstr());
			return Error::USER_DATA;
		}

		// A value will be set
		foundVar->m_mat4(3, 3) = 0.0f;

		// Process var
		if(foundVar->isConstant())
		{
			// Const

			switch(foundVar->getDataType())
			{
			case ShaderVariableDataType::INT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_int));
				break;
			case ShaderVariableDataType::IVEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec2));
				break;
			case ShaderVariableDataType::IVEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec3));
				break;
			case ShaderVariableDataType::IVEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec4));
				break;
			case ShaderVariableDataType::UINT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_uint));
				break;
			case ShaderVariableDataType::UVEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec2));
				break;
			case ShaderVariableDataType::UVEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec3));
				break;
			case ShaderVariableDataType::UVEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec4));
				break;
			case ShaderVariableDataType::FLOAT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_float));
				break;
			case ShaderVariableDataType::VEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec2));
				break;
			case ShaderVariableDataType::VEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec3));
				break;
			case ShaderVariableDataType::VEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec4));
				break;
			default:
				ANKI_ASSERT(0);
				break;
			}
		}
		else
		{
			// Not built-in

			if(foundVar->isInstanced())
			{
				ANKI_RESOURCE_LOGE("Only some builtin variables can be instanced: %s", foundVar->getName().cstr());
				return Error::USER_DATA;
			}

			switch(foundVar->getDataType())
			{
			case ShaderVariableDataType::INT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_int));
				break;
			case ShaderVariableDataType::IVEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec2));
				break;
			case ShaderVariableDataType::IVEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec3));
				break;
			case ShaderVariableDataType::IVEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_ivec4));
				break;
			case ShaderVariableDataType::UINT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_uint));
				break;
			case ShaderVariableDataType::UVEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec2));
				break;
			case ShaderVariableDataType::UVEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec3));
				break;
			case ShaderVariableDataType::UVEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_uvec4));
				break;
			case ShaderVariableDataType::FLOAT:
				ANKI_CHECK(inputEl.getAttributeNumber("value", foundVar->m_float));
				break;
			case ShaderVariableDataType::VEC2:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec2));
				break;
			case ShaderVariableDataType::VEC3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec3));
				break;
			case ShaderVariableDataType::VEC4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_vec4));
				break;
			case ShaderVariableDataType::MAT3:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_mat3));
				break;
			case ShaderVariableDataType::MAT4:
				ANKI_CHECK(inputEl.getAttributeNumbers("value", foundVar->m_mat4));
				break;
			case ShaderVariableDataType::TEXTURE_2D:
			case ShaderVariableDataType::TEXTURE_2D_ARRAY:
			case ShaderVariableDataType::TEXTURE_3D:
			case ShaderVariableDataType::TEXTURE_CUBE:
			{
				CString texfname;
				ANKI_CHECK(inputEl.getAttributeText("value", texfname));
				ANKI_CHECK(getManager().loadResource(texfname, foundVar->m_tex, async));
				break;
			}

			default:
				ANKI_ASSERT(0);
				break;
			}
		}

		// Advance
		ANKI_CHECK(inputEl.getNextSiblingElement("input", inputEl));
	}

	return Error::NONE;
}

const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey& key_) const
{
	RenderingKey key = key_;
	key.setLod(min<U32>(m_lodCount - 1, key.getLod()));

	if(!isInstanced())
	{
		ANKI_ASSERT(key.getInstanceCount() == 1);
	}

	ANKI_ASSERT(!key.isSkinned() || m_builtinMutators[BuiltinMutatorId::BONES]);
	ANKI_ASSERT(!key.hasVelocity() || m_builtinMutators[BuiltinMutatorId::VELOCITY]);

	key.setInstanceCount(1 << getInstanceGroupIdx(key.getInstanceCount()));

	MaterialVariant& variant = m_variantMatrix[key.getPass()][key.getLod()][getInstanceGroupIdx(key.getInstanceCount())]
											  [key.isSkinned()][key.hasVelocity()];

	// Check if it's initialized
	{
		RLockGuard<RWMutex> lock(m_variantMatrixMtx);
		if(variant.m_prog.isCreated())
		{
			return variant;
		}
	}

	// Not initialized, init it
	WLockGuard<RWMutex> lock(m_variantMatrixMtx);

	// Check again
	if(variant.m_prog.isCreated())
	{
		return variant;
	}

	ShaderProgramResourceVariantInitInfo initInfo(m_prog);

	for(const SubMutation& m : m_nonBuiltinsMutation)
	{
		initInfo.addMutation(m.m_mutator->m_name, m.m_value);
	}

	if(m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT])
	{
		initInfo.addMutation(m_builtinMutators[BuiltinMutatorId::INSTANCE_COUNT]->m_name, key.getInstanceCount());
	}

	if(m_builtinMutators[BuiltinMutatorId::PASS])
	{
		initInfo.addMutation(m_builtinMutators[BuiltinMutatorId::PASS]->m_name, MutatorValue(key.getPass()));
	}

	if(m_builtinMutators[BuiltinMutatorId::LOD])
	{
		initInfo.addMutation(m_builtinMutators[BuiltinMutatorId::LOD]->m_name, MutatorValue(key.getLod()));
	}

	if(m_builtinMutators[BuiltinMutatorId::BONES])
	{
		initInfo.addMutation(m_builtinMutators[BuiltinMutatorId::BONES]->m_name, key.isSkinned() != 0);
	}

	if(m_builtinMutators[BuiltinMutatorId::VELOCITY])
	{
		initInfo.addMutation(m_builtinMutators[BuiltinMutatorId::VELOCITY]->m_name, key.hasVelocity() != 0);
	}

	for(const MaterialVariable& var : m_vars)
	{
		if(!var.isConstant())
		{
			continue;
		}

		if(!var.valueSetByMaterial())
		{
			continue;
		}

		switch(var.m_dataType)
		{
		case ShaderVariableDataType::INT:
			initInfo.addConstant(var.getName(), var.getValue<I32>());
			break;
		case ShaderVariableDataType::IVEC2:
			initInfo.addConstant(var.getName(), var.getValue<IVec2>());
			break;
		case ShaderVariableDataType::IVEC3:
			initInfo.addConstant(var.getName(), var.getValue<IVec3>());
			break;
		case ShaderVariableDataType::IVEC4:
			initInfo.addConstant(var.getName(), var.getValue<IVec4>());
			break;
		case ShaderVariableDataType::FLOAT:
			initInfo.addConstant(var.getName(), var.getValue<F32>());
			break;
		case ShaderVariableDataType::VEC2:
			initInfo.addConstant(var.getName(), var.getValue<Vec2>());
			break;
		case ShaderVariableDataType::VEC3:
			initInfo.addConstant(var.getName(), var.getValue<Vec3>());
			break;
		case ShaderVariableDataType::VEC4:
			initInfo.addConstant(var.getName(), var.getValue<Vec4>());
			break;
		default:
			ANKI_ASSERT(0);
		}
	}

	const ShaderProgramResourceVariant* progVariant;
	m_prog->getOrCreateVariant(initInfo, progVariant);

	// Init the variant
	initVariant(*progVariant, variant, key.getInstanceCount());

	return variant;
}

void MaterialResource::initVariant(
	const ShaderProgramResourceVariant& progVariant, MaterialVariant& variant, U32 instanceCount) const
{
	// Find the block instance
	const ShaderProgramBinary& binary = m_prog->getBinary();
	const ShaderProgramBinaryVariant& binaryVariant = progVariant.getBinaryVariant();
	const ShaderProgramBinaryBlockInstance* binaryBlockInstance = nullptr;
	for(const ShaderProgramBinaryBlockInstance& instance : binaryVariant.m_uniformBlocks)
	{
		if(instance.m_index == m_uboIdx)
		{
			binaryBlockInstance = &instance;
			break;
		}
	}

	if(binaryBlockInstance == nullptr)
	{
		ANKI_RESOURCE_LOGF(
			"The uniform block doesn't appear to be active for variant. Material: %s", getFilename().cstr());
	}

	// Some init
	variant.m_prog = progVariant.getProgram();
	variant.m_blockInfos.create(getAllocator(), m_vars.getSize());
	variant.m_opaqueBindings.create(getAllocator(), m_vars.getSize(), -1);
	variant.m_uniBlockSize = binaryBlockInstance->m_size;
	ANKI_ASSERT(variant.m_uniBlockSize > 0);

	// Initialize the block infos, active vars and bindings
	for(const MaterialVariable& var : m_vars)
	{
		if(var.m_constant)
		{
			for(const ShaderProgramResourceConstant& c : m_prog->getConstants())
			{
				if(c.m_name == var.m_name)
				{
					variant.m_activeVars.set(var.m_index, progVariant.isConstantActive(c));
					break;
				}
			}
		}
		else if(var.inBlock())
		{
			for(const ShaderProgramBinaryVariableInstance& instance : binaryBlockInstance->m_variables)
			{
				if(instance.m_index == var.m_indexInBinary)
				{
					variant.m_activeVars.set(var.m_index, true);
					variant.m_blockInfos[var.m_index] = instance.m_blockInfo;

					if(var.m_instanced)
					{
						variant.m_blockInfos[var.m_index].m_arraySize = I16(instanceCount);
					}
					else
					{
						break;
					}
				}
				else if(instance.m_index == var.m_indexInBinary2ndElement)
				{
					// Then we need to update the stride
					ANKI_ASSERT(variant.m_blockInfos[var.m_index].m_offset >= 0);

					const I16 stride = I16(instance.m_blockInfo.m_offset - variant.m_blockInfos[var.m_index].m_offset);
					ANKI_ASSERT(stride >= 4);
					variant.m_blockInfos[var.m_index].m_arrayStride = stride;
				}
			}
		}
		else
		{
			ANKI_ASSERT(var.isSampler() || var.isTexture());
			for(const ShaderProgramBinaryOpaqueInstance& instance : binaryVariant.m_opaques)
			{
				if(instance.m_index == var.m_indexInBinary)
				{
					variant.m_activeVars.set(var.m_index, true);
					variant.m_opaqueBindings[var.m_index] = I16(binary.m_opaques[instance.m_index].m_binding);
					break;
				}
			}
		}
	}

	// No make sure that the vars that are active
	for(const MaterialVariable& var : m_vars)
	{
		if(var.m_builtin == BuiltinMaterialVariableId::NONE && variant.m_activeVars.get(var.m_index)
			&& !var.valueSetByMaterial())
		{
			ANKI_RESOURCE_LOGF("An active variable doesn't have its value set by the material: %s", var.m_name.cstr());
		}

		ANKI_ASSERT(!(var.m_instanced && var.m_indexInBinary2ndElement == MAX_U32));
	}

// Debug print
#if 0
	printf("binary variant idx %u\n", U32(&binaryVariant - binary.m_variants.getBegin()));
	for(const MaterialVariable& var : m_vars)
	{

		printf("Var %s %s\n", var.m_name.cstr(), variant.m_activeVars.get(var.m_index) ? "active" : "inactive");
		if(var.inBlock() && variant.m_activeVars.get(var.m_index))
		{
			const ShaderVariableBlockInfo& inf = variant.m_blockInfos[var.m_index];
			printf("\tblockInfo %d,%d,%d,%d\n", inf.m_offset, inf.m_arraySize, inf.m_arrayStride, inf.m_matrixStride);
		}
	}
#endif
}

U32 MaterialResource::getInstanceGroupIdx(U32 instanceCount)
{
	ANKI_ASSERT(instanceCount > 0);
	instanceCount = nextPowerOfTwo(instanceCount);
	ANKI_ASSERT(instanceCount <= MAX_INSTANCES);
	return U32(std::log2(F32(instanceCount)));
}

} // end namespace anki
