Browse Source

Automatic instancing

Panagiotis Christopoulos Charitos 10 years ago
parent
commit
0ad569f397

+ 1 - 0
include/anki/core/Counters.h

@@ -26,6 +26,7 @@ enum class Counter
 	RENDERER_PPS_TIME,
 	RENDERER_SHADOW_PASSES,
 	RENDERER_LIGHTS_COUNT,
+	RENDERER_MERGED_DRAWCALLS,
 	SCENE_UPDATE_TIME,
 	SWAP_BUFFERS_TIME,
 	GL_CLIENT_WAIT_TIME,

+ 5 - 3
include/anki/misc/Xml.h

@@ -34,7 +34,8 @@ public:
 		, m_alloc(b.m_alloc)
 	{}
 
-	XmlElement(tinyxml2::XMLElement* el, GenericMemoryPoolAllocator<U8> alloc)
+	XmlElement(const tinyxml2::XMLElement* el,
+		GenericMemoryPoolAllocator<U8> alloc)
 		: m_el(el)
 		, m_alloc(alloc)
 	{}
@@ -92,7 +93,7 @@ public:
 	ANKI_USE_RESULT Error getSiblingElementsCount(U32& out) const;
 
 private:
-	tinyxml2::XMLElement* m_el;
+	const tinyxml2::XMLElement* m_el;
 	GenericMemoryPoolAllocator<U8> m_alloc;
 
 	ANKI_USE_RESULT Error check() const;
@@ -108,7 +109,8 @@ public:
 	ANKI_USE_RESULT Error parse(
 		const CString& xmlText, GenericMemoryPoolAllocator<U8> alloc);
 
-	ANKI_USE_RESULT Error getChildElement(const CString& name, XmlElement& out);
+	ANKI_USE_RESULT Error getChildElement(const CString& name,
+		XmlElement& out) const;
 
 private:
 	tinyxml2::XMLDocument m_doc;

+ 4 - 10
include/anki/renderer/Drawer.h

@@ -15,6 +15,7 @@ namespace anki {
 
 // Forward
 class Renderer;
+struct RenderContext;
 
 /// @addtogroup renderer
 /// @{
@@ -46,18 +47,11 @@ private:
 	Renderer* m_r;
 
 	void setupUniforms(
-		const VisibleNode& visibleNode,
+		RenderContext& ctx,
 		const RenderComponent& renderable,
-		const FrustumComponent& fr,
-		F32 flod,
-		CommandBufferPtr cmdb);
+		const RenderingKey& key);
 
-	ANKI_USE_RESULT Error renderSingle(
-		const FrustumComponent& fr,
-		RenderingStage stage,
-		Pass pass,
-		CommandBufferPtr cmdb,
-		const VisibleNode& visibleNode);
+	ANKI_USE_RESULT Error renderSingle(RenderContext& ctx);
 };
 /// @}
 

+ 7 - 1
include/anki/resource/Common.h

@@ -22,6 +22,13 @@ class ResourcePointer;
 /// @addtogroup resource
 /// @{
 
+/// @name Constants
+/// @{
+const U MAX_LODS = 3;
+const U MAX_INSTANCES = 64;
+const U MAX_INSTANCE_GROUPS = log2(MAX_INSTANCES) + 1;
+/// @}
+
 /// Deleter for ResourcePtr.
 template<typename T>
 class ResourcePtrDeleter
@@ -49,7 +56,6 @@ ANKI_FORWARD(TextureResource, TextureResourcePtr)
 ANKI_FORWARD(ShaderResource, ShaderResourcePtr)
 ANKI_FORWARD(Material, MaterialResourcePtr)
 ANKI_FORWARD(Mesh, MeshResourcePtr)
-ANKI_FORWARD(BucketMesh, BucketMeshResourcePtr)
 ANKI_FORWARD(Skeleton, SkeletonResourcePtr)
 ANKI_FORWARD(ParticleEmitterResource, ParticleEmitterResourcePtr)
 ANKI_FORWARD(Model, ModelResourcePtr)

+ 151 - 115
include/anki/resource/Material.h

@@ -7,6 +7,7 @@
 
 #include "anki/resource/ResourceObject.h"
 #include "anki/resource/ShaderResource.h"
+#include "anki/resource/TextureResource.h"
 #include "anki/resource/RenderingKey.h"
 #include "anki/Math.h"
 #include "anki/util/Visitor.h"
@@ -17,34 +18,47 @@ namespace anki {
 // Forward
 class XmlElement;
 class Material;
-class MaterialProgramCreator;
-class MaterialProgramCreatorInputVariable;
-
+class MaterialLoader;
+class MaterialLoaderInputVariable;
 template<typename T>
 class MaterialVariableTemplate;
-
 class MaterialVariable;
+class MaterialVariant;
 
 /// @addtogroup resource
 /// @{
 
-/// Material variable base. Its a visitable
-typedef VisitableCommonBase<
-	MaterialVariable,
+/// The ID of a buildin material variable
+enum class BuiltinMaterialVariableId: U8
+{
+	NONE = 0,
+	MVP_MATRIX,
+	MV_MATRIX,
+	VP_MATRIX,
+	NORMAL_MATRIX,
+	BILLBOARD_MVP_MATRIX,
+	MAX_TESS_LEVEL,
+	MS_DEPTH_MAP,
+	COUNT
+};
+
+/// Material variable base. It's a visitable.
+using MateriaVariableVisitable = VisitableCommonBase<
+	MaterialVariable, // The base
 	MaterialVariableTemplate<F32>,
 	MaterialVariableTemplate<Vec2>,
 	MaterialVariableTemplate<Vec3>,
 	MaterialVariableTemplate<Vec4>,
 	MaterialVariableTemplate<Mat3>,
 	MaterialVariableTemplate<Mat4>,
-	MaterialVariableTemplate<TextureResourcePtr>>
-	MateriaVariableVisitable;
+	MaterialVariableTemplate<TextureResourcePtr>>;
 
 /// Holds the shader variables. Its a container for shader program variables
 /// that share the same name
 class MaterialVariable: public MateriaVariableVisitable, public NonCopyable
 {
 	friend class Material;
+	friend class MaterialVariant;
 
 public:
 	using Base = MateriaVariableVisitable;
@@ -52,47 +66,30 @@ public:
 	MaterialVariable()
 	{}
 
-	virtual ~MaterialVariable();
-
-	virtual void destroy(ResourceAllocator<U8> alloc) = 0;
-
-	template<typename T>
-	const T* begin() const
-	{
-		ANKI_ASSERT(Base::isTypeOf<MaterialVariableTemplate<T>>());
-		auto derived = static_cast<const MaterialVariableTemplate<T>*>(this);
-		return derived->begin();
-	}
+	~MaterialVariable()
+	{}
 
-	template<typename T>
-	const T* end() const
+	/// Get the name.
+	CString getName() const
 	{
-		ANKI_ASSERT(Base::isTypeOf<MaterialVariableTemplate<T>>());
-		auto derived = static_cast<const MaterialVariableTemplate<T>*>(this);
-		return derived->end();
+		return m_name.toCString();
 	}
 
-	template<typename T>
-	const T& operator[](U idx) const
+	/// Get the builtin info.
+	BuiltinMaterialVariableId getBuiltin() const
 	{
-		ANKI_ASSERT(Base::isTypeOf<MaterialVariableTemplate<T>>());
-		auto derived = static_cast<const MaterialVariableTemplate<T>*>(this);
-		return (*derived)[idx];
+		return m_builtin;
 	}
 
-	/// Get the name of all the shader program variables
-	CString getName() const
+	U32 getTextureUnit() const
 	{
-		return m_name.toCString();
+		ANKI_ASSERT(m_textureUnit != -1);
+		return static_cast<U32>(m_textureUnit);
 	}
 
-	/// If false then it should be buildin
-	virtual Bool hasValues() const = 0;
-
-	U32 getArraySize() const
+	ShaderVariableDataType getShaderVariableType() const
 	{
-		ANKI_ASSERT(m_varBlkInfo.m_arraySize > 0);
-		return m_varBlkInfo.m_arraySize;
+		return m_varType;
 	}
 
 	Bool isInstanced() const
@@ -100,94 +97,111 @@ public:
 		return m_instanced;
 	}
 
-	U32 getTextureUnit() const
-	{
-		ANKI_ASSERT(m_textureUnit != -1);
-		return static_cast<U32>(m_textureUnit);
-	}
-
 	template<typename T>
 	void writeShaderBlockMemory(
+		const MaterialVariant& variant,
 		const T* elements,
 		U32 elementsCount,
 		void* buffBegin,
-		const void* buffEnd) const
-	{
-		ANKI_ASSERT(Base::isTypeOf<MaterialVariableTemplate<T>>());
-		ANKI_ASSERT(m_varType == getShaderVariableTypeFromTypename<T>());
-		anki::writeShaderBlockMemory(m_varType, m_varBlkInfo,
-			elements, elementsCount, buffBegin, buffEnd);
-	}
-
-	ShaderVariableDataType getShaderVariableType() const
-	{
-		return m_varType;
-	}
+		const void* buffEnd) const;
 
 protected:
+	U8 m_idx = MAX_U8; ///< Index in the Material::m_vars array.
 	ShaderVariableDataType m_varType = ShaderVariableDataType::NONE;
-	ShaderVariableBlockInfo m_varBlkInfo;
 	I16 m_textureUnit = -1;
 	String m_name;
-
+	BuiltinMaterialVariableId m_builtin = BuiltinMaterialVariableId::NONE;
 	Bool8 m_instanced = false;
+
+	/// Deallocate stuff.
+	void destroy(ResourceAllocator<U8> alloc)
+	{
+		m_name.destroy(alloc);
+	}
 };
 
 /// Material variable with data. A complete type
-template<typename TData>
+template<typename T>
 class MaterialVariableTemplate: public MaterialVariable
 {
+	friend class Material;
+	friend class MaterialVariant;
+
 public:
-	using Type = TData;
+	using Base = MaterialVariable;
+	using Type = T;
 
 	MaterialVariableTemplate()
 	{
-		setupVisitable(this);
+		Base::setupVisitable(this);
 	}
 
 	~MaterialVariableTemplate()
 	{}
 
-	void create(ResourceAllocator<U8> alloc,
-		const CString& name, const TData* x, U32 size);
-
-	void destroy(ResourceAllocator<U8> alloc)
+	const T& getValue() const
 	{
-		m_name.destroy(alloc);
-		m_data.destroy(alloc);
+		checkGetValue();
+		return m_value;
 	}
 
-	const TData* begin() const
+private:
+	T m_value;
+
+	void checkGetValue() const
 	{
-		ANKI_ASSERT(hasValues());
-		return m_data.begin();
+		ANKI_ASSERT(isTypeOf<MaterialVariableTemplate<T>>());
+		ANKI_ASSERT(
+			MaterialVariable::m_builtin == BuiltinMaterialVariableId::NONE);
 	}
-	const TData* end() const
+
+	ANKI_USE_RESULT Error init(U idx,
+		const MaterialLoaderInputVariable& in, Material& mtl);
+};
+
+/// Material variant.
+class MaterialVariant: public NonCopyable
+{
+	friend class Material;
+	friend class MaterialVariable;
+
+public:
+	MaterialVariant();
+
+	~MaterialVariant();
+
+	ShaderPtr getShader(ShaderType type) const
 	{
-		ANKI_ASSERT(hasValues());
-		return m_data.end();
+		return m_shaders[U(type)]->getGrShader();
 	}
 
-	const TData& operator[](U idx) const
+	U getDefaultBlockSize() const
 	{
-		ANKI_ASSERT(hasValues());
-		return m_data[idx];
+		ANKI_ASSERT(m_shaderBlockSize);
+		return m_shaderBlockSize;
 	}
 
-	/// Implements hasValues
-	Bool hasValues() const
+	/// Return true of the the variable is active.
+	Bool variableActive(const MaterialVariable& var) const
 	{
-		return m_data.getSize() > 0;
+		return m_varActive[var.m_idx];
 	}
 
-	static ANKI_USE_RESULT Error _newInstance(
-		const MaterialProgramCreatorInputVariable& in,
-		ResourceAllocator<U8> alloc,
-		TempResourceAllocator<U8> talloc,
-		MaterialVariable*& out);
-
 private:
-	DArray<TData> m_data;
+	/// All shaders except compute and geometry.
+	Array<ShaderResourcePtr, 5> m_shaders;
+	U32 m_shaderBlockSize = 0;
+	DArray<ShaderVariableBlockInfo> m_blockInfo;
+	DArray<Bool8> m_varActive;
+
+	ANKI_USE_RESULT Error init(const RenderingKey& key, Material& mtl,
+		MaterialLoader& loader);
+
+	void destroy(ResourceAllocator<U8> alloc)
+	{
+		m_blockInfo.destroy(alloc);
+		m_varActive.destroy(alloc);
+	}
 };
 
 /// Material resource.
@@ -217,8 +231,7 @@ private:
 ///							[a_series_of_numbers | path/to/image.ankitex]
 ///						</value>
 ///						[<const>0 | 1</const>] (3)
-///						[<instanced>0 | 1</instanced>]
-///						[<arraySize>N</<arraySize>]
+/// 					[<inShadow>0 | 1</inShadow>] (4)
 ///					</input>
 ///				</inputs>]
 ///
@@ -250,32 +263,24 @@ private:
 /// (2): The \<value\> can be left empty for build-in variables
 /// (3): The \<const\> will mark a variable as constant and it cannot be changed
 ///      at all. Default is 0
+/// (4): Optimization. Set to 1 if the var will be used in shadow passes as well
 class Material: public ResourceObject
 {
 	friend class MaterialVariable;
+	friend class MaterialVariant;
 
 public:
 	Material(ResourceManager* manager);
 
 	~Material();
 
-	// Variable accessors
-	const DArray<MaterialVariable*>& getVariables() const
-	{
-		return m_vars;
-	}
-
-	U32 getDefaultBlockSize() const
-	{
-		return m_shaderBlockSize;
-	}
-
 	/// Load a material file
 	ANKI_USE_RESULT Error load(const ResourceFilename& filename);
 
 	/// For sorting
 	Bool operator<(const Material& b) const
 	{
+		ANKI_ASSERT(m_hash != 0 && b.m_hash != 0);
 		return m_hash < b.m_hash;
 	}
 
@@ -299,37 +304,68 @@ public:
 		return m_forwardShading;
 	}
 
-	ShaderPtr getShader(const RenderingKey& key, ShaderType type) const;
+	Bool isInstanced() const
+	{
+		return m_instanced;
+	}
 
-	void fillResourceGroupInitializer(ResourceGroupInitializer& rcinit);
+	const MaterialVariant& getVariant(const RenderingKey& key) const;
 
-private:
-	DArray<MaterialVariable*> m_vars;
+	const DArray<MaterialVariable*>& getVariables() const
+	{
+		return m_vars;
+	}
 
-	/// [pass][lod][tess][shader]
-	Array4d<ShaderResourcePtr, U(Pass::COUNT), MAX_LODS, 2, 5> m_shaders;
+	void fillResourceGroupInitializer(ResourceGroupInitializer& rcinit);
 
-	U32 m_shaderBlockSize;
+	static U getInstanceGroupIdx(U instanceCount);
 
+private:
 	/// Used for sorting
-	U64 m_hash;
+	U64 m_hash = 0;
 
 	Bool8 m_shadow = true;
 	Bool8 m_tessellation = false;
 	Bool8 m_forwardShading = false;
 	U8 m_lodCount = 1;
+	Bool8 m_instanced = false;
+
+	DArray<MaterialVariant> m_variants;
+
+	/// This is a matrix of variants. It holds indices to m_variants. If the
+	/// idx is MAX_U16 then the variant is not present
+	Array4d<U16, U(Pass::COUNT), MAX_LODS, 2, MAX_INSTANCE_GROUPS>
+		m_variantMatrix;
+
+	DArray<MaterialVariable*> m_vars;
 
-	/// Parse what is within the @code <material></material> @endcode
-	ANKI_USE_RESULT Error parseMaterialTag(const XmlElement& el);
+	/// Populate the m_varNames.
+	ANKI_USE_RESULT Error createVars(const MaterialLoader& loader);
+
+	/// Create the variants.
+	ANKI_USE_RESULT Error createVariants(MaterialLoader& loader);
 
 	/// Create a unique shader source in chache. If already exists do nothing
 	ANKI_USE_RESULT Error createProgramSourceToCache(
-		const StringAuto& source, StringAuto& out);
-
-	/// Read all shader programs and pupulate the @a vars and @a nameToVar
-	/// containers
-	ANKI_USE_RESULT Error populateVariables(const MaterialProgramCreator& mspc);
+		const String& source, StringAuto& out);
 };
+
+//==============================================================================
+template<typename T>
+inline void MaterialVariable::writeShaderBlockMemory(
+	const MaterialVariant& variant,
+	const T* elements,
+	U32 elementsCount,
+	void* buffBegin,
+	const void* buffEnd) const
+{
+	ANKI_ASSERT(Base::isTypeOf<MaterialVariableTemplate<T>>());
+	ANKI_ASSERT(m_varType == getShaderVariableTypeFromTypename<T>());
+
+	const auto& blockInfo = variant.m_blockInfo[m_idx];
+	anki::writeShaderBlockMemory(m_varType, blockInfo, elements, elementsCount,
+		buffBegin, buffEnd);
+}
 /// @}
 
 } // end namespace anki

+ 68 - 47
include/anki/resource/MaterialProgramCreator.h → include/anki/resource/MaterialLoader.h

@@ -8,6 +8,7 @@
 #include "anki/util/StringList.h"
 #include "anki/Gr.h"
 #include "anki/resource/Common.h"
+#include "anki/resource/Material.h"
 
 namespace anki {
 
@@ -16,7 +17,7 @@ class XmlElement;
 
 /// Material loader variable. It's the information on whatever is inside
 /// \<input\>
-class MaterialProgramCreatorInputVariable: public NonCopyable
+class MaterialLoaderInputVariable: public NonCopyable
 {
 public:
 	TempResourceAllocator<U8> m_alloc;
@@ -25,8 +26,8 @@ public:
 	ShaderVariableDataType m_type = ShaderVariableDataType::NONE;
 	StringList m_value;
 	Bool8 m_constant = false;
-	U16 m_arraySize = 0;
 	Bool8 m_instanced = false;
+	BuiltinMaterialVariableId m_builtin = BuiltinMaterialVariableId::NONE;
 
 	String m_line;
 	ShaderTypeBit m_shaderDefinedMask = ShaderTypeBit::NONE; ///< Defined in
@@ -35,21 +36,21 @@ public:
 	Bool8 m_inBlock = true;
 
 	I16 m_binding = -1; ///< Texture unit
-	I32 m_offset = -1; ///< Offset inside the UBO
-	I32 m_arrayStride = -1;
-	/// Identifying the stride between columns of a column-major matrix or
-	/// rows of a row-major matrix
-	I32 m_matrixStride = -1;
+	U16 m_index = 666;
 
-	MaterialProgramCreatorInputVariable()
+	ShaderVariableBlockInfo m_blockInfo;
+
+	Bool8 m_inShadow = true;
+
+	MaterialLoaderInputVariable()
 	{}
 
-	MaterialProgramCreatorInputVariable(MaterialProgramCreatorInputVariable&& b)
+	MaterialLoaderInputVariable(MaterialLoaderInputVariable&& b)
 	{
 		move(b);
 	}
 
-	~MaterialProgramCreatorInputVariable()
+	~MaterialLoaderInputVariable()
 	{
 		m_name.destroy(m_alloc);
 		m_typeStr.destroy(m_alloc);
@@ -57,78 +58,70 @@ public:
 		m_line.destroy(m_alloc);
 	}
 
-	MaterialProgramCreatorInputVariable& operator=(
-		MaterialProgramCreatorInputVariable&& b)
+	MaterialLoaderInputVariable& operator=(
+		MaterialLoaderInputVariable&& b)
 	{
 		move(b);
 		return *this;
 	}
 
-	void move(MaterialProgramCreatorInputVariable& b)
-	{
-		m_alloc = std::move(b.m_alloc);
-		m_name = std::move(b.m_name);
-		m_type = b.m_type;
-		m_typeStr = std::move(b.m_typeStr);
-		m_value = std::move(b.m_value);
-		m_constant = b.m_constant;
-		m_arraySize = b.m_arraySize;
-		m_instanced = b.m_instanced;
-		m_line = std::move(b.m_line);
-		m_shaderDefinedMask = b.m_shaderDefinedMask;
-		m_shaderReferencedMask = b.m_shaderReferencedMask;
-		m_inBlock = b.m_inBlock;
-		m_binding = b.m_binding;
-		m_offset = b.m_offset;
-		m_arrayStride = b.m_arrayStride;
-		m_matrixStride = b.m_matrixStride;
-	}
+	void move(MaterialLoaderInputVariable& b);
 
-	Bool duplicate(const MaterialProgramCreatorInputVariable& b) const
+	Bool duplicate(const MaterialLoaderInputVariable& b) const
 	{
 		return b.m_name == m_name
 			&& b.m_type == m_type
 			&& b.m_value == m_value
 			&& b.m_constant == m_constant
-			&& b.m_arraySize == m_arraySize
-			&& b.m_instanced == m_instanced;
+			&& b.m_instanced == m_instanced
+			&& b.m_builtin == m_builtin
+			&& b.m_inShadow == m_inShadow;
 	}
 };
 
 /// Creator of shader programs. This class parses between
-/// @code <shaderProgams></shaderPrograms> @endcode located inside a
 /// @code <material></material> @endcode and creates the source of a custom
 /// program.
 ///
 /// @note Be carefull when you change the methods. Some change may create more
 ///       unique shaders and this is never good.
-class MaterialProgramCreator
+class MaterialLoader
 {
 public:
-	using Input = MaterialProgramCreatorInputVariable;
+	using Input = MaterialLoaderInputVariable;
 
-	explicit MaterialProgramCreator(TempResourceAllocator<U8> alloc);
+	explicit MaterialLoader(TempResourceAllocator<U8> alloc);
 
-	~MaterialProgramCreator();
+	~MaterialLoader();
 
-	/// Parse what is within the
-	/// @code <programs></programs> @endcode
-	ANKI_USE_RESULT Error parseProgramsTag(const XmlElement& el);
+	ANKI_USE_RESULT Error parseXmlDocument(const XmlDocument& doc);
 
-	/// Get the shader program source code
-	const String& getProgramSource(ShaderType shaderType_) const
+	/// Get the shader source code
+	const String& getShaderSource(ShaderType shaderType_) const
 	{
 		U shaderType = enumToType(shaderType_);
 		ANKI_ASSERT(!m_sourceBaked[shaderType].isEmpty());
 		return m_sourceBaked[shaderType];
 	}
 
-	const List<Input>& getInputVariables() const
+	/// After parsing the program mutate the sources for a specific use case.
+	void mutate(const RenderingKey& key);
+
+	/// Iterate all the variables.
+	template<typename TFunc>
+	ANKI_USE_RESULT Error iterateAllInputVariables(TFunc func) const
 	{
-		return m_inputs;
+		for(const Input& in : m_inputs)
+		{
+			if(!in.m_constant)
+			{
+				ANKI_CHECK(func(in));
+			}
+		}
+		return ErrorCode::NONE;
 	}
 
-	Bool hasTessellation() const
+	Bool getTessellationEnabled() const
 	{
 		return m_tessellation;
 	}
@@ -138,6 +131,26 @@ public:
 		return m_blockSize;
 	}
 
+	Bool getInstancingEnabled() const
+	{
+		return m_instanced;
+	}
+
+	U getLodCount() const
+	{
+		return m_lodCount;
+	}
+
+	Bool getShadowEnabled() const
+	{
+		return m_shadow;
+	}
+
+	Bool isForwardShading() const
+	{
+		return m_forwardShading;
+	}
+
 private:
 	TempResourceAllocator<char> m_alloc;
 	Array<StringList, 5> m_source; ///< Shader program final source
@@ -150,6 +163,14 @@ private:
 	U32 m_texBinding = 0;
 	ShaderTypeBit m_instanceIdMask = ShaderTypeBit::NONE;
 	Bool8 m_tessellation = false;
+	U8 m_lodCount = 0;
+	Bool8 m_shadow = false;
+	Bool8 m_forwardShading = false;
+	U32 m_nextIndex = 0;
+
+	/// Parse what is within the
+	/// @code <programs></programs> @endcode
+	ANKI_USE_RESULT Error parseProgramsTag(const XmlElement& el);
 
 	/// Parse what is within the
 	/// @code <program></program> @endcode

+ 6 - 7
include/anki/resource/Model.h

@@ -79,9 +79,11 @@ private:
 	Array<MeshResourcePtr, MAX_LODS> m_meshes; ///< One for each LOD
 	U8 m_meshCount = 0;
 	MaterialResourcePtr m_mtl;
-	mutable SpinLock m_lock;
 
-	mutable Array3d<PipelinePtr, U(Pass::COUNT), MAX_LODS, 2> m_pplines;
+	mutable Array4d<PipelinePtr, U(Pass::COUNT), MAX_LODS, 2,
+		MAX_INSTANCE_GROUPS> m_pplines;
+	mutable SpinLock m_lock; ///< Protect m_pplines
+
 	Array<ResourceGroupPtr, MAX_LODS> m_grResources;
 
 	/// Return the maximum number of LODs
@@ -101,12 +103,9 @@ private:
 /// <model>
 /// 	<modelPatches>
 /// 		<modelPatch>
-/// 			[<mesh>path/to/mesh.mesh</mesh>
+/// 			<mesh>path/to/mesh.mesh</mesh>
 ///				[<mesh1>path/to/mesh_lod_1.mesh</mesh1>]
-///				[<mesh2>path/to/mesh_lod_2.mesh</mesh2>]] |
-/// 			[<bucketMesh>path/to/mesh.bmesh</bucketMesh>
-///				[<bucketMesh1>path/to/mesh_lod_1.bmesh</bucketMesh1>]
-///				[<bucketMesh2>path/to/mesh_lod_2.bmesh</bucketMesh2>]]
+///				[<mesh2>path/to/mesh_lod_2.mesh</mesh2>]
 /// 			<material>path/to/material.mtl</material>
 /// 		</modelPatch>
 /// 		...

+ 14 - 10
include/anki/resource/RenderingKey.h

@@ -5,8 +5,7 @@
 
 #pragma once
 
-#include "anki/util/StdTypes.h"
-#include "anki/util/Assert.h"
+#include "anki/resource/Common.h"
 
 namespace anki {
 
@@ -18,8 +17,6 @@ enum class Pass: U8
 	COUNT
 };
 
-const U MAX_LODS = 3;
-
 /// A key that consistst of the rendering pass and the level of detail
 class RenderingKey
 {
@@ -27,19 +24,24 @@ public:
 	Pass m_pass;
 	U8 m_lod;
 	Bool8 m_tessellation;
+	U8 m_instanceCount;
 
-	explicit RenderingKey(Pass pass, U8 lod, Bool tessellation)
+	RenderingKey(Pass pass, U8 lod, Bool tessellation, U instanceCount)
 		: m_pass(pass)
 		, m_lod(lod)
 		, m_tessellation(tessellation)
-	{}
+		, m_instanceCount(instanceCount)
+	{
+		ANKI_ASSERT(m_instanceCount <= MAX_INSTANCES && m_instanceCount != 0);
+		ANKI_ASSERT(m_lod <= MAX_LODS);
+	}
 
 	RenderingKey()
-		: RenderingKey(Pass::MS_FS, 0, false)
+		: RenderingKey(Pass::MS_FS, 0, false, 1)
 	{}
 
 	RenderingKey(const RenderingKey& b)
-		: RenderingKey(b.m_pass, b.m_lod, b.m_tessellation)
+		: RenderingKey(b.m_pass, b.m_lod, b.m_tessellation, b.m_instanceCount)
 	{}
 };
 
@@ -49,7 +51,8 @@ class RenderingKeyHasher
 public:
 	PtrSize operator()(const RenderingKey& key) const
 	{
-		return U8(key.m_pass) | (key.m_lod << 8) | (key.m_tessellation << 16);
+		return U8(key.m_pass) | (key.m_lod << 8) | (key.m_tessellation << 16)
+			| (key.m_instanceCount << 24);
 	}
 };
 
@@ -60,7 +63,8 @@ public:
 	Bool operator()(const RenderingKey& a, const RenderingKey& b) const
 	{
 		return a.m_pass == b.m_pass && a.m_lod == b.m_lod
-			&& a.m_tessellation == b.m_tessellation;
+			&& a.m_tessellation == b.m_tessellation
+			&& a.m_instanceCount == b.m_instanceCount;
 	}
 };
 

+ 18 - 21
include/anki/resource/ResourceManager.h

@@ -28,7 +28,8 @@ class Renderer;
 template<typename Type>
 class TypeResourceManager
 {
-public:
+protected:
+	/// @privatesection
 	using Container = List<Type*>;
 
 	TypeResourceManager()
@@ -40,8 +41,6 @@ public:
 		m_ptrs.destroy(m_alloc);
 	}
 
-	/// @privatesection
-	/// @{
 	Type* findLoadedResource(const CString& filename)
 	{
 		auto it = find(filename);
@@ -51,19 +50,17 @@ public:
 	void registerResource(Type* ptr)
 	{
 		ANKI_ASSERT(ptr->getRefcount().load() == 0);
-		ANKI_ASSERT(find(ptr->getUuid().toCString()) == m_ptrs.getEnd());
+		ANKI_ASSERT(find(ptr->getFilename()) == m_ptrs.getEnd());
 		m_ptrs.pushBack(m_alloc, ptr);
 	}
 
 	void unregisterResource(Type* ptr)
 	{
-		auto it = find(ptr->getUuid().toCString());
+		auto it = find(ptr->getFilename());
 		ANKI_ASSERT(it != m_ptrs.end());
 		m_ptrs.erase(m_alloc, it);
 	}
-	/// @}
 
-protected:
 	void init(ResourceAllocator<U8> alloc)
 	{
 		m_alloc = alloc;
@@ -79,7 +76,7 @@ private:
 
 		for(it = m_ptrs.getBegin(); it != m_ptrs.getEnd(); ++it)
 		{
-			if((*it)->getUuid() == filename)
+			if((*it)->getFilename() == filename)
 			{
 				break;
 			}
@@ -91,19 +88,18 @@ private:
 
 /// Resource manager. It holds a few global variables
 class ResourceManager:
-	TypeResourceManager<Animation>,
-	TypeResourceManager<TextureResource>,
-	TypeResourceManager<ShaderResource>,
-	TypeResourceManager<Material>,
-	TypeResourceManager<Mesh>,
-	TypeResourceManager<BucketMesh>,
-	TypeResourceManager<Skeleton>,
-	TypeResourceManager<ParticleEmitterResource>,
-	TypeResourceManager<Model>,
-	TypeResourceManager<Script>,
-	TypeResourceManager<DummyRsrc>,
-	TypeResourceManager<CollisionResource>,
-	TypeResourceManager<GenericResource>
+	public TypeResourceManager<Animation>,
+	public TypeResourceManager<TextureResource>,
+	public TypeResourceManager<ShaderResource>,
+	public TypeResourceManager<Material>,
+	public TypeResourceManager<Mesh>,
+	public TypeResourceManager<Skeleton>,
+	public TypeResourceManager<ParticleEmitterResource>,
+	public TypeResourceManager<Model>,
+	public TypeResourceManager<Script>,
+	public TypeResourceManager<DummyRsrc>,
+	public TypeResourceManager<CollisionResource>,
+	public TypeResourceManager<GenericResource>
 {
 	template<typename T>
 	friend class ResourcePtrDeleter;
@@ -239,6 +235,7 @@ private:
 	U32 m_textureAnisotropy;
 	String m_shadersPrependedSource;
 	AsyncLoader* m_asyncLoader = nullptr; ///< Async loading thread
+	U64 m_uuid = 0;
 };
 /// @}
 

+ 2 - 1
include/anki/resource/ResourceManager.inl.h

@@ -48,7 +48,8 @@ Error ResourceManager::loadResource(
 				&& "Forgot to deallocate");
 		}
 
-		ptr->setUuid(filename);
+		ptr->setFilename(filename);
+		ptr->setUuid(++m_uuid);
 
 		// Reset the memory pool if no-one is using it.
 		// NOTE: Check because resources load other resources

+ 24 - 9
include/anki/resource/ResourceObject.h

@@ -21,13 +21,14 @@ class XmlDocument;
 /// The base of all resource objects.
 class ResourceObject
 {
+	friend class ResourceManager;
+
 public:
 	ResourceObject(ResourceManager* manager);
 
 	virtual ~ResourceObject();
 
 	/// @privatesection
-	/// @{
 	ResourceManager& getManager()
 	{
 		return *m_manager;
@@ -46,16 +47,30 @@ public:
 		return m_refcount;
 	}
 
-	const String& getUuid() const
+	CString getFilename() const
 	{
-		ANKI_ASSERT(!m_uuid.isEmpty());
-		return m_uuid;
+		ANKI_ASSERT(!m_fname.isEmpty());
+		return m_fname.toCString();
+	}
+
+anki_internal:
+	void setFilename(const CString& fname)
+	{
+		ANKI_ASSERT(m_fname.isEmpty());
+		m_fname.create(getAllocator(), fname);
 	}
 
-	void setUuid(const CString& uuid)
+	void setUuid(U64 uuid)
 	{
-		ANKI_ASSERT(m_uuid.isEmpty());
-		m_uuid.create(getAllocator(), uuid);
+		ANKI_ASSERT(uuid > 0);
+		m_uuid = uuid;
+	}
+
+	/// To check if 2 resource pointers are actually the same.
+	U64 getUuid() const
+	{
+		ANKI_ASSERT(m_uuid > 0);
+		return m_uuid;
 	}
 
 	ANKI_USE_RESULT Error openFile(
@@ -69,12 +84,12 @@ public:
 	ANKI_USE_RESULT Error openFileParseXml(
 		const ResourceFilename& filename,
 		XmlDocument& xml);
-	/// @}
 
 private:
 	ResourceManager* m_manager;
 	Atomic<I32> m_refcount;
-	String m_uuid; ///< Unique resource name.
+	String m_fname; ///< Unique resource name.
+	U64 m_uuid = 0;
 };
 /// @}
 

+ 3 - 15
include/anki/scene/ModelNode.h

@@ -36,17 +36,10 @@ public:
 		const CString& name, const ModelPatch* modelPatch);
 
 private:
-	Obb m_obb; ///< In world space
-	const ModelPatch* m_modelPatch; ///< The resource
-	DArray<ObbSpatialComponent*> m_spatials;
+	Obb m_obb; ///< In world space. ModelNode will update it.
+	const ModelPatch* m_modelPatch = nullptr; ///< The resource
 
-	void updateInstanceSpatials(
-		const MoveComponent* instanceMoves[],
-		U32 instanceMovesCount);
-
-	ANKI_USE_RESULT Error buildRendering(RenderingBuildData& data) const;
-
-	void getRenderWorldTransform(U index, Transform& trf) const;
+	ANKI_USE_RESULT Error buildRendering(RenderingBuildInfo& data) const;
 };
 
 /// The model scene node
@@ -68,14 +61,9 @@ public:
 		return *m_model;
 	}
 
-	/// Override SceneNode::frameUpdate
-	ANKI_USE_RESULT Error frameUpdate(F32, F32) override;
-
 private:
 	ModelResourcePtr m_model; ///< The resource
 	DArray<ModelPatchNode*> m_modelPatches;
-	DArray<Transform> m_transforms; ///< Cache the transforms of instances
-	Timestamp m_transformsTimestamp;
 
 	void onMoveComponentUpdate(MoveComponent& move);
 };

+ 1 - 6
include/anki/scene/ParticleEmitter.h

@@ -180,8 +180,6 @@ private:
 	DArray<ParticleBase*> m_particles;
 	F32 m_timeLeftForNextEmission = 0.0;
 	Obb m_obb;
-	DArray<Transform> m_transforms; ///< InstanceTransforms
-	Timestamp m_transformsTimestamp = 0;
 
 	// Opt: We dont have to make extra calculations if the ParticleEmitter's
 	// rotation is the identity
@@ -201,10 +199,7 @@ private:
 	void createParticlesSimulation(SceneGraph* scene);
 	void createParticlesSimpleSimulation();
 
-	ANKI_USE_RESULT Error doInstancingCalcs();
-
-	ANKI_USE_RESULT Error buildRendering(RenderingBuildData& data) const;
-	void getRenderWorldTransform(U index, Transform& trf) const;
+	ANKI_USE_RESULT Error buildRendering(RenderingBuildInfo& data) const;
 
 	void onMoveComponentUpdate(MoveComponent& move);
 };

+ 37 - 140
include/anki/scene/RenderComponent.h

@@ -15,97 +15,48 @@ namespace anki {
 /// @addtogroup scene
 /// @{
 
-/// The ID of a buildin material variable
-enum class BuildinMaterialVariableId: U8
-{
-	NO_BUILDIN = 0,
-	MVP_MATRIX,
-	MV_MATRIX,
-	VP_MATRIX,
-	NORMAL_MATRIX,
-	BILLBOARD_MVP_MATRIX,
-	MAX_TESS_LEVEL,
-	BLURRING,
-	MS_DEPTH_MAP,
-	COUNT
-};
-
 // Forward
 class RenderComponentVariable;
 
 template<typename T>
 class RenderComponentVariableTemplate;
 
-/// RenderComponent variable base. Its a visitable
-typedef VisitableCommonBase<
-	RenderComponentVariable, //< The base
+/// RenderComponent variable base. It's a visitable.
+using RenderComponentVariableVisitable = VisitableCommonBase<
+	RenderComponentVariable, // The base
 	RenderComponentVariableTemplate<F32>,
 	RenderComponentVariableTemplate<Vec2>,
 	RenderComponentVariableTemplate<Vec3>,
 	RenderComponentVariableTemplate<Vec4>,
 	RenderComponentVariableTemplate<Mat3>,
 	RenderComponentVariableTemplate<Mat4>,
-	RenderComponentVariableTemplate<TextureResourcePtr>>
-	RenderComponentVariableVisitable;
+	RenderComponentVariableTemplate<TextureResourcePtr>>;
 
 /// A wrapper on top of MaterialVariable
 class RenderComponentVariable: public RenderComponentVariableVisitable
 {
 public:
-	typedef RenderComponentVariableVisitable Base;
+	using Base = RenderComponentVariableVisitable;
 
 	RenderComponentVariable(const MaterialVariable* mvar);
 	virtual ~RenderComponentVariable();
 
-	BuildinMaterialVariableId getBuildinId() const
-	{
-		return m_buildinId;
-	}
-
-	CString getName() const
-	{
-		return m_mvar->getName();
-	}
-
-	template<typename T>
-	const T* begin() const
-	{
-		ANKI_ASSERT(Base::isTypeOf<RenderComponentVariableTemplate<T>>());
-		auto derived =
-			static_cast<const RenderComponentVariableTemplate<T>*>(this);
-		return derived->begin();
-	}
-
+	/// This will trigger copy on write
 	template<typename T>
-	const T* end() const
+	void setValue(const T& value)
 	{
 		ANKI_ASSERT(Base::isTypeOf<RenderComponentVariableTemplate<T>>());
-		auto derived =
-			static_cast<const RenderComponentVariableTemplate<T>*>(this);
-		return derived->end();
+		auto derived = static_cast<RenderComponentVariableTemplate<T>*>(this);
+		derived->setValue(value);
 	}
 
 	template<typename T>
-	const T& operator[](U idx) const
+	const T& getValue() const
 	{
 		ANKI_ASSERT(Base::isTypeOf<RenderComponentVariableTemplate<T>>());
 		auto derived =
 			static_cast<const RenderComponentVariableTemplate<T>*>(this);
-		return (*derived)[idx];
-	}
-
-	/// This will trigger copy on write
-	template<typename T>
-	void setValues(const T* values, U32 size, SceneAllocator<U8> alloc)
-	{
-		ANKI_ASSERT(Base::isTypeOf<RenderComponentVariableTemplate<T>>());
-		auto derived = static_cast<RenderComponentVariableTemplate<T>*>(this);
-		derived->setValues(values, size, alloc);
-	}
-
-	U32 getArraySize() const
-	{
-		return m_mvar->getArraySize();
+		return derived->getValue();
 	}
 
 	const MaterialVariable& getMaterialVariable() const
@@ -113,19 +64,8 @@ public:
 		return *m_mvar;
 	}
 
-	Bool isInstanced() const
-	{
-		return m_mvar->isInstanced();
-	}
-
-	/// A custom cleanup method
-	virtual void destroy(SceneAllocator<U8> alloc) = 0;
-
 protected:
 	const MaterialVariable* m_mvar = nullptr;
-
-private:
-	BuildinMaterialVariableId m_buildinId;
 };
 
 /// RenderComponent variable. This class should not be visible to other
@@ -134,7 +74,8 @@ template<typename T>
 class RenderComponentVariableTemplate: public RenderComponentVariable
 {
 public:
-	typedef T Type;
+	using Base = RenderComponentVariable;
+	using Type = T;
 
 	RenderComponentVariableTemplate(const MaterialVariable* mvar)
 		: RenderComponentVariable(mvar)
@@ -143,72 +84,28 @@ public:
 	}
 
 	~RenderComponentVariableTemplate()
-	{
-		ANKI_ASSERT(m_copy == nullptr && "Forgot to delete");
-	}
+	{}
 
-	const T* begin() const
+	void setValue(const T& value)
 	{
-		ANKI_ASSERT((m_mvar->hasValues() || m_copy != nullptr)
-			&& "Variable does not have any values");
-		return (m_copy) ? m_copy : m_mvar->template begin<T>();
+		ANKI_ASSERT(isTypeOf<RenderComponentVariableTemplate<T>>());
+		ANKI_ASSERT(Base::getMaterialVariable().getBuiltin()
+			== BuiltinMaterialVariableId::NONE);
+		m_copy = value;
 	}
 
-	const T* end() const
+	const T& getValue() const
 	{
-		ANKI_ASSERT((m_mvar->hasValues() || m_copy != nullptr)
-			&& "Variable does not have any values");
-		return (m_copy) ? (m_copy + getArraySize()) : m_mvar->template end<T>();
-	}
-
-	const T& operator[](U idx) const
-	{
-		ANKI_ASSERT((m_mvar->hasValues() || m_copy != nullptr)
-			&& "Variable does not have any values");
-		ANKI_ASSERT(idx < getArraySize());
-
-		// NOTE Working on GCC is wrong
-		if(m_copy)
-		{
-			return m_copy[idx];
-		}
-		else
-		{
-			return m_mvar->template operator[]<T>(idx);
-		}
-	}
-
-	void setValues(const T* values, U32 size, SceneAllocator<U8> alloc)
-	{
-		ANKI_ASSERT(size <= m_mvar->getArraySize());
-		if(m_copy == nullptr)
-		{
-			m_copy = alloc.newArray<T>(getArraySize());
-		}
-
-		for(U i = 0; i < size; i++)
-		{
-			m_copy[i] = values[i];
-		}
-	}
-
-	/// Call that manually
-	void destroy(SceneAllocator<U8> alloc)
-	{
-		if(m_copy)
-		{
-			alloc.deleteArray(m_copy, getArraySize());
-			m_copy = nullptr;
-		}
+		return m_copy;
 	}
 
 private:
-	T* m_copy = nullptr; ///< Copy of the data
+	T m_copy; ///< Copy of the data
 };
 
 /// Rendering data input and output. This is a structure because we don't want
 /// to change what buildRendering accepts all the time
-class RenderingBuildData
+class RenderingBuildInfo
 {
 public:
 	RenderingKey m_key;
@@ -228,8 +125,7 @@ public:
 		return c.getType() == Type::RENDER;
 	}
 
-	/// @param node Pass node to steal it's allocator
-	RenderComponent(SceneNode* node, const Material* mtl);
+	RenderComponent(SceneNode* node, const Material* mtl, U64 hash = 0);
 
 	~RenderComponent();
 
@@ -259,7 +155,7 @@ public:
 	/// Given an array of submeshes that are visible append jobs to the GL
 	/// job chain
 	virtual ANKI_USE_RESULT Error buildRendering(
-		RenderingBuildData& data) const = 0;
+		RenderingBuildInfo& data) const = 0;
 
 	/// Access the material
 	const Material& getMaterial() const
@@ -269,21 +165,13 @@ public:
 	}
 
 	/// Information for movables. It's actualy an array of transformations.
-	/// @param index The index of the transform to get
-	/// @param[out] trf The transform to set
-	virtual void getRenderWorldTransform(U index, Transform& trf) const
+	virtual void getRenderWorldTransform(Bool& hasWorldTransforms,
+		Transform& trf) const
 	{
-		ANKI_ASSERT(getHasWorldTransforms());
-		(void)index;
+		hasWorldTransforms = false;
 		(void)trf;
 	}
 
-	/// Return true if the renderable has world transforms
-	virtual Bool getHasWorldTransforms() const
-	{
-		return false;
-	}
-
 	Bool getCastsShadow() const
 	{
 		const Material& mtl = getMaterial();
@@ -304,9 +192,18 @@ public:
 		return err;
 	}
 
+	Bool canMergeDrawcalls(const RenderComponent& b) const
+	{
+		return m_mtl->isInstanced() && m_hash != 0 && m_hash == b.m_hash;
+	}
+
 private:
 	Variables m_vars;
 	const Material* m_mtl;
+
+	/// If 2 components have the same hash the renderer may potentially try
+	/// to merge them.
+	U64 m_hash = 0;
 };
 /// @}
 

+ 1 - 1
include/anki/scene/StaticGeometryNode.h

@@ -31,7 +31,7 @@ public:
 private:
 	const ModelPatch* m_modelPatch;
 
-	ANKI_USE_RESULT Error buildRendering(RenderingBuildData& data) const;
+	ANKI_USE_RESULT Error buildRendering(RenderingBuildInfo& data) const;
 };
 
 /// Static geometry scene node

+ 7 - 0
include/anki/util/StdTypes.h

@@ -170,6 +170,13 @@ private:
 			return ErrorCode::OUT_OF_MEMORY; \
 		} \
 	} while(0)
+
+/// Macro to nuliffy a pointer on debug builds.
+#if ANKI_DEBUG == 1
+#	define ANKI_DBG_NULLIFY_PTR = nullptr
+#else
+#	define ANKI_DBG_NULLIFY_PTR
+#endif
 /// @}
 
 } // end namespace anki

+ 2 - 2
shaders/FsCommonFrag.glsl

@@ -177,7 +177,7 @@ vec3 computeLightColor(vec3 diffCol)
 		vec3 frag2Light = light.posRadius.xyz - fragPos;
 		float att = computeAttenuationFactor(light.posRadius.w, frag2Light);
 
-#if LOD > 0
+#if LOD > 1
 		const float shadow = 1.0;
 #else
 		float shadow = 1.0;
@@ -211,7 +211,7 @@ vec3 computeLightColor(vec3 diffCol)
 			light.outerCosInnerCos.y,
 			light.lightDir.xyz);
 
-#if LOD > 0
+#if LOD > 1
 		const float shadow = 1.0;
 #else
 		float shadow = 1.0;

+ 2 - 0
shaders/Pps.frag.glsl

@@ -152,7 +152,9 @@ void main()
 
 	out_color = tonemap(out_color, u_luminance.averageLuminancePad3.x, 0.0);
 
+#if 1
 	out_color = fog(out_color, in_uv);
+#endif
 
 #if BLOOM_ENABLED
 	vec3 bloom = textureLod(u_ppsBloomLfRt, in_uv, 0.0).rgb;

+ 2 - 1
src/core/Counters.cpp

@@ -39,6 +39,7 @@ static const Array<CounterInfo, (U)Counter::COUNT> cinfo = {{
 	{"RENDERER_PPS_TIME", CF_PER_FRAME | CF_PER_RUN | CF_F64},
 	{"RENDERER_SHADOW_PASSES", CF_PER_FRAME | CF_PER_RUN | CF_U64},
 	{"RENDERER_LIGHTS_COUNT", CF_PER_FRAME | CF_PER_RUN | CF_U64},
+	{"RENDERER_MERGED_DRAWCALLS", CF_PER_FRAME | CF_PER_RUN | CF_U64},
 	{"SCENE_UPDATE_TIME", CF_PER_RUN | CF_F64},
 	{"SWAP_BUFFERS_TIME", CF_PER_RUN | CF_F64},
 	{"GL_CLIENT_WAIT_TIME", CF_PER_FRAME | CF_PER_RUN | CF_F64},
@@ -49,7 +50,7 @@ static const Array<CounterInfo, (U)Counter::COUNT> cinfo = {{
 	{"GL_CLIENT_BUFFERS_SIZE", CF_PER_FRAME | CF_PER_RUN | CF_U64}
 }};
 
-#define MAX_NAME "24"
+#define MAX_NAME "25"
 
 //==============================================================================
 Error CountersManager::create(

+ 2 - 2
src/misc/Xml.cpp

@@ -279,7 +279,7 @@ Error XmlElement::getSiblingElementsCount(U32& out) const
 	Error err = check();
 	if(!err)
 	{
-		tinyxml2::XMLElement* el = m_el;
+		const tinyxml2::XMLElement* el = m_el;
 
 		I count = -1;
 		do
@@ -337,7 +337,7 @@ Error XmlDocument::parse(
 
 //==============================================================================
 ANKI_USE_RESULT Error XmlDocument::getChildElement(
-	const CString& name, XmlElement& out)
+	const CString& name, XmlElement& out) const
 {
 	Error err = ErrorCode::NONE;
 	out = XmlElement(m_doc.FirstChildElement(&name[0]), m_alloc);

+ 205 - 202
src/renderer/Drawer.cpp

@@ -22,30 +22,36 @@ namespace anki {
 //==============================================================================
 // Misc                                                                        =
 //==============================================================================
-static const U MATERIAL_BLOCK_MAX_SIZE = 1024 * 4;
+
+/// Common stuff to pass to renderSingle
+struct RenderContext
+{
+	FrustumComponent* m_frc ANKI_DBG_NULLIFY_PTR;
+	RenderingStage m_stage;
+	Pass m_pass;
+	CommandBufferPtr m_cmdb;
+	const VisibleNode* m_visibleNode ANKI_DBG_NULLIFY_PTR;
+	const VisibleNode* m_nextVisibleNode ANKI_DBG_NULLIFY_PTR;
+	const MaterialVariant* m_variant ANKI_DBG_NULLIFY_PTR;
+	Array<Mat4, MAX_INSTANCES> m_cachedTrfs;
+	U m_cachedTrfCount = 0;
+	F32 m_flod;
+};
 
 /// Visitor that sets a uniform
 class SetupRenderableVariableVisitor
 {
 public:
-	// Used to get the visible spatials
-	const VisibleNode* m_visibleNode;
-
-	const RenderComponent* m_renderable; ///< To get the transforms
-	const RenderComponentVariable* m_rvar;
-	const FrustumComponent* m_fr;
-	const RenderableDrawer* m_drawer;
-	U8 m_instanceCount;
-	CommandBufferPtr m_cmdBuff;
+	RenderContext* m_ctx ANKI_DBG_NULLIFY_PTR;
+	const RenderableDrawer* m_drawer ANKI_DBG_NULLIFY_PTR;
 	SArray<U8> m_uniformBuffer;
 
-	F32 m_flod;
-
 	/// Set a uniform in a client block
 	template<typename T>
 	void uniSet(const MaterialVariable& mtlVar, const T* value, U32 size)
 	{
 		mtlVar.writeShaderBlockMemory<T>(
+			*m_ctx->m_variant,
 			value,
 			size,
 			&m_uniformBuffer[0],
@@ -53,169 +59,131 @@ public:
 	}
 
 	template<typename TRenderableVariableTemplate>
-	Error visit(const TRenderableVariableTemplate& rvar)
-	{
-		typedef typename TRenderableVariableTemplate::Type DataType;
-		const MaterialVariable& glvar = rvar.getMaterialVariable();
+	Error visit(const TRenderableVariableTemplate& rvar);
+};
 
-		// Array size
-		U arraySize;
-		if(rvar.isInstanced())
-		{
-			arraySize = std::min<U>(m_instanceCount, glvar.getArraySize());
-		}
-		else
-		{
-			arraySize = glvar.getArraySize();
-		}
+//==============================================================================
+template<typename TRenderableVariableTemplate>
+Error SetupRenderableVariableVisitor::visit(
+	const TRenderableVariableTemplate& rvar)
+{
+	using DataType = typename TRenderableVariableTemplate::Type;
+	const MaterialVariable& mvar = rvar.getMaterialVariable();
 
-		// Set uniform
-		//
-		Bool hasWorldTrfs = m_renderable->getHasWorldTransforms();
-		const Mat4& vp = m_fr->getViewProjectionMatrix();
-		const Mat4& v = m_fr->getViewMatrix();
+	// Array size
+	U cachedTrfs = m_ctx->m_cachedTrfCount;
+	ANKI_ASSERT(cachedTrfs <= MAX_INSTANCES);
 
-		switch(rvar.getBuildinId())
+	// Set uniform
+	//
+	const Mat4& vp = m_ctx->m_frc->getViewProjectionMatrix();
+	const Mat4& v = m_ctx->m_frc->getViewMatrix();
+
+	switch(mvar.getBuiltin())
+	{
+	case BuiltinMaterialVariableId::NONE:
+		uniSet<DataType>(mvar, &rvar.getValue(), 1);
+		break;
+	case BuiltinMaterialVariableId::MVP_MATRIX:
 		{
-		case BuildinMaterialVariableId::NO_BUILDIN:
-			uniSet<DataType>(glvar, rvar.begin(), arraySize);
-			break;
-		case BuildinMaterialVariableId::MVP_MATRIX:
-			if(hasWorldTrfs)
-			{
-				Mat4* mvp = m_drawer->m_r->getFrameAllocator().
-					newArray<Mat4>(arraySize);
+			ANKI_ASSERT(cachedTrfs > 0);
 
-				for(U i = 0; i < arraySize; i++)
-				{
-					Transform worldTrf;
+			DArrayAuto<Mat4> mvp(m_drawer->m_r->getFrameAllocator());
+			mvp.create(cachedTrfs);
 
-					m_renderable->getRenderWorldTransform(
-						m_visibleNode->getSpatialIndex(i), worldTrf);
+			for(U i = 0; i < cachedTrfs; i++)
+			{
+				mvp[i] = vp * m_ctx->m_cachedTrfs[i];
+			}
 
-					mvp[i] = vp * Mat4(worldTrf);
-				}
+			uniSet(mvar, &mvp[0], cachedTrfs);
+		}
+		break;
+	case BuiltinMaterialVariableId::MV_MATRIX:
+		{
+			ANKI_ASSERT(cachedTrfs > 0);
 
-				uniSet(glvar, &mvp[0], arraySize);
+			DArrayAuto<Mat4> mv(m_drawer->m_r->getFrameAllocator());
+			mv.create(cachedTrfs);
 
-				m_drawer->m_r->getFrameAllocator().deleteArray(mvp, arraySize);
-			}
-			else
+			for(U i = 0; i < cachedTrfs; i++)
 			{
-				ANKI_ASSERT(arraySize == 1 && "Shouldn't instance that one");
-				uniSet(glvar, &vp, 1);
+				mv[i] = v * m_ctx->m_cachedTrfs[i];
 			}
-			break;
-		case BuildinMaterialVariableId::MV_MATRIX:
-			{
-				ANKI_ASSERT(hasWorldTrfs);
-				Mat4* mv = m_drawer->m_r->getFrameAllocator().
-					newArray<Mat4>(arraySize);
-
-				for(U i = 0; i < arraySize; i++)
-				{
-					Transform worldTrf;
 
-					m_renderable->getRenderWorldTransform(
-						m_visibleNode->getSpatialIndex(i), worldTrf);
-
-					mv[i] = v * Mat4(worldTrf);
-				}
+			uniSet(mvar, &mv[0], cachedTrfs);
+		}
+		break;
+	case BuiltinMaterialVariableId::VP_MATRIX:
+		ANKI_ASSERT(cachedTrfs == 0 && "Cannot have transform");
+		uniSet(mvar, &vp, 1);
+		break;
+	case BuiltinMaterialVariableId::NORMAL_MATRIX:
+		{
+			ANKI_ASSERT(cachedTrfs > 0);
 
-				uniSet(glvar, &mv[0], arraySize);
+			DArrayAuto<Mat3> normMats(m_drawer->m_r->getFrameAllocator());
+			normMats.create(cachedTrfs);
 
-				m_drawer->m_r->getFrameAllocator().deleteArray(mv, arraySize);
-			}
-			break;
-		case BuildinMaterialVariableId::VP_MATRIX:
-			uniSet(glvar, &vp, 1);
-			break;
-		case BuildinMaterialVariableId::NORMAL_MATRIX:
-			if(hasWorldTrfs)
+			for(U i = 0; i < cachedTrfs; i++)
 			{
-				Mat3* normMats = m_drawer->m_r->getFrameAllocator().
-					newArray<Mat3>(arraySize);
-
-				for(U i = 0; i < arraySize; i++)
-				{
-					Transform worldTrf;
-
-					m_renderable->getRenderWorldTransform(
-						m_visibleNode->getSpatialIndex(i), worldTrf);
+				Mat4 mv = v * m_ctx->m_cachedTrfs[i];
+				normMats[i] = mv.getRotationPart();
+				normMats[i].reorthogonalize();
+			}
 
-					Mat4 mv = v * Mat4(worldTrf);
-					normMats[i] = mv.getRotationPart();
-					normMats[i].reorthogonalize();
-				}
+			uniSet(mvar, &normMats[0], cachedTrfs);
+		}
+		break;
+	case BuiltinMaterialVariableId::BILLBOARD_MVP_MATRIX:
+		{
+			// Calc the billboard rotation matrix
+			Mat3 rot = v.getRotationPart().getTransposed();
 
-				uniSet(glvar, &normMats[0], arraySize);
+			DArrayAuto<Mat4> bmvp(m_drawer->m_r->getFrameAllocator());
+			bmvp.create(cachedTrfs);
 
-				m_drawer->m_r->getFrameAllocator().deleteArray(
-					normMats, arraySize);
-			}
-			else
+			for(U i = 0; i < cachedTrfs; i++)
 			{
-				ANKI_ASSERT(arraySize == 1
-					&& "Having that instanced doesn't make sense");
-
-				Mat3 normMat = v.getRotationPart();
-
-				uniSet(glvar, &normMat, 1);
+				Mat4 trf = m_ctx->m_cachedTrfs[i];
+				trf.setRotationPart(rot);
+				bmvp[i] = vp * trf;
 			}
-			break;
-		case BuildinMaterialVariableId::BILLBOARD_MVP_MATRIX:
-			{
-				// Calc the billboard rotation matrix
-				Mat3 rot =
-					m_fr->getViewMatrix().getRotationPart().getTransposed();
-
-				Mat4* bmvp = m_drawer->m_r->getFrameAllocator().
-					newArray<Mat4>(arraySize);
-
-				for(U i = 0; i < arraySize; i++)
-				{
-					Transform trf;
-					m_renderable->getRenderWorldTransform(i, trf);
-					trf.setRotation(Mat3x4(rot));
-					bmvp[i] = vp * Mat4(trf);
-				}
 
-				uniSet(glvar, &bmvp[0], arraySize);
+			uniSet(mvar, &bmvp[0], cachedTrfs);
+		}
+		break;
+	case BuiltinMaterialVariableId::MAX_TESS_LEVEL:
+		{
+			const RenderComponentVariable& base = rvar;
+			F32 maxtess = base.getValue<F32>();
+			F32 tess = 0.0;
 
-				m_drawer->m_r->getFrameAllocator().deleteArray(bmvp, arraySize);
+			if(m_ctx->m_flod >= 1.0)
+			{
+				tess = 1.0;
 			}
-			break;
-		case BuildinMaterialVariableId::MAX_TESS_LEVEL:
+			else
 			{
-				F32 maxtess =
-					rvar.RenderComponentVariable:: template operator[]<F32>(0);
-				F32 tess = 0.0;
-
-				if(m_flod >= 1.0)
-				{
-					tess = 1.0;
-				}
-				else
-				{
-					tess = maxtess - m_flod * maxtess;
-					tess = std::max(tess, 1.0f);
-				}
-
-				uniSet(glvar, &tess, 1);
+				tess = maxtess - m_ctx->m_flod * maxtess;
+				tess = std::max(tess, 1.0f);
 			}
-			break;
-		case BuildinMaterialVariableId::MS_DEPTH_MAP:
-			// Do nothing
-			break;
-		default:
-			ANKI_ASSERT(0);
-			break;
-		}
 
-		return ErrorCode::NONE;
+			uniSet(mvar, &tess, 1);
+		}
+		break;
+	case BuiltinMaterialVariableId::MS_DEPTH_MAP:
+		// Do nothing
+		break;
+	default:
+		ANKI_ASSERT(0);
+		break;
 	}
-};
 
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
 // Texture specialization
 template<>
 void SetupRenderableVariableVisitor::uniSet<TextureResourcePtr>(
@@ -231,14 +199,11 @@ class RenderTask: public ThreadPool::Task
 {
 public:
 	RenderableDrawer* m_drawer;
-	CommandBufferPtr m_cmdb;
-	FrustumComponent* m_frc;
-	RenderingStage m_stage;
-	Pass m_pass;
+	RenderContext m_ctx;
 
 	Error operator()(U32 threadId, PtrSize threadsCount) override
 	{
-		VisibilityTestResults& vis = m_frc->getVisibilityTestResults();
+		VisibilityTestResults& vis = m_ctx.m_frc->getVisibilityTestResults();
 
 		PtrSize start, end;
 		U problemSize = vis.getRenderablesEnd() - vis.getRenderablesBegin();
@@ -246,9 +211,18 @@ public:
 
 		for(U i = start; i < end; ++i)
 		{
-			VisibleNode* node = vis.getRenderablesBegin() + i;
-			ANKI_CHECK(
-				m_drawer->renderSingle(*m_frc, m_stage, m_pass, m_cmdb, *node));
+			m_ctx.m_visibleNode = vis.getRenderablesBegin() + i;
+
+			if(i + 1 < end)
+			{
+				m_ctx.m_nextVisibleNode = vis.getRenderablesBegin() + i + 1;
+			}
+			else
+			{
+				m_ctx.m_nextVisibleNode = nullptr;
+			}
+
+			ANKI_CHECK(m_drawer->renderSingle(m_ctx));
 		}
 
 		return ErrorCode::NONE;
@@ -264,39 +238,34 @@ RenderableDrawer::~RenderableDrawer()
 {}
 
 //==============================================================================
-void RenderableDrawer::setupUniforms(
-	const VisibleNode& visibleNode,
-	const RenderComponent& renderable,
-	const FrustumComponent& fr,
-	F32 flod,
-	CommandBufferPtr cmdb)
+void RenderableDrawer::setupUniforms(RenderContext& ctx,
+	const RenderComponent& renderable, const RenderingKey& key)
 {
 	const Material& mtl = renderable.getMaterial();
+	const MaterialVariant& variant = mtl.getVariant(key);
+	ctx.m_variant = &variant;
 
 	// Get some memory for uniforms
-	ANKI_ASSERT(mtl.getDefaultBlockSize() < MATERIAL_BLOCK_MAX_SIZE);
 	U8* uniforms = nullptr;
-	cmdb->updateDynamicUniforms(mtl.getDefaultBlockSize(), uniforms);
+	ctx.m_cmdb->updateDynamicUniforms(variant.getDefaultBlockSize(), uniforms);
 
 	// Call the visitor
 	SetupRenderableVariableVisitor visitor;
-	visitor.m_visibleNode = &visibleNode;
-	visitor.m_renderable = &renderable;
-	visitor.m_fr = &fr;
+	visitor.m_ctx = &ctx;
 	visitor.m_drawer = this;
-	visitor.m_instanceCount = visibleNode.m_spatialsCount;
-	visitor.m_cmdBuff = cmdb;
-	visitor.m_uniformBuffer = SArray<U8>(uniforms, mtl.getDefaultBlockSize());
-	visitor.m_flod = flod;
+	visitor.m_uniformBuffer =
+		SArray<U8>(uniforms, variant.getDefaultBlockSize());
 
 	for(auto it = renderable.getVariablesBegin();
 		it != renderable.getVariablesEnd(); ++it)
 	{
 		RenderComponentVariable* rvar = *it;
 
-		visitor.m_rvar = rvar;
-		Error err = rvar->acceptVisitor(visitor);
-		(void)err;
+		if(variant.variableActive(rvar->getMaterialVariable()))
+		{
+			Error err = rvar->acceptVisitor(visitor);
+			(void)err;
+		}
 	}
 }
 
@@ -317,10 +286,10 @@ Error RenderableDrawer::render(FrustumComponent& frc,
 		{
 			auto& task = tasks[i];
 			task.m_drawer = this;
-			task.m_cmdb = cmdbs[i];
-			task.m_frc = &frc;
-			task.m_stage = stage;
-			task.m_pass = pass;
+			task.m_ctx.m_cmdb = cmdbs[i];
+			task.m_ctx.m_frc = &frc;
+			task.m_ctx.m_stage = stage;
+			task.m_ctx.m_pass = pass;
 
 			threadPool.assignNewTask(i, &task);
 		}
@@ -331,10 +300,10 @@ Error RenderableDrawer::render(FrustumComponent& frc,
 	{
 		RenderTask task;
 		task.m_drawer = this;
-		task.m_cmdb = cmdbs[0];
-		task.m_frc = &frc;
-		task.m_stage = stage;
-		task.m_pass = pass;
+		task.m_ctx.m_cmdb = cmdbs[0];
+		task.m_ctx.m_frc = &frc;
+		task.m_ctx.m_stage = stage;
+		task.m_ctx.m_pass = pass;
 
 		err = task(0, 1);
 	}
@@ -343,56 +312,90 @@ Error RenderableDrawer::render(FrustumComponent& frc,
 }
 
 //==============================================================================
-Error RenderableDrawer::renderSingle(
-	const FrustumComponent& fr,
-	RenderingStage stage,
-	Pass pass,
-	CommandBufferPtr cmdb,
-	const VisibleNode& visibleNode)
+Error RenderableDrawer::renderSingle(RenderContext& ctx)
 {
-	RenderingBuildData build;
-
 	// Get components
 	const RenderComponent& renderable =
-		visibleNode.m_node->getComponent<RenderComponent>();
+		ctx.m_visibleNode->m_node->getComponent<RenderComponent>();
+
+	// Check if it can merge drawcalls
+	if(ctx.m_nextVisibleNode)
+	{
+		const RenderComponent& nextRenderable =
+			ctx.m_nextVisibleNode->m_node->getComponent<RenderComponent>();
+
+		if(nextRenderable.canMergeDrawcalls(renderable)
+			&& ctx.m_cachedTrfCount <= MAX_INSTANCES)
+		{
+			// Can merge, will cache the drawcall and skip the drawcall
+			Bool hasTransform;
+			Transform trf;
+			renderable.getRenderWorldTransform(hasTransform, trf);
+			ANKI_ASSERT(hasTransform);
+
+			ctx.m_cachedTrfs[ctx.m_cachedTrfCount++] = Mat4(trf);
+
+			return ErrorCode::NONE;
+		}
+	}
+
 	const Material& mtl = renderable.getMaterial();
 
-	if((stage == RenderingStage::BLEND && !mtl.getForwardShading())
-		|| (stage == RenderingStage::MATERIAL && mtl.getForwardShading()))
+	if((ctx.m_stage == RenderingStage::BLEND && !mtl.getForwardShading())
+		|| (ctx.m_stage == RenderingStage::MATERIAL && mtl.getForwardShading()))
 	{
-		return ErrorCode::NONE;
+		ANKI_ASSERT(0);
+	}
+
+	// Stash the transform
+	{
+		Bool hasTransform;
+		Transform trf;
+		renderable.getRenderWorldTransform(hasTransform, trf);
+
+		if(hasTransform)
+		{
+			ctx.m_cachedTrfs[ctx.m_cachedTrfCount++] = Mat4(trf);
+		}
 	}
 
 	// Calculate the key
 	RenderingKey key;
 
-	Vec4 camPos = fr.getFrustumOrigin();
-
-	F32 dist = (visibleNode.m_node->getComponent<SpatialComponent>().
+	Vec4 camPos = ctx.m_frc->getFrustumOrigin();
+	F32 dist = (ctx.m_visibleNode->m_node->getComponent<SpatialComponent>().
 		getSpatialOrigin() - camPos).getLength();
 	F32 flod = m_r->calculateLod(dist);
+	ctx.m_flod = flod;
+
+	RenderingBuildInfo build;
 	build.m_key.m_lod = flod;
-	build.m_key.m_pass = pass;
+	build.m_key.m_pass = ctx.m_pass;
 	build.m_key.m_tessellation =
 		m_r->getTessellationEnabled()
 		&& mtl.getTessellationEnabled()
 		&& build.m_key.m_lod == 0;
+	build.m_key.m_instanceCount =
+		ctx.m_cachedTrfCount == 0 ? 1 : ctx.m_cachedTrfCount;
 
-	if(pass == Pass::SM)
+	if(ctx.m_pass == Pass::SM && build.m_key.m_lod > 1)
 	{
 		build.m_key.m_tessellation = false;
 	}
 
 	// Enqueue uniform state updates
-	setupUniforms(visibleNode, renderable, fr, flod, cmdb);
+	setupUniforms(ctx, renderable, key);
 
 	// Enqueue vertex, program and drawcall
-	build.m_subMeshIndicesArray = &visibleNode.m_spatialIndices[0];
-	build.m_subMeshIndicesCount = visibleNode.m_spatialsCount;
-	build.m_cmdb = cmdb;
+	build.m_subMeshIndicesArray = &ctx.m_visibleNode->m_spatialIndices[0];
+	build.m_subMeshIndicesCount = ctx.m_visibleNode->m_spatialsCount;
+	build.m_cmdb = ctx.m_cmdb;
 
 	ANKI_CHECK(renderable.buildRendering(build));
 
+	// Rendered something, reset the cached transforms
+	ctx.m_cachedTrfCount = 0;
+
 	return ErrorCode::NONE;
 }
 

+ 1 - 1
src/resource/ImageLoader.cpp

@@ -502,7 +502,7 @@ Error ImageLoader::load(ResourceFilePtr file, const CString& filename,
 	}
 
 	// load from this extension
-	U32 bpp;
+	U32 bpp = 0;
 	m_textureType = TextureType::_2D;
 	m_compression = DataCompression::RAW;
 

+ 264 - 271
src/resource/Material.cpp

@@ -4,7 +4,7 @@
 // http://www.anki3d.org/LICENSE
 
 #include "anki/resource/Material.h"
-#include "anki/resource/MaterialProgramCreator.h"
+#include "anki/resource/MaterialLoader.h"
 #include "anki/resource/ResourceManager.h"
 #include "anki/core/App.h"
 #include "anki/util/Logger.h"
@@ -17,7 +17,6 @@
 #include "anki/renderer/Renderer.h"
 #include "anki/renderer/Ms.h"
 #include <algorithm>
-#include <sstream>
 
 namespace anki {
 
@@ -47,57 +46,38 @@ Error UpdateTexturesVisitor
 	const MaterialVariableTemplate<TextureResourcePtr>& var)
 {
 	m_init->m_textures[var.getTextureUnit()].m_texture =
-		(*var.begin())->getGrTexture();
+		var.getValue()->getGrTexture();
 	return ErrorCode::NONE;
 }
 
 //==============================================================================
-// MaterialVariable                                                            =
+// MaterialVariableTemplate                                                    =
 //==============================================================================
 
-//==============================================================================
-MaterialVariable::~MaterialVariable()
-{}
-
-//==============================================================================
-template<typename T>
-void MaterialVariableTemplate<T>::create(ResourceAllocator<U8> alloc,
-	const CString& name, const T* x, U32 size)
-{
-	m_name.create(alloc, name);
-
-	if(size > 0)
-	{
-		m_data.create(alloc, size);
-		for(U i = 0; i < size; i++)
-		{
-			m_data[i] = x[i];
-		}
-	}
-}
-
 //==============================================================================
 template<typename T>
-Error MaterialVariableTemplate<T>::_newInstance(
-	const MaterialProgramCreator::Input& in,
-	ResourceAllocator<U8> alloc, TempResourceAllocator<U8> talloc,
-	MaterialVariable*& out2)
+Error MaterialVariableTemplate<T>::init(U idx,
+	const MaterialLoader::Input& in, Material& mtl)
 {
-	MaterialVariableTemplate* out;
-	DArrayAuto<F32> floats(talloc);
+	m_idx = idx;
+	m_varType = in.m_type;
+	m_name.create(mtl.getAllocator(), in.m_name);
+	m_builtin = in.m_builtin;
+	m_instanced = in.m_instanced;
 
-	// Get the float values
+	// Set value
 	if(in.m_value.getSize() > 0)
 	{
+		ANKI_ASSERT(m_builtin == BuiltinMaterialVariableId::NONE);
+
 		// Has values
+		DArrayAuto<F32> floats(mtl.getTempAllocator());
 
-		U floatsNeeded = in.m_arraySize * (sizeof(T) / sizeof(F32));
+		U floatsNeeded = sizeof(T) / sizeof(F32);
 
 		if(in.m_value.getSize() != floatsNeeded)
 		{
-			ANKI_LOGE("Incorrect number of values. Variable %s",
-				&in.m_name[0]);
-
+			ANKI_LOGE("Incorrect number of values. Variable %s", &in.m_name[0]);
 			return ErrorCode::USER_DATA;
 		}
 
@@ -112,348 +92,342 @@ Error MaterialVariableTemplate<T>::_newInstance(
 			floats[i] = d;
 			++it;
 		}
-	}
 
-	// Create new instance
-	out = alloc.newInstance<MaterialVariableTemplate<T>>();
-
-	if(floats.getSize() > 0)
-	{
-		out->create(alloc, in.m_name.toCString(),
-			(T*)&floats[0], in.m_arraySize);
+		memcpy(&m_value, &floats[0], sizeof(T));
 	}
 	else
 	{
-		// Buildin
-		out->create(alloc, in.m_name.toCString(), nullptr, 0);
+		ANKI_ASSERT(m_builtin != BuiltinMaterialVariableId::NONE);
 	}
 
-	// Set some values
-	out->m_instanced = in.m_instanced;
-	out->m_varType = in.m_type;
-	out->m_textureUnit = in.m_binding;
-	out->m_varBlkInfo.m_arraySize = in.m_arraySize;
+	return ErrorCode::NONE;
+}
 
-	// Set UBO data
-	if(out && in.m_inBlock)
+//==============================================================================
+template<>
+Error MaterialVariableTemplate<TextureResourcePtr>::init(U idx,
+	const MaterialLoader::Input& in, Material& mtl)
+{
+	m_idx = idx;
+	m_varType = in.m_type;
+	m_textureUnit = in.m_binding;
+	m_name.create(mtl.getAllocator(), in.m_name);
+	m_builtin = in.m_builtin;
+	m_instanced = false;
+
+	if(in.m_value.getSize() > 0)
 	{
-		out->m_varBlkInfo.m_offset = in.m_offset;
-		ANKI_ASSERT(out->m_varBlkInfo.m_offset >= 0);
-		out->m_varBlkInfo.m_arrayStride = in.m_arrayStride;
-		out->m_varBlkInfo.m_matrixStride = in.m_matrixStride;
-	}
+		ANKI_ASSERT(m_builtin == BuiltinMaterialVariableId::NONE);
 
-	out2 = out;
+		ANKI_CHECK(mtl.getManager().loadResource(
+			in.m_value.getBegin()->toCString(), m_value));
+	}
+	else
+	{
+		ANKI_ASSERT(m_builtin != BuiltinMaterialVariableId::NONE);
+	}
 
 	return ErrorCode::NONE;
 }
 
 //==============================================================================
-// Material                                                                    =
+// MaterialVariant                                                             =
 //==============================================================================
 
 //==============================================================================
-Material::Material(ResourceManager* manager)
-	: ResourceObject(manager)
+MaterialVariant::MaterialVariant()
 {}
 
 //==============================================================================
-Material::~Material()
+MaterialVariant::~MaterialVariant()
+{}
+
+//==============================================================================
+Error MaterialVariant::init(const RenderingKey& key2, Material& mtl,
+	MaterialLoader& loader)
 {
-	auto alloc = getAllocator();
+	RenderingKey key = key2;
 
-	for(auto it : m_vars)
+	// Disable tessellation under some keys
+	if(key.m_tessellation)
 	{
-		if(it)
+		// tessellation is disabled on LODs > 0
+		if(key.m_lod > 0)
 		{
-			MaterialVariable* mvar = &(*it);
-			mvar->destroy(alloc);
-			alloc.deleteInstance(mvar);
+			key.m_tessellation = false;
 		}
-	}
 
-	m_vars.destroy(alloc);
-}
-
-//==============================================================================
-Error Material::load(const ResourceFilename& filename)
-{
-	XmlDocument doc;
-	ANKI_CHECK(openFileParseXml(filename, doc));
-
-	XmlElement el;
-	ANKI_CHECK(doc.getChildElement("material", el));
-	ANKI_CHECK(parseMaterialTag(el));
+		// tesselation is disabled on shadow passes
+		if(key.m_pass == Pass::SM)
+		{
+			key.m_tessellation = false;
+		}
+	}
 
-	return ErrorCode::NONE;
-}
+	loader.mutate(key);
 
-//==============================================================================
-Error Material::parseMaterialTag(const XmlElement& materialEl)
-{
-	XmlElement el;
-
-	// levelsOfDetail
-	//
-	XmlElement lodEl;
-	ANKI_CHECK(materialEl.getChildElementOptional("levelsOfDetail", lodEl));
+	m_shaderBlockSize = loader.getUniformBlockSize();
 
-	if(lodEl)
-	{
-		I64 tmp;
-		ANKI_CHECK(lodEl.getI64(tmp));
-		m_lodCount = (tmp < 1) ? 1 : tmp;
-	}
-	else
+	U count = 0;
+	Error err = loader.iterateAllInputVariables([&](
+		const MaterialLoader::Input& in) -> Error
 	{
-		m_lodCount = 1;
-	}
+		m_varActive[count] = true;
+		if(!in.m_inShadow && key.m_pass == Pass::SM)
+		{
+			m_varActive[count] = false;
+		}
 
-	// shadow
-	//
-	XmlElement shadowEl;
-	ANKI_CHECK(materialEl.getChildElementOptional("shadow", shadowEl));
+		if(in.m_inBlock && m_varActive[count])
+		{
+			m_blockInfo[count] = in.m_blockInfo;
+		}
 
-	if(shadowEl)
-	{
-		I64 tmp;
-		ANKI_CHECK(shadowEl.getI64(tmp));
-		m_shadow = tmp;
-	}
+		++count;
 
-	// forwardShading
-	//
-	XmlElement fsEl;
-	ANKI_CHECK(materialEl.getChildElementOptional("forwardShading", fsEl));
-	if(fsEl)
-	{
-		I64 tmp;
-		ANKI_CHECK(fsEl.getI64(tmp));
-		m_forwardShading = tmp;
-	}
+		return ErrorCode::NONE;
+	});
+	(void) err;
 
-	// shaderProgram
 	//
-	ANKI_CHECK(materialEl.getChildElement("programs", el));
-	MaterialProgramCreator loader(getTempAllocator());
-	ANKI_CHECK(loader.parseProgramsTag(el));
-
-	m_tessellation = loader.hasTessellation();
-	U tessCount = m_tessellation ? 2 : 1;
-	U passesCount = m_shadow ? 2 : 1;
-
-	m_hash = 0;
-	for(ShaderType shader = ShaderType::VERTEX;
-		shader <= ShaderType::FRAGMENT;
-		++shader)
+	// Shaders
+	//
+	for(ShaderType stype = ShaderType::VERTEX; stype <= ShaderType::FRAGMENT;
+		++stype)
 	{
-		Bool isTessellationShader = shader == ShaderType::TESSELLATION_CONTROL
-			|| shader == ShaderType::TESSELLATION_EVALUATION;
-
-		if(!m_tessellation && isTessellationShader)
+		if(stype == ShaderType::GEOMETRY)
 		{
-			// Skip tessellation if not enabled
 			continue;
 		}
 
-		if(shader == ShaderType::GEOMETRY)
+		if((stype == ShaderType::TESSELLATION_CONTROL
+			|| stype == ShaderType::TESSELLATION_EVALUATION)
+			&& !key.m_tessellation)
 		{
-			// Skip geometry for now
 			continue;
 		}
 
-		for(U level = 0; level < m_lodCount; ++level)
-		{
-			if(level > 0 && isTessellationShader)
-			{
-				continue;
-			}
+		const String& src = loader.getShaderSource(stype);
 
-			for(U pid = 0; pid < passesCount; ++pid)
-			{
-				for(U tess = 0; tess < tessCount; ++tess)
-				{
-					if(tess == 0 && isTessellationShader)
-					{
-						continue;
-					}
+		StringAuto filename(mtl.getTempAllocator());
+		ANKI_CHECK(mtl.createProgramSourceToCache(src, filename));
 
-					if(tess > 0 && shader == ShaderType::FRAGMENT)
-					{
-						continue;
-					}
+		ShaderResourcePtr& shader = m_shaders[U(stype)];
 
-					StringAuto src(getTempAllocator());
-					src.sprintf(
-						"#define LOD %u\n"
-						"#define PASS %u\n"
-						"#define TESSELLATION %u\n"
-						"%s\n",
-						level, pid, tess, &loader.getProgramSource(shader)[0]);
+		ANKI_CHECK(
+			mtl.getManager().loadResource(filename.toCString(), shader));
 
-					StringAuto filename(getTempAllocator());
+		// Update the hash
+		mtl.m_hash ^= computeHash(&src[0], src.getLength());
+	}
 
-					ANKI_CHECK(createProgramSourceToCache(src, filename));
+	return ErrorCode::NONE;
+}
 
-					ShaderResourcePtr& progr =
-						m_shaders[pid][level][tess][U(shader)];
-					ANKI_CHECK(getManager().loadResource(filename.toCString(), progr));
+//==============================================================================
+// Material                                                                    =
+//==============================================================================
 
-					// Update the hash
-					m_hash ^= computeHash(&src[0], src.getLength());
-				}
-			}
-		}
+//==============================================================================
+Material::Material(ResourceManager* manager)
+	: ResourceObject(manager)
+{
+	// Init m_variantMatrix
+	U16* arr = &m_variantMatrix[0][0][0][0];
+	U count = sizeof(m_variantMatrix) / sizeof(m_variantMatrix[0][0][0][0]);
+	for(U i = 0; i < count; ++i)
+	{
+		arr[i] = MAX_U16;
 	}
+}
 
-	ANKI_CHECK(populateVariables(loader));
+//==============================================================================
+Material::~Material()
+{
+	auto alloc = getAllocator();
 
-	// Get uniform block size
-	m_shaderBlockSize = loader.getUniformBlockSize();
+	m_variants.destroy(alloc);
 
-	return ErrorCode::NONE;
+	for(MaterialVariable* var : m_vars)
+	{
+		var->destroy(alloc);
+		alloc.deleteInstance(var);
+	}
+	m_vars.destroy(alloc);
 }
 
 //==============================================================================
-Error Material::createProgramSourceToCache(
-	const StringAuto& source, StringAuto& out)
+Error Material::load(const ResourceFilename& filename)
 {
-	auto alloc = getTempAllocator();
+	XmlDocument doc;
+	ANKI_CHECK(openFileParseXml(filename, doc));
 
-	// Create the hash
-	U64 h = computeHash(&source[0], source.getLength());
+	MaterialLoader loader(getTempAllocator());
+	ANKI_CHECK(loader.parseXmlDocument(doc));
 
-	// Create out
-	out.sprintf("mtl_%llu.glsl", h);
+	m_lodCount = loader.getLodCount();
+	m_shadow = loader.getShadowEnabled();
+	m_forwardShading = loader.isForwardShading();
+	m_tessellation = loader.getTessellationEnabled();
 
-	// Create path
-	StringAuto fullFname(alloc);
-	fullFname.sprintf("%s/%s",
-		&getManager()._getCacheDirectory()[0],
-		&out[0]);
-
-	// If file not exists write it
-	if(!fileExists(fullFname.toCString()))
-	{
-		// If not create it
-		File f;
-		ANKI_CHECK(f.open(fullFname.toCString(), File::OpenFlag::WRITE));
-		ANKI_CHECK(f.writeText("%s\n", &source[0]));
-	}
+	// Start initializing
+	ANKI_CHECK(createVars(loader));
+	ANKI_CHECK(createVariants(loader));
 
 	return ErrorCode::NONE;
 }
 
 //==============================================================================
-Error Material::populateVariables(const MaterialProgramCreator& loader)
+Error Material::createVars(const MaterialLoader& loader)
 {
-	U varCount = 0;
-	for(const auto& in : loader.getInputVariables())
+	// Count them and allocate
+	U count = 0;
+	Error err = loader.iterateAllInputVariables([&](
+		const MaterialLoader::Input&) -> Error
 	{
-		if(!in.m_constant)
-		{
-			++varCount;
-		}
-	}
+		++count;
+		return ErrorCode::NONE;
+	});
 
-	m_vars.create(getAllocator(), varCount);
+	m_vars.create(getAllocator(), count);
+	auto alloc = getAllocator();
 
-	varCount = 0;
-	for(const auto& in : loader.getInputVariables())
+	// Find the name
+	count = 0;
+	err = loader.iterateAllInputVariables([&](
+		const MaterialLoader::Input& in) -> Error
 	{
-		if(in.m_constant)
-		{
-			continue;
-		}
-
 		MaterialVariable* mtlvar = nullptr;
 
+#define ANKI_INIT_VAR(type_) { \
+	MaterialVariableTemplate<type_>* var = \
+		alloc.newInstance<MaterialVariableTemplate<type_>>(); \
+	ANKI_CHECK(var->init(count, in, *this)); \
+	mtlvar = var; }
+
 		switch(in.m_type)
 		{
-		// samplers
 		case ShaderVariableDataType::SAMPLER_2D:
 		case ShaderVariableDataType::SAMPLER_2D_ARRAY:
 		case ShaderVariableDataType::SAMPLER_CUBE:
-			{
-				TextureResourcePtr tp;
-
-				if(in.m_value.getSize() > 0)
-				{
-					ANKI_CHECK(getManager().loadResource(
-						in.m_value.getBegin()->toCString(), tp));
-				}
-
-				auto alloc = getAllocator();
-				MaterialVariableTemplate<TextureResourcePtr>* tvar =
-					alloc.newInstance<
-					MaterialVariableTemplate<TextureResourcePtr>>();
-
-				if(tvar)
-				{
-					tvar->create(alloc, in.m_name.toCString(), &tp, 1);
-				}
-
-				mtlvar = tvar;
-				mtlvar->m_varBlkInfo.m_arraySize = 1;
-				mtlvar->m_textureUnit = in.m_binding;
-			}
+			ANKI_INIT_VAR(TextureResourcePtr);
 			break;
 		case ShaderVariableDataType::FLOAT:
-			ANKI_CHECK(MaterialVariableTemplate<F32>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(F32);
 			break;
 		case ShaderVariableDataType::VEC2:
-			ANKI_CHECK(MaterialVariableTemplate<Vec2>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(Vec2);
 			break;
 		case ShaderVariableDataType::VEC3:
-			ANKI_CHECK(MaterialVariableTemplate<Vec3>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(Vec3);
 			break;
 		case ShaderVariableDataType::VEC4:
-			ANKI_CHECK(MaterialVariableTemplate<Vec4>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(Vec4);
 			break;
 		case ShaderVariableDataType::MAT3:
-			ANKI_CHECK(MaterialVariableTemplate<Mat3>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(Mat3);
 			break;
 		case ShaderVariableDataType::MAT4:
-			ANKI_CHECK(MaterialVariableTemplate<Mat4>::_newInstance(in,
-				getAllocator(), getTempAllocator(),
-				mtlvar));
+			ANKI_INIT_VAR(Mat4);
 			break;
-		// default is error
 		default:
 			ANKI_ASSERT(0);
 		}
 
-		if(mtlvar == nullptr)
+#undef ANKI_INIT_VAR
+
+		m_vars[count] = mtlvar;
+		++count;
+		return ErrorCode::NONE;
+	});
+
+	return err;
+}
+
+//==============================================================================
+Error Material::createVariants(MaterialLoader& loader)
+{
+	U tessStates = m_tessellation ? 2 : 1;
+	U instStates = m_instanced ? MAX_INSTANCE_GROUPS : 1;
+	U passStates = m_shadow ? 2 : 1;
+
+	// Init the variant matrix
+	U variantsCount = 0;
+	for(U p = 0; p < passStates; ++p)
+	{
+		for(U l = 0; l < m_lodCount; ++l)
 		{
-			return ErrorCode::USER_DATA;
+			for(U t = 0; t < tessStates; ++t)
+			{
+				for(U i = 0; i < instStates; ++i)
+				{
+					// Enable variant
+					m_variantMatrix[p][l][t][i] = variantsCount++;
+				}
+			}
 		}
+	}
 
-		m_vars[varCount++] = mtlvar;
+	// Create the variants
+	m_variants.create(getAllocator(), variantsCount);
+
+	for(U p = 0; p < passStates; ++p)
+	{
+		for(U l = 0; l < m_lodCount; ++l)
+		{
+			for(U t = 0; t < tessStates; ++t)
+			{
+				for(U i = 0; i < instStates; ++i)
+				{
+					U idx = m_variantMatrix[p][l][t][i];
+					if(idx == MAX_U16)
+					{
+						continue;
+					}
+
+					RenderingKey key;
+					key.m_pass = Pass(p);
+					key.m_lod = l;
+					key.m_tessellation = t;
+					key.m_instanceCount = 1 << i;
+
+					ANKI_CHECK(m_variants[idx].init(key, *this, loader));
+				}
+			}
+		}
 	}
 
 	return ErrorCode::NONE;
 }
 
 //==============================================================================
-ShaderPtr Material::getShader(const RenderingKey& key, ShaderType type) const
+Error Material::createProgramSourceToCache(
+	const String& source, StringAuto& out)
 {
-	ANKI_ASSERT(!(key.m_tessellation && !m_tessellation));
-	ANKI_ASSERT(key.m_lod < m_lodCount);
-	ANKI_ASSERT(!(key.m_pass == Pass::SM && !m_shadow));
+	auto alloc = getTempAllocator();
+
+	// Create the hash
+	U64 h = computeHash(&source[0], source.getLength());
 
-	ShaderResourcePtr resource =
-		m_shaders[U(key.m_pass)][key.m_lod][key.m_tessellation][U(type)];
-	return resource->getGrShader();
+	// Create out
+	out.sprintf("mtl_%llu.glsl", h);
+
+	// Create path
+	StringAuto fullFname(alloc);
+	fullFname.sprintf("%s/%s",
+		&getManager()._getCacheDirectory()[0],
+		&out[0]);
+
+	// If file not exists write it
+	if(!fileExists(fullFname.toCString()))
+	{
+		// If not create it
+		File f;
+		ANKI_CHECK(f.open(fullFname.toCString(), File::OpenFlag::WRITE));
+		ANKI_CHECK(f.writeText("%s\n", &source[0]));
+	}
+
+	return ErrorCode::NONE;
 }
 
 //==============================================================================
@@ -470,4 +444,23 @@ void Material::fillResourceGroupInitializer(ResourceGroupInitializer& rcinit)
 	}
 }
 
+//==============================================================================
+const MaterialVariant& Material::getVariant(const RenderingKey& key) const
+{
+	U16 idx = m_variantMatrix[U(key.m_pass)][key.m_lod][key.m_tessellation][
+		getInstanceGroupIdx(key.m_instanceCount)];
+
+	ANKI_ASSERT(idx != MAX_U16);
+	return m_variants[idx];
+}
+
+//==============================================================================
+U Material::getInstanceGroupIdx(U instanceCount)
+{
+	ANKI_ASSERT(instanceCount > 0);
+	instanceCount = nextPowerOfTwo(instanceCount);
+	ANKI_ASSERT(instanceCount <= MAX_INSTANCES);
+	return log2(instanceCount);
+}
+
 } // end namespace anki

+ 354 - 192
src/resource/MaterialProgramCreator.cpp → src/resource/MaterialLoader.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include "anki/resource/MaterialProgramCreator.h"
+#include "anki/resource/MaterialLoader.h"
 #include "anki/util/Assert.h"
 #include "anki/misc/Xml.h"
 #include "anki/util/Logger.h"
@@ -18,11 +18,8 @@ namespace anki {
 
 //==============================================================================
 /// Given a string return info about the shader
-static ANKI_USE_RESULT Error getShaderInfo(
-	const CString& str,
-	ShaderType& type,
-	ShaderTypeBit& bit,
-	U& idx)
+static ANKI_USE_RESULT Error getShaderInfo(const CString& str, ShaderType& type,
+	ShaderTypeBit& bit, U& idx)
 {
 	Error err = ErrorCode::NONE;
 
@@ -66,7 +63,7 @@ static ANKI_USE_RESULT Error getShaderInfo(
 }
 
 //==============================================================================
-ANKI_USE_RESULT Error computeShaderVariableDataType(
+static ANKI_USE_RESULT Error computeShaderVariableDataType(
 	const CString& str, ShaderVariableDataType& out)
 {
 	Error err = ErrorCode::NONE;
@@ -117,16 +114,86 @@ ANKI_USE_RESULT Error computeShaderVariableDataType(
 }
 
 //==============================================================================
-// MaterialProgramCreator                                                      =
+static ANKI_USE_RESULT Error computeBuiltin(const CString& name,
+	BuiltinMaterialVariableId& out)
+{
+	if(name == "anki_mvp")
+	{
+		out = BuiltinMaterialVariableId::MVP_MATRIX;
+	}
+	else if(name == "anki_mv")
+	{
+		out = BuiltinMaterialVariableId::MV_MATRIX;
+	}
+	else if(name == "anki_vp")
+	{
+		out = BuiltinMaterialVariableId::VP_MATRIX;
+	}
+	else if(name == "anki_n")
+	{
+		out = BuiltinMaterialVariableId::NORMAL_MATRIX;
+	}
+	else if(name == "anki_billboardMvp")
+	{
+		out = BuiltinMaterialVariableId::BILLBOARD_MVP_MATRIX;
+	}
+	else if(name == "anki_tessLevel")
+	{
+		out = BuiltinMaterialVariableId::MAX_TESS_LEVEL;
+	}
+	else if(name == "anki_msDepthRt")
+	{
+		out = BuiltinMaterialVariableId::MS_DEPTH_MAP;
+	}
+	else if(name.find("anki_") == 0)
+	{
+		ANKI_LOGE("Unknown builtin %s", &name[0]);
+		return ErrorCode::USER_DATA;
+	}
+	else
+	{
+		out = BuiltinMaterialVariableId::NONE;
+	}
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+// MaterialLoaderInputVariable                                                 =
+//==============================================================================
+
 //==============================================================================
+void MaterialLoaderInputVariable::move(MaterialLoaderInputVariable& b)
+{
+	m_alloc = std::move(b.m_alloc);
+	m_name = std::move(b.m_name);
+	m_typeStr = std::move(b.m_typeStr);
+	m_type = b.m_type;
+	m_value = std::move(b.m_value);
+	m_constant = b.m_constant;
+	m_instanced = b.m_instanced;
+	m_builtin = b.m_builtin;
+	m_line = std::move(b.m_line);
+	m_shaderDefinedMask = b.m_shaderDefinedMask;
+	m_shaderReferencedMask = b.m_shaderReferencedMask;
+	m_inBlock = b.m_inBlock;
+	m_binding = b.m_binding;
+	m_index = b.m_index;
+	m_blockInfo = b.m_blockInfo;
+	m_inShadow = b.m_inShadow;
+}
 
 //==============================================================================
-MaterialProgramCreator::MaterialProgramCreator(TempResourceAllocator<U8> alloc)
+// MaterialLoader                                                              =
+//==============================================================================
+
+//==============================================================================
+MaterialLoader::MaterialLoader(TempResourceAllocator<U8> alloc)
 	: m_alloc(alloc)
 {}
 
 //==============================================================================
-MaterialProgramCreator::~MaterialProgramCreator()
+MaterialLoader::~MaterialLoader()
 {
 	for(auto& it : m_source)
 	{
@@ -139,12 +206,63 @@ MaterialProgramCreator::~MaterialProgramCreator()
 	}
 
 	m_inputs.destroy(m_alloc);
-
 	m_uniformBlock.destroy(m_alloc);
 }
 
 //==============================================================================
-Error MaterialProgramCreator::parseProgramsTag(const XmlElement& el)
+Error MaterialLoader::parseXmlDocument(const XmlDocument& doc)
+{
+	XmlElement materialEl, el;
+
+	// <levelsOfDetail>
+	ANKI_CHECK(doc.getChildElement("material", materialEl));
+
+	// <levelsOfDetail>
+	ANKI_CHECK(materialEl.getChildElementOptional("levelsOfDetail", el));
+	if(el)
+	{
+		I64 tmp;
+		ANKI_CHECK(el.getI64(tmp));
+		m_lodCount = (tmp < 1) ? 1 : tmp;
+	}
+	else
+	{
+		m_lodCount = 1;
+	}
+
+	if(m_lodCount > MAX_LODS)
+	{
+		ANKI_LOGW("Too many <levelsOfDetail>");
+		m_lodCount = MAX_LODS;
+	}
+
+	// <shadow>
+	ANKI_CHECK(materialEl.getChildElementOptional("shadow", el));
+	if(el)
+	{
+		I64 tmp;
+		ANKI_CHECK(el.getI64(tmp));
+		m_shadow = tmp;
+	}
+
+	// <forwardShading>
+	ANKI_CHECK(materialEl.getChildElementOptional("forwardShading", el));
+	if(el)
+	{
+		I64 tmp;
+		ANKI_CHECK(el.getI64(tmp));
+		m_forwardShading = tmp;
+	}
+
+	// <programs>
+	ANKI_CHECK(materialEl.getChildElement("programs", el));
+	ANKI_CHECK(parseProgramsTag(el));
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+Error MaterialLoader::parseProgramsTag(const XmlElement& el)
 {
 	//
 	// First gather all the inputs
@@ -179,7 +297,7 @@ Error MaterialProgramCreator::parseProgramsTag(const XmlElement& el)
 	// Sanity checks
 	//
 
-	// Check that all input is referenced
+	// Check that all inputs are referenced
 	for(auto& in : m_inputs)
 	{
 		if(in.m_shaderDefinedMask != in.m_shaderReferencedMask)
@@ -190,22 +308,11 @@ Error MaterialProgramCreator::parseProgramsTag(const XmlElement& el)
 		}
 	}
 
-	//
-	// Merge strings
-	//
-	for(U i = 0; i < m_sourceBaked.getSize(); i++)
-	{
-		if(!m_source[i].isEmpty())
-		{
-			m_source[i].join(m_alloc, "\n", m_sourceBaked[i]);
-		}
-	}
-
 	return ErrorCode::NONE;
 }
 
 //==============================================================================
-Error MaterialProgramCreator::parseProgramTag(
+Error MaterialLoader::parseProgramTag(
 	const XmlElement& programEl)
 {
 	XmlElement el;
@@ -251,7 +358,7 @@ Error MaterialProgramCreator::parseProgramTag(
 	{
 		lines.pushBackSprintf(m_alloc,
 			"\nlayout(UBO_BINDING(0, 0), std140, row_major) "
-			"uniform bDefaultBlock\n{");
+			"uniform _ublk00\n{");
 
 		for(auto& str : m_uniformBlock)
 		{
@@ -294,7 +401,7 @@ Error MaterialProgramCreator::parseProgramTag(
 }
 
 //==============================================================================
-Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
+Error MaterialLoader::parseInputsTag(const XmlElement& programEl)
 {
 	XmlElement el;
 	CString cstr;
@@ -325,6 +432,18 @@ Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
 		ANKI_CHECK(el.getText(cstr));
 		inpvar.m_name.create(m_alloc, cstr);
 
+		ANKI_CHECK(computeBuiltin(inpvar.m_name.toCString(), inpvar.m_builtin));
+
+		// Check if instanced
+		if(inpvar.m_builtin == BuiltinMaterialVariableId::MVP_MATRIX
+			|| inpvar.m_builtin == BuiltinMaterialVariableId::NORMAL_MATRIX
+			|| inpvar.m_builtin
+				== BuiltinMaterialVariableId::BILLBOARD_MVP_MATRIX)
+		{
+			inpvar.m_instanced = true;
+			m_instanced = true;
+		}
+
 		// <type>
 		ANKI_CHECK(inputEl.getChildElement("type", el));
 		ANKI_CHECK(el.getText(cstr));
@@ -332,21 +451,34 @@ Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
 		ANKI_CHECK(computeShaderVariableDataType(cstr, inpvar.m_type));
 
 		// <value>
-		XmlElement valueEl;
-		ANKI_CHECK(inputEl.getChildElement("value", valueEl));
-		ANKI_CHECK(valueEl.getText(cstr));
+		ANKI_CHECK(inputEl.getChildElement("value", el));
+		ANKI_CHECK(el.getText(cstr));
 		if(cstr)
 		{
 			inpvar.m_value.splitString(m_alloc, cstr, ' ');
+
+			if(inpvar.m_builtin != BuiltinMaterialVariableId::NONE)
+			{
+				ANKI_LOGE("Builtins cannot have value %s", &inpvar.m_name[0]);
+				return ErrorCode::USER_DATA;
+			}
+		}
+		else
+		{
+			if(inpvar.m_builtin == BuiltinMaterialVariableId::NONE)
+			{
+				ANKI_LOGE("Non-builtins should have a value %s",
+					&inpvar.m_name[0]);
+				return ErrorCode::USER_DATA;
+			}
 		}
 
 		// <const>
-		XmlElement constEl;
-		ANKI_CHECK(inputEl.getChildElementOptional("const", constEl));
-		if(constEl)
+		ANKI_CHECK(inputEl.getChildElementOptional("const", el));
+		if(el)
 		{
 			I64 tmp;
-			ANKI_CHECK(constEl.getI64(tmp));
+			ANKI_CHECK(el.getI64(tmp));
 			inpvar.m_constant = tmp;
 		}
 		else
@@ -354,53 +486,24 @@ Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
 			inpvar.m_constant = false;
 		}
 
-		// <arraySize>
-		XmlElement arrSizeEl;
-		ANKI_CHECK(inputEl.getChildElementOptional("arraySize", arrSizeEl));
-		if(arrSizeEl)
+		if(inpvar.m_builtin != BuiltinMaterialVariableId::NONE
+			&& inpvar.m_constant)
 		{
-			I64 tmp;
-			ANKI_CHECK(arrSizeEl.getI64(tmp));
-			if(tmp <= 0)
-			{
-				ANKI_LOGE("Incorrect arraySize value %d", tmp);
-				return ErrorCode::USER_DATA;
-			}
-
-			inpvar.m_arraySize = tmp;
-		}
-		else
-		{
-			inpvar.m_arraySize = 1;
+			ANKI_LOGE("Builtins cannot be consts %s", &inpvar.m_name[0]);
+			return ErrorCode::USER_DATA;
 		}
 
-		// <instanced>
-		XmlElement instancedEl;
-		ANKI_CHECK(
-			inputEl.getChildElementOptional("instanced", instancedEl));
-
-		if(instancedEl)
+		// <inShadow>
+		ANKI_CHECK(inputEl.getChildElementOptional("inShadow", el));
+		if(el)
 		{
 			I64 tmp;
-			ANKI_CHECK(instancedEl.getI64(tmp));
-			inpvar.m_instanced = tmp;
-
-			if(inpvar.m_instanced && inpvar.m_arraySize == 1)
-			{
-				ANKI_LOGE("On instanced the array size should be >1");
-				return ErrorCode::USER_DATA;
-			}
+			ANKI_CHECK(el.getI64(tmp));
+			inpvar.m_inShadow = tmp;
 		}
 		else
 		{
-			inpvar.m_instanced = false;
-		}
-
-		// If one input var is instanced notify the whole program that
-		// it's instanced
-		if(inpvar.m_instanced)
-		{
-			m_instanced = true;
+			inpvar.m_inShadow = true;
 		}
 
 		// Now you have the info to check if duplicate
@@ -440,124 +543,42 @@ Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
 			goto advance;
 		}
 
-		if(inpvar.m_constant == false)
+		if(!inpvar.m_constant)
 		{
 			// Handle NON-consts
 
-			inpvar.m_line.sprintf(
-				m_alloc, "%s %s", &inpvar.m_typeStr[0], &inpvar.m_name[0]);
-
-			if(inpvar.m_arraySize > 1)
-			{
-				String tmp;
-				tmp.sprintf(m_alloc, "[%uU]", inpvar.m_arraySize);
-				inpvar.m_line.append(m_alloc, tmp);
-				tmp.destroy(m_alloc);
-			}
-
-			inpvar.m_line.append(m_alloc, ";");
-
-			// Can put it block
 			if(inpvar.m_type >= ShaderVariableDataType::SAMPLERS_FIRST
 				&& inpvar.m_type <= ShaderVariableDataType::SAMPLERS_LAST)
 			{
-				String tmp;
-
-				tmp.sprintf(
-					m_alloc, "layout(binding = %u) uniform %s",
-					m_texBinding, &inpvar.m_line[0]);
-
-				inpvar.m_line.destroy(m_alloc);
-				inpvar.m_line = std::move(tmp);
+				// Not in block
 
 				inpvar.m_inBlock = false;
-				inpvar.m_binding = m_texBinding;
-				++m_texBinding;
+				inpvar.m_binding = m_texBinding++;
+
+				inpvar.m_line.sprintf(
+					m_alloc, "layout(TEX_BINDING(0, %u)) uniform tex%u",
+					inpvar.m_binding, inpvar.m_binding);
 			}
 			else
 			{
 				// In block
-				String tmp;
 
-				tmp.create(m_alloc, inpvar.m_line);
-				m_uniformBlock.emplaceBack(m_alloc);
-				m_uniformBlock.getBack() = std::move(tmp);
-
-				m_uniformBlockReferencedMask |= glshaderbit;
 				inpvar.m_inBlock = true;
+				inpvar.m_index = m_nextIndex++;
+				m_uniformBlockReferencedMask |= glshaderbit;
 
-				// std140 rules
-				inpvar.m_offset = m_blockSize;
+				inpvar.m_line.sprintf(m_alloc,
+					"#if %s\n"
+					"%s var%u %s\n;"
+					"#endif",
+					inpvar.m_inShadow ? "SHADOW" : "1",
+					&inpvar.m_typeStr[0],
+					inpvar.m_index,
+					inpvar.m_instanced ? "[INSTANCE_COUNT]" : " ");
 
-				if(inpvar.m_type == ShaderVariableDataType::FLOAT)
-				{
-					inpvar.m_arrayStride = sizeof(Vec4);
-
-					if(inpvar.m_arraySize == 1)
-					{
-						// No need to align the inpvar.m_offset
-						m_blockSize += sizeof(F32);
-					}
-					else
-					{
-						alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-						m_blockSize += sizeof(Vec4) * inpvar.m_arraySize;
-					}
-				}
-				else if(inpvar.m_type == ShaderVariableDataType::VEC2)
-				{
-					inpvar.m_arrayStride = sizeof(Vec4);
-
-					if(inpvar.m_arraySize == 1)
-					{
-						alignRoundUp(sizeof(Vec2), inpvar.m_offset);
-						m_blockSize += sizeof(Vec2);
-					}
-					else
-					{
-						alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-						m_blockSize += sizeof(Vec4) * inpvar.m_arraySize;
-					}
-				}
-				else if(inpvar.m_type == ShaderVariableDataType::VEC3)
-				{
-					alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-					inpvar.m_arrayStride = sizeof(Vec4);
-
-					if(inpvar.m_arraySize == 1)
-					{
-						m_blockSize += sizeof(Vec3);
-					}
-					else
-					{
-						m_blockSize += sizeof(Vec4) * inpvar.m_arraySize;
-					}
-				}
-				else if(inpvar.m_type == ShaderVariableDataType::VEC4)
-				{
-					inpvar.m_arrayStride = sizeof(Vec4);
-					alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-					m_blockSize += sizeof(Vec4) * inpvar.m_arraySize;
-				}
-				else if(inpvar.m_type == ShaderVariableDataType::MAT3)
-				{
-					alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-					inpvar.m_arrayStride = sizeof(Vec4) * 3;
-					m_blockSize += sizeof(Vec4) * 3 * inpvar.m_arraySize;
-					inpvar.m_matrixStride = sizeof(Vec4);
-				}
-				else if(inpvar.m_type == ShaderVariableDataType::MAT4)
-				{
-					alignRoundUp(sizeof(Vec4), inpvar.m_offset);
-					inpvar.m_arrayStride = sizeof(Mat4);
-					m_blockSize += sizeof(Mat4) * inpvar.m_arraySize;
-					inpvar.m_matrixStride = sizeof(Vec4);
-				}
-				else
-				{
-					ANKI_LOGE("Unsupported type %s", &inpvar.m_typeStr[0]);
-					return ErrorCode::USER_DATA;
-				}
+				String tmp;
+				tmp.create(m_alloc, inpvar.m_line);
+				m_uniformBlock.emplaceBack(m_alloc, std::move(tmp));
 			}
 		}
 		else
@@ -570,20 +591,19 @@ Error MaterialProgramCreator::parseInputsTag(const XmlElement& programEl)
 				return ErrorCode::USER_DATA;
 			}
 
-			if(inpvar.m_arraySize > 1)
-			{
-				ANKI_LOGE("Const arrays currently cannot be handled");
-				return ErrorCode::USER_DATA;
-			}
-
 			inpvar.m_inBlock = false;
+			inpvar.m_index = m_nextIndex++;
 
 			String initList;
 			inpvar.m_value.join(m_alloc, ", ", initList);
 
-			inpvar.m_line.sprintf(m_alloc, "const %s %s = %s(%s);",
-				&inpvar.m_typeStr[0], &inpvar.m_name[0], &inpvar.m_typeStr[0],
+			inpvar.m_line.sprintf(m_alloc,
+				"const %s var%u = %s(%s);",
+				&inpvar.m_typeStr[0],
+				&inpvar.m_index,
+				&inpvar.m_typeStr[0],
 				&initList[0]);
+
 			initList.destroy(m_alloc);
 		}
 
@@ -601,7 +621,7 @@ advance:
 }
 
 //==============================================================================
-Error MaterialProgramCreator::parseOperationTag(
+Error MaterialLoader::parseOperationTag(
 	const XmlElement& operationTag,
 	ShaderType glshader,
 	ShaderTypeBit glshaderbit,
@@ -673,15 +693,19 @@ Error MaterialProgramCreator::parseOperationTag(
 			{
 				ANKI_CHECK(argEl.getText(cstr));
 
-				if(glshader == ShaderType::VERTEX)
-				{
-					argsList.pushBackSprintf("%s [gl_InstanceID]", &cstr[0]);
-
-					m_instanceIdMask |= glshaderbit;
-				}
-				else if(glshader == ShaderType::FRAGMENT)
+				if(glshader == ShaderType::VERTEX
+					|| glshader == ShaderType::FRAGMENT)
 				{
-					argsList.pushBackSprintf("%s [vInstanceId]", &cstr[0]);
+					argsList.pushBackSprintf(
+						"%s%u\n"
+						"#if INSTANCE_COUNT > 1\n"
+						"[%s]\n"
+						"#endif",
+						input->m_binding >= 0 ? "tex" : "var",
+						input->m_binding >= 0
+							? input->m_binding : input->m_index,
+						(glshader == ShaderType::VERTEX)
+							? "gl_InstanceID" : "vInstanceId");
 
 					m_instanceIdMask |= glshaderbit;
 				}
@@ -746,4 +770,142 @@ Error MaterialProgramCreator::parseOperationTag(
 	return ErrorCode::NONE;
 }
 
+//==============================================================================
+void MaterialLoader::mutate(const RenderingKey& key)
+{
+	U instanceCount = key.m_instanceCount;
+	Bool tessellation = key.m_tessellation;
+	U lod = key.m_lod;
+	Pass pass = key.m_pass;
+
+	// Preconditions
+	if(tessellation)
+	{
+		ANKI_ASSERT(m_tessellation);
+	}
+
+	if(instanceCount > 1)
+	{
+		ANKI_ASSERT(m_instanced);
+	}
+
+	// Compute the block info for each var
+	m_blockSize = 0;
+	for(Input& inpvar : m_inputs)
+	{
+		// Invalidate the var's block info
+		inpvar.m_blockInfo = ShaderVariableBlockInfo();
+
+		if(!inpvar.m_inBlock)
+		{
+			continue;
+		}
+
+		if(pass == Pass::SM && !inpvar.m_inShadow)
+		{
+			continue;
+		}
+
+		// std140 rules
+		inpvar.m_blockInfo.m_offset = m_blockSize;
+		inpvar.m_blockInfo.m_arraySize = inpvar.m_instanced ? instanceCount : 1;
+
+		if(inpvar.m_type == ShaderVariableDataType::FLOAT)
+		{
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(inpvar.m_blockInfo.m_arraySize == 1)
+			{
+				// No need to align the inpvar.m_offset
+				m_blockSize += sizeof(F32);
+			}
+			else
+			{
+				alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+				m_blockSize += sizeof(Vec4) * inpvar.m_blockInfo.m_arraySize;
+			}
+		}
+		else if(inpvar.m_type == ShaderVariableDataType::VEC2)
+		{
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(inpvar.m_blockInfo.m_arraySize == 1)
+			{
+				alignRoundUp(sizeof(Vec2), inpvar.m_blockInfo.m_offset);
+				m_blockSize += sizeof(Vec2);
+			}
+			else
+			{
+				alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+				m_blockSize += sizeof(Vec4) * inpvar.m_blockInfo.m_arraySize;
+			}
+		}
+		else if(inpvar.m_type == ShaderVariableDataType::VEC3)
+		{
+			alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(inpvar.m_blockInfo.m_arraySize == 1)
+			{
+				m_blockSize += sizeof(Vec3);
+			}
+			else
+			{
+				m_blockSize += sizeof(Vec4) * inpvar.m_blockInfo.m_arraySize;
+			}
+		}
+		else if(inpvar.m_type == ShaderVariableDataType::VEC4)
+		{
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Vec4);
+			alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+			m_blockSize += sizeof(Vec4) * inpvar.m_blockInfo.m_arraySize;
+		}
+		else if(inpvar.m_type == ShaderVariableDataType::MAT3)
+		{
+			alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Vec4) * 3;
+			m_blockSize += sizeof(Vec4) * 3 * inpvar.m_blockInfo.m_arraySize;
+			inpvar.m_blockInfo.m_matrixStride = sizeof(Vec4);
+		}
+		else if(inpvar.m_type == ShaderVariableDataType::MAT4)
+		{
+			alignRoundUp(sizeof(Vec4), inpvar.m_blockInfo.m_offset);
+			inpvar.m_blockInfo.m_arrayStride = sizeof(Mat4);
+			m_blockSize += sizeof(Mat4) * inpvar.m_blockInfo.m_arraySize;
+			inpvar.m_blockInfo.m_matrixStride = sizeof(Vec4);
+		}
+		else
+		{
+			ANKI_ASSERT(0);
+		}
+	}
+
+	// Update the defines
+	StringAuto defines(m_alloc);
+	defines.sprintf(
+		"#define INSTANCE_COUNT %u\n"
+		"#define TESSELATION %u\n"
+		"#define LOD %u\n"
+		"#define PASS %s\n",
+		instanceCount,
+		tessellation,
+		lod,
+		(pass == Pass::SM) ? "DEPTH" : "COLOR");
+
+	// Merge strings
+	for(U i = 0; i < m_sourceBaked.getSize(); i++)
+	{
+		if(!m_source[i].isEmpty())
+		{
+			m_sourceBaked[i].destroy(m_alloc);
+
+			String tmp;
+			m_source[i].join(m_alloc, "\n", tmp);
+
+			m_sourceBaked[i].sprintf(m_alloc, "%s%s", &defines[0], &tmp[0]);
+			tmp.destroy(m_alloc);
+		}
+	}
+}
+
 } // end namespace anki

+ 15 - 7
src/resource/Model.cpp

@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015, Panagiotis Christopoulos Charitos.
+// Copyright (C) 2009-2015, Panagiotis Christopoulos Cinclude/anki/resource/Model.h
 // All rights reserved.
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
@@ -127,31 +127,39 @@ PipelinePtr ModelPatch::getPipeline(const RenderingKey& key) const
 		ANKI_ASSERT(0);
 	}
 
+	if(key.m_instanceCount > 1 && !m_mtl->isInstanced())
+	{
+		ANKI_ASSERT(0);
+	}
+
 	LockGuard<SpinLock> lock(m_lock);
 
-	PipelinePtr& ppline =
-		m_pplines[U(key.m_pass)][key.m_lod][key.m_tessellation];
+	PipelinePtr& ppline = m_pplines[U(key.m_pass)][key.m_lod]
+		[key.m_tessellation]
+		[Material::getInstanceGroupIdx(key.m_instanceCount)];
 
 	// Lazily create it
 	if(ANKI_UNLIKELY(!ppline.isCreated()))
 	{
+		const MaterialVariant& variant = m_mtl->getVariant(key);
+
 		PipelineInitializer pplineInit;
 		computePipelineInitializer(key, pplineInit);
 
 		pplineInit.m_shaders[U(ShaderType::VERTEX)] =
-			m_mtl->getShader(key, ShaderType::VERTEX);
+			variant.getShader(ShaderType::VERTEX);
 
 		if(key.m_tessellation)
 		{
 			pplineInit.m_shaders[U(ShaderType::TESSELLATION_CONTROL)] =
-				m_mtl->getShader(key, ShaderType::TESSELLATION_CONTROL);
+				variant.getShader(ShaderType::TESSELLATION_CONTROL);
 
 			pplineInit.m_shaders[U(ShaderType::TESSELLATION_EVALUATION)] =
-				m_mtl->getShader(key, ShaderType::TESSELLATION_EVALUATION);
+				variant.getShader(ShaderType::TESSELLATION_EVALUATION);
 		}
 
 		pplineInit.m_shaders[U(ShaderType::FRAGMENT)] =
-			m_mtl->getShader(key, ShaderType::FRAGMENT);
+			variant.getShader(ShaderType::FRAGMENT);
 
 		// Create
 		ppline = m_model->getManager().getGrManager()

+ 7 - 4
src/resource/ParticleEmitterResource.cpp

@@ -256,11 +256,14 @@ Error ParticleEmitterResource::load(const ResourceFilename& filename)
 	m_lodCount = m_material->getLodCount();
 	for(U i = 0; i < m_lodCount; ++i)
 	{
-		pinit.m_shaders[U(ShaderType::VERTEX)] = m_material->getShader(
-			RenderingKey(Pass::MS_FS, i, false), ShaderType::VERTEX);
+		RenderingKey key(Pass::MS_FS, i, false, 1);
+		const MaterialVariant& variant = m_material->getVariant(key);
 
-		pinit.m_shaders[U(ShaderType::FRAGMENT)] = m_material->getShader(
-			RenderingKey(Pass::MS_FS, i, false), ShaderType::FRAGMENT);
+		pinit.m_shaders[U(ShaderType::VERTEX)] =
+			variant.getShader(ShaderType::VERTEX);
+
+		pinit.m_shaders[U(ShaderType::FRAGMENT)] =
+			variant.getShader(ShaderType::FRAGMENT);
 
 		m_pplines[i] = getManager().getGrManager().newInstance<Pipeline>(pinit);
 	}

+ 0 - 1
src/resource/ResourceManager.cpp

@@ -64,7 +64,6 @@ Error ResourceManager::create(Initializer& init)
 	ANKI_RESOURCE(ShaderResource)
 	ANKI_RESOURCE(Material)
 	ANKI_RESOURCE(Mesh)
-	ANKI_RESOURCE(BucketMesh)
 	ANKI_RESOURCE(Skeleton)
 	ANKI_RESOURCE(ParticleEmitterResource)
 	ANKI_RESOURCE(Model)

+ 1 - 1
src/resource/ResourceObject.cpp

@@ -18,7 +18,7 @@ ResourceObject::ResourceObject(ResourceManager* manager)
 //==============================================================================
 ResourceObject::~ResourceObject()
 {
-	m_uuid.destroy(getAllocator());
+	m_fname.destroy(getAllocator());
 }
 
 //==============================================================================

+ 17 - 168
src/scene/ModelNode.cpp

@@ -23,27 +23,29 @@ namespace anki {
 class ModelPatchRenderComponent: public RenderComponent
 {
 public:
-	ModelPatchNode* m_node;
+	const ModelPatchNode& getNode() const
+	{
+		return static_cast<const ModelPatchNode&>(getSceneNode());
+	}
 
 	ModelPatchRenderComponent(ModelPatchNode* node)
-		: RenderComponent(node, &node->m_modelPatch->getMaterial())
-		, m_node(node)
+		: RenderComponent(node, &node->m_modelPatch->getMaterial(),
+			node->m_modelPatch->getMaterial().getUuid())
 	{}
 
 	ANKI_USE_RESULT Error buildRendering(
-		RenderingBuildData& data) const override
-	{
-		return m_node->buildRendering(data);
-	}
-
-	void getRenderWorldTransform(U index, Transform& trf) const override
+		RenderingBuildInfo& data) const override
 	{
-		m_node->getRenderWorldTransform(index, trf);
+		return getNode().buildRendering(data);
 	}
 
-	Bool getHasWorldTransforms() const override
+	void getRenderWorldTransform(Bool& hasTransform,
+		Transform& trf) const override
 	{
-		return true;
+		hasTransform = true;
+		const SceneNode* node = getNode().getParent();
+		ANKI_ASSERT(node);
+		trf = node->getComponent<MoveComponent>().getWorldTransform();
 	}
 };
 
@@ -58,14 +60,7 @@ ModelPatchNode::ModelPatchNode(SceneGraph* scene)
 
 //==============================================================================
 ModelPatchNode::~ModelPatchNode()
-{
-	for(SpatialComponent* sp : m_spatials)
-	{
-		getSceneAllocator().deleteInstance(sp);
-	}
-
-	m_spatials.destroy(getSceneAllocator());
-}
+{}
 
 //==============================================================================
 Error ModelPatchNode::create(const CString& name,
@@ -94,14 +89,12 @@ Error ModelPatchNode::create(const CString& name,
 }
 
 //==============================================================================
-Error ModelPatchNode::buildRendering(RenderingBuildData& data) const
+Error ModelPatchNode::buildRendering(RenderingBuildInfo& data) const
 {
 	// That will not work on multi-draw and instanced at the same time. Make
 	// sure that there is no multi-draw anywhere
 	ANKI_ASSERT(m_modelPatch->getSubMeshesCount() == 0);
 
-	auto instancesCount = data.m_subMeshIndicesCount;
-
 	Array<U32, ANKI_GL_MAX_SUB_DRAWCALLS> indicesCountArray;
 	Array<PtrSize, ANKI_GL_MAX_SUB_DRAWCALLS> indicesOffsetArray;
 	U32 drawcallCount;
@@ -129,94 +122,12 @@ Error ModelPatchNode::buildRendering(RenderingBuildData& data) const
 	U32 offset = indicesOffsetArray[0] / sizeof(U16);
 	data.m_cmdb->drawElements(
 		indicesCountArray[0],
-		instancesCount,
+		data.m_key.m_instanceCount,
 		offset);
 
 	return ErrorCode::NONE;
 }
 
-//==============================================================================
-void ModelPatchNode::getRenderWorldTransform(U index, Transform& trf) const
-{
-	const SceneNode* parent = getParent();
-	ANKI_ASSERT(parent);
-	const MoveComponent& move = parent->getComponent<MoveComponent>();
-
-	if(index == 0)
-	{
-		// Asking for the first instance which is this
-		trf = move.getWorldTransform();
-	}
-	else
-	{
-		// Asking for a next instance
-		const SceneNode* parent = getParent();
-		ANKI_ASSERT(parent);
-		const ModelNode* mnode = staticCastPtr<const ModelNode*>(parent);
-
-		--index;
-		trf = mnode->m_transforms[index];
-	}
-}
-
-//==============================================================================
-void ModelPatchNode::updateInstanceSpatials(
-	const MoveComponent* instanceMoves[],
-	U32 instanceMovesCount)
-{
-	Bool fullUpdate = false;
-
-	const U oldSize = m_spatials.getSize();
-	const U newSize = instanceMovesCount;
-
-	if(oldSize < newSize)
-	{
-		// We need to add spatials
-
-		fullUpdate = true;
-
-		m_spatials.resize(getSceneAllocator(), newSize);
-
-		U diff = newSize - oldSize;
-		U index = oldSize;
-		while(diff-- != 0)
-		{
-			ObbSpatialComponent* newSpatial = getSceneAllocator().
-				newInstance<ObbSpatialComponent>(this);
-
-			addComponent(newSpatial);
-			m_spatials[index++] = newSpatial;
-		}
-	}
-	else if(oldSize > newSize)
-	{
-		// Need to remove spatials
-
-		fullUpdate = true;
-
-		// TODO
-		ANKI_ASSERT(0 && "TODO");
-	}
-
-	U count = newSize;
-	const Obb& localBoundingShape = m_modelPatch->getBoundingShape();
-	while(count-- != 0)
-	{
-		ObbSpatialComponent& sp = *m_spatials[count];
-		ANKI_ASSERT(count < instanceMovesCount);
-		const MoveComponent& inst = *instanceMoves[count];
-
-		if(sp.getTimestamp() < inst.getTimestamp() || fullUpdate)
-		{
-			sp.m_obb = localBoundingShape.getTransformed(
-				inst.getWorldTransform());
-
-			sp.markForUpdate();
-			sp.setSpatialOrigin(inst.getWorldTransform().getOrigin());
-		}
-	}
-}
-
 //==============================================================================
 // ModelMoveFeedbackComponent                                                  =
 //==============================================================================
@@ -252,14 +163,12 @@ public:
 //==============================================================================
 ModelNode::ModelNode(SceneGraph* scene)
 	: SceneNode(scene)
-	, m_transformsTimestamp(0)
 {}
 
 //==============================================================================
 ModelNode::~ModelNode()
 {
 	m_modelPatches.destroy(getSceneAllocator());
-	m_transforms.destroy(getSceneAllocator());
 }
 
 //==============================================================================
@@ -299,66 +208,6 @@ Error ModelNode::create(const CString& name, const CString& modelFname)
 	return ErrorCode::NONE;
 }
 
-//==============================================================================
-Error ModelNode::frameUpdate(F32, F32)
-{
-	// Gather the move components of the instances
-	DArrayAuto<const MoveComponent*> instanceMoves(getFrameAllocator());
-	U instanceMovesCount = 0;
-	Timestamp instancesTimestamp = 0;
-
-	instanceMoves.create(64);
-
-	Error err = visitChildren([&](SceneNode& sn) -> Error
-	{
-		if(sn.tryGetComponent<InstanceComponent>())
-		{
-			MoveComponent& move = sn.getComponent<MoveComponent>();
-
-			instanceMoves[instanceMovesCount++] = &move;
-
-			instancesTimestamp =
-				max(instancesTimestamp, move.getTimestamp());
-		}
-
-		return ErrorCode::NONE;
-	});
-	(void)err;
-
-	// If having instances
-	if(instanceMovesCount > 0)
-	{
-		Bool fullUpdate = false;
-
-		if(instanceMovesCount != m_transforms.getSize())
-		{
-			fullUpdate = true;
-			m_transforms.resize(getSceneAllocator(), instanceMovesCount);
-		}
-
-		if(fullUpdate || m_transformsTimestamp < instancesTimestamp)
-		{
-			m_transformsTimestamp = instancesTimestamp;
-
-			for(U i = 0; i < instanceMovesCount; ++i)
-			{
-				m_transforms[i] = instanceMoves[i]->getWorldTransform();
-			}
-		}
-
-		// Update children
-		auto it = m_modelPatches.getBegin();
-		auto end = m_modelPatches.getEnd();
-		for(; it != end; ++it)
-		{
-			(*it)->updateInstanceSpatials(
-				&instanceMoves[0], instanceMovesCount);
-		}
-	}
-
-	return ErrorCode::NONE;
-}
-
 //==============================================================================
 void ModelNode::onMoveComponentUpdate(MoveComponent& move)
 {

+ 14 - 173
src/scene/ParticleEmitter.cpp

@@ -197,28 +197,27 @@ void Particle::revive(const ParticleEmitter& pe,
 class ParticleEmitterRenderComponent: public RenderComponent
 {
 public:
-	ParticleEmitter* m_node = nullptr;
+	const ParticleEmitter& getNode() const
+	{
+		return static_cast<const ParticleEmitter&>(getSceneNode());
+	}
 
 	ParticleEmitterRenderComponent(ParticleEmitter* node)
 		: RenderComponent(node,
 			&node->m_particleEmitterResource->getMaterial())
-		, m_node(node)
 	{}
 
 	ANKI_USE_RESULT Error buildRendering(
-		RenderingBuildData& data) const override
-	{
-		return m_node->buildRendering(data);
-	}
-
-	void getRenderWorldTransform(U index, Transform& trf) const override
+		RenderingBuildInfo& data) const override
 	{
-		m_node->getRenderWorldTransform(index, trf);
+		return getNode().buildRendering(data);
 	}
 
-	Bool getHasWorldTransforms() const override
+	void getRenderWorldTransform(Bool& hasTransform,
+		Transform& trf) const override
 	{
-		return true;
+		hasTransform = true;
+		trf = getNode().getComponent<MoveComponent>().getWorldTransform();
 	}
 };
 
@@ -237,7 +236,7 @@ public:
 	ANKI_USE_RESULT Error update(
 		SceneNode& node, F32, F32, Bool& updated) override
 	{
-		updated = false;
+		updated = false; // Don't care about updates for this component
 
 		MoveComponent& move = node.getComponent<MoveComponent>();
 		if(move.getTimestamp() == node.getGlobalTimestamp())
@@ -271,7 +270,6 @@ ParticleEmitter::~ParticleEmitter()
 	}
 
 	m_particles.destroy(getSceneAllocator());
-	m_transforms.destroy(getSceneAllocator());
 }
 
 //==============================================================================
@@ -352,9 +350,9 @@ Error ParticleEmitter::create(
 }
 
 //==============================================================================
-Error ParticleEmitter::buildRendering(RenderingBuildData& data) const
+Error ParticleEmitter::buildRendering(RenderingBuildInfo& data) const
 {
-	ANKI_ASSERT(data.m_subMeshIndicesCount <= m_transforms.getSize() + 1);
+	ANKI_ASSERT(data.m_subMeshIndicesCount == 1);
 
 	if(m_aliveParticlesCount == 0)
 	{
@@ -558,164 +556,7 @@ Error ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime)
 		m_timeLeftForNextEmission -= crntTime - prevUpdateTime;
 	}
 
-	// Do something more
-	return doInstancingCalcs();
-}
-
-//==============================================================================
-Error ParticleEmitter::doInstancingCalcs()
-{
-	Error err = ErrorCode::NONE;
-
-#if 0
-	//
-	// Gather the move components of the instances
-	//
-	SceneFrameDArrayAuto<MoveComponent*> instanceMoves(
-		getFrameAllocator());
-	U instanceMovesCount = 0;
-	Timestamp instancesTimestamp = 0;
-
-	err = instanceMoves.create(64);
-	if(err)	return err;
-
-	err = SceneNode::visitChildren([&](SceneNode& sn) -> Error
-	{
-		if(sn.tryGetComponent<InstanceComponent>())
-		{
-			MoveComponent& move = sn.getComponent<MoveComponent>();
-
-			instanceMoves[instanceMovesCount++] = &move;
-
-			instancesTimestamp =
-				std::max(instancesTimestamp, move.getTimestamp());
-		}
-
-		return ErrorCode::NONE;
-	});
-
-	//
-	// If instancing
-	//
-	if(instanceMovesCount > 0)
-	{
-		Bool transformsNeedUpdate = false;
-
-		// Check if an instance was added or removed and reset the spatials and
-		// the transforms
-		if(instanceMovesCount != m_transforms.getSize())
-		{
-			transformsNeedUpdate = true;
-
-			// Check if instances added or removed
-			if(m_transforms.getSize() < instanceMovesCount)
-			{
-				// Instances added
-
-				U diff = instanceMovesCount - m_transforms.getSize();
-
-				while(diff-- != 0)
-				{
-					ObbSpatialComponent* newSpatial = getSceneAllocator().
-						newInstance<ObbSpatialComponent>(this);
-
-					if(newSpatial == nullptr)
-					{
-						err = ErrorCode::OUT_OF_MEMORY;
-						break;
-					}
-
-					err = addComponent(newSpatial);
-					if(err)
-					{
-						break;
-					}
-				}
-			}
-			else
-			{
-				// Instances removed
-
-				// TODO
-				ANKI_ASSERT(0 && "TODO");
-			}
-
-			err = m_transforms.resize(getSceneAllocator(), instanceMovesCount);
-		}
-
-		if(!err && (transformsNeedUpdate
-			|| m_transformsTimestamp < instancesTimestamp))
-		{
-			m_transformsTimestamp = instancesTimestamp;
-
-			// Update the transforms
-			for(U i = 0; i < instanceMovesCount; i++)
-			{
-				m_transforms[i] = instanceMoves[i]->getWorldTransform();
-			}
-		}
-
-		// Update the spatials anyway
-		if(!err)
-		{
-			U count = 0;
-			SpatialComponent* meSpatial = this;
-			err = iterateComponentsOfType<SpatialComponent>(
-				[&](SpatialComponent& sp) -> Error
-			{
-				Error err2 = ErrorCode::NONE;
-
-				// Skip the first
-				if(&sp != meSpatial)
-				{
-					ObbSpatialComponent* msp =
-						staticCastPtr<ObbSpatialComponent*>(&sp);
-
-					if(msp)
-					{
-						Obb aobb = m_obb;
-						aobb.setCenter(Vec4(0.0));
-						msp->m_obb = aobb.getTransformed(m_transforms[count]);
-						++count;
-						msp->markForUpdate();
-					}
-					else
-					{
-						err2 = ErrorCode::OUT_OF_MEMORY;
-					}
-				}
-
-				return err2;
-			});
-
-			ANKI_ASSERT(count == m_transforms.getSize());
-		}
-	} // end if instancing
-#endif
-
-	return err;
-}
-
-//==============================================================================
-void ParticleEmitter::getRenderWorldTransform(U index, Transform& trf) const
-{
-	if(index == 0)
-	{
-		// Don't transform the particle positions. They are already in world
-		// space
-		trf = Transform::getIdentity();
-	}
-	else
-	{
-		--index;
-		ANKI_ASSERT(index < m_transforms.getSize());
-
-		// The particle positions are already in word space. Move them back to
-		// local space
-		const MoveComponent& move = getComponent<MoveComponent>();
-		Transform invTrf = move.getWorldTransform().getInverse();
-		trf = m_transforms[index].combineTransformations(invTrf);
-	}
+	return ErrorCode::NONE;
 }
 
 } // end namespace anki

+ 10 - 46
src/scene/RenderComponent.cpp

@@ -20,34 +20,24 @@ struct CreateNewRenderComponentVariableVisitor
 {
 	const MaterialVariable* m_mvar = nullptr;
 	mutable RenderComponent::Variables* m_vars = nullptr;
-	mutable U32* m_count = nullptr;
+	mutable U32 m_count = 0;
 	mutable SceneAllocator<U8> m_alloc;
 
 	template<typename TMaterialVariableTemplate>
-	Error visit(const TMaterialVariableTemplate&) const
+	Error visit(const TMaterialVariableTemplate& mvart) const
 	{
 		using Type = typename TMaterialVariableTemplate::Type;
 
 		RenderComponentVariableTemplate<Type>* rvar =
 			m_alloc.newInstance<RenderComponentVariableTemplate<Type>>(m_mvar);
 
-		(*m_vars)[(*m_count)++] = rvar;
+		rvar->setValue(mvart.getValue());
+
+		(*m_vars)[m_count++] = rvar;
 		return ErrorCode::NONE;
 	}
 };
 
-/// The names of the buildins
-static Array<const char*, (U)BuildinMaterialVariableId::COUNT - 1>
-	buildinNames = {{
-	"uMvp",
-	"uMv",
-	"uVp",
-	"uN",
-	"uBillboardMvp",
-	"uMaxTessLevel",
-	"uBlurring",
-	"uMsDepthMap"}};
-
 //==============================================================================
 // RenderComponentVariable                                                     =
 //==============================================================================
@@ -58,27 +48,6 @@ RenderComponentVariable::RenderComponentVariable(
 	: m_mvar(mvar)
 {
 	ANKI_ASSERT(m_mvar);
-
-	// Set buildin id
-	CString name = getName();
-
-	m_buildinId = BuildinMaterialVariableId::NO_BUILDIN;
-	for(U i = 0; i < buildinNames.getSize(); i++)
-	{
-		if(name == buildinNames[i])
-		{
-			m_buildinId = (BuildinMaterialVariableId)(i + 1);
-			break;
-		}
-	}
-
-	// Sanity checks
-	if(!m_mvar->hasValues()
-		&& m_buildinId == BuildinMaterialVariableId::NO_BUILDIN)
-	{
-		ANKI_LOGW("Material variable no buildin and not initialized: %s",
-			&name[0]);
-	}
 }
 
 //==============================================================================
@@ -90,9 +59,10 @@ RenderComponentVariable::~RenderComponentVariable()
 //==============================================================================
 
 //==============================================================================
-RenderComponent::RenderComponent(SceneNode* node, const Material* mtl)
+RenderComponent::RenderComponent(SceneNode* node, const Material* mtl, U64 hash)
 	: SceneComponent(Type::RENDER, node)
 	, m_mtl(mtl)
+	, m_hash(hash)
 {}
 
 //==============================================================================
@@ -101,7 +71,6 @@ RenderComponent::~RenderComponent()
 	auto alloc = m_node->getSceneAllocator();
 	for(RenderComponentVariable* var : m_vars)
 	{
-		var->destroy(alloc);
 		alloc.deleteInstance(var);
 	}
 
@@ -115,19 +84,14 @@ Error RenderComponent::create()
 	auto alloc = m_node->getSceneAllocator();
 
 	// Create the material variables using a visitor
+	m_vars.create(alloc, mtl.getVariables().getSize());
+
 	CreateNewRenderComponentVariableVisitor vis;
-	U32 count = 0;
 	vis.m_vars = &m_vars;
-	vis.m_count = &count;
 	vis.m_alloc = alloc;
 
-	m_vars.create(alloc, mtl.getVariables().getSize());
-
-	auto it = mtl.getVariables().getBegin();
-	auto end = mtl.getVariables().getEnd();
-	for(; it != end; it++)
+	for(const MaterialVariable* mv : mtl.getVariables())
 	{
-		const MaterialVariable* mv = (*it);
 		vis.m_mvar = mv;
 		ANKI_CHECK(mv->acceptVisitor(vis));
 	}

+ 2 - 2
src/scene/StaticGeometryNode.cpp

@@ -24,7 +24,7 @@ public:
 		, m_node(node)
 	{}
 
-	Error buildRendering(RenderingBuildData& data) const override
+	Error buildRendering(RenderingBuildInfo& data) const override
 	{
 		return m_node->buildRendering(data);
 	}
@@ -75,7 +75,7 @@ Error StaticGeometryPatchNode::create(
 }
 
 //==============================================================================
-Error StaticGeometryPatchNode::buildRendering(RenderingBuildData& data) const
+Error StaticGeometryPatchNode::buildRendering(RenderingBuildInfo& data) const
 {
 	Array<U32, ANKI_GL_MAX_SUB_DRAWCALLS> indicesCountArray;
 	Array<PtrSize, ANKI_GL_MAX_SUB_DRAWCALLS> indicesOffsetArray;

+ 1 - 1
src/ui/CMakeLists.txt

@@ -1,4 +1,4 @@
 file(GLOB ANKI_UI_SOURCES *.cpp)
 
 add_library(ankiui ${ANKI_UI_SOURCES})
-target_link_libraries(ankiui ankiresource)
+target_link_libraries(ankiui ankiresource FREETYPE_LIB)

+ 16 - 0
tests/resource/MaterialLoader.cpp

@@ -0,0 +1,16 @@
+// Copyright (C) 2009-2015, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "tests/framework/Framework.h"
+#include "anki/resource/MaterialLoader.h"
+
+namespace anki {
+
+//==============================================================================
+ANKI_TEST(Resource, MaterialLoader)
+{
+}
+
+} // end namespace anki