Browse Source

Merge pull request #58 from godlikepanos/shadercompiler

Shadercompiler part 1: Implement but don't integrate
Panagiotis Christopoulos Charitos 6 years ago
parent
commit
da373d4c14
72 changed files with 5431 additions and 930 deletions
  1. 3 2
      CMakeLists.txt
  2. 1 0
      src/anki/AnKi.h
  3. 5 0
      src/anki/Gr.h
  4. 10 0
      src/anki/ShaderCompiler.h
  5. 1 0
      src/anki/Util.h
  6. 1 1
      src/anki/core/StagingGpuMemoryManager.h
  7. 1 1
      src/anki/gr/CMakeLists.txt
  8. 15 0
      src/anki/gr/Enums.h
  9. 0 51
      src/anki/gr/Shader.h
  10. 0 36
      src/anki/gr/common/Misc.h
  11. 1 1
      src/anki/gr/gl/StateTracker.h
  12. 1 1
      src/anki/gr/gl/TextureImpl.h
  13. 1 1
      src/anki/gr/utils/ClassGpuAllocator.cpp
  14. 0 0
      src/anki/gr/utils/ClassGpuAllocator.h
  15. 2 2
      src/anki/gr/utils/FrameGpuAllocator.cpp
  16. 0 0
      src/anki/gr/utils/FrameGpuAllocator.h
  17. 1 1
      src/anki/gr/utils/Functions.cpp
  18. 88 0
      src/anki/gr/utils/Functions.h
  19. 0 0
      src/anki/gr/utils/InstantiationMacros.h
  20. 1 1
      src/anki/gr/utils/StackGpuAllocator.cpp
  21. 0 0
      src/anki/gr/utils/StackGpuAllocator.h
  22. 1 1
      src/anki/gr/vulkan/GpuMemoryManager.h
  23. 1 1
      src/anki/gr/vulkan/Pipeline.cpp
  24. 1 1
      src/anki/gr/vulkan/ShaderImpl.cpp
  25. 1 1
      src/anki/gr/vulkan/TextureImpl.cpp
  26. 1 1
      src/anki/gr/vulkan/TextureImpl.h
  27. 1 1
      src/anki/gr/vulkan/VulkanObject.cpp
  28. 1 1
      src/anki/gr/vulkan/VulkanObject.h
  29. 1 1
      src/anki/resource/ShaderProgramPreProcessor.h
  30. 1 1
      src/anki/resource/ShaderProgramResource.cpp
  31. 1 1
      src/anki/resource/TransferGpuAllocator.h
  32. 712 687
      src/anki/script/lua_glue_gen.py
  33. 2 0
      src/anki/shader_compiler/CMakeLists.txt
  34. 37 0
      src/anki/shader_compiler/Common.h
  35. 271 0
      src/anki/shader_compiler/Glslang.cpp
  36. 75 0
      src/anki/shader_compiler/Glslang.h
  37. 216 0
      src/anki/shader_compiler/ShaderProgramBinary.h
  38. 64 0
      src/anki/shader_compiler/ShaderProgramBinary.xml
  39. 78 0
      src/anki/shader_compiler/ShaderProgramBinaryExtra.h
  40. 597 0
      src/anki/shader_compiler/ShaderProgramCompiler.cpp
  41. 74 0
      src/anki/shader_compiler/ShaderProgramCompiler.h
  42. 1451 0
      src/anki/shader_compiler/ShaderProgramParser.cpp
  43. 319 0
      src/anki/shader_compiler/ShaderProgramParser.h
  44. 18 12
      src/anki/util/BitSet.h
  45. 1 1
      src/anki/util/CMakeLists.txt
  46. 19 0
      src/anki/util/DynamicArray.h
  47. 23 0
      src/anki/util/File.cpp
  48. 3 0
      src/anki/util/File.h
  49. 24 7
      src/anki/util/Functions.h
  50. 1 1
      src/anki/util/HighRezTimerWindows.cpp
  51. 11 39
      src/anki/util/Memory.cpp
  52. 52 17
      src/anki/util/Memory.h
  53. 45 0
      src/anki/util/Serializer.cpp
  54. 198 0
      src/anki/util/Serializer.h
  55. 234 0
      src/anki/util/Serializer.inl.h
  56. 50 6
      src/anki/util/Singleton.h
  57. 2 0
      src/anki/util/StdTypes.h
  58. 38 40
      src/anki/util/String.cpp
  59. 19 3
      src/anki/util/String.h
  60. 1 1
      src/anki/util/WeakArray.h
  61. 243 0
      src/anki/util/serializer.py
  62. 1 1
      tests/gr/ClassGpuAllocator.cpp
  63. 1 1
      tests/gr/StackGpuAllocator.cpp
  64. 156 0
      tests/shader_compiler/ShaderProgramCompiler.cpp
  65. 76 0
      tests/shader_compiler/ShaderProgramParser.cpp
  66. 1 1
      tests/util/HashMap.cpp
  67. 2 2
      tests/util/HighRezTimer.cpp
  68. 71 0
      tests/util/Serializer.cpp
  69. 75 0
      tests/util/SerializerTest.h
  70. 24 0
      tests/util/SerializerTest.xml
  71. 3 2
      tests/util/SparseArray.cpp
  72. 1 1
      tools/count_lines.sh

+ 3 - 2
CMakeLists.txt

@@ -180,7 +180,7 @@ if(NOT MSVC)
 	if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
 		add_definitions("-O3 -DNDEBUG")
 	elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo")
-		add_definitions("-O3 -g3")
+		add_definitions("-O3 -g3 -fno-omit-frame-pointer")
 	elseif(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
 		add_definitions("-O0 -g3")
 	else()
@@ -412,7 +412,8 @@ set(THIRD_PARTY_LIBS ${THIRD_PARTY_LIBS} BulletSoftBody BulletDynamics BulletCol
 	ankiimgui)
 
 # Add anki sub libraries
-set(ANKI_SUB_DIRS importer core script renderer scene ui input physics resource misc gr collision math util)
+set(ANKI_SUB_DIRS importer core script renderer scene ui input physics resource misc gr collision math util
+	shader_compiler)
 foreach(TMP ${ANKI_SUB_DIRS})
 	add_subdirectory(src/anki/${TMP})
 endforeach()

+ 1 - 0
src/anki/AnKi.h

@@ -17,3 +17,4 @@
 #include <anki/Gr.h>
 #include <anki/Core.h>
 #include <anki/Importer.h>
+#include <anki/ShaderCompiler.h>

+ 5 - 0
src/anki/Gr.h

@@ -20,6 +20,11 @@
 #include <anki/gr/RenderGraph.h>
 #include <anki/gr/ShaderCompiler.h>
 
+#include <anki/gr/utils/ClassGpuAllocator.h>
+#include <anki/gr/utils/FrameGpuAllocator.h>
+#include <anki/gr/utils/Functions.h>
+#include <anki/gr/utils/StackGpuAllocator.h>
+
 /// @defgroup graphics Graphics API abstraction
 
 /// @defgroup opengl OpenGL backend

+ 10 - 0
src/anki/ShaderCompiler.h

@@ -0,0 +1,10 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/shader_compiler/ShaderProgramCompiler.h>
+
+/// @defgroup shader_compiler Shader compiler

+ 1 - 0
src/anki/Util.h

@@ -38,6 +38,7 @@
 #include <anki/util/SparseArray.h>
 #include <anki/util/ObjectAllocator.h>
 #include <anki/util/Tracer.h>
+#include <anki/util/Serializer.h>
 
 /// @defgroup util Utilities (like STL)
 

+ 1 - 1
src/anki/core/StagingGpuMemoryManager.h

@@ -7,7 +7,7 @@
 
 #include <anki/core/Common.h>
 #include <anki/gr/Buffer.h>
-#include <anki/gr/common/FrameGpuAllocator.h>
+#include <anki/gr/utils/FrameGpuAllocator.h>
 
 namespace anki
 {

+ 1 - 1
src/anki/gr/CMakeLists.txt

@@ -1,4 +1,4 @@
-file(GLOB SOURCES *.cpp common/*.cpp)
+file(GLOB SOURCES *.cpp utils/*.cpp)
 
 if(GL)
 	set(GR_BACKEND "gl")

+ 15 - 0
src/anki/gr/Enums.h

@@ -469,6 +469,12 @@ enum class ShaderTypeBit : U8
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(ShaderTypeBit, inline)
 
+inline ShaderTypeBit shaderTypeToBit(ShaderType type)
+{
+	ANKI_ASSERT(type < ShaderType::COUNT);
+	return ShaderTypeBit(1 << U32(type));
+}
+
 enum class ShaderVariableDataType : U8
 {
 	NONE,
@@ -499,6 +505,15 @@ enum class ShaderVariableDataType : U8
 	NUMERICS_FIRST = INT,
 	NUMERICS_LAST = MAT4,
 
+	NUMERIC_1_COMPONENT_FIRST = INT,
+	NUMERIC_1_COMPONENT_LAST = FLOAT,
+	NUMERIC_2_COMPONENT_FIRST = IVEC2,
+	NUMERIC_2_COMPONENT_LAST = VEC2,
+	NUMERIC_3_COMPONENT_FIRST = IVEC3,
+	NUMERIC_3_COMPONENT_LAST = VEC3,
+	NUMERIC_4_COMPONENT_FIRST = IVEC4,
+	NUMERIC_4_COMPONENT_LAST = VEC4,
+
 	MATRIX_FIRST = MAT3,
 	MATRIX_LAST = MAT4,
 

+ 0 - 51
src/anki/gr/Shader.h

@@ -15,57 +15,6 @@ namespace anki
 /// @addtogroup graphics
 /// @{
 
-/// Using an AnKi typename get the ShaderVariableDataType. Used for debugging.
-template<typename T>
-ShaderVariableDataType getShaderVariableTypeFromTypename();
-
-#define ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(typename_, type_) \
-	template<> \
-	inline ShaderVariableDataType getShaderVariableTypeFromTypename<typename_>() \
-	{ \
-		return ShaderVariableDataType::type_; \
-	}
-
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(I32, INT)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec2, IVEC2)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec3, IVEC3)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec4, IVEC4)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(U32, UINT)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec2, UVEC2)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec3, UVEC3)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec4, UVEC4)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(F32, FLOAT)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec2, VEC2)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec3, VEC3)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec4, VEC4)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Mat3, MAT3)
-ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Mat4, MAT4)
-
-#undef ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET
-
-/// Shader block information.
-class ShaderVariableBlockInfo
-{
-public:
-	I16 m_offset = -1; ///< Offset inside the block
-
-	I16 m_arraySize = -1; ///< Number of elements.
-
-	/// Stride between the each array element if the variable is array.
-	I16 m_arrayStride = -1;
-
-	/// Identifying the stride between columns of a column-major matrix or rows of a row-major matrix.
-	I16 m_matrixStride = -1;
-};
-
-/// Populate the memory of a variable that is inside a shader block.
-void writeShaderBlockMemory(ShaderVariableDataType type,
-	const ShaderVariableBlockInfo& varBlkInfo,
-	const void* elements,
-	U32 elementsCount,
-	void* buffBegin,
-	const void* buffEnd);
-
 /// Specialization constant value.
 class ShaderSpecializationConstValue
 {

+ 0 - 36
src/anki/gr/common/Misc.h

@@ -1,36 +0,0 @@
-// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <anki/gr/Common.h>
-
-namespace anki
-{
-
-inline Bool stencilTestDisabled(StencilOperation stencilFail,
-	StencilOperation stencilPassDepthFail,
-	StencilOperation stencilPassDepthPass,
-	CompareOperation compare)
-{
-	return stencilFail == StencilOperation::KEEP && stencilPassDepthFail == StencilOperation::KEEP
-		   && stencilPassDepthPass == StencilOperation::KEEP && compare == CompareOperation::ALWAYS;
-}
-
-inline Bool blendingDisabled(BlendFactor srcFactorRgb,
-	BlendFactor dstFactorRgb,
-	BlendFactor srcFactorA,
-	BlendFactor dstFactorA,
-	BlendOperation opRgb,
-	BlendOperation opA)
-{
-	Bool dontWantBlend = srcFactorRgb == BlendFactor::ONE && dstFactorRgb == BlendFactor::ZERO
-						 && srcFactorA == BlendFactor::ONE && dstFactorA == BlendFactor::ZERO
-						 && (opRgb == BlendOperation::ADD || opRgb == BlendOperation::SUBTRACT)
-						 && (opA == BlendOperation::ADD || opA == BlendOperation::SUBTRACT);
-	return dontWantBlend;
-}
-
-} // end namespace anki

+ 1 - 1
src/anki/gr/gl/StateTracker.h

@@ -19,7 +19,7 @@
 #include <anki/gr/gl/ShaderProgramImpl.h>
 #include <anki/gr/Framebuffer.h>
 #include <anki/gr/gl/FramebufferImpl.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 
 namespace anki
 {

+ 1 - 1
src/anki/gr/gl/TextureImpl.h

@@ -7,7 +7,7 @@
 
 #include <anki/gr/Texture.h>
 #include <anki/gr/gl/GlObject.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 #include <anki/util/HashMap.h>
 
 namespace anki

+ 1 - 1
src/anki/gr/common/ClassGpuAllocator.cpp → src/anki/gr/utils/ClassGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/ClassGpuAllocator.h>
+#include <anki/gr/utils/ClassGpuAllocator.h>
 #include <anki/util/List.h>
 #include <anki/util/BitSet.h>
 

+ 0 - 0
src/anki/gr/common/ClassGpuAllocator.h → src/anki/gr/utils/ClassGpuAllocator.h


+ 2 - 2
src/anki/gr/common/FrameGpuAllocator.cpp → src/anki/gr/utils/FrameGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/FrameGpuAllocator.h>
+#include <anki/gr/utils/FrameGpuAllocator.h>
 
 namespace anki
 {
@@ -90,4 +90,4 @@ PtrSize FrameGpuAllocator::getUnallocatedMemorySize() const
 }
 #endif
 
-} // end namespace anki
+} // end namespace anki

+ 0 - 0
src/anki/gr/common/FrameGpuAllocator.h → src/anki/gr/utils/FrameGpuAllocator.h


+ 1 - 1
src/anki/gr/Shader.cpp → src/anki/gr/utils/Functions.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/Shader.h>
+#include <anki/gr/utils/Functions.h>
 
 namespace anki
 {

+ 88 - 0
src/anki/gr/utils/Functions.h

@@ -0,0 +1,88 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/gr/Common.h>
+#include <anki/Math.h>
+
+namespace anki
+{
+
+inline Bool stencilTestDisabled(StencilOperation stencilFail,
+	StencilOperation stencilPassDepthFail,
+	StencilOperation stencilPassDepthPass,
+	CompareOperation compare)
+{
+	return stencilFail == StencilOperation::KEEP && stencilPassDepthFail == StencilOperation::KEEP
+		   && stencilPassDepthPass == StencilOperation::KEEP && compare == CompareOperation::ALWAYS;
+}
+
+inline Bool blendingDisabled(BlendFactor srcFactorRgb,
+	BlendFactor dstFactorRgb,
+	BlendFactor srcFactorA,
+	BlendFactor dstFactorA,
+	BlendOperation opRgb,
+	BlendOperation opA)
+{
+	Bool dontWantBlend = srcFactorRgb == BlendFactor::ONE && dstFactorRgb == BlendFactor::ZERO
+						 && srcFactorA == BlendFactor::ONE && dstFactorA == BlendFactor::ZERO
+						 && (opRgb == BlendOperation::ADD || opRgb == BlendOperation::SUBTRACT)
+						 && (opA == BlendOperation::ADD || opA == BlendOperation::SUBTRACT);
+	return dontWantBlend;
+}
+
+/// Using an AnKi typename get the ShaderVariableDataType. Used for debugging.
+template<typename T>
+ShaderVariableDataType getShaderVariableTypeFromTypename();
+
+#define ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(typename_, type_) \
+	template<> \
+	inline ShaderVariableDataType getShaderVariableTypeFromTypename<typename_>() \
+	{ \
+		return ShaderVariableDataType::type_; \
+	}
+
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(I32, INT)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec2, IVEC2)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec3, IVEC3)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(IVec4, IVEC4)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(U32, UINT)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec2, UVEC2)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec3, UVEC3)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(UVec4, UVEC4)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(F32, FLOAT)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec2, VEC2)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec3, VEC3)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Vec4, VEC4)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Mat3, MAT3)
+ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET(Mat4, MAT4)
+
+#undef ANKI_SPECIALIZE_SHADER_VAR_TYPE_GET
+
+/// Shader block information.
+class ShaderVariableBlockInfo
+{
+public:
+	I16 m_offset = -1; ///< Offset inside the block
+
+	I16 m_arraySize = -1; ///< Number of elements.
+
+	/// Stride between the each array element if the variable is array.
+	I16 m_arrayStride = -1;
+
+	/// Identifying the stride between columns of a column-major matrix or rows of a row-major matrix.
+	I16 m_matrixStride = -1;
+};
+
+/// Populate the memory of a variable that is inside a shader block.
+void writeShaderBlockMemory(ShaderVariableDataType type,
+	const ShaderVariableBlockInfo& varBlkInfo,
+	const void* elements,
+	U32 elementsCount,
+	void* buffBegin,
+	const void* buffEnd);
+
+} // end namespace anki

+ 0 - 0
src/anki/gr/common/InstantiationMacros.h → src/anki/gr/utils/InstantiationMacros.h


+ 1 - 1
src/anki/gr/common/StackGpuAllocator.cpp → src/anki/gr/utils/StackGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/StackGpuAllocator.h>
+#include <anki/gr/utils/StackGpuAllocator.h>
 
 namespace anki
 {

+ 0 - 0
src/anki/gr/common/StackGpuAllocator.h → src/anki/gr/utils/StackGpuAllocator.h


+ 1 - 1
src/anki/gr/vulkan/GpuMemoryManager.h

@@ -5,7 +5,7 @@
 
 #pragma once
 
-#include <anki/gr/common/ClassGpuAllocator.h>
+#include <anki/gr/utils/ClassGpuAllocator.h>
 #include <anki/gr/vulkan/Common.h>
 
 namespace anki

+ 1 - 1
src/anki/gr/vulkan/Pipeline.cpp

@@ -5,7 +5,7 @@
 
 #include <anki/gr/vulkan/Pipeline.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 #include <anki/util/Tracer.h>
 
 namespace anki

+ 1 - 1
src/anki/gr/vulkan/ShaderImpl.cpp

@@ -5,7 +5,7 @@
 
 #include <anki/gr/vulkan/ShaderImpl.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 #include <SPIRV-Cross/spirv_cross.hpp>
 
 #define ANKI_DUMP_SHADERS ANKI_EXTRA_CHECKS

+ 1 - 1
src/anki/gr/vulkan/TextureImpl.cpp

@@ -9,7 +9,7 @@
 #include <anki/gr/vulkan/GrManagerImpl.h>
 #include <anki/gr/CommandBuffer.h>
 #include <anki/gr/vulkan/CommandBufferImpl.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 
 namespace anki
 {

+ 1 - 1
src/anki/gr/vulkan/TextureImpl.h

@@ -8,7 +8,7 @@
 #include <anki/gr/Texture.h>
 #include <anki/gr/vulkan/VulkanObject.h>
 #include <anki/gr/vulkan/GpuMemoryManager.h>
-#include <anki/gr/common/Misc.h>
+#include <anki/gr/utils/Functions.h>
 #include <anki/gr/vulkan/SamplerFactory.h>
 #include <anki/util/HashMap.h>
 

+ 1 - 1
src/anki/gr/vulkan/VulkanObject.cpp

@@ -39,7 +39,7 @@ namespace anki
 	}
 
 #define ANKI_INSTANTIATE_GR_OBJECT_DELIMITER()
-#include <anki/gr/common/InstantiationMacros.h>
+#include <anki/gr/utils/InstantiationMacros.h>
 #undef ANKI_INSTANTIATE_GR_OBJECT_DELIMITER
 #undef ANKI_INSTANTIATE_GR_OBJECT
 

+ 1 - 1
src/anki/gr/vulkan/VulkanObject.h

@@ -34,7 +34,7 @@ public:
 	template<> \
 	const GrManagerImpl& VulkanObject<type_, type_##Impl>::getGrManagerImpl() const;
 #define ANKI_INSTANTIATE_GR_OBJECT_DELIMITER()
-#include <anki/gr/common/InstantiationMacros.h>
+#include <anki/gr/utils/InstantiationMacros.h>
 #undef ANKI_INSTANTIATE_GR_OBJECT_DELIMITER
 #undef ANKI_INSTANTIATE_GR_OBJECT
 /// @}

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

@@ -148,7 +148,7 @@ public:
 		return m_inputs;
 	}
 
-	ShaderTypeBit getShaderStages() const
+	ShaderTypeBit getShaderTypes() const
 	{
 		return m_shaderTypes;
 	}

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

@@ -230,7 +230,7 @@ Error ShaderProgramResource::load(const ResourceFilename& filename, Bool async)
 
 	// Set some other vars
 	m_descriptorSet = U8(pp.getDescritproSet());
-	m_shaderStages = pp.getShaderStages();
+	m_shaderStages = pp.getShaderTypes();
 	if(instancedMutatorIdx != MAX_U)
 	{
 		m_instancingMutator = &m_mutators[instancedMutatorIdx];

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

@@ -6,7 +6,7 @@
 #pragma once
 
 #include <anki/resource/Common.h>
-#include <anki/gr/common/StackGpuAllocator.h>
+#include <anki/gr/utils/StackGpuAllocator.h>
 #include <anki/util/List.h>
 
 namespace anki

+ 712 - 687
src/anki/script/lua_glue_gen.py

@@ -13,769 +13,794 @@ import xml.etree.ElementTree as et
 identation_level = 0
 out_file = None
 
+
 def parse_commandline():
-	""" Parse the command line arguments """
+    """ Parse the command line arguments """
+
+    parser = optparse.OptionParser(usage="usage: %prog [options]", description="Create LUA bindings using XML")
 
-	parser = optparse.OptionParser(usage = "usage: %prog [options]", description = "Create LUA bindings using XML")
+    parser.add_option(
+        "-i", "--input", dest="inp", type="string", help="specify the XML files to parse. Seperate with :")
 
-	parser.add_option("-i", "--input", dest = "inp", type = "string",
-		help = "specify the XML files to parse. Seperate with :")
+    (options, args) = parser.parse_args()
 
-	(options, args) = parser.parse_args()
+    if not options.inp:
+        parser.error("argument is missing")
 
-	if not options.inp:
-		parser.error("argument is missing")
+    return options.inp.split(":")
 
-	return options.inp.split(":")
 
 def type_sig(value):
-	""" Calculate the signature of a type """
-	return hash(value)
+    """ Calculate the signature of a type """
+    return hash(value)
+
 
 def get_base_fname(path):
-	""" From path/to/a/file.ext return the "file" """
-	return os.path.splitext(os.path.basename(path))[0]
+    """ From path/to/a/file.ext return the "file" """
+    return os.path.splitext(os.path.basename(path))[0]
+
 
 def wglue(txt):
-	""" Write glue code to the output """
-	global out_file
-	global identation_level
-	out_file.write("%s%s\n" % ("\t" * identation_level, txt))
+    """ Write glue code to the output """
+    global out_file
+    global identation_level
+    out_file.write("%s%s\n" % ("\t" * identation_level, txt))
+
 
 def ident(number):
-	""" Increase or recrease identation for the wglue """
-	global identation_level
-	identation_level += number
+    """ Increase or recrease identation for the wglue """
+    global identation_level
+    identation_level += number
+
 
 def type_is_bool(type):
-	""" Check if a type is boolean """
+    """ Check if a type is boolean """
+
+    return type == "Bool" or type == "bool"
 
-	return type == "Bool" or type == "bool"
 
 def type_is_number(type):
-	""" Check if a type is number """
+    """ Check if a type is number """
+
+    numbers = [
+        "U8", "U16", "U32", "U64", "I8", "I16", "I32", "I64", "U", "I", "PtrSize", "F32", "F64", "int", "unsigned",
+        "unsigned int", "short", "unsigned short", "uint", "float", "double"
+    ]
 
-	numbers = ["U8", "U16", "U32", "U64", "I8", "I16", "I32", "I64", "U", "I", "PtrSize", "F32", "F64", \
-		"int", "unsigned", "unsigned int", "short", "unsigned short", "uint", "float", "double"]
+    it_is = False
+    for num in numbers:
+        if num == type:
+            it_is = True
+            break
 
-	it_is = False
-	for num in numbers:
-		if num == type:
-			it_is = True
-			break
+    return it_is
 
-	return it_is
 
 def parse_type_decl(arg_txt):
-	""" Parse an arg text """
+    """ Parse an arg text """
 
-	tokens = arg_txt.split(" ")
-	tokens_size = len(tokens)
+    tokens = arg_txt.split(" ")
+    tokens_size = len(tokens)
 
-	type = tokens[tokens_size - 1]
-	type_len = len(type)
-	is_ptr = False
-	is_ref = False
-	if type[type_len - 1] == "&":
-		is_ref = True
-	elif type[type_len - 1] == "*":
-		is_ptr = True
+    type = tokens[tokens_size - 1]
+    type_len = len(type)
+    is_ptr = False
+    is_ref = False
+    if type[type_len - 1] == "&":
+        is_ref = True
+    elif type[type_len - 1] == "*":
+        is_ptr = True
 
-	if is_ref or is_ptr:
-		type = type[:-1]
+    if is_ref or is_ptr:
+        type = type[:-1]
 
-	is_const = False
-	if tokens[0] == "const":
-		is_const = True
+    is_const = False
+    if tokens[0] == "const":
+        is_const = True
+
+    return (type, is_ref, is_ptr, is_const)
 
-	return (type, is_ref, is_ptr, is_const)
 
 def ret(ret_el):
-	""" Push return value """
-
-	if ret_el is None:
-		wglue("return 0;")
-		return
-
-	wglue("// Push return value")
-
-	type_txt = ret_el.text
-	(type, is_ref, is_ptr, is_const) = parse_type_decl(type_txt)
-
-	if is_ptr:
-		wglue("if(ANKI_UNLIKELY(ret == nullptr))")
-		wglue("{")
-		ident(1)
-		wglue("lua_pushstring(l, \"Glue code returned nullptr\");")
-		wglue("return -1;")
-		ident(-1)
-		wglue("}")
-		wglue("")
-
-	if type_is_bool(type):
-		wglue("lua_pushboolean(l, ret);")
-	elif type_is_number(type):
-		wglue("lua_pushnumber(l, lua_Number(ret));")
-	elif type == "char" or type == "CString":
-		wglue("lua_pushstring(l, &ret[0]);")
-	elif type == "Error":
-		wglue("if(ANKI_UNLIKELY(ret))")
-		wglue("{")
-		ident(1)
-		wglue("lua_pushstring(l, \"Glue code returned an error\");")
-		wglue("return -1;")
-		ident(-1)
-		wglue("}")
-		wglue("")
-		wglue("lua_pushnumber(l, lua_Number(ret));")
-	else:
-		if is_ptr or is_ref:
-		 	wglue("voidp = lua_newuserdata(l, sizeof(LuaUserData));")
-			wglue("ud = static_cast<LuaUserData*>(voidp);")
-			wglue("luaL_setmetatable(l, \"%s\");" % type)
-
-			wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
-			if is_ptr:
-				wglue("ud->initPointed(&luaUserDataTypeInfo%s, const_cast<%s*>(ret));" % (type, type))
-			elif is_ref:
-				wglue("ud->initPointed(&luaUserDataTypeInfo%s, const_cast<%s*>(&ret));" % (type, type))
-		else:
-			wglue("size = LuaUserData::computeSizeForGarbageCollected<%s>();" % type)
-			wglue("voidp = lua_newuserdata(l, size);")
-			wglue("luaL_setmetatable(l, \"%s\");" % type)
-
-			wglue("ud = static_cast<LuaUserData*>(voidp);")
-			wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
-			wglue("ud->initGarbageCollected(&luaUserDataTypeInfo%s);" % type)
-
-			wglue("::new(ud->getData<%s>()) %s(std::move(ret));" % (type, type))
-
-	wglue("")
-	wglue("return 1;")
+    """ Push return value """
+
+    if ret_el is None:
+        wglue("return 0;")
+        return
+
+    wglue("// Push return value")
+
+    type_txt = ret_el.text
+    (type, is_ref, is_ptr, is_const) = parse_type_decl(type_txt)
+
+    if is_ptr:
+        wglue("if(ANKI_UNLIKELY(ret == nullptr))")
+        wglue("{")
+        ident(1)
+        wglue("lua_pushstring(l, \"Glue code returned nullptr\");")
+        wglue("return -1;")
+        ident(-1)
+        wglue("}")
+        wglue("")
+
+    if type_is_bool(type):
+        wglue("lua_pushboolean(l, ret);")
+    elif type_is_number(type):
+        wglue("lua_pushnumber(l, lua_Number(ret));")
+    elif type == "char" or type == "CString":
+        wglue("lua_pushstring(l, &ret[0]);")
+    elif type == "Error":
+        wglue("if(ANKI_UNLIKELY(ret))")
+        wglue("{")
+        ident(1)
+        wglue("lua_pushstring(l, \"Glue code returned an error\");")
+        wglue("return -1;")
+        ident(-1)
+        wglue("}")
+        wglue("")
+        wglue("lua_pushnumber(l, lua_Number(ret));")
+    else:
+        if is_ptr or is_ref:
+            wglue("voidp = lua_newuserdata(l, sizeof(LuaUserData));")
+            wglue("ud = static_cast<LuaUserData*>(voidp);")
+            wglue("luaL_setmetatable(l, \"%s\");" % type)
+
+            wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
+            if is_ptr:
+                wglue("ud->initPointed(&luaUserDataTypeInfo%s, const_cast<%s*>(ret));" % (type, type))
+            elif is_ref:
+                wglue("ud->initPointed(&luaUserDataTypeInfo%s, const_cast<%s*>(&ret));" % (type, type))
+        else:
+            wglue("size = LuaUserData::computeSizeForGarbageCollected<%s>();" % type)
+            wglue("voidp = lua_newuserdata(l, size);")
+            wglue("luaL_setmetatable(l, \"%s\");" % type)
+
+            wglue("ud = static_cast<LuaUserData*>(voidp);")
+            wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
+            wglue("ud->initGarbageCollected(&luaUserDataTypeInfo%s);" % type)
+
+            wglue("::new(ud->getData<%s>()) %s(std::move(ret));" % (type, type))
+
+    wglue("")
+    wglue("return 1;")
+
 
 def arg(arg_txt, stack_index, index):
-	""" Write the pop code for a single argument """
-
-	(type, is_ref, is_ptr, is_const) = parse_type_decl(arg_txt)
-
-	if type_is_bool(type) or type_is_number(type):
-		wglue("%s arg%d;" % (type, index))
-		wglue("if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, %d, arg%d)))" % (stack_index, index))
-		wglue("{")
-		ident(1)
-		wglue("return -1;")
-		ident(-1)
-		wglue("}")
-	elif type == "char" or type == "CString":
-		wglue("const char* arg%d;" % index)
-		wglue("if(ANKI_UNLIKELY(LuaBinder::checkString(l, %d, arg%d)))" % (stack_index, index))
-		wglue("{")
-		ident(1)
-		wglue("return -1;")
-		ident(-1)
-		wglue("}")
-	else:
-		wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
-		wglue("if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, %d, luaUserDataTypeInfo%s, ud)))" % (stack_index, type))
-		wglue("{")
-		ident(1)
-		wglue("return -1;")
-		ident(-1)
-		wglue("}")
-		wglue("")
-
-		wglue("%s* iarg%d = ud->getData<%s>();" % (type, index, type))
-
-		if is_ptr:
-			wglue("%s arg%d(iarg%d);" % (arg_txt, index, index))
-		else:
-			wglue("%s arg%d(*iarg%d);" % (arg_txt, index, index))
+    """ Write the pop code for a single argument """
+
+    (type, is_ref, is_ptr, is_const) = parse_type_decl(arg_txt)
+
+    if type_is_bool(type) or type_is_number(type):
+        wglue("%s arg%d;" % (type, index))
+        wglue("if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, %d, arg%d)))" % (stack_index, index))
+        wglue("{")
+        ident(1)
+        wglue("return -1;")
+        ident(-1)
+        wglue("}")
+    elif type == "char" or type == "CString":
+        wglue("const char* arg%d;" % index)
+        wglue("if(ANKI_UNLIKELY(LuaBinder::checkString(l, %d, arg%d)))" % (stack_index, index))
+        wglue("{")
+        ident(1)
+        wglue("return -1;")
+        ident(-1)
+        wglue("}")
+    else:
+        wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % type)
+        wglue("if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, %d, luaUserDataTypeInfo%s, ud)))" % (stack_index, type))
+        wglue("{")
+        ident(1)
+        wglue("return -1;")
+        ident(-1)
+        wglue("}")
+        wglue("")
+
+        wglue("%s* iarg%d = ud->getData<%s>();" % (type, index, type))
+
+        if is_ptr:
+            wglue("%s arg%d(iarg%d);" % (arg_txt, index, index))
+        else:
+            wglue("%s arg%d(*iarg%d);" % (arg_txt, index, index))
 
 
 def args(args_el, stack_index):
-	""" Write the pop code for argument parsing and return the arg list """
+    """ Write the pop code for argument parsing and return the arg list """
 
-	if args_el is None:
-		return ""
+    if args_el is None:
+        return ""
 
-	wglue("// Pop arguments")
-	arg_index = 0
+    wglue("// Pop arguments")
+    arg_index = 0
 
-	# Do the work
-	args_str = ""
-	arg_index = 0
-	for arg_el in args_el.iter("arg"):
-		arg(arg_el.text, stack_index, arg_index)
-		args_str += "arg%d, " % arg_index
-		wglue("")
-		stack_index += 1
-		arg_index += 1
+    # Do the work
+    args_str = ""
+    arg_index = 0
+    for arg_el in args_el.iter("arg"):
+        arg(arg_el.text, stack_index, arg_index)
+        args_str += "arg%d, " % arg_index
+        wglue("")
+        stack_index += 1
+        arg_index += 1
 
-	if len(args_str) > 0:
-		args_str = args_str[:-2]
+    if len(args_str) > 0:
+        args_str = args_str[:-2]
+
+    return args_str
 
-	return args_str
 
 def count_args(args_el):
-	""" Count the number of arguments """
+    """ Count the number of arguments """
+
+    if args_el is None:
+        return 0
 
-	if args_el is None:
-		return 0
+    count = 0
+    for arg_el in args_el.iter("arg"):
+        count += 1
 
-	count = 0
-	for arg_el in args_el.iter("arg"):
-		count += 1
+    return count
 
-	return count
 
 def check_args(args_el, bias):
-	""" Check number of args. Call that first because it throws error """
+    """ Check number of args. Call that first because it throws error """
 
-	if args_el is not None:
-		count = bias + count_args(args_el)
-	else:
-		count = bias
+    if args_el is not None:
+        count = bias + count_args(args_el)
+    else:
+        count = bias
+
+    wglue("if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, %d)))" % count)
+    wglue("{")
+    ident(1)
+    wglue("return -1;")
+    ident(-1)
+    wglue("}")
+    wglue("")
 
-	wglue("if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, %d)))" % count)
-	wglue("{")
-	ident(1)
-	wglue("return -1;")
-	ident(-1)
-	wglue("}")
-	wglue("")
 
 def get_meth_alias(meth_el):
-	""" Return the method alias """
-
-	meth_name = meth_el.get("name")
-
-	if meth_name == "operator+":
-		meth_alias = "__add"
-	elif meth_name == "operator-":
-		meth_alias = "__sub"
-	elif meth_name == "operator*":
-		meth_alias = "__mul"
-	elif meth_name == "operator/":
-		meth_alias = "__div"
-	elif meth_name == "operator==":
-		meth_alias = "__eq"
-	elif meth_name == "operator<":
-		meth_alias = "__lt"
-	elif meth_name == "operator<=":
-		meth_alias = "__le"
-	elif meth_name == "operator>":
-		meth_alias = "__gt"
-	elif meth_name == "operator>=":
-		meth_alias = "__ge"
-	elif meth_name == "operator=":
-		meth_alias = "copy"
-	else:
-		meth_alias = meth_name
-
-	meth_alias_txt = meth_el.get("alias")
-	if meth_alias_txt is not None:
-		meth_alias = meth_alias_txt
-
-	return meth_alias
+    """ Return the method alias """
+
+    meth_name = meth_el.get("name")
+
+    if meth_name == "operator+":
+        meth_alias = "__add"
+    elif meth_name == "operator-":
+        meth_alias = "__sub"
+    elif meth_name == "operator*":
+        meth_alias = "__mul"
+    elif meth_name == "operator/":
+        meth_alias = "__div"
+    elif meth_name == "operator==":
+        meth_alias = "__eq"
+    elif meth_name == "operator<":
+        meth_alias = "__lt"
+    elif meth_name == "operator<=":
+        meth_alias = "__le"
+    elif meth_name == "operator>":
+        meth_alias = "__gt"
+    elif meth_name == "operator>=":
+        meth_alias = "__ge"
+    elif meth_name == "operator=":
+        meth_alias = "copy"
+    else:
+        meth_alias = meth_name
+
+    meth_alias_txt = meth_el.get("alias")
+    if meth_alias_txt is not None:
+        meth_alias = meth_alias_txt
+
+    return meth_alias
+
 
 def write_local_vars():
-	wglue("LuaUserData* ud;")
-	wglue("(void)ud;")
-	wglue("void* voidp;")
-	wglue("(void)voidp;")
-	wglue("PtrSize size;")
-	wglue("(void)size;")
-	wglue("")
+    wglue("LuaUserData* ud;")
+    wglue("(void)ud;")
+    wglue("void* voidp;")
+    wglue("(void)voidp;")
+    wglue("PtrSize size;")
+    wglue("(void)size;")
+    wglue("")
+
 
 def method(class_name, meth_el):
-	""" Handle a method """
-
-	meth_name = meth_el.get("name")
-	meth_alias = get_meth_alias(meth_el)
-
-	wglue("/// Pre-wrap method %s::%s." % (class_name, meth_name))
-	wglue("static inline int pwrap%s%s(lua_State* l)" % (class_name, meth_alias))
-	wglue("{")
-	ident(1)
-	write_local_vars()
-
-	check_args(meth_el.find("args"), 1)
-
-	# Get this pointer
-	wglue("// Get \"this\" as \"self\"")
-	wglue("if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfo%s, ud))" % class_name)
-	wglue("{")
-	ident(1)
-	wglue("return -1;")
-	ident(-1)
-	wglue("}")
-	wglue("")
-	wglue("%s* self = ud->getData<%s>();" % (class_name, class_name))
-	wglue("")
-
-	args_str = args(meth_el.find("args"), 2)
-
-	# Return value
-	ret_txt = None
-	ret_el = meth_el.find("return")
-	if ret_el is not None:
-		ret_txt = ret_el.text
-
-	# Method call
-	wglue("// Call the method")
-	call = meth_el.find("overrideCall")
-	if call is not None:
-		call = call.text
-
-	if call is not None:
-		wglue("%s" % call)
-	else:
-		if ret_txt is None:
-			wglue("self->%s(%s);" % (meth_name, args_str))
-		else:
-			wglue("%s ret = self->%s(%s);" % (ret_txt, meth_name, args_str))
-
-	wglue("")
-	ret(ret_el)
-
-	ident(-1)
-	wglue("}")
-	wglue("")
-
-	# Write the actual function
-	wglue("/// Wrap method %s::%s." % (class_name, meth_name))
-	wglue("static int wrap%s%s(lua_State* l)" % (class_name, meth_alias))
-	wglue("{")
-	ident(1)
-	wglue("int res = pwrap%s%s(l);" % (class_name, meth_alias))
-	wglue("if(res >= 0)")
-	wglue("{")
-	ident(1)
-	wglue("return res;")
-	ident(-1)
-	wglue("}")
-	wglue("")
-	wglue("lua_error(l);")
-	wglue("return 0;")
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Handle a method """
+
+    meth_name = meth_el.get("name")
+    meth_alias = get_meth_alias(meth_el)
+
+    wglue("/// Pre-wrap method %s::%s." % (class_name, meth_name))
+    wglue("static inline int pwrap%s%s(lua_State* l)" % (class_name, meth_alias))
+    wglue("{")
+    ident(1)
+    write_local_vars()
+
+    check_args(meth_el.find("args"), 1)
+
+    # Get this pointer
+    wglue("// Get \"this\" as \"self\"")
+    wglue("if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfo%s, ud))" % class_name)
+    wglue("{")
+    ident(1)
+    wglue("return -1;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+    wglue("%s* self = ud->getData<%s>();" % (class_name, class_name))
+    wglue("")
+
+    args_str = args(meth_el.find("args"), 2)
+
+    # Return value
+    ret_txt = None
+    ret_el = meth_el.find("return")
+    if ret_el is not None:
+        ret_txt = ret_el.text
+
+    # Method call
+    wglue("// Call the method")
+    call = meth_el.find("overrideCall")
+    if call is not None:
+        call = call.text
+
+    if call is not None:
+        wglue("%s" % call)
+    else:
+        if ret_txt is None:
+            wglue("self->%s(%s);" % (meth_name, args_str))
+        else:
+            wglue("%s ret = self->%s(%s);" % (ret_txt, meth_name, args_str))
+
+    wglue("")
+    ret(ret_el)
+
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    # Write the actual function
+    wglue("/// Wrap method %s::%s." % (class_name, meth_name))
+    wglue("static int wrap%s%s(lua_State* l)" % (class_name, meth_alias))
+    wglue("{")
+    ident(1)
+    wglue("int res = pwrap%s%s(l);" % (class_name, meth_alias))
+    wglue("if(res >= 0)")
+    wglue("{")
+    ident(1)
+    wglue("return res;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+    wglue("lua_error(l);")
+    wglue("return 0;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def static_method(class_name, meth_el):
-	""" Handle a static method """
-
-	meth_name = meth_el.get("name")
-	meth_alias = get_meth_alias(meth_el)
-
-	wglue("/// Pre-wrap static method %s::%s." % (class_name, meth_name))
-	wglue("static inline int pwrap%s%s(lua_State* l)" % (class_name, meth_alias))
-	wglue("{")
-	ident(1)
-	write_local_vars()
-
-	check_args(meth_el.find("args"), 0)
-
-	# Args
-	args_str = args(meth_el.find("args"), 1)
-
-	# Return value
-	ret_txt = None
-	ret_el = meth_el.find("return")
-	if ret_el is not None:
-		ret_txt = ret_el.text
-
-	# Method call
-	wglue("// Call the method")
-	if ret_txt is None:
-		wglue("%s::%s(%s);" % (class_name, meth_name, args_str))
-	else:
-		wglue("%s ret = %s::%s(%s);" % (ret_txt, class_name, meth_name, args_str))
-
-	wglue("")
-	ret(ret_el)
-
-	ident(-1)
-	wglue("}")
-	wglue("")
-
-	# Write the actual function
-	wglue("/// Wrap static method %s::%s." % (class_name, meth_name))
-	wglue("static int wrap%s%s(lua_State* l)" % (class_name, meth_alias))
-	wglue("{")
-	ident(1)
-	wglue("int res = pwrap%s%s(l);" % (class_name, meth_alias))
-	wglue("if(res >= 0)")
-	wglue("{")
-	ident(1)
-	wglue("return res;")
-	ident(-1)
-	wglue("}")
-	wglue("")
-	wglue("lua_error(l);")
-	wglue("return 0;")
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Handle a static method """
+
+    meth_name = meth_el.get("name")
+    meth_alias = get_meth_alias(meth_el)
+
+    wglue("/// Pre-wrap static method %s::%s." % (class_name, meth_name))
+    wglue("static inline int pwrap%s%s(lua_State* l)" % (class_name, meth_alias))
+    wglue("{")
+    ident(1)
+    write_local_vars()
+
+    check_args(meth_el.find("args"), 0)
+
+    # Args
+    args_str = args(meth_el.find("args"), 1)
+
+    # Return value
+    ret_txt = None
+    ret_el = meth_el.find("return")
+    if ret_el is not None:
+        ret_txt = ret_el.text
+
+    # Method call
+    wglue("// Call the method")
+    if ret_txt is None:
+        wglue("%s::%s(%s);" % (class_name, meth_name, args_str))
+    else:
+        wglue("%s ret = %s::%s(%s);" % (ret_txt, class_name, meth_name, args_str))
+
+    wglue("")
+    ret(ret_el)
+
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    # Write the actual function
+    wglue("/// Wrap static method %s::%s." % (class_name, meth_name))
+    wglue("static int wrap%s%s(lua_State* l)" % (class_name, meth_alias))
+    wglue("{")
+    ident(1)
+    wglue("int res = pwrap%s%s(l);" % (class_name, meth_alias))
+    wglue("if(res >= 0)")
+    wglue("{")
+    ident(1)
+    wglue("return res;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+    wglue("lua_error(l);")
+    wglue("return 0;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def constructor(constr_el, class_name, constructor_idx):
-	""" Handle constructor """
+    """ Handle constructor """
 
-	wglue("/// Pre-wrap constructor for %s." % (class_name))
-	wglue("static inline int pwrap%sCtor%d(lua_State* l)" % (class_name, constructor_idx))
-	wglue("{")
-	ident(1)
-	write_local_vars()
+    wglue("/// Pre-wrap constructor for %s." % (class_name))
+    wglue("static inline int pwrap%sCtor%d(lua_State* l)" % (class_name, constructor_idx))
+    wglue("{")
+    ident(1)
+    write_local_vars()
 
-	check_args(constr_el.find("args"), 0)
+    check_args(constr_el.find("args"), 0)
 
-	# Args
-	args_str = args(constr_el.find("args"), 1)
+    # Args
+    args_str = args(constr_el.find("args"), 1)
 
-	# Create new userdata
-	wglue("// Create user data")
+    # Create new userdata
+    wglue("// Create user data")
 
-	wglue("size = LuaUserData::computeSizeForGarbageCollected<%s>();" % class_name)
-	wglue("voidp = lua_newuserdata(l, size);")
-	wglue("luaL_setmetatable(l, luaUserDataTypeInfo%s.m_typeName);" % class_name)
-	wglue("ud = static_cast<LuaUserData*>(voidp);")
-	wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % class_name)
-	wglue("ud->initGarbageCollected(&luaUserDataTypeInfo%s);" % class_name)
-	wglue("::new(ud->getData<%s>()) %s(%s);" % (class_name, class_name, args_str))
-	wglue("")
+    wglue("size = LuaUserData::computeSizeForGarbageCollected<%s>();" % class_name)
+    wglue("voidp = lua_newuserdata(l, size);")
+    wglue("luaL_setmetatable(l, luaUserDataTypeInfo%s.m_typeName);" % class_name)
+    wglue("ud = static_cast<LuaUserData*>(voidp);")
+    wglue("extern LuaUserDataTypeInfo luaUserDataTypeInfo%s;" % class_name)
+    wglue("ud->initGarbageCollected(&luaUserDataTypeInfo%s);" % class_name)
+    wglue("::new(ud->getData<%s>()) %s(%s);" % (class_name, class_name, args_str))
+    wglue("")
 
-	wglue("return 1;")
+    wglue("return 1;")
+
+    ident(-1)
+    wglue("}")
+    wglue("")
 
-	ident(-1)
-	wglue("}")
-	wglue("")
 
 def constructors(constructors_el, class_name):
-	""" Wrap all constructors """
-
-	idx = 0
-	func_names_and_arg_counts = []
-
-	# Create the pre-wrap C functions
-	for constructor_el in constructors_el.iter("constructor"):
-		arg_count = count_args(constructor_el.find("args"))
-
-		# Iterate all arg counts and make sure there are no duplicates
-		for i in range(idx):
-			if func_names_and_arg_counts[i][1] == arg_count:
-				raise Exception("Every constructor overload should have a unique arg count. class: %s" % class_name)
-
-		constructor(constructor_el, class_name, idx)
-		func_names_and_arg_counts.append(["pwrap%sCtor%d" % (class_name, idx), arg_count])
-		idx += 1
-
-	if idx == 0:
-		raise Exception("Found no <constructor>")
-
-	# Create the landing function
-	wglue("/// Wrap constructors for %s." % class_name)
-	wglue("static int wrap%sCtor(lua_State* l)" % class_name)
-	wglue("{")
-	ident(1)
-	if idx == 1:
-		wglue("int res = pwrap%sCtor0(l);" % class_name)
-	else:
-		wglue("// Chose the right overload")
-		wglue("const int argCount = lua_gettop(l);")
-		wglue("int res = 0;")
-		wglue("switch(argCount)")
-		wglue("{")
-		for name_and_arg_count in func_names_and_arg_counts:
-			func_name = name_and_arg_count[0]
-			arg_count = name_and_arg_count[1]
-			wglue("case %d:" % arg_count)
-			wglue("res = %s(l); break;" % func_name)
-
-		wglue("default:")
-		wglue("lua_pushfstring(l, \"Wrong overloaded new. Wrong number of arguments: %d\", argCount);")
-		wglue("res = -1;")
-		wglue("}")
-	wglue("")
-
-	wglue("if(res >= 0)")
-	wglue("{")
-	ident(1)
-	wglue("return res;")
-	ident(-1)
-	wglue("}")
-	wglue("")
-	wglue("lua_error(l);")
-	wglue("return 0;")
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Wrap all constructors """
+
+    idx = 0
+    func_names_and_arg_counts = []
+
+    # Create the pre-wrap C functions
+    for constructor_el in constructors_el.iter("constructor"):
+        arg_count = count_args(constructor_el.find("args"))
+
+        # Iterate all arg counts and make sure there are no duplicates
+        for i in range(idx):
+            if func_names_and_arg_counts[i][1] == arg_count:
+                raise Exception("Every constructor overload should have a unique arg count. class: %s" % class_name)
+
+        constructor(constructor_el, class_name, idx)
+        func_names_and_arg_counts.append(["pwrap%sCtor%d" % (class_name, idx), arg_count])
+        idx += 1
+
+    if idx == 0:
+        raise Exception("Found no <constructor>")
+
+    # Create the landing function
+    wglue("/// Wrap constructors for %s." % class_name)
+    wglue("static int wrap%sCtor(lua_State* l)" % class_name)
+    wglue("{")
+    ident(1)
+    if idx == 1:
+        wglue("int res = pwrap%sCtor0(l);" % class_name)
+    else:
+        wglue("// Chose the right overload")
+        wglue("const int argCount = lua_gettop(l);")
+        wglue("int res = 0;")
+        wglue("switch(argCount)")
+        wglue("{")
+        for name_and_arg_count in func_names_and_arg_counts:
+            func_name = name_and_arg_count[0]
+            arg_count = name_and_arg_count[1]
+            wglue("case %d:" % arg_count)
+            wglue("res = %s(l); break;" % func_name)
+
+        wglue("default:")
+        wglue("lua_pushfstring(l, \"Wrong overloaded new. Wrong number of arguments: %d\", argCount);")
+        wglue("res = -1;")
+        wglue("}")
+    wglue("")
+
+    wglue("if(res >= 0)")
+    wglue("{")
+    ident(1)
+    wglue("return res;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+    wglue("lua_error(l);")
+    wglue("return 0;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def destructor(class_name):
-	""" Create a destructor """
-
-	wglue("/// Wrap destructor for %s." % (class_name))
-	wglue("static int wrap%sDtor(lua_State* l)" % class_name)
-	wglue("{")
-	ident(1)
-	write_local_vars()
-
-	check_args(None, 1)
-
-	wglue("if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfo%s, ud)))" % class_name)
-	wglue("{")
-	ident(1)
-	wglue("return -1;")
-	ident(-1)
-	wglue("}")
-	wglue("");
-
-	wglue("if(ud->isGarbageCollected())")
-	wglue("{")
-	ident(1)
-	wglue("%s* inst = ud->getData<%s>();" % (class_name, class_name))
-	wglue("inst->~%s();" % class_name)
-	ident(-1)
-	wglue("}")
-	wglue("")
-
-	wglue("return 0;")
-
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Create a destructor """
+
+    wglue("/// Wrap destructor for %s." % (class_name))
+    wglue("static int wrap%sDtor(lua_State* l)" % class_name)
+    wglue("{")
+    ident(1)
+    write_local_vars()
+
+    check_args(None, 1)
+
+    wglue("if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfo%s, ud)))" % class_name)
+    wglue("{")
+    ident(1)
+    wglue("return -1;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    wglue("if(ud->isGarbageCollected())")
+    wglue("{")
+    ident(1)
+    wglue("%s* inst = ud->getData<%s>();" % (class_name, class_name))
+    wglue("inst->~%s();" % class_name)
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    wglue("return 0;")
+
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def class_(class_el):
-	""" Create a class """
-
-	class_name = class_el.get("name")
-
-	# Write serializer
-	serialize = class_el.get("serialize") is not None and class_el.get("serialize") == "true"
-	if serialize:
-		# Serialize
-		serialize_cb_name = "serialize%s" % class_name
-		wglue("/// Serialize %s" % class_name)
-		wglue("static void %s(LuaUserData& self, void* data, PtrSize& size)" % serialize_cb_name)
-		wglue("{")
-		ident(1)
-		wglue("%s* obj = self.getData<%s>();" % (class_name, class_name))
-		wglue("obj->serialize(data, size);")
-		ident(-1)
-		wglue("}")
-		wglue("")
-
-		# Deserialize
-		deserialize_cb_name = "deserialize%s" % class_name
-		wglue("/// De-serialize %s" % class_name)
-		wglue("static void %s(const void* data, LuaUserData& self)" % deserialize_cb_name)
-		wglue("{")
-		ident(1)
-		wglue("ANKI_ASSERT(data);")
-		wglue("%s* obj = self.getData<%s>();" % (class_name, class_name))
-		wglue("::new(obj) %s();" % class_name)
-		wglue("obj->deserialize(data);")
-		ident(-1)
-		wglue("}")
-		wglue("")
-	else:
-		serialize_cb_name = "nullptr"
-		deserialize_cb_name = "nullptr"
-
-	# Write the type info
-	wglue("LuaUserDataTypeInfo luaUserDataTypeInfo%s = {" % class_name)
-	ident(1)
-	wglue("%d, \"%s\", LuaUserData::computeSizeForGarbageCollected<%s>(), %s, %s"
-			% (type_sig(class_name), class_name, class_name, serialize_cb_name, deserialize_cb_name))
-	ident(-1)
-	wglue("};")
-	wglue("")
-
-	# Specialize the getDataTypeInfoFor
-	wglue("template<>")
-	wglue("const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<%s>()" % class_name)
-	wglue("{")
-	ident(1)
-	wglue("return luaUserDataTypeInfo%s;" % class_name)
-	ident(-1)
-	wglue("}")
-	wglue("")
-
-	# Constructor declarations
-	has_constructor = False
-	constructors_el = class_el.find("constructors")
-	if constructors_el is not None:
-		has_constructor = True
-		constructors(constructors_el, class_name)
-
-	# Destructor declarations
-	if has_constructor:
-		destructor(class_name)
-
-	# Methods LUA C functions declarations
-	meth_names_aliases = []
-	meths_el = class_el.find("methods")
-	if meths_el is not None:
-		for meth_el in meths_el.iter("method"):
-			is_static = meth_el.get("static")
-			is_static = is_static is not None and is_static == "1"
-
-			if is_static:
-				static_method(class_name, meth_el)
-			else:
-				method(class_name, meth_el)
-
-			meth_name = meth_el.get("name")
-			meth_alias = get_meth_alias(meth_el)
-			meth_names_aliases.append([meth_name, meth_alias, is_static])
-
-	# Start class declaration
-	wglue("/// Wrap class %s." % class_name)
-	wglue("static inline void wrap%s(lua_State* l)" % class_name)
-	wglue("{")
-	ident(1)
-	wglue("LuaBinder::createClass(l, &luaUserDataTypeInfo%s);" % class_name)
-
-	# Register constructor
-	if has_constructor:
-		wglue("LuaBinder::pushLuaCFuncStaticMethod(l, luaUserDataTypeInfo%s.m_typeName, \"new\", wrap%sCtor);"
-				% (class_name, class_name))
-
-	# Register destructor
-	if has_constructor:
-		wglue("LuaBinder::pushLuaCFuncMethod(l, \"__gc\", wrap%sDtor);" % class_name)
-
-	# Register methods
-	if len(meth_names_aliases) > 0:
-		for meth_name_alias in meth_names_aliases:
-			meth_alias = meth_name_alias[1]
-			is_static = meth_name_alias[2]
-			if is_static:
-				wglue("LuaBinder::pushLuaCFuncStaticMethod(l, luaUserDataTypeInfo%s.m_typeName, \"%s\", wrap%s%s);"
-						% (class_name, meth_alias, class_name, meth_alias))
-			else:
-				wglue("LuaBinder::pushLuaCFuncMethod(l, \"%s\", wrap%s%s);" % (meth_alias, class_name, meth_alias))
-
-	wglue("lua_settop(l, 0);")
-
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Create a class """
+
+    class_name = class_el.get("name")
+
+    # Write serializer
+    serialize = class_el.get("serialize") is not None and class_el.get("serialize") == "true"
+    if serialize:
+        # Serialize
+        serialize_cb_name = "serialize%s" % class_name
+        wglue("/// Serialize %s" % class_name)
+        wglue("static void %s(LuaUserData& self, void* data, PtrSize& size)" % serialize_cb_name)
+        wglue("{")
+        ident(1)
+        wglue("%s* obj = self.getData<%s>();" % (class_name, class_name))
+        wglue("obj->serialize(data, size);")
+        ident(-1)
+        wglue("}")
+        wglue("")
+
+        # Deserialize
+        deserialize_cb_name = "deserialize%s" % class_name
+        wglue("/// De-serialize %s" % class_name)
+        wglue("static void %s(const void* data, LuaUserData& self)" % deserialize_cb_name)
+        wglue("{")
+        ident(1)
+        wglue("ANKI_ASSERT(data);")
+        wglue("%s* obj = self.getData<%s>();" % (class_name, class_name))
+        wglue("::new(obj) %s();" % class_name)
+        wglue("obj->deserialize(data);")
+        ident(-1)
+        wglue("}")
+        wglue("")
+    else:
+        serialize_cb_name = "nullptr"
+        deserialize_cb_name = "nullptr"
+
+    # Write the type info
+    wglue("LuaUserDataTypeInfo luaUserDataTypeInfo%s = {" % class_name)
+    ident(1)
+    wglue("%d, \"%s\", LuaUserData::computeSizeForGarbageCollected<%s>(), %s, %s" %
+          (type_sig(class_name), class_name, class_name, serialize_cb_name, deserialize_cb_name))
+    ident(-1)
+    wglue("};")
+    wglue("")
+
+    # Specialize the getDataTypeInfoFor
+    wglue("template<>")
+    wglue("const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<%s>()" % class_name)
+    wglue("{")
+    ident(1)
+    wglue("return luaUserDataTypeInfo%s;" % class_name)
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    # Constructor declarations
+    has_constructor = False
+    constructors_el = class_el.find("constructors")
+    if constructors_el is not None:
+        has_constructor = True
+        constructors(constructors_el, class_name)
+
+    # Destructor declarations
+    if has_constructor:
+        destructor(class_name)
+
+    # Methods LUA C functions declarations
+    meth_names_aliases = []
+    meths_el = class_el.find("methods")
+    if meths_el is not None:
+        for meth_el in meths_el.iter("method"):
+            is_static = meth_el.get("static")
+            is_static = is_static is not None and is_static == "1"
+
+            if is_static:
+                static_method(class_name, meth_el)
+            else:
+                method(class_name, meth_el)
+
+            meth_name = meth_el.get("name")
+            meth_alias = get_meth_alias(meth_el)
+            meth_names_aliases.append([meth_name, meth_alias, is_static])
+
+    # Start class declaration
+    wglue("/// Wrap class %s." % class_name)
+    wglue("static inline void wrap%s(lua_State* l)" % class_name)
+    wglue("{")
+    ident(1)
+    wglue("LuaBinder::createClass(l, &luaUserDataTypeInfo%s);" % class_name)
+
+    # Register constructor
+    if has_constructor:
+        wglue("LuaBinder::pushLuaCFuncStaticMethod(l, luaUserDataTypeInfo%s.m_typeName, \"new\", wrap%sCtor);" %
+              (class_name, class_name))
+
+    # Register destructor
+    if has_constructor:
+        wglue("LuaBinder::pushLuaCFuncMethod(l, \"__gc\", wrap%sDtor);" % class_name)
+
+    # Register methods
+    if len(meth_names_aliases) > 0:
+        for meth_name_alias in meth_names_aliases:
+            meth_alias = meth_name_alias[1]
+            is_static = meth_name_alias[2]
+            if is_static:
+                wglue("LuaBinder::pushLuaCFuncStaticMethod(l, luaUserDataTypeInfo%s.m_typeName, \"%s\", wrap%s%s);" %
+                      (class_name, meth_alias, class_name, meth_alias))
+            else:
+                wglue("LuaBinder::pushLuaCFuncMethod(l, \"%s\", wrap%s%s);" % (meth_alias, class_name, meth_alias))
+
+    wglue("lua_settop(l, 0);")
+
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def function(func_el):
-	""" Handle a plain function """
-
-	func_name = func_el.get("name")
-	func_alias = get_meth_alias(func_el)
-
-	wglue("/// Pre-wrap function %s." % func_name)
-	wglue("static inline int pwrap%s(lua_State* l)" % func_alias)
-	wglue("{")
-	ident(1)
-	write_local_vars()
-
-	check_args(func_el.find("args"), 0)
-
-	# Args
-	args_str = args(func_el.find("args"), 1)
-
-	# Return value
-	ret_txt = None
-	ret_el = func_el.find("return")
-	if ret_el is not None:
-		ret_txt = ret_el.text
-
-	# Call
-	wglue("// Call the function")
-	call = func_el.find("overrideCall")
-	if call is not None:
-		call = call.text
-
-	if call is not None:
-		wglue("%s" % call)
-	else:
-		if ret_txt is None:
-			wglue("%s(%s);" % (func_name, args_str))
-		else:
-			wglue("%s ret = %s(%s);" % (ret_txt, func_name, args_str))
-
-	wglue("")
-	ret(ret_el)
-
-	ident(-1)
-	wglue("}")
-	wglue("")
-
-	# Write the actual function
-	wglue("/// Wrap function %s." % func_name)
-	wglue("static int wrap%s(lua_State* l)" % func_alias)
-	wglue("{")
-	ident(1)
-	wglue("int res = pwrap%s(l);" % func_alias)
-	wglue("if(res >= 0)")
-	wglue("{")
-	ident(1)
-	wglue("return res;")
-	ident(-1)
-	wglue("}")
-	wglue("")
-	wglue("lua_error(l);")
-	wglue("return 0;")
-	ident(-1)
-	wglue("}")
-	wglue("")
+    """ Handle a plain function """
+
+    func_name = func_el.get("name")
+    func_alias = get_meth_alias(func_el)
+
+    wglue("/// Pre-wrap function %s." % func_name)
+    wglue("static inline int pwrap%s(lua_State* l)" % func_alias)
+    wglue("{")
+    ident(1)
+    write_local_vars()
+
+    check_args(func_el.find("args"), 0)
+
+    # Args
+    args_str = args(func_el.find("args"), 1)
+
+    # Return value
+    ret_txt = None
+    ret_el = func_el.find("return")
+    if ret_el is not None:
+        ret_txt = ret_el.text
+
+    # Call
+    wglue("// Call the function")
+    call = func_el.find("overrideCall")
+    if call is not None:
+        call = call.text
+
+    if call is not None:
+        wglue("%s" % call)
+    else:
+        if ret_txt is None:
+            wglue("%s(%s);" % (func_name, args_str))
+        else:
+            wglue("%s ret = %s(%s);" % (ret_txt, func_name, args_str))
+
+    wglue("")
+    ret(ret_el)
+
+    ident(-1)
+    wglue("}")
+    wglue("")
+
+    # Write the actual function
+    wglue("/// Wrap function %s." % func_name)
+    wglue("static int wrap%s(lua_State* l)" % func_alias)
+    wglue("{")
+    ident(1)
+    wglue("int res = pwrap%s(l);" % func_alias)
+    wglue("if(res >= 0)")
+    wglue("{")
+    ident(1)
+    wglue("return res;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+    wglue("lua_error(l);")
+    wglue("return 0;")
+    ident(-1)
+    wglue("}")
+    wglue("")
+
 
 def main():
-	""" Main function """
-
-	global out_file
-	filenames = parse_commandline()
-
-	for filename in filenames:
-		out_filename = get_base_fname(filename) + ".cpp"
-		out_file = open(out_filename, "w")
-
-		tree = et.parse(filename)
-		root = tree.getroot()
-
-		# Head
-		head = root.find("head")
-		if head is not None:
-			wglue("%s" % head.text)
-			wglue("")
-
-		# Classes
-		class_names = []
-		for cls in root.iter("classes"):
-			for cl in cls.iter("class"):
-				class_(cl)
-				class_names.append(cl.get("name"))
-
-		# Functions
-		func_names = []
-		for fs in root.iter("functions"):
-			for f in fs.iter("function"):
-				function(f)
-				func_names.append(f.get("name"))
-
-		# Wrap function
-		wglue("/// Wrap the module.")
-		wglue("void wrapModule%s(lua_State* l)" % get_base_fname(filename))
-		wglue("{")
-		ident(1)
-		for class_name in class_names:
-			wglue("wrap%s(l);" % class_name)
-		for func_name in func_names:
-			wglue("LuaBinder::pushLuaCFunc(l, \"%s\", wrap%s);" % (func_name, func_name))
-		ident(-1)
-		wglue("}")
-		wglue("")
-
-		# Tail
-		tail = root.find("tail")
-		if tail is not None:
-			wglue("%s" % tail.text)
-			wglue("")
-
-		out_file.close()
+    """ Main function """
+
+    global out_file
+    filenames = parse_commandline()
+
+    for filename in filenames:
+        out_filename = get_base_fname(filename) + ".cpp"
+        out_file = open(out_filename, "w")
+
+        tree = et.parse(filename)
+        root = tree.getroot()
+
+        # Head
+        head = root.find("head")
+        if head is not None:
+            wglue("%s" % head.text)
+            wglue("")
+
+        # Classes
+        class_names = []
+        for cls in root.iter("classes"):
+            for cl in cls.iter("class"):
+                class_(cl)
+                class_names.append(cl.get("name"))
+
+        # Functions
+        func_names = []
+        for fs in root.iter("functions"):
+            for f in fs.iter("function"):
+                function(f)
+                func_names.append(f.get("name"))
+
+        # Wrap function
+        wglue("/// Wrap the module.")
+        wglue("void wrapModule%s(lua_State* l)" % get_base_fname(filename))
+        wglue("{")
+        ident(1)
+        for class_name in class_names:
+            wglue("wrap%s(l);" % class_name)
+        for func_name in func_names:
+            wglue("LuaBinder::pushLuaCFunc(l, \"%s\", wrap%s);" % (func_name, func_name))
+        ident(-1)
+        wglue("}")
+        wglue("")
+
+        # Tail
+        tail = root.find("tail")
+        if tail is not None:
+            wglue("%s" % tail.text)
+            wglue("")
+
+        out_file.close()
+
 
 if __name__ == "__main__":
-	main()
+    main()

+ 2 - 0
src/anki/shader_compiler/CMakeLists.txt

@@ -0,0 +1,2 @@
+file(GLOB SOURCES *.cpp)
+addAnkiSourceFiles(${SOURCES})

+ 37 - 0
src/anki/shader_compiler/Common.h

@@ -0,0 +1,37 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/util/Logger.h>
+#include <anki/util/String.h>
+#include <anki/util/BitSet.h>
+
+namespace anki
+{
+
+/// @addtogroup shader_compiler
+/// @{
+
+#define ANKI_SHADER_COMPILER_LOGI(...) ANKI_LOG("SHCO", NORMAL, __VA_ARGS__)
+#define ANKI_SHADER_COMPILER_LOGE(...) ANKI_LOG("SHCO", ERROR, __VA_ARGS__)
+#define ANKI_SHADER_COMPILER_LOGW(...) ANKI_LOG("SHCO", WARNING, __VA_ARGS__)
+#define ANKI_SHADER_COMPILER_LOGF(...) ANKI_LOG("SHCO", FATAL, __VA_ARGS__)
+
+constexpr U32 MAX_SHADER_PROGRAM_INPUT_VARIABLES = 128;
+constexpr U32 MAX_SHADER_BINARY_NAME_LENGTH = 63;
+
+using ActiveProgramInputVariableMask = BitSet<MAX_SHADER_PROGRAM_INPUT_VARIABLES, U64>;
+using MutatorValue = I32; ///< The type of the mutator value
+
+/// An interface used by the ShaderProgramParser and ShaderProgramCompiler to abstract file loading.
+class ShaderProgramFilesystemInterface
+{
+public:
+	virtual ANKI_USE_RESULT Error readAllText(CString filename, StringAuto& txt) = 0;
+};
+/// @}
+
+} // end namespace anki

+ 271 - 0
src/anki/shader_compiler/Glslang.cpp

@@ -0,0 +1,271 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/Glslang.h>
+#include <anki/util/StringList.h>
+
+#if ANKI_COMPILER_GCC_COMPATIBLE
+#	pragma GCC diagnostic push
+#	pragma GCC diagnostic ignored "-Wundef"
+#	pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <glslang/Public/ShaderLang.h>
+#include <glslang/SPIRV/GlslangToSpv.h>
+#include <glslang/StandAlone/DirStackFileIncluder.h>
+#if ANKI_COMPILER_GCC_COMPATIBLE
+#	pragma GCC diagnostic pop
+#endif
+
+namespace anki
+{
+
+class GlslangCtx
+{
+public:
+	GlslangCtx()
+	{
+		glslang::InitializeProcess();
+	}
+
+	~GlslangCtx()
+	{
+		glslang::FinalizeProcess();
+	}
+};
+
+GlslangCtx g_glslangCtx;
+
+static TBuiltInResource setGlslangLimits()
+{
+	TBuiltInResource c = {};
+
+	c.maxLights = 32;
+	c.maxClipPlanes = 6;
+	c.maxTextureUnits = 32;
+	c.maxTextureCoords = 32;
+	c.maxVertexAttribs = 64;
+	c.maxVertexUniformComponents = 4096;
+	c.maxVaryingFloats = 64;
+	c.maxVertexTextureImageUnits = 32;
+	c.maxCombinedTextureImageUnits = 80;
+	c.maxTextureImageUnits = 32;
+	c.maxFragmentUniformComponents = 4096;
+	c.maxDrawBuffers = 32;
+	c.maxVertexUniformVectors = 128;
+	c.maxVaryingVectors = 8;
+	c.maxFragmentUniformVectors = 16;
+	c.maxVertexOutputVectors = 16;
+	c.maxFragmentInputVectors = 15;
+	c.minProgramTexelOffset = -8;
+	c.maxProgramTexelOffset = 7;
+	c.maxClipDistances = 8;
+	c.maxComputeWorkGroupCountX = 65535;
+	c.maxComputeWorkGroupCountY = 65535;
+	c.maxComputeWorkGroupCountZ = 65535;
+	c.maxComputeWorkGroupSizeX = 1024;
+	c.maxComputeWorkGroupSizeY = 1024;
+	c.maxComputeWorkGroupSizeZ = 64;
+	c.maxComputeUniformComponents = 1024;
+	c.maxComputeTextureImageUnits = 16;
+	c.maxComputeImageUniforms = 8;
+	c.maxComputeAtomicCounters = 8;
+	c.maxComputeAtomicCounterBuffers = 1;
+	c.maxVaryingComponents = 60;
+	c.maxVertexOutputComponents = 64;
+	c.maxGeometryInputComponents = 64;
+	c.maxGeometryOutputComponents = 128;
+	c.maxFragmentInputComponents = 128;
+	c.maxImageUnits = 8;
+	c.maxCombinedImageUnitsAndFragmentOutputs = 8;
+	c.maxCombinedShaderOutputResources = 8;
+	c.maxImageSamples = 0;
+	c.maxVertexImageUniforms = 0;
+	c.maxTessControlImageUniforms = 0;
+	c.maxTessEvaluationImageUniforms = 0;
+	c.maxGeometryImageUniforms = 0;
+	c.maxFragmentImageUniforms = 8;
+	c.maxCombinedImageUniforms = 8;
+	c.maxGeometryTextureImageUnits = 16;
+	c.maxGeometryOutputVertices = 256;
+	c.maxGeometryTotalOutputComponents = 1024;
+	c.maxGeometryUniformComponents = 1024;
+	c.maxGeometryVaryingComponents = 64;
+	c.maxTessControlInputComponents = 128;
+	c.maxTessControlOutputComponents = 128;
+	c.maxTessControlTextureImageUnits = 16;
+	c.maxTessControlUniformComponents = 1024;
+	c.maxTessControlTotalOutputComponents = 4096;
+	c.maxTessEvaluationInputComponents = 128;
+	c.maxTessEvaluationOutputComponents = 128;
+	c.maxTessEvaluationTextureImageUnits = 16;
+	c.maxTessEvaluationUniformComponents = 1024;
+	c.maxTessPatchComponents = 120;
+	c.maxPatchVertices = 32;
+	c.maxTessGenLevel = 64;
+	c.maxViewports = 16;
+	c.maxVertexAtomicCounters = 0;
+	c.maxTessControlAtomicCounters = 0;
+	c.maxTessEvaluationAtomicCounters = 0;
+	c.maxGeometryAtomicCounters = 0;
+	c.maxFragmentAtomicCounters = 8;
+	c.maxCombinedAtomicCounters = 8;
+	c.maxAtomicCounterBindings = 1;
+	c.maxVertexAtomicCounterBuffers = 0;
+	c.maxTessControlAtomicCounterBuffers = 0;
+	c.maxTessEvaluationAtomicCounterBuffers = 0;
+	c.maxGeometryAtomicCounterBuffers = 0;
+	c.maxFragmentAtomicCounterBuffers = 1;
+	c.maxCombinedAtomicCounterBuffers = 1;
+	c.maxAtomicCounterBufferSize = 16384;
+	c.maxTransformFeedbackBuffers = 4;
+	c.maxTransformFeedbackInterleavedComponents = 64;
+	c.maxCullDistances = 8;
+	c.maxCombinedClipAndCullDistances = 8;
+	c.maxSamples = 4;
+
+	c.limits.nonInductiveForLoops = 1;
+	c.limits.whileLoops = 1;
+	c.limits.doWhileLoops = 1;
+	c.limits.generalUniformIndexing = 1;
+	c.limits.generalAttributeMatrixVectorIndexing = 1;
+	c.limits.generalVaryingIndexing = 1;
+	c.limits.generalSamplerIndexing = 1;
+	c.limits.generalVariableIndexing = 1;
+	c.limits.generalConstantMatrixVectorIndexing = 1;
+
+	return c;
+}
+
+static TBuiltInResource GLSLANG_LIMITS = setGlslangLimits();
+
+static EShLanguage ankiToGlslangShaderType(ShaderType shaderType)
+{
+	EShLanguage gslangShader;
+	switch(shaderType)
+	{
+	case ShaderType::VERTEX:
+		gslangShader = EShLangVertex;
+		break;
+	case ShaderType::FRAGMENT:
+		gslangShader = EShLangFragment;
+		break;
+	case ShaderType::TESSELLATION_EVALUATION:
+		gslangShader = EShLangTessEvaluation;
+		break;
+	case ShaderType::TESSELLATION_CONTROL:
+		gslangShader = EShLangTessControl;
+		break;
+	case ShaderType::GEOMETRY:
+		gslangShader = EShLangGeometry;
+		break;
+	case ShaderType::COMPUTE:
+		gslangShader = EShLangCompute;
+		break;
+	default:
+		ANKI_ASSERT(0);
+		gslangShader = EShLangCount;
+	};
+
+	return gslangShader;
+}
+
+static void logShaderErrorCode(CString error, CString source, GenericMemoryPoolAllocator<U8> alloc)
+{
+	StringAuto prettySrc(alloc);
+	StringListAuto lines(alloc);
+
+	static const char* padding = "==============================================================================";
+
+	lines.splitString(source, '\n', true);
+
+	I lineno = 0;
+	for(auto it = lines.getBegin(); it != lines.getEnd(); ++it)
+	{
+		++lineno;
+		StringAuto tmp(alloc);
+
+		if(!it->isEmpty())
+		{
+			tmp.sprintf("%8d: %s\n", lineno, &(*it)[0]);
+		}
+		else
+		{
+			tmp.sprintf("%8d:\n", lineno);
+		}
+
+		prettySrc.append(tmp);
+	}
+
+	ANKI_SHADER_COMPILER_LOGE("Shader compilation failed:\n%s\n%s\n%s\n%s\n%s\n%s",
+		padding,
+		&error[0],
+		padding,
+		&prettySrc[0],
+		padding,
+		&error[0]);
+}
+
+Error preprocessGlsl(CString in, StringAuto& out)
+{
+	glslang::TShader shader(EShLangVertex);
+	Array<const char*, 1> csrc = {{&in[0]}};
+	shader.setStrings(&csrc[0], 1);
+
+	DirStackFileIncluder includer;
+	EShMessages messages = EShMsgDefault;
+	std::string glslangOut;
+	if(!shader.preprocess(&GLSLANG_LIMITS, 450, ENoProfile, false, false, messages, &glslangOut, includer))
+	{
+		ANKI_SHADER_COMPILER_LOGE("Preprocessing failed:\n%s", shader.getInfoLog());
+		return Error::USER_DATA;
+	}
+
+	out.append(glslangOut.c_str());
+
+	return Error::NONE;
+}
+
+Error compilerGlslToSpirv(
+	CString src, ShaderType shaderType, GenericMemoryPoolAllocator<U8> tmpAlloc, DynamicArrayAuto<U8>& spirv)
+{
+	const EShLanguage stage = ankiToGlslangShaderType(shaderType);
+	const EShMessages messages = EShMessages(EShMsgSpvRules | EShMsgVulkanRules);
+	const glslang::EShTargetLanguageVersion langVersion = glslang::EShTargetSpv_1_3;
+
+	glslang::TShader shader(stage);
+	Array<const char*, 1> csrc = {{&src[0]}};
+	shader.setStrings(&csrc[0], 1);
+	shader.setEnvTarget(glslang::EShTargetSpv, langVersion);
+	if(!shader.parse(&GLSLANG_LIMITS, 100, false, messages))
+	{
+		logShaderErrorCode(shader.getInfoLog(), src, tmpAlloc);
+		return Error::USER_DATA;
+	}
+
+	// Setup the program
+	glslang::TProgram program;
+	program.addShader(&shader);
+
+	if(!program.link(messages))
+	{
+		ANKI_SHADER_COMPILER_LOGE("glslang failed to link a shader");
+		return Error::USER_DATA;
+	}
+
+	// Gen SPIRV
+	glslang::SpvOptions spvOptions;
+	spvOptions.optimizeSize = true;
+	spvOptions.disableOptimizer = false;
+	std::vector<unsigned int> glslangSpirv;
+	glslang::GlslangToSpv(*program.getIntermediate(stage), glslangSpirv, &spvOptions);
+
+	// Store
+	spirv.resize(glslangSpirv.size() * sizeof(unsigned int));
+	memcpy(&spirv[0], &glslangSpirv[0], spirv.getSizeInBytes());
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 75 - 0
src/anki/shader_compiler/Glslang.h

@@ -0,0 +1,75 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/shader_compiler/Common.h>
+#include <anki/util/String.h>
+#include <anki/gr/Enums.h>
+#include <anki/gr/utils/Functions.h>
+
+namespace anki
+{
+
+/// @addtogroup shader_compiler
+/// @{
+
+/// A variable inside a uniform or storage blocks or push constants.
+class GlslReflectionBlockVariable
+{
+public:
+	String m_name;
+	ShaderVariableBlockInfo m_info;
+	ShaderVariableDataType m_type = ShaderVariableDataType::NONE;
+};
+
+/// A uniform buffer or storage buffer.
+class GlslReflectionBlock
+{
+public:
+	DynamicArray<GlslReflectionBlockVariable> m_variables;
+	PtrSize m_size = MAX_PTR_SIZE;
+	U32 m_binding = MAX_U32;
+	U8 m_set = MAX_U8;
+};
+
+/// A spec constant variable.
+class GlslReflectionSpecializationConstant
+{
+public:
+	String m_name;
+	U32 m_id = MAX_U32;
+	ShaderVariableDataType m_type = ShaderVariableDataType::NONE;
+};
+
+/// GLSL reflection.
+class GlslReflection
+{
+public:
+	GlslReflection(GenericMemoryPoolAllocator<U8> alloc)
+		: m_alloc(alloc)
+	{
+	}
+
+	~GlslReflection();
+
+	DynamicArray<GlslReflectionBlock> m_uniformBlocks;
+	DynamicArray<GlslReflectionBlock> m_storageBlocks;
+	GlslReflectionBlock* m_pushConstants = nullptr;
+	DynamicArray<GlslReflectionSpecializationConstant> m_specializationConstants;
+
+private:
+	GenericMemoryPoolAllocator<U8> m_alloc;
+};
+
+/// Run glslang's preprocessor.
+ANKI_USE_RESULT Error preprocessGlsl(CString in, StringAuto& out);
+
+/// Compile glsl to SPIR-V.
+ANKI_USE_RESULT Error compilerGlslToSpirv(
+	CString src, ShaderType shaderType, GenericMemoryPoolAllocator<U8> tmpAlloc, DynamicArrayAuto<U8>& spirv);
+/// @}
+
+} // end namespace anki

+ 216 - 0
src/anki/shader_compiler/ShaderProgramBinary.h

@@ -0,0 +1,216 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// WARNING: This file is auto generated.
+
+#pragma once
+
+#include <anki/shader_compiler/Common.h>
+#include <anki/shader_compiler/ShaderProgramBinaryExtra.h>
+#include <anki/gr/Enums.h>
+
+namespace anki
+{
+
+/// Shader program input variable.
+class ShaderProgramBinaryInput
+{
+public:
+	Array<char, MAX_SHADER_BINARY_NAME_LENGTH + 1> m_name;
+	U32 m_firstSpecializationConstantIndex; ///< It's MAX_U32 if it's not a constant.
+	Bool m_instanced;
+	ShaderVariableDataType m_dataType;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doArray(
+			"m_name", offsetof(ShaderProgramBinaryInput, m_name), &self.m_name[0], MAX_SHADER_BINARY_NAME_LENGTH + 1);
+		s.doValue("m_firstSpecializationConstantIndex",
+			offsetof(ShaderProgramBinaryInput, m_firstSpecializationConstantIndex),
+			self.m_firstSpecializationConstantIndex);
+		s.doValue("m_instanced", offsetof(ShaderProgramBinaryInput, m_instanced), self.m_instanced);
+		s.doValue("m_dataType", offsetof(ShaderProgramBinaryInput, m_dataType), self.m_dataType);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ShaderProgramBinaryInput&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ShaderProgramBinaryInput&>(serializer, *this);
+	}
+};
+
+/// Shader program mutator.
+class ShaderProgramBinaryMutator
+{
+public:
+	Array<char, MAX_SHADER_BINARY_NAME_LENGTH + 1> m_name;
+	MutatorValue* m_values;
+	U32 m_valueCount;
+	Bool m_instanceCount;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doArray(
+			"m_name", offsetof(ShaderProgramBinaryMutator, m_name), &self.m_name[0], MAX_SHADER_BINARY_NAME_LENGTH + 1);
+		s.doValue("m_valueCount", offsetof(ShaderProgramBinaryMutator, m_valueCount), self.m_valueCount);
+		s.doValue("m_instanceCount", offsetof(ShaderProgramBinaryMutator, m_instanceCount), self.m_instanceCount);
+		s.doDynamicArray("m_values", offsetof(ShaderProgramBinaryMutator, m_values), self.m_values, self.m_valueCount);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ShaderProgramBinaryMutator&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ShaderProgramBinaryMutator&>(serializer, *this);
+	}
+};
+
+/// ShaderProgramBinaryVariant class.
+class ShaderProgramBinaryVariant
+{
+public:
+	ActiveProgramInputVariableMask m_activeVariables = {false};
+	MutatorValue* m_mutatorValues;
+	ShaderVariableBlockInfo* m_blockInfos;
+	I16* m_bindings;
+	U32 m_mutatorValueCount;
+	U32 m_inputVariableCount;
+	Array<U32, U32(ShaderType::COUNT)> m_binaryIndices; ///< Index in ShaderProgramBinary::m_codeBlocks.
+	U32 m_blockSize;
+	Bool m_usesPushConstants;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doValue("m_activeVariables", offsetof(ShaderProgramBinaryVariant, m_activeVariables), self.m_activeVariables);
+		s.doValue(
+			"m_mutatorValueCount", offsetof(ShaderProgramBinaryVariant, m_mutatorValueCount), self.m_mutatorValueCount);
+		s.doValue("m_inputVariableCount",
+			offsetof(ShaderProgramBinaryVariant, m_inputVariableCount),
+			self.m_inputVariableCount);
+		s.doArray("m_binaryIndices",
+			offsetof(ShaderProgramBinaryVariant, m_binaryIndices),
+			&self.m_binaryIndices[0],
+			U32(ShaderType::COUNT));
+		s.doValue("m_blockSize", offsetof(ShaderProgramBinaryVariant, m_blockSize), self.m_blockSize);
+		s.doValue(
+			"m_usesPushConstants", offsetof(ShaderProgramBinaryVariant, m_usesPushConstants), self.m_usesPushConstants);
+		s.doDynamicArray("m_mutatorValues",
+			offsetof(ShaderProgramBinaryVariant, m_mutatorValues),
+			self.m_mutatorValues,
+			self.m_mutatorValueCount);
+		s.doDynamicArray("m_blockInfos",
+			offsetof(ShaderProgramBinaryVariant, m_blockInfos),
+			self.m_blockInfos,
+			self.m_inputVariableCount);
+		s.doDynamicArray(
+			"m_bindings", offsetof(ShaderProgramBinaryVariant, m_bindings), self.m_bindings, self.m_inputVariableCount);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ShaderProgramBinaryVariant&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ShaderProgramBinaryVariant&>(serializer, *this);
+	}
+};
+
+/// ShaderProgramBinaryCode class.
+class ShaderProgramBinaryCode
+{
+public:
+	U8* m_binary;
+	PtrSize m_binarySize;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doValue("m_binarySize", offsetof(ShaderProgramBinaryCode, m_binarySize), self.m_binarySize);
+		s.doDynamicArray("m_binary", offsetof(ShaderProgramBinaryCode, m_binary), self.m_binary, self.m_binarySize);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ShaderProgramBinaryCode&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ShaderProgramBinaryCode&>(serializer, *this);
+	}
+};
+
+/// ShaderProgramBinary class.
+class ShaderProgramBinary
+{
+public:
+	Array<U8, 8> m_magic;
+	ShaderProgramBinaryMutator* m_mutators;
+	ShaderProgramBinaryInput* m_inputVariables;
+	ShaderProgramBinaryCode* m_codeBlocks;
+	ShaderProgramBinaryVariant* m_variants;
+	U32 m_mutatorCount;
+	U32 m_inputVariableCount;
+	U32 m_codeBlockCount;
+	U32 m_variantCount;
+	U32 m_descriptorSet;
+	ShaderTypeBit m_presentShaderTypes;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doArray("m_magic", offsetof(ShaderProgramBinary, m_magic), &self.m_magic[0], 8);
+		s.doValue("m_mutatorCount", offsetof(ShaderProgramBinary, m_mutatorCount), self.m_mutatorCount);
+		s.doValue(
+			"m_inputVariableCount", offsetof(ShaderProgramBinary, m_inputVariableCount), self.m_inputVariableCount);
+		s.doValue("m_codeBlockCount", offsetof(ShaderProgramBinary, m_codeBlockCount), self.m_codeBlockCount);
+		s.doValue("m_variantCount", offsetof(ShaderProgramBinary, m_variantCount), self.m_variantCount);
+		s.doValue("m_descriptorSet", offsetof(ShaderProgramBinary, m_descriptorSet), self.m_descriptorSet);
+		s.doValue(
+			"m_presentShaderTypes", offsetof(ShaderProgramBinary, m_presentShaderTypes), self.m_presentShaderTypes);
+		s.doDynamicArray("m_mutators", offsetof(ShaderProgramBinary, m_mutators), self.m_mutators, self.m_mutatorCount);
+		s.doDynamicArray("m_inputVariables",
+			offsetof(ShaderProgramBinary, m_inputVariables),
+			self.m_inputVariables,
+			self.m_inputVariableCount);
+		s.doDynamicArray(
+			"m_codeBlocks", offsetof(ShaderProgramBinary, m_codeBlocks), self.m_codeBlocks, self.m_codeBlockCount);
+		s.doDynamicArray("m_variants", offsetof(ShaderProgramBinary, m_variants), self.m_variants, self.m_variantCount);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ShaderProgramBinary&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ShaderProgramBinary&>(serializer, *this);
+	}
+};
+
+} // end namespace anki

+ 64 - 0
src/anki/shader_compiler/ShaderProgramBinary.xml

@@ -0,0 +1,64 @@
+<serializer>
+	<includes>
+		<include file="&lt;anki/shader_compiler/Common.h&gt;"/>
+		<include file="&lt;anki/shader_compiler/ShaderProgramBinaryExtra.h&gt;"/>
+		<include file="&lt;anki/gr/Enums.h&gt;"/>
+	</includes>
+
+	<classes>
+		<class name="ShaderProgramBinaryInput" comment="Shader program input variable">
+			<members>
+				<member name="m_name" type="char" array_size="MAX_SHADER_BINARY_NAME_LENGTH + 1" />
+				<member name="m_firstSpecializationConstantIndex" type="U32" comment="It's MAX_U32 if it's not a constant" />
+				<member name="m_instanced" type="Bool" />
+				<member name="m_dataType" type="ShaderVariableDataType" />
+			</members>
+		</class>
+
+		<class name="ShaderProgramBinaryMutator" comment="Shader program mutator">
+			<members>
+				<member name="m_name" type="char" array_size="MAX_SHADER_BINARY_NAME_LENGTH + 1" />
+				<member name="m_values" type="MutatorValue" pointer="true" array_size="m_valueCount" />
+				<member name="m_valueCount" type="U32" />
+				<member name="m_instanceCount" type="Bool" />
+			</members>
+		</class>
+
+		<class name="ShaderProgramBinaryVariant">
+			<members>
+				<member name="m_activeVariables" type="ActiveProgramInputVariableMask" constructor="false" />
+				<member name="m_mutatorValues" type="MutatorValue" pointer="true" array_size="m_mutatorValueCount" />
+				<member name="m_blockInfos" type="ShaderVariableBlockInfo" pointer="true" array_size="m_inputVariableCount" />
+				<member name="m_bindings" type="I16" pointer="true" array_size="m_inputVariableCount" />
+				<member name="m_mutatorValueCount" type="U32" />
+				<member name="m_inputVariableCount" type="U32" />
+				<member name="m_binaryIndices" type="U32" array_size="U32(ShaderType::COUNT)" comment="Index in ShaderProgramBinary::m_codeBlocks" />
+				<member name="m_blockSize" type="U32" />
+				<member name="m_usesPushConstants" type="Bool" />
+			</members>
+		</class>
+
+		<class name="ShaderProgramBinaryCode">
+			<members>
+				<member name="m_binary" type="U8" pointer="true" array_size="m_binarySize" />
+				<member name="m_binarySize" type="PtrSize" />
+			</members>
+		</class>
+
+		<class name="ShaderProgramBinary">
+			<members>
+				<member name="m_magic" type="U8" array_size="8" />
+				<member name="m_mutators" type="ShaderProgramBinaryMutator" pointer="true" array_size="m_mutatorCount" />
+				<member name="m_inputVariables" type="ShaderProgramBinaryInput" pointer="true" array_size="m_inputVariableCount" />
+				<member name="m_codeBlocks" type="ShaderProgramBinaryCode" pointer="true" array_size="m_codeBlockCount" />
+				<member name="m_variants" type="ShaderProgramBinaryVariant" pointer="true" array_size="m_variantCount" />
+				<member name="m_mutatorCount" type="U32" />
+				<member name="m_inputVariableCount" type="U32" />
+				<member name="m_codeBlockCount" type="U32" />
+				<member name="m_variantCount" type="U32" />
+				<member name="m_descriptorSet" type="U32" />
+				<member name="m_presentShaderTypes" type="ShaderTypeBit" />
+			</members>
+		</class>
+	</classes>
+</serializer>

+ 78 - 0
src/anki/shader_compiler/ShaderProgramBinaryExtra.h

@@ -0,0 +1,78 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/Common.h>
+#include <anki/gr/utils/Functions.h>
+#include <anki/util/Serializer.h>
+
+namespace anki
+{
+
+/// Serialize/deserialize ShaderVariableBlockInfo
+template<typename TSerializer, typename TShaderVariableBlockInfo>
+void serializeShaderVariableBlockInfo(TShaderVariableBlockInfo x, TSerializer& s)
+{
+	s.doValue("m_offset", offsetof(ShaderVariableBlockInfo, m_offset), x.m_offset);
+	s.doValue("m_arraySize", offsetof(ShaderVariableBlockInfo, m_arraySize), x.m_arraySize);
+	s.doValue("m_arrayStride", offsetof(ShaderVariableBlockInfo, m_arrayStride), x.m_arrayStride);
+	s.doValue("m_matrixStride", offsetof(ShaderVariableBlockInfo, m_matrixStride), x.m_matrixStride);
+}
+
+/// Serialize ShaderVariableBlockInfo
+template<>
+class SerializeFunctor<ShaderVariableBlockInfo>
+{
+public:
+	template<typename TSerializer>
+	void operator()(const ShaderVariableBlockInfo& x, TSerializer& serializer)
+	{
+		serializeShaderVariableBlockInfo<TSerializer, const ShaderVariableBlockInfo&>(x, serializer);
+	}
+};
+
+/// Deserialize ShaderVariableBlockInfo
+template<>
+class DeserializeFunctor<ShaderVariableBlockInfo>
+{
+public:
+	template<typename TDeserializer>
+	void operator()(ShaderVariableBlockInfo& x, TDeserializer& deserialize)
+	{
+		serializeShaderVariableBlockInfo<TDeserializer, ShaderVariableBlockInfo&>(x, deserialize);
+	}
+};
+
+/// Serialize ActiveProgramInputVariableMask
+template<typename TSerializer, typename T>
+void serializeActiveProgramInputVariableMask(T x, TSerializer& s)
+{
+	s.doArray("bitset", 0, &x.getData()[0], x.getData().getSize());
+}
+
+/// Serialize ActiveProgramInputVariableMask
+template<>
+class SerializeFunctor<ActiveProgramInputVariableMask>
+{
+public:
+	template<typename TSerializer>
+	void operator()(const ActiveProgramInputVariableMask& x, TSerializer& serializer)
+	{
+		serializeActiveProgramInputVariableMask<TSerializer, const ActiveProgramInputVariableMask&>(x, serializer);
+	}
+};
+
+/// Deserialize ActiveProgramInputVariableMask
+template<>
+class DeserializeFunctor<ActiveProgramInputVariableMask>
+{
+public:
+	template<typename TDeserializer>
+	void operator()(ActiveProgramInputVariableMask& x, TDeserializer& deserialize)
+	{
+		serializeShaderVariableBlockInfo<TDeserializer, ActiveProgramInputVariableMask&>(x, deserialize);
+	}
+};
+
+} // end namespace anki

+ 597 - 0
src/anki/shader_compiler/ShaderProgramCompiler.cpp

@@ -0,0 +1,597 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/ShaderProgramCompiler.h>
+#include <anki/shader_compiler/ShaderProgramParser.h>
+#include <anki/shader_compiler/Glslang.h>
+#include <anki/util/Serializer.h>
+#include <anki/util/HashMap.h>
+#include <SPIRV-Cross/spirv_glsl.hpp>
+
+namespace anki
+{
+
+static const char* SHADER_BINARY_MAGIC = "ANKISDR1";
+
+Error ShaderProgramBinaryWrapper::serializeToFile(CString fname) const
+{
+	ANKI_ASSERT(m_binary);
+
+	File file;
+	ANKI_CHECK(file.open(fname, FileOpenFlag::WRITE | FileOpenFlag::BINARY));
+
+	BinarySerializer serializer;
+	ANKI_CHECK(serializer.serialize(*m_binary, m_alloc, file));
+
+	if(memcmp(SHADER_BINARY_MAGIC, &m_binary->m_magic[0], 0) != 0)
+	{
+		ANKI_SHADER_COMPILER_LOGE("Corrupted or wrong version of shader binary: %s", fname.cstr());
+		return Error::USER_DATA;
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramBinaryWrapper::deserializeFromFile(CString fname)
+{
+	cleanup();
+
+	File file;
+	ANKI_CHECK(file.open(fname, FileOpenFlag::READ | FileOpenFlag::BINARY));
+
+	BinaryDeserializer deserializer;
+	ANKI_CHECK(deserializer.deserialize(m_binary, m_alloc, file));
+
+	m_singleAllocation = true;
+
+	return Error::NONE;
+}
+
+void ShaderProgramBinaryWrapper::cleanup()
+{
+	if(m_binary == nullptr)
+	{
+		return;
+	}
+
+	if(!m_singleAllocation)
+	{
+		for(PtrSize i = 0; i < m_binary->m_mutatorCount; ++i)
+		{
+			m_alloc.getMemoryPool().free(m_binary->m_mutators[i].m_values);
+		}
+
+		m_alloc.getMemoryPool().free(m_binary->m_inputVariables);
+
+		for(PtrSize i = 0; i < m_binary->m_codeBlockCount; ++i)
+		{
+			m_alloc.getMemoryPool().free(m_binary->m_codeBlocks[i].m_binary);
+		}
+		m_alloc.getMemoryPool().free(m_binary->m_codeBlocks);
+
+		for(PtrSize i = 0; i < m_binary->m_variantCount; ++i)
+		{
+			m_alloc.getMemoryPool().free(m_binary->m_variants[i].m_mutatorValues);
+			m_alloc.getMemoryPool().free(m_binary->m_variants[i].m_blockInfos);
+			m_alloc.getMemoryPool().free(m_binary->m_variants[i].m_bindings);
+		}
+		m_alloc.getMemoryPool().free(m_binary->m_variants);
+	}
+
+	m_alloc.getMemoryPool().free(m_binary);
+}
+
+/// Spin the dials. Used to compute all mutator combinations.
+static Bool spinDials(DynamicArrayAuto<U32>& dials, ConstWeakArray<ShaderProgramParserMutator> mutators)
+{
+	ANKI_ASSERT(dials.getSize() == mutators.getSize() && dials.getSize() > 0);
+	Bool done = true;
+
+	U crntDial = dials.getSize() - 1;
+	while(true)
+	{
+		// Turn dial
+		++dials[crntDial];
+
+		if(dials[crntDial] >= mutators[crntDial].getValues().getSize())
+		{
+			if(crntDial == 0)
+			{
+				// Reached the 1st dial, stop spinning
+				done = true;
+				break;
+			}
+			else
+			{
+				dials[crntDial] = 0;
+				--crntDial;
+			}
+		}
+		else
+		{
+			done = false;
+			break;
+		}
+	}
+
+	return done;
+}
+
+static Error compileVariant(ConstWeakArray<MutatorValue> mutation,
+	const ShaderProgramParser& parser,
+	ShaderProgramBinaryVariant& variant,
+	DynamicArrayAuto<ShaderProgramBinaryCode>& codeBlocks,
+	DynamicArrayAuto<U64>& codeBlockHashes,
+	GenericMemoryPoolAllocator<U8> tmpAlloc,
+	GenericMemoryPoolAllocator<U8> binaryAlloc)
+{
+	variant = {};
+
+	// Generate the source and the rest for the variant
+	ShaderProgramParserVariant parserVariant;
+	ANKI_CHECK(parser.generateVariant(mutation, parserVariant));
+
+	// Active vars
+	{
+		variant.m_activeVariables = ActiveProgramInputVariableMask(false);
+		for(PtrSize i = 0; i < parser.getInputs().getSize(); ++i)
+		{
+			if(parserVariant.isInputActive(parser.getInputs()[i]))
+			{
+				variant.m_activeVariables.set(i, true);
+			}
+		}
+	}
+
+	// Compile stages
+	for(ShaderType shaderType = ShaderType::FIRST; shaderType < ShaderType::COUNT; ++shaderType)
+	{
+		if(!(shaderTypeToBit(shaderType) & parser.getShaderTypes()))
+		{
+			variant.m_binaryIndices[shaderType] = MAX_U32;
+			continue;
+		}
+
+		// Compile
+		DynamicArrayAuto<U8> spirv(tmpAlloc);
+		ANKI_CHECK(compilerGlslToSpirv(parserVariant.getSource(shaderType), shaderType, tmpAlloc, spirv));
+		ANKI_ASSERT(spirv.getSize() > 0);
+
+		// Check if the spirv is already generated
+		const U64 newHash = computeHash(&spirv[0], spirv.getSize());
+		Bool found = false;
+		for(PtrSize i = 0; i < codeBlockHashes.getSize(); ++i)
+		{
+			if(codeBlockHashes[i] == newHash)
+			{
+				// Found it
+				variant.m_binaryIndices[shaderType] = U32(i);
+				found = true;
+				break;
+			}
+		}
+
+		// Create it if not found
+		if(!found)
+		{
+			U8* code = binaryAlloc.allocate(spirv.getSizeInBytes());
+			memcpy(code, &spirv[0], spirv.getSizeInBytes());
+
+			ShaderProgramBinaryCode block;
+			block.m_binary = code;
+			block.m_binarySize = spirv.getSizeInBytes();
+			codeBlocks.emplaceBack(block);
+
+			codeBlockHashes.emplaceBack(newHash);
+
+			variant.m_binaryIndices[shaderType] = U32(codeBlocks.getSize() - 1);
+		}
+	}
+
+	// Mutator values
+	variant.m_mutatorValues = binaryAlloc.newArray<I32>(parser.getMutators().getSize());
+	for(PtrSize i = 0; i < parser.getMutators().getSize(); ++i)
+	{
+		variant.m_mutatorValues[i] = mutation[i];
+	}
+
+	// Input vars
+	{
+		ShaderVariableBlockInfo defaultInfo;
+		defaultInfo.m_arraySize = -1;
+		defaultInfo.m_arrayStride = -1;
+		defaultInfo.m_matrixStride = -1;
+		defaultInfo.m_offset = -1;
+		variant.m_blockInfos = binaryAlloc.newArray<ShaderVariableBlockInfo>(parser.getInputs().getSize(), defaultInfo);
+
+		variant.m_bindings = binaryAlloc.newArray<I16>(parser.getInputs().getSize(), -1);
+
+		for(PtrSize i = 0; i < parser.getInputs().getSize(); ++i)
+		{
+			const ShaderProgramParserInput& parserInput = parser.getInputs()[i];
+			if(!parserVariant.isInputActive(parserInput))
+			{
+				continue;
+			}
+
+			if(parserInput.inUbo())
+			{
+				variant.m_blockInfos[i] = parserVariant.getBlockInfo(parserInput);
+			}
+
+			if(parserInput.isSampler() || parserInput.isTexture())
+			{
+				variant.m_bindings[i] = I16(parserVariant.getBinding(parserInput));
+			}
+		}
+	}
+
+	// Misc
+	variant.m_mutatorValueCount = U32(parser.getMutators().getSize());
+	variant.m_inputVariableCount = U32(parser.getInputs().getSize());
+	variant.m_blockSize = parserVariant.getBlockSize();
+	variant.m_usesPushConstants = parserVariant.usesPushConstants();
+
+	return Error::NONE;
+}
+
+Error compileShaderProgram(CString fname,
+	ShaderProgramFilesystemInterface& fsystem,
+	GenericMemoryPoolAllocator<U8> tempAllocator,
+	U32 pushConstantsSize,
+	U32 backendMinor,
+	U32 backendMajor,
+	GpuVendor gpuVendor,
+	ShaderProgramBinaryWrapper& binaryW)
+{
+	// Initialize the binary
+	binaryW.cleanup();
+	binaryW.m_singleAllocation = false;
+	GenericMemoryPoolAllocator<U8> binaryAllocator = binaryW.m_alloc;
+	binaryW.m_binary = binaryAllocator.newInstance<ShaderProgramBinary>();
+	ShaderProgramBinary& binary = *binaryW.m_binary;
+	binary = {};
+	memcpy(&binary.m_magic[0], SHADER_BINARY_MAGIC, 8);
+
+	// Parse source
+	ShaderProgramParser parser(
+		fname, &fsystem, tempAllocator, pushConstantsSize, backendMinor, backendMajor, gpuVendor);
+	ANKI_CHECK(parser.parse());
+
+	// Inputs
+	if(parser.getInputs().getSize() > 0)
+	{
+		binary.m_inputVariableCount = U32(parser.getInputs().getSize());
+		binary.m_inputVariables = binaryAllocator.newArray<ShaderProgramBinaryInput>(binary.m_inputVariableCount);
+
+		for(U32 i = 0; i < binary.m_inputVariableCount; ++i)
+		{
+			ShaderProgramBinaryInput& out = binary.m_inputVariables[i];
+			const ShaderProgramParserInput& in = parser.getInputs()[i];
+
+			ANKI_ASSERT(in.getName().getLength() < out.m_name.getSize());
+			memcpy(&out.m_name[0], in.getName().cstr(), in.getName().getLength() + 1);
+
+			out.m_firstSpecializationConstantIndex = MAX_U32;
+			in.isConstant(&out.m_firstSpecializationConstantIndex);
+
+			out.m_instanced = in.isInstanced();
+			out.m_dataType = in.getDataType();
+		}
+	}
+	else
+	{
+		ANKI_ASSERT(binary.m_inputVariableCount == 0 && binary.m_inputVariables == nullptr);
+	}
+
+	// Mutators
+	if(parser.getMutators().getSize() > 0)
+	{
+		binary.m_mutatorCount = U32(parser.getMutators().getSize());
+		binary.m_mutators = binaryAllocator.newArray<ShaderProgramBinaryMutator>(binary.m_mutatorCount);
+
+		for(U32 i = 0; i < binary.m_mutatorCount; ++i)
+		{
+			ShaderProgramBinaryMutator& out = binary.m_mutators[i];
+			const ShaderProgramParserMutator& in = parser.getMutators()[i];
+
+			ANKI_ASSERT(in.getName().getLength() < out.m_name.getSize());
+			memcpy(&out.m_name[0], in.getName().cstr(), in.getName().getLength() + 1);
+
+			out.m_valueCount = U32(in.getValues().getSize());
+			out.m_values = binaryAllocator.newArray<I32>(out.m_valueCount);
+			memcpy(out.m_values, &in.getValues()[0], in.getValues().getSizeInBytes());
+
+			out.m_instanceCount = in.isInstanceCount();
+		}
+	}
+	else
+	{
+		ANKI_ASSERT(binary.m_mutatorCount == 0 && binary.m_mutators == nullptr);
+	}
+
+	// Create all variants
+	if(parser.getMutators().getSize() > 0)
+	{
+		// Initialize
+		DynamicArrayAuto<MutatorValue> mutation(tempAllocator, parser.getMutators().getSize());
+		DynamicArrayAuto<MutatorValue> mutation2(tempAllocator, parser.getMutators().getSize());
+		DynamicArrayAuto<U32> dials(tempAllocator, parser.getMutators().getSize(), 0);
+		DynamicArrayAuto<ShaderProgramBinaryVariant> variants(binaryAllocator);
+		DynamicArrayAuto<ShaderProgramBinaryCode> codeBlocks(binaryAllocator);
+		DynamicArrayAuto<U64> codeBlockHashes(tempAllocator);
+		HashMapAuto<U64, U> mutationToVariantIdx(tempAllocator);
+
+		// Spin for all possible combinations of mutators and
+		// - Create the spirv
+		// - Populate the binary variant
+		do
+		{
+			// Create the mutation
+			for(PtrSize i = 0; i < parser.getMutators().getSize(); ++i)
+			{
+				mutation[i] = parser.getMutators()[i].getValues()[dials[i]];
+				mutation2[i] = mutation[i];
+			}
+			const Bool rewritten =
+				parser.rewriteMutation(WeakArray<MutatorValue>(mutation.getBegin(), mutation.getSize()));
+
+			// Create the variant
+			ShaderProgramBinaryVariant& variant = *variants.emplaceBack();
+			if(!rewritten)
+			{
+				// New and unique variant, add it
+				ANKI_CHECK(compileVariant(
+					mutation, parser, variant, codeBlocks, codeBlockHashes, tempAllocator, binaryAllocator));
+
+				mutationToVariantIdx.emplace(
+					computeHash(&mutation[0], mutation.getSizeInBytes()), variants.getSize() - 1);
+			}
+			else
+			{
+				// Check if the original variant exists
+				auto it = mutationToVariantIdx.find(computeHash(&mutation[0], mutation.getSizeInBytes()));
+				U originalVariantIdx = (it != mutationToVariantIdx.getEnd()) ? *it : MAX_U;
+
+				if(originalVariantIdx == MAX_U)
+				{
+					// Original variant not found, create it
+
+					ShaderProgramBinaryVariant& other = *variants.emplaceBack();
+					originalVariantIdx = variants.getSize() - 1;
+
+					ANKI_CHECK(compileVariant(
+						mutation, parser, other, codeBlocks, codeBlockHashes, tempAllocator, binaryAllocator));
+
+					mutationToVariantIdx.emplace(
+						computeHash(&mutation[0], mutation.getSizeInBytes()), originalVariantIdx);
+				}
+
+				// Copy the original variant to the current variant
+				{
+					const ShaderProgramBinaryVariant& other = variants[originalVariantIdx];
+
+					variant = other;
+
+					variant.m_mutatorValues = binaryAllocator.newArray<MutatorValue>(variant.m_mutatorValueCount);
+					memcpy(variant.m_mutatorValues,
+						mutation2.getBegin(),
+						sizeof(variant.m_mutatorValues[0]) * variant.m_mutatorValueCount);
+
+					if(variant.m_inputVariableCount > 0)
+					{
+						variant.m_blockInfos =
+							binaryAllocator.newArray<ShaderVariableBlockInfo>(variant.m_inputVariableCount);
+						memcpy(variant.m_blockInfos,
+							other.m_blockInfos,
+							sizeof(variant.m_blockInfos[0]) * variant.m_inputVariableCount);
+
+						variant.m_bindings = binaryAllocator.newArray<I16>(variant.m_inputVariableCount);
+						memcpy(variant.m_bindings,
+							other.m_bindings,
+							sizeof(variant.m_bindings[0]) * variant.m_inputVariableCount);
+					}
+
+					mutationToVariantIdx.emplace(
+						computeHash(&mutation2[0], mutation2.getSizeInBytes()), &variant - &variants[0]);
+				}
+			}
+		} while(!spinDials(dials, parser.getMutators()));
+
+		// Store to binary
+		binary.m_variantCount = U32(variants.getSize());
+		PtrSize size, storage;
+		variants.moveAndReset(binary.m_variants, size, storage);
+
+		binary.m_codeBlockCount = U32(codeBlocks.getSize());
+		codeBlocks.moveAndReset(binary.m_codeBlocks, size, storage);
+	}
+	else
+	{
+		DynamicArrayAuto<MutatorValue> mutation(tempAllocator);
+		DynamicArrayAuto<ShaderProgramBinaryCode> codeBlocks(binaryAllocator);
+		DynamicArrayAuto<U64> codeBlockHashes(tempAllocator);
+
+		binary.m_variantCount = 1;
+		binary.m_variants = binaryAllocator.newInstance<ShaderProgramBinaryVariant>();
+
+		ANKI_CHECK(compileVariant(
+			mutation, parser, binary.m_variants[0], codeBlocks, codeBlockHashes, tempAllocator, binaryAllocator));
+		ANKI_ASSERT(codeBlocks.getSize() == U32(__builtin_popcount(U32(parser.getShaderTypes()))));
+
+		binary.m_codeBlockCount = U32(codeBlocks.getSize());
+		PtrSize size, storage;
+		codeBlocks.moveAndReset(binary.m_codeBlocks, size, storage);
+	}
+
+	// Misc
+	binary.m_descriptorSet = parser.getDescritproSet();
+	binary.m_presentShaderTypes = parser.getShaderTypes();
+
+	return Error::NONE;
+}
+
+void disassembleShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable)
+{
+#define ANKI_TAB "    "
+
+	GenericMemoryPoolAllocator<U8> alloc = humanReadable.getAllocator();
+	StringListAuto lines(alloc);
+
+	lines.pushBack("**MUTATORS**\n");
+	if(binary.m_mutatorCount > 0)
+	{
+		for(U i = 0; i < binary.m_mutatorCount; ++i)
+		{
+			lines.pushBackSprintf(ANKI_TAB "\"%s\"", &binary.m_mutators[i].m_name[0]);
+			for(U j = 0; j < binary.m_mutators[i].m_valueCount; ++j)
+			{
+				lines.pushBackSprintf(" %d", binary.m_mutators[i].m_values[j]);
+			}
+			lines.pushBack("\n");
+		}
+	}
+	else
+	{
+		lines.pushBack(ANKI_TAB "N/A\n");
+	}
+
+	lines.pushBack("\n**INPUT VARIABLES**\n");
+	if(binary.m_inputVariableCount > 0)
+	{
+		for(U i = 0; i < binary.m_inputVariableCount; ++i)
+		{
+			const ShaderProgramBinaryInput& input = binary.m_inputVariables[i];
+			lines.pushBackSprintf(ANKI_TAB "\"%s\" ", &input.m_name[0]);
+			if(input.m_firstSpecializationConstantIndex < MAX_U32)
+			{
+				lines.pushBackSprintf(
+					"firstSpecializationConstant %" PRIu32 " ", input.m_firstSpecializationConstantIndex);
+			}
+			lines.pushBackSprintf("instanced %" PRIu32 " ", U32(input.m_instanced));
+			lines.pushBackSprintf("dataType %" PRIu8 "\n", U8(input.m_dataType));
+		}
+	}
+	else
+	{
+		lines.pushBack(ANKI_TAB "N/A\n");
+	}
+
+	lines.pushBack("\n**BINARIES**\n");
+	for(U i = 0; i < binary.m_codeBlockCount; ++i)
+	{
+		spirv_cross::CompilerGLSL::Options options;
+		options.vulkan_semantics = true;
+
+		const unsigned int* spvb = reinterpret_cast<const unsigned int*>(binary.m_codeBlocks[i].m_binary);
+		ANKI_ASSERT((binary.m_codeBlocks[i].m_binarySize % (sizeof(unsigned int))) == 0);
+		std::vector<unsigned int> spv(spvb, spvb + binary.m_codeBlocks[i].m_binarySize / sizeof(unsigned int));
+		spirv_cross::CompilerGLSL compiler(spv);
+		compiler.set_common_options(options);
+
+		std::string glsl = compiler.compile();
+		StringListAuto sourceLines(alloc);
+		sourceLines.splitString(glsl.c_str(), '\n');
+		StringAuto newGlsl(alloc);
+		sourceLines.join("\n" ANKI_TAB ANKI_TAB, newGlsl);
+
+		lines.pushBackSprintf(ANKI_TAB "%" PRIuFAST32 " \n" ANKI_TAB ANKI_TAB "%s\n", i, newGlsl.cstr());
+	}
+
+	lines.pushBack("\n**SHADER VARIANTS**\n");
+	for(U i = 0; i < binary.m_variantCount; ++i)
+	{
+		const ShaderProgramBinaryVariant& variant = binary.m_variants[i];
+
+		lines.pushBackSprintf(ANKI_TAB "%" PRIuFAST32 "\n", i);
+
+		// Misc
+		ANKI_ASSERT(variant.m_activeVariables.getData().getSize() == 2);
+		lines.pushBackSprintf(ANKI_TAB ANKI_TAB "activeVariables 0b%" ANKI_PRIb64 " 0b%" ANKI_PRIb64 " ",
+			ANKI_FORMAT_U64(variant.m_activeVariables.getData()[1]),
+			ANKI_FORMAT_U64(variant.m_activeVariables.getData()[0]));
+		lines.pushBackSprintf(
+			"blockSize %" PRIu32 " usesPushConstants %" PRIu8 "\n", variant.m_blockSize, variant.m_usesPushConstants);
+
+		// Mutator values
+		lines.pushBack(ANKI_TAB ANKI_TAB "mutatorValues ");
+		if(variant.m_mutatorValueCount > 0)
+		{
+			for(U j = 0; j < variant.m_mutatorValueCount; ++j)
+			{
+				lines.pushBackSprintf(
+					"\"%s\" %" PRId32 " ", &binary.m_mutators[j].m_name[0], variant.m_mutatorValues[j]);
+			}
+		}
+		else
+		{
+			lines.pushBack("N/A");
+		}
+		lines.pushBack("\n");
+
+		// Block infos
+		lines.pushBack(ANKI_TAB ANKI_TAB "blockInfos ");
+		if(variant.m_inputVariableCount > 0)
+		{
+			for(U j = 0; j < variant.m_inputVariableCount; ++j)
+			{
+				const ShaderVariableBlockInfo& inf = variant.m_blockInfos[j];
+				lines.pushBackSprintf("%" PRIi16 "|%" PRIi16 "|%" PRIi16 "|%" PRIi16 " ",
+					inf.m_offset,
+					inf.m_arraySize,
+					inf.m_arrayStride,
+					inf.m_matrixStride);
+			}
+		}
+		else
+		{
+			lines.pushBack("N/A");
+		}
+		lines.pushBack("\n");
+
+		// Bindings
+		lines.pushBack(ANKI_TAB ANKI_TAB "bindings ");
+		if(variant.m_inputVariableCount > 0)
+		{
+			for(U j = 0; j < variant.m_inputVariableCount; ++j)
+			{
+				if(variant.m_bindings[j] < 0)
+				{
+					lines.pushBack("N/A ");
+				}
+				else
+				{
+					lines.pushBackSprintf("%" PRIi16 " ", variant.m_bindings[j]);
+				}
+			}
+		}
+		else
+		{
+			lines.pushBack("N/A");
+		}
+		lines.pushBack("\n");
+
+		// Binary indices
+		lines.pushBack(ANKI_TAB ANKI_TAB "binaries ");
+		for(ShaderType shaderType = ShaderType::FIRST; shaderType < ShaderType::COUNT; ++shaderType)
+		{
+			if(variant.m_binaryIndices[shaderType] < MAX_U32)
+			{
+				lines.pushBackSprintf("%" PRIu32 " ", variant.m_binaryIndices[shaderType]);
+			}
+			else
+			{
+				lines.pushBack("N/A ");
+			}
+		}
+		lines.pushBack("\n");
+	}
+
+	lines.join("", humanReadable);
+
+#undef ANKI_TAB
+}
+
+} // end namespace anki

+ 74 - 0
src/anki/shader_compiler/ShaderProgramCompiler.h

@@ -0,0 +1,74 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/shader_compiler/ShaderProgramBinary.h>
+#include <anki/util/String.h>
+#include <anki/gr/Common.h>
+
+namespace anki
+{
+
+/// @addtogroup shader_compiler
+/// @{
+
+/// A wrapper over the POD ShaderProgramBinary class.
+/// @memberof ShaderProgramCompiler
+class ShaderProgramBinaryWrapper : public NonCopyable
+{
+	friend Error compileShaderProgram(CString fname,
+		ShaderProgramFilesystemInterface& fsystem,
+		GenericMemoryPoolAllocator<U8> tempAllocator,
+		U32 pushConstantsSize,
+		U32 backendMinor,
+		U32 backendMajor,
+		GpuVendor gpuVendor,
+		ShaderProgramBinaryWrapper& binary);
+
+public:
+	ShaderProgramBinaryWrapper(GenericMemoryPoolAllocator<U8> alloc)
+		: m_alloc(alloc)
+	{
+	}
+
+	~ShaderProgramBinaryWrapper()
+	{
+		cleanup();
+	}
+
+	ANKI_USE_RESULT Error serializeToFile(CString fname) const;
+
+	ANKI_USE_RESULT Error deserializeFromFile(CString fname);
+
+	const ShaderProgramBinary& getBinary() const
+	{
+		ANKI_ASSERT(m_binary);
+		return *m_binary;
+	}
+
+private:
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	ShaderProgramBinary* m_binary = nullptr;
+	Bool m_singleAllocation = false;
+
+	void cleanup();
+};
+
+/// Takes an AnKi special shader program and spits a binary.
+ANKI_USE_RESULT Error compileShaderProgram(CString fname,
+	ShaderProgramFilesystemInterface& fsystem,
+	GenericMemoryPoolAllocator<U8> tempAllocator,
+	U32 pushConstantsSize,
+	U32 backendMinor,
+	U32 backendMajor,
+	GpuVendor gpuVendor,
+	ShaderProgramBinaryWrapper& binary);
+
+/// Create a human readable representation of the shader binary.
+void disassembleShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable);
+/// @}
+
+} // end namespace anki

+ 1451 - 0
src/anki/shader_compiler/ShaderProgramParser.cpp

@@ -0,0 +1,1451 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/ShaderProgramParser.h>
+#include <anki/shader_compiler/Glslang.h>
+
+namespace anki
+{
+
+#define ANKI_PP_ERROR_MALFORMED() \
+	ANKI_SHADER_COMPILER_LOGE("%s: Malformed expression: %s", fname.cstr(), line.cstr()); \
+	return Error::USER_DATA
+
+#define ANKI_PP_ERROR_MALFORMED_MSG(msg_) \
+	ANKI_SHADER_COMPILER_LOGE("%s: " msg_ ": %s", fname.cstr(), line.cstr()); \
+	return Error::USER_DATA
+
+static const Array<CString, U32(ShaderType::COUNT)> SHADER_STAGE_NAMES = {
+	{"VERTEX", "TESSELLATION_CONTROL", "TESSELLATION_EVALUATION", "GEOMETRY", "FRAGMENT", "COMPUTE"}};
+
+static const char* SHADER_HEADER = R"(#version 450 core
+#define ANKI_BACKEND_MINOR %u
+#define ANKI_BACKEND_MAJOR %u
+#define ANKI_VENDOR_%s 1
+
+#define gl_VertexID gl_VertexIndex
+#define gl_InstanceID gl_InstanceIndex
+
+#extension GL_EXT_control_flow_attributes : require
+#define ANKI_UNROLL [[unroll]]
+#define ANKI_LOOP [[dont_unroll]]
+#define ANKI_BRANCH [[branch]]
+#define ANKI_FLATTEN [[flatten]]
+
+#extension GL_KHR_shader_subgroup_vote : require
+#extension GL_KHR_shader_subgroup_ballot : require
+#extension GL_KHR_shader_subgroup_shuffle : require
+#extension GL_KHR_shader_subgroup_arithmetic : require
+
+#extension GL_EXT_samplerless_texture_functions : require
+#extension GL_EXT_shader_image_load_formatted : require
+#extension GL_EXT_nonuniform_qualifier : enable
+
+#define ANKI_MAX_BINDLESS_TEXTURES %u
+#define ANKI_MAX_BINDLESS_IMAGES %u
+
+#define F32 float
+#define Vec2 vec2
+#define Vec3 vec3
+#define Vec4 vec4
+
+#define U32 uint
+#define UVec2 uvec2
+#define UVec3 uvec3
+#define UVec4 uvec4
+
+#define I32 int
+#define IVec2 ivec2
+#define IVec3 ivec3
+#define IVec4 ivec4
+
+#define Mat3 mat3
+#define Mat4 mat4
+#define Mat3x4 mat3x4
+
+#define Bool bool
+)";
+
+static ANKI_USE_RESULT Error computeShaderVariableDataType(const CString& str, ShaderVariableDataType& out)
+{
+	Error err = Error::NONE;
+
+	if(str == "I32")
+	{
+		out = ShaderVariableDataType::INT;
+	}
+	else if(str == "IVec2")
+	{
+		out = ShaderVariableDataType::IVEC2;
+	}
+	else if(str == "IVec3")
+	{
+		out = ShaderVariableDataType::IVEC3;
+	}
+	else if(str == "IVec4")
+	{
+		out = ShaderVariableDataType::IVEC4;
+	}
+	else if(str == "U32")
+	{
+		out = ShaderVariableDataType::UINT;
+	}
+	else if(str == "UVec2")
+	{
+		out = ShaderVariableDataType::UVEC2;
+	}
+	else if(str == "UVec3")
+	{
+		out = ShaderVariableDataType::UVEC3;
+	}
+	else if(str == "UVec4")
+	{
+		out = ShaderVariableDataType::UVEC4;
+	}
+	else if(str == "F32")
+	{
+		out = ShaderVariableDataType::FLOAT;
+	}
+	else if(str == "Vec2")
+	{
+		out = ShaderVariableDataType::VEC2;
+	}
+	else if(str == "Vec3")
+	{
+		out = ShaderVariableDataType::VEC3;
+	}
+	else if(str == "Vec4")
+	{
+		out = ShaderVariableDataType::VEC4;
+	}
+	else if(str == "Mat3")
+	{
+		out = ShaderVariableDataType::MAT3;
+	}
+	else if(str == "Mat4")
+	{
+		out = ShaderVariableDataType::MAT4;
+	}
+	else if(str == "texture2D")
+	{
+		out = ShaderVariableDataType::TEXTURE_2D;
+	}
+	else if(str == "texture2DArray")
+	{
+		out = ShaderVariableDataType::TEXTURE_2D_ARRAY;
+	}
+	else if(str == "textureCube")
+	{
+		out = ShaderVariableDataType::TEXTURE_CUBE;
+	}
+	else if(str == "sampler")
+	{
+		out = ShaderVariableDataType::SAMPLER;
+	}
+	else
+	{
+		ANKI_SHADER_COMPILER_LOGE("Incorrect variable type %s", &str[0]);
+		err = Error::USER_DATA;
+	}
+
+	return err;
+}
+
+static U32 computeSpecConstantIdsRequired(ShaderVariableDataType type)
+{
+	U32 out;
+	if(type >= ShaderVariableDataType::NUMERIC_1_COMPONENT_FIRST
+		&& type <= ShaderVariableDataType::NUMERIC_1_COMPONENT_LAST)
+	{
+		out = 1;
+	}
+	else if(type >= ShaderVariableDataType::NUMERIC_2_COMPONENT_FIRST
+			&& type <= ShaderVariableDataType::NUMERIC_2_COMPONENT_LAST)
+	{
+		out = 2;
+	}
+	else if(type >= ShaderVariableDataType::NUMERIC_3_COMPONENT_FIRST
+			&& type <= ShaderVariableDataType::NUMERIC_3_COMPONENT_LAST)
+	{
+		out = 3;
+	}
+	else if(type >= ShaderVariableDataType::NUMERIC_4_COMPONENT_FIRST
+			&& type <= ShaderVariableDataType::NUMERIC_4_COMPONENT_LAST)
+	{
+		out = 4;
+	}
+	else
+	{
+		out = MAX_U32;
+	}
+
+	return out;
+}
+
+/// 0: is int, 1: is uint, 2: is float
+static U32 shaderVariableScalarType(ShaderVariableDataType type)
+{
+	U32 out;
+	switch(type)
+	{
+	case ShaderVariableDataType::INT:
+	case ShaderVariableDataType::IVEC2:
+	case ShaderVariableDataType::IVEC3:
+	case ShaderVariableDataType::IVEC4:
+		out = 0;
+		break;
+	case ShaderVariableDataType::UINT:
+	case ShaderVariableDataType::UVEC2:
+	case ShaderVariableDataType::UVEC3:
+	case ShaderVariableDataType::UVEC4:
+		out = 1;
+		break;
+	case ShaderVariableDataType::FLOAT:
+	case ShaderVariableDataType::VEC2:
+	case ShaderVariableDataType::VEC3:
+	case ShaderVariableDataType::VEC4:
+		out = 2;
+		break;
+	default:
+		ANKI_ASSERT(0);
+		out = MAX_U32;
+		break;
+	}
+	return out;
+}
+
+ShaderProgramParser::ShaderProgramParser(CString fname,
+	ShaderProgramFilesystemInterface* fsystem,
+	GenericMemoryPoolAllocator<U8> alloc,
+	U32 pushConstantsSize,
+	U32 backendMinor,
+	U32 backendMajor,
+	GpuVendor gpuVendor)
+	: m_alloc(alloc)
+	, m_fname(alloc, fname)
+	, m_fsystem(fsystem)
+	, m_pushConstSize(pushConstantsSize)
+	, m_backendMinor(backendMinor)
+	, m_backendMajor(backendMajor)
+	, m_gpuVendor(gpuVendor)
+{
+}
+
+ShaderProgramParser::~ShaderProgramParser()
+{
+}
+
+void ShaderProgramParser::tokenizeLine(CString line, DynamicArrayAuto<StringAuto>& tokens) const
+{
+	ANKI_ASSERT(line.getLength() > 0);
+
+	StringAuto l(m_alloc, line);
+
+	// Replace all tabs with spaces
+	for(char& c : l)
+	{
+		if(c == '\t')
+		{
+			c = ' ';
+		}
+	}
+
+	// Split
+	StringListAuto spaceTokens(m_alloc);
+	spaceTokens.splitString(l, ' ', false);
+
+	// Create the array
+	for(const String& s : spaceTokens)
+	{
+		tokens.emplaceBack(m_alloc, s);
+	}
+}
+
+Error ShaderProgramParser::parsePragmaDescriptorSet(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	if(begin >= end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	if(begin->toNumber(m_set))
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	if(m_set >= MAX_DESCRIPTOR_SETS)
+	{
+		ANKI_PP_ERROR_MALFORMED_MSG("The descriptor set index is too high");
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parsePragmaStart(const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	if(begin >= end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	ShaderType shaderType = ShaderType::COUNT;
+	if(*begin == "vert")
+	{
+		shaderType = ShaderType::VERTEX;
+	}
+	else if(*begin == "tessc")
+	{
+		shaderType = ShaderType::TESSELLATION_CONTROL;
+	}
+	else if(*begin == "tesse")
+	{
+	}
+	else if(*begin == "geom")
+	{
+		shaderType = ShaderType::GEOMETRY;
+	}
+	else if(*begin == "frag")
+	{
+		shaderType = ShaderType::FRAGMENT;
+	}
+	else if(*begin == "comp")
+	{
+		shaderType = ShaderType::COMPUTE;
+	}
+	else
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	m_codeLines.pushBackSprintf("#ifdef ANKI_%s", SHADER_STAGE_NAMES[shaderType].cstr());
+
+	++begin;
+	if(begin != end)
+	{
+		// Should be the last token
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	// Set the mask
+	ShaderTypeBit mask = ShaderTypeBit(1 << U(shaderType));
+	if(!!(mask & m_shaderTypes))
+	{
+		ANKI_PP_ERROR_MALFORMED_MSG("Can't have #pragma start <shader> appearing more than once");
+	}
+	m_shaderTypes |= mask;
+
+	// Check bounds
+	if(m_insideShader)
+	{
+		ANKI_PP_ERROR_MALFORMED_MSG("Can't have #pragma start before you close the previous pragma start");
+	}
+	m_insideShader = true;
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parsePragmaEnd(const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	// Check tokens
+	if(begin != end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	// Check bounds
+	if(!m_insideShader)
+	{
+		ANKI_PP_ERROR_MALFORMED_MSG("Can't have #pragma end before you open with a pragma start");
+	}
+	m_insideShader = false;
+
+	// Write code
+	m_codeLines.pushBack("#endif // Shader guard");
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parsePragmaInput(const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	if(begin >= end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	if(m_insideShader)
+	{
+		ANKI_PP_ERROR_MALFORMED_MSG("Can't have #pragma input inside shader blocks");
+	}
+
+	m_inputs.emplaceBack(m_alloc);
+	Input& input = m_inputs.getBack();
+	input.m_idx = U32(m_inputs.getSize() - 1);
+
+	// const
+	Bool isConst;
+	{
+		if(*begin == "const")
+		{
+			isConst = true;
+			++begin;
+		}
+		else
+		{
+			isConst = false;
+		}
+	}
+
+	// instanced
+	{
+		if(begin >= end)
+		{
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		input.m_instanced = false;
+		if(*begin == "instanced")
+		{
+			input.m_instanced = true;
+			m_foundAtLeastOneInstancedInput = true;
+			++begin;
+		}
+	}
+
+	// type
+	const StringAuto& dataTypeStr = *begin;
+	{
+		if(begin >= end)
+		{
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		if(computeShaderVariableDataType(*begin, input.m_dataType))
+		{
+			ANKI_PP_ERROR_MALFORMED();
+		}
+		++begin;
+	}
+
+	// name
+	{
+		if(begin >= end)
+		{
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		// Check if there are duplicates
+		for(PtrSize i = 0; i < m_inputs.getSize() - 1; ++i)
+		{
+			if(m_inputs[i].m_name == *begin)
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Duplicate input");
+			}
+		}
+
+		if(begin->getLength() > MAX_SHADER_BINARY_NAME_LENGTH)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Too big name");
+		}
+
+		input.m_name.create(begin->toCString());
+		++begin;
+	}
+
+	// Append to source
+
+	const Bool isSampler = input.m_dataType == ShaderVariableDataType::SAMPLER;
+	const Bool isTexture = input.m_dataType >= ShaderVariableDataType::TEXTURE_FIRST
+						   && input.m_dataType <= ShaderVariableDataType::TEXTURE_LAST;
+
+	if(isConst)
+	{
+		// Const
+
+		if(isSampler || isTexture || input.m_instanced)
+		{
+			// No const samplers or instanced
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		const U32 vecComponents = computeSpecConstantIdsRequired(input.m_dataType);
+		if(vecComponents == MAX_U32)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Type can't be const");
+		}
+
+		const U32 scalarType = shaderVariableScalarType(input.m_dataType);
+
+		// Add an tag for later pre-processing (when trying to see if the variable is present)
+		m_codeLines.pushBackSprintf("#pragma _anki_input_present_%s", input.m_name.cstr());
+
+		m_globalsLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", input.m_name.cstr());
+		m_globalsLines.pushBackSprintf("#define %s_DEFINED 1", input.m_name.cstr());
+
+		const Array<CString, 3> typeNames = {{"I32", "I32", "F32"}};
+
+		input.m_specConstId = m_specConstIdx;
+
+		StringAuto inputDeclaration(m_alloc);
+		for(U32 comp = 0; comp < vecComponents; ++comp)
+		{
+			m_globalsLines.pushBackSprintf("layout(constant_id = %u) const %s _anki_const_%s_%u = %s(0);",
+				m_specConstIdx,
+				typeNames[scalarType].cstr(),
+				input.m_name.cstr(),
+				comp,
+				typeNames[scalarType].cstr());
+
+			if(comp == 0)
+			{
+				inputDeclaration.sprintf("%s %s = %s(_anki_const_%s_%u",
+					dataTypeStr.cstr(),
+					input.m_name.cstr(),
+					dataTypeStr.cstr(),
+					input.m_name.cstr(),
+					comp);
+			}
+			else
+			{
+				StringAuto tmp(m_alloc);
+				tmp.sprintf(", _anki_const_%s_%u", input.m_name.cstr(), comp);
+				inputDeclaration.append(tmp);
+			}
+
+			if(comp == vecComponents - 1)
+			{
+				inputDeclaration.append(");");
+			}
+
+			++m_specConstIdx;
+		}
+
+		m_globalsLines.pushBack(inputDeclaration);
+
+		m_globalsLines.pushBack("#else");
+		m_globalsLines.pushBackSprintf("#define %s_DEFINED 0", input.m_name.cstr());
+		m_globalsLines.pushBack("#endif");
+	}
+	else if(isSampler || isTexture)
+	{
+		// Sampler or texture
+
+		if(input.m_instanced)
+		{
+			// Samplers and textures can't be instanced
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		// Add an tag for later pre-processing (when trying to see if the variable is present)
+		m_codeLines.pushBackSprintf("#pragma _anki_input_present_%s", input.m_name.cstr());
+
+		m_globalsLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", input.m_name.cstr());
+		m_globalsLines.pushBackSprintf("#define %s_DEFINED 1", input.m_name.cstr());
+
+		m_globalsLines.pushBackSprintf("layout(set = _ANKI_DSET, binding = _ANKI_%s_BINDING) uniform %s %s;",
+			input.m_name.cstr(),
+			dataTypeStr.cstr(),
+			input.m_name.cstr());
+
+		m_globalsLines.pushBack("#else");
+		m_globalsLines.pushBackSprintf("#define %s_DEFINED 0", input.m_name.cstr());
+		m_globalsLines.pushBack("#endif");
+	}
+	else
+	{
+		// UBO
+
+		const char* name = input.m_name.cstr();
+		const char* type = dataTypeStr.cstr();
+
+		// Add an tag for later pre-processing (when trying to see if the variable is present)
+		m_codeLines.pushBackSprintf("#pragma _anki_input_present_%s", name);
+
+		if(input.m_instanced)
+		{
+			m_uboStructLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", name);
+			m_uboStructLines.pushBack("#if _ANKI_INSTANCE_COUNT > 1");
+			m_uboStructLines.pushBackSprintf("%s _anki_uni_%s[_ANKI_INSTANCE_COUNT];", type, name);
+			m_uboStructLines.pushBack("#else");
+			m_uboStructLines.pushBackSprintf("%s _anki_uni_%s;", type, name);
+			m_uboStructLines.pushBack("#endif");
+			m_uboStructLines.pushBack("#endif");
+
+			m_globalsLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", name);
+			m_globalsLines.pushBack("#ifdef ANKI_VERTEX_SHADER");
+			m_globalsLines.pushBackSprintf("#define %s_DEFINED 1", name);
+			m_globalsLines.pushBack("#if _ANKI_INSTANCE_COUNT > 1");
+			m_globalsLines.pushBackSprintf("const %s %s = _anki_unis._anki_uni_%s[gl_InstanceID];", type, name, name);
+			m_globalsLines.pushBack("#else");
+			m_globalsLines.pushBackSprintf("const %s %s = _anki_unis._anki_uni_%s;", type, name, name);
+			m_globalsLines.pushBack("#endif");
+			m_globalsLines.pushBack("#endif //ANKI_VERTEX_SHADER");
+			m_globalsLines.pushBack("#else");
+			m_globalsLines.pushBackSprintf("#define %s_DEFINED 0", name);
+			m_globalsLines.pushBack("#endif");
+		}
+		else
+		{
+			m_uboStructLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", name);
+			m_uboStructLines.pushBackSprintf("%s _anki_uni_%s;", type, name);
+			m_uboStructLines.pushBack("#endif");
+
+			m_globalsLines.pushBackSprintf("#if _ANKI_ACTIVATE_INPUT_%s", name);
+			m_globalsLines.pushBackSprintf("const %s %s = _anki_unis_._anki_uni_%s;", type, name, name);
+			m_globalsLines.pushBackSprintf("#define %s_DEFINED 1", name);
+			m_globalsLines.pushBack("#else");
+			m_globalsLines.pushBackSprintf("#define %s_DEFINED 0", name);
+			m_globalsLines.pushBack("#endif");
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parsePragmaMutator(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	if(begin >= end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	m_mutators.emplaceBack(m_alloc);
+	Mutator& mutator = m_mutators.getBack();
+
+	// Instanced
+	{
+		if(*begin == "instanceCount")
+		{
+			mutator.m_instanceCount = true;
+
+			// Check
+			if(m_instancedMutatorIdx != MAX_U32)
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Can't have more than one instanced mutators");
+			}
+
+			m_instancedMutatorIdx = U32(m_mutators.getSize() - 1);
+			++begin;
+		}
+		else
+		{
+			mutator.m_instanceCount = false;
+		}
+	}
+
+	// Name
+	{
+		if(begin >= end)
+		{
+			// Need to have a name
+			ANKI_PP_ERROR_MALFORMED();
+		}
+
+		// Check for duplicate mutators
+		for(U i = 0; i < m_mutators.getSize() - 1; ++i)
+		{
+			if(m_mutators[i].m_name == *begin)
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Duplicate mutator");
+			}
+		}
+
+		if(begin->getLength() > MAX_SHADER_BINARY_NAME_LENGTH)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Too big name");
+		}
+
+		mutator.m_name.create(begin->toCString());
+		++begin;
+	}
+
+	// Values
+	{
+		// Gather them
+		for(; begin < end; ++begin)
+		{
+			MutatorValue value = 0;
+
+			if(tokenIsComment(begin->toCString()))
+			{
+				break;
+			}
+
+			if(begin->toNumber(value))
+			{
+				ANKI_PP_ERROR_MALFORMED();
+			}
+
+			mutator.m_values.emplaceBack(value);
+		}
+
+		// Check for correct count
+		if(mutator.m_values.getSize() < 2)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Mutator with less that 2 values doesn't make sense");
+		}
+
+		std::sort(mutator.m_values.getBegin(), mutator.m_values.getEnd());
+
+		// Check for duplicates
+		for(U i = 1; i < mutator.m_values.getSize(); ++i)
+		{
+			if(mutator.m_values[i - 1] == mutator.m_values[i])
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Same value appeared more than once");
+			}
+		}
+	}
+
+	// Update some source
+	if(mutator.m_instanceCount)
+	{
+		m_globalsLines.pushFrontSprintf("#define _ANKI_INSTANCE_COUNT %s", mutator.m_name.cstr());
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parsePragmaRewriteMutation(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	// Some basic sanity checks
+	const U tokenCount = end - begin;
+	constexpr U minTokenCount = 2 + 1 + 2; // Mutator + value + "to" + mutator + value
+	if(tokenCount < minTokenCount)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	MutationRewrite& rewrite = *m_mutationRewrites.emplaceBack(m_alloc);
+	Bool servingFrom = true;
+
+	do
+	{
+		if(*begin == "to")
+		{
+			if(servingFrom == false)
+			{
+				ANKI_PP_ERROR_MALFORMED();
+			}
+
+			servingFrom = false;
+		}
+		else
+		{
+			// Mutator & value
+
+			// Get mutator and value
+			const CString mutatorName = *begin;
+			++begin;
+			if(begin == end)
+			{
+				ANKI_PP_ERROR_MALFORMED();
+			}
+			const CString valueStr = *begin;
+			MutatorValue value;
+			if(valueStr.toNumber(value))
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Malformed value");
+			}
+
+			// Get or create new record
+			if(servingFrom)
+			{
+				MutationRewrite::Record& rec = *rewrite.m_records.emplaceBack();
+				for(U i = 0; i < m_mutators.getSize(); ++i)
+				{
+					if(m_mutators[i].getName() == mutatorName)
+					{
+						rec.m_mutatorIndex = U32(i);
+						break;
+					}
+				}
+
+				if(rec.m_mutatorIndex == MAX_U32)
+				{
+					ANKI_PP_ERROR_MALFORMED_MSG("Mutator not found");
+				}
+
+				if(!mutatorHasValue(m_mutators[rec.m_mutatorIndex], value))
+				{
+					ANKI_PP_ERROR_MALFORMED_MSG("Incorect value for mutator");
+				}
+
+				rec.m_valueFrom = value;
+			}
+			else
+			{
+				Bool found = false;
+				for(MutationRewrite::Record& rec : rewrite.m_records)
+				{
+					if(m_mutators[rec.m_mutatorIndex].m_name == mutatorName)
+					{
+						if(!mutatorHasValue(m_mutators[rec.m_mutatorIndex], value))
+						{
+							ANKI_PP_ERROR_MALFORMED_MSG("Incorect value for mutator");
+						}
+
+						rec.m_valueTo = value;
+						found = true;
+						break;
+					}
+				}
+
+				if(!found)
+				{
+					ANKI_PP_ERROR_MALFORMED();
+				}
+			}
+		}
+
+		++begin;
+	} while(begin < end && !tokenIsComment(*begin));
+
+	// Sort for some later cross checking
+	std::sort(rewrite.m_records.getBegin(),
+		rewrite.m_records.getEnd(),
+		[](const MutationRewrite::Record& a, const MutationRewrite::Record& b) {
+			return a.m_mutatorIndex < b.m_mutatorIndex;
+		});
+
+	// More cross checking
+	for(U i = 1; i < rewrite.m_records.getSize(); ++i)
+	{
+		if(rewrite.m_records[i - 1].m_mutatorIndex == rewrite.m_records[i].m_mutatorIndex)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Mutator appeared more than once");
+		}
+	}
+
+	for(U i = 0; i < m_mutationRewrites.getSize() - 1; ++i)
+	{
+		const MutationRewrite& other = m_mutationRewrites[i];
+
+		if(other.m_records.getSize() != rewrite.m_records.getSize())
+		{
+			continue;
+		}
+
+		Bool same = true;
+		for(U j = 0; j < rewrite.m_records.getSize(); ++j)
+		{
+			if(rewrite.m_records[j] != other.m_records[j])
+			{
+				same = false;
+				break;
+			}
+		}
+
+		if(same)
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Mutation already exists");
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parseInclude(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname, U32 depth)
+{
+	// Gather the path
+	StringAuto path(m_alloc);
+	for(; begin < end; ++begin)
+	{
+		path.append(*begin);
+	}
+
+	if(path.isEmpty())
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	// Check
+	const char firstChar = path[0];
+	const char lastChar = path[path.getLength() - 1];
+
+	if((firstChar == '\"' && lastChar == '\"') || (firstChar == '<' && lastChar == '>'))
+	{
+		StringAuto fname2(m_alloc);
+		fname2.create(path.begin() + 1, path.begin() + path.getLength() - 1);
+
+		if(parseFile(fname2, depth + 1))
+		{
+			ANKI_PP_ERROR_MALFORMED_MSG("Error parsing include. See previous errors");
+		}
+	}
+	else
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth)
+{
+	// Tokenize
+	DynamicArrayAuto<StringAuto> tokens(m_alloc);
+	tokenizeLine(line, tokens);
+	ANKI_ASSERT(tokens.getSize() > 0);
+
+	const StringAuto* token = tokens.getBegin();
+	const StringAuto* end = tokens.getEnd();
+
+	// Skip the hash
+	Bool foundAloneHash = false;
+	if(*token == "#")
+	{
+		++token;
+		foundAloneHash = true;
+	}
+
+	if((token < end) && ((foundAloneHash && *token == "include") || *token == "#include"))
+	{
+		// We _must_ have an #include
+		ANKI_CHECK(parseInclude(token + 1, end, line, fname, depth));
+	}
+	else if((token < end) && ((foundAloneHash && *token == "pragma") || *token == "#pragma"))
+	{
+		// We may have a #pragma once or a #pragma anki or something else
+
+		++token;
+
+		if(*token == "once")
+		{
+			// Pragma once
+
+			if(foundPragmaOnce)
+			{
+				ANKI_PP_ERROR_MALFORMED_MSG("Can't have more than one #pragma once per file");
+			}
+
+			if(token + 1 != end)
+			{
+				ANKI_PP_ERROR_MALFORMED();
+			}
+
+			// Add the guard unique for this file
+			foundPragmaOnce = true;
+			const U64 hash = fname.computeHash();
+			m_codeLines.pushBackSprintf("#ifndef _ANKI_INCL_GUARD_%llu\n"
+										"#define _ANKI_INCL_GUARD_%llu",
+				hash,
+				hash);
+		}
+		else if(*token == "anki")
+		{
+			// Must be a #pragma anki
+
+			++token;
+
+			if(*token == "mutator")
+			{
+				ANKI_CHECK(parsePragmaMutator(token + 1, end, line, fname));
+			}
+			else if(*token == "input")
+			{
+				ANKI_CHECK(parsePragmaInput(token + 1, end, line, fname));
+			}
+			else if(*token == "start")
+			{
+				ANKI_CHECK(parsePragmaStart(token + 1, end, line, fname));
+			}
+			else if(*token == "end")
+			{
+				ANKI_CHECK(parsePragmaEnd(token + 1, end, line, fname));
+			}
+			else if(*token == "descriptor_set")
+			{
+				ANKI_CHECK(parsePragmaDescriptorSet(token + 1, end, line, fname));
+			}
+			else if(*token == "rewrite_mutation")
+			{
+				ANKI_CHECK(parsePragmaRewriteMutation(token + 1, end, line, fname));
+			}
+			else
+			{
+				ANKI_PP_ERROR_MALFORMED();
+			}
+		}
+		else
+		{
+			// Some other pragma
+			ANKI_SHADER_COMPILER_LOGW("Ignoring: %s", line.cstr());
+			m_codeLines.pushBack(line);
+		}
+	}
+	else
+	{
+		// Ignore
+		m_codeLines.pushBack(line);
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parseFile(CString fname, U32 depth)
+{
+	// First check the depth
+	if(depth > MAX_INCLUDE_DEPTH)
+	{
+		ANKI_SHADER_COMPILER_LOGE("The include depth is too high. Probably circular includance");
+	}
+
+	Bool foundPragmaOnce = false;
+
+	// Load file in lines
+	StringAuto txt(m_alloc);
+	ANKI_CHECK(m_fsystem->readAllText(fname, txt));
+
+	StringListAuto lines(m_alloc);
+	lines.splitString(txt.toCString(), '\n');
+	if(lines.getSize() < 1)
+	{
+		ANKI_SHADER_COMPILER_LOGE("Source is empty");
+	}
+
+	// Parse lines
+	for(const String& line : lines)
+	{
+		if(line.find("pragma") != CString::NPOS || line.find("include") != CString::NPOS)
+		{
+			// Possibly a preprocessor directive we care
+			ANKI_CHECK(parseLine(line.toCString(), fname, foundPragmaOnce, depth));
+		}
+		else
+		{
+			// Just append the line
+			m_codeLines.pushBack(line.toCString());
+		}
+	}
+
+	if(foundPragmaOnce)
+	{
+		// Append the guard
+		m_codeLines.pushBack("#endif // Include guard");
+	}
+
+	if(m_insideShader)
+	{
+		ANKI_SHADER_COMPILER_LOGE("Forgot a \"pragma anki end\"");
+		return Error::USER_DATA;
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::parse()
+{
+	ANKI_ASSERT(!m_fname.isEmpty());
+	ANKI_ASSERT(m_codeLines.isEmpty());
+
+	const CString fname = m_fname;
+
+	// Parse recursively
+	ANKI_CHECK(parseFile(fname, 0));
+
+	// Checks
+	{
+		if(m_foundAtLeastOneInstancedInput != (m_instancedMutatorIdx != MAX_U32))
+		{
+			ANKI_SHADER_COMPILER_LOGE("If there is an instanced mutator there should be at least one instanced input");
+			return Error::USER_DATA;
+		}
+
+		if(!!(m_shaderTypes & ShaderTypeBit::COMPUTE))
+		{
+			if(m_shaderTypes != ShaderTypeBit::COMPUTE)
+			{
+				ANKI_SHADER_COMPILER_LOGE("Can't combine compute shader with other types of shaders");
+				return Error::USER_DATA;
+			}
+
+			if(m_instancedMutatorIdx != MAX_U32)
+			{
+				ANKI_SHADER_COMPILER_LOGE("Can't have instanced mutators in compute programs");
+				return Error::USER_DATA;
+			}
+		}
+		else
+		{
+			if(!(m_shaderTypes & ShaderTypeBit::VERTEX))
+			{
+				ANKI_SHADER_COMPILER_LOGE("Missing vertex shader");
+				return Error::USER_DATA;
+			}
+
+			if(!(m_shaderTypes & ShaderTypeBit::FRAGMENT))
+			{
+				ANKI_SHADER_COMPILER_LOGE("Missing fragment shader");
+				return Error::USER_DATA;
+			}
+		}
+	}
+
+	// Create the UBO source code
+	{
+		m_uboStructLines.pushFrontSprintf("#define _ANKI_DSET %u", m_set);
+
+		m_uboStructLines.pushFront("struct _AnkiUniforms {");
+		m_uboStructLines.pushBack("};");
+
+		m_uboStructLines.pushBack("#if _ANKI_USE_PUSH_CONSTANTS == 1");
+		m_uboStructLines.pushBackSprintf(
+			"layout(push_constant, std140, row_major) uniform _anki_pc {_AnkiUniforms _anki_unis;};");
+		m_uboStructLines.pushBack("#else");
+		m_uboStructLines.pushBack(
+			"layout(set = _ANKI_DSET, binding = 0, row_major) uniform _anki_ubo {_AnkiUniforms _anki_unis;};");
+		m_uboStructLines.pushBack("#endif\n");
+
+		m_uboStructLines.join("\n", m_uboSource);
+		m_uboStructLines.destroy();
+	}
+
+	// Create the globals source code
+	if(m_globalsLines.getSize() > 0)
+	{
+		m_globalsLines.pushBack("\n");
+		m_globalsLines.join("\n", m_globalsSource);
+		m_globalsLines.destroy();
+	}
+
+	// Create the code lines
+	if(m_codeLines.getSize())
+	{
+		m_codeLines.pushBack("\n");
+		m_codeLines.join("\n", m_codeSource);
+		m_codeLines.destroy();
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::findActiveInputVars(CString source, BitSet<MAX_SHADER_PROGRAM_INPUT_VARIABLES>& active) const
+{
+	StringAuto preprocessedSrc(m_alloc);
+	ANKI_CHECK(preprocessGlsl(source, preprocessedSrc));
+
+	StringListAuto lines(m_alloc);
+	lines.splitString(preprocessedSrc, '\n');
+
+	for(const String& line : lines)
+	{
+		const CString prefix = "#pragma _anki_input_present_";
+		if(line.find(prefix) == String::NPOS)
+		{
+			continue;
+		}
+
+		ANKI_ASSERT(line.getLength() > prefix.getLength());
+		const CString varName = line.getBegin() + prefix.getLength();
+
+		// Find the input var
+		Bool found = false;
+		for(const Input& in : m_inputs)
+		{
+			if(in.m_name == varName)
+			{
+				active.set(in.m_idx);
+				found = true;
+				break;
+			}
+		}
+		(void)found;
+		ANKI_ASSERT(found);
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramParser::generateVariant(
+	ConstWeakArray<MutatorValue> mutation, ShaderProgramParserVariant& variant) const
+{
+	// Sanity checks
+	ANKI_ASSERT(m_codeSource.getLength() > 0);
+	ANKI_ASSERT(mutation.getSize() == m_mutators.getSize());
+	for(U i = 0; i < mutation.getSize(); ++i)
+	{
+		ANKI_ASSERT(mutatorHasValue(m_mutators[i], mutation[i]) && "Value not found");
+	}
+
+	// Init variant
+	::new(&variant) ShaderProgramParserVariant();
+	variant.m_alloc = m_alloc;
+	variant.m_bindings.create(m_alloc, m_inputs.getSize(), -1);
+	variant.m_blockInfos.create(m_alloc, m_inputs.getSize());
+
+	// Get instance count, one mutation has it
+	U32 instanceCount = 1;
+	if(m_instancedMutatorIdx != MAX_U32)
+	{
+		instanceCount = mutation[m_instancedMutatorIdx];
+	}
+
+	// Create the mutator defines
+	StringAuto mutatorDefines(m_alloc);
+	for(U i = 0; i < mutation.getSize(); ++i)
+	{
+		mutatorDefines.append(StringAuto(m_alloc).sprintf("#define %s %d\n", m_mutators[i].m_name.cstr(), mutation[i]));
+	}
+
+	// Create the header
+	StringAuto header(m_alloc);
+	header.sprintf(SHADER_HEADER,
+		m_backendMinor,
+		m_backendMajor,
+		GPU_VENDOR_STR[m_gpuVendor].cstr(),
+		MAX_BINDLESS_TEXTURES,
+		MAX_BINDLESS_IMAGES);
+
+	// Find active vars by running the preprocessor
+	StringAuto activeInputs(m_alloc);
+	if(m_inputs.getSize() > 0)
+	{
+		StringAuto src(m_alloc);
+		src.append(header);
+		src.append("#define ANKI_VERTEX_SHADER 1\n"); // Something random to avoid compilation errors
+		src.append(mutatorDefines);
+		src.append(m_codeSource);
+		ANKI_CHECK(findActiveInputVars(src, variant.m_activeInputVarsMask));
+
+		StringListAuto lines(m_alloc);
+		for(const Input& in : m_inputs)
+		{
+			const Bool active = variant.m_activeInputVarsMask.get(in.m_idx);
+			lines.pushBackSprintf("#define _ANKI_ACTIVATE_INPUT_%s %u", in.m_name.cstr(), active);
+		}
+
+		lines.pushBack("\n");
+		lines.join("\n", activeInputs);
+	}
+
+	// Initialize the active vars that are inside a UBO
+	for(const Input& in : m_inputs)
+	{
+		if(!variant.m_activeInputVarsMask.get(in.m_idx))
+		{
+			continue;
+		}
+
+		if(!in.inUbo())
+		{
+			continue;
+		}
+
+		ShaderVariableBlockInfo& blockInfo = variant.m_blockInfos[in.m_idx];
+
+		// std140 rules
+		blockInfo.m_offset = I16(variant.m_uniBlockSize);
+		blockInfo.m_arraySize = (in.m_instanced) ? I16(instanceCount) : 1;
+
+		if(in.m_dataType == ShaderVariableDataType::FLOAT || in.m_dataType == ShaderVariableDataType::INT
+			|| in.m_dataType == ShaderVariableDataType::UINT)
+		{
+			blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(blockInfo.m_arraySize == 1)
+			{
+				// No need to align the in.m_offset
+				variant.m_uniBlockSize += sizeof(F32);
+			}
+			else
+			{
+				alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+				variant.m_uniBlockSize += sizeof(Vec4) * blockInfo.m_arraySize;
+			}
+		}
+		else if(in.m_dataType == ShaderVariableDataType::VEC2 || in.m_dataType == ShaderVariableDataType::IVEC2
+				|| in.m_dataType == ShaderVariableDataType::UVEC2)
+		{
+			blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(blockInfo.m_arraySize == 1)
+			{
+				alignRoundUp(sizeof(Vec2), blockInfo.m_offset);
+				variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec2);
+			}
+			else
+			{
+				alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+				variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec4) * blockInfo.m_arraySize;
+			}
+		}
+		else if(in.m_dataType == ShaderVariableDataType::VEC3 || in.m_dataType == ShaderVariableDataType::IVEC3
+				|| in.m_dataType == ShaderVariableDataType::UVEC3)
+		{
+			alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+			blockInfo.m_arrayStride = sizeof(Vec4);
+
+			if(blockInfo.m_arraySize == 1)
+			{
+				variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec3);
+			}
+			else
+			{
+				variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec4) * blockInfo.m_arraySize;
+			}
+		}
+		else if(in.m_dataType == ShaderVariableDataType::VEC4 || in.m_dataType == ShaderVariableDataType::IVEC4
+				|| in.m_dataType == ShaderVariableDataType::UVEC4)
+		{
+			blockInfo.m_arrayStride = sizeof(Vec4);
+			alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+			variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec4) * blockInfo.m_arraySize;
+		}
+		else if(in.m_dataType == ShaderVariableDataType::MAT3)
+		{
+			alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+			blockInfo.m_arrayStride = sizeof(Vec4) * 3;
+			variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Vec4) * 3 * blockInfo.m_arraySize;
+			blockInfo.m_matrixStride = sizeof(Vec4);
+		}
+		else if(in.m_dataType == ShaderVariableDataType::MAT4)
+		{
+			alignRoundUp(sizeof(Vec4), blockInfo.m_offset);
+			blockInfo.m_arrayStride = sizeof(Mat4);
+			variant.m_uniBlockSize = blockInfo.m_offset + sizeof(Mat4) * blockInfo.m_arraySize;
+			blockInfo.m_matrixStride = sizeof(Vec4);
+		}
+		else
+		{
+			ANKI_ASSERT(0);
+		}
+	}
+
+	// Find if it's using push constants
+	StringAuto pushConstantDefineSrc(m_alloc);
+	{
+		variant.m_usesPushConstants = variant.m_uniBlockSize <= m_pushConstSize;
+		pushConstantDefineSrc.sprintf("#define _ANKI_USE_PUSH_CONSTANTS %u\n", variant.m_usesPushConstants);
+	}
+
+	// Handle the bindings for the textures and samplers
+	StringAuto bindingDefines(m_alloc);
+	{
+		StringListAuto defines(m_alloc);
+		U32 texOrSamplerBinding = variant.m_usesPushConstants ? 0 : 1;
+		for(const Input& in : m_inputs)
+		{
+			if(!variant.m_activeInputVarsMask.get(in.m_idx))
+			{
+				continue;
+			}
+
+			if(!in.isSampler() && !in.isTexture())
+			{
+				continue;
+			}
+
+			defines.pushBackSprintf("#define _ANKI_%s_BINDING %u", in.m_name.cstr(), texOrSamplerBinding);
+			variant.m_bindings[in.m_idx] = I16(texOrSamplerBinding++);
+		}
+
+		defines.pushBack(" ");
+
+		if(!defines.isEmpty())
+		{
+			defines.join("\n", bindingDefines);
+		}
+	}
+
+	// Generate souce per stage
+	for(ShaderType shaderType = ShaderType::FIRST; shaderType < ShaderType::COUNT; ++shaderType)
+	{
+		if(!((1u << ShaderTypeBit(shaderType)) & m_shaderTypes))
+		{
+			continue;
+		}
+
+		// Create the final source without the bindings
+		StringAuto finalSource(m_alloc);
+		finalSource.append(header);
+		finalSource.append(mutatorDefines);
+		finalSource.append(StringAuto(m_alloc).sprintf("#define ANKI_%s 1\n", SHADER_STAGE_NAMES[shaderType].cstr()));
+		finalSource.append(activeInputs);
+		finalSource.append(pushConstantDefineSrc);
+		finalSource.append(bindingDefines);
+		finalSource.append(m_uboSource);
+		finalSource.append(m_globalsSource);
+		finalSource.append(m_codeSource);
+
+		// Move the source
+		variant.m_sources[shaderType] = std::move(finalSource);
+	}
+
+	return Error::NONE;
+}
+
+Bool ShaderProgramParser::rewriteMutation(WeakArray<MutatorValue> mutation) const
+{
+	// Checks
+	ANKI_ASSERT(mutation.getSize() == m_mutators.getSize());
+	for(PtrSize i = 0; i < mutation.getSize(); ++i)
+	{
+		ANKI_ASSERT(mutatorHasValue(m_mutators[i], mutation[i]));
+	}
+
+	// Early exit
+	if(mutation.getSize() == 0)
+	{
+		return false;
+	}
+
+	// Find if mutation exists
+	for(const MutationRewrite& rewrite : m_mutationRewrites)
+	{
+		Bool found = true;
+		for(U i = 0; i < rewrite.m_records.getSize(); ++i)
+		{
+			if(rewrite.m_records[i].m_valueFrom != mutation[rewrite.m_records[i].m_mutatorIndex])
+			{
+				found = false;
+				break;
+			}
+		}
+
+		if(found)
+		{
+			// Rewrite it
+			for(U i = 0; i < rewrite.m_records.getSize(); ++i)
+			{
+				mutation[rewrite.m_records[i].m_mutatorIndex] = rewrite.m_records[i].m_valueTo;
+			}
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+Bool ShaderProgramParser::mutatorHasValue(const ShaderProgramParserMutator& mutator, MutatorValue value)
+{
+	for(MutatorValue v : mutator.m_values)
+	{
+		if(value == v)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+} // end namespace anki

+ 319 - 0
src/anki/shader_compiler/ShaderProgramParser.h

@@ -0,0 +1,319 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/shader_compiler/Common.h>
+#include <anki/util/StringList.h>
+#include <anki/util/WeakArray.h>
+#include <anki/util/DynamicArray.h>
+#include <anki/util/BitSet.h>
+#include <anki/gr/utils/Functions.h>
+
+namespace anki
+{
+
+// Forward
+class ShaderProgramParser;
+class ShaderProgramParserVariant;
+
+/// @addtogroup shader_compiler
+/// @{
+
+/// @memberof ShaderProgramParser
+class ShaderProgramParserMutator
+{
+	friend ShaderProgramParser;
+
+public:
+	ShaderProgramParserMutator(GenericMemoryPoolAllocator<U8> alloc)
+		: m_name(alloc)
+		, m_values(alloc)
+	{
+	}
+
+	CString getName() const
+	{
+		return m_name;
+	}
+
+	ConstWeakArray<MutatorValue> getValues() const
+	{
+		return m_values;
+	}
+
+	Bool isInstanceCount() const
+	{
+		return m_instanceCount;
+	}
+
+private:
+	StringAuto m_name;
+	DynamicArrayAuto<MutatorValue> m_values;
+	Bool m_instanceCount = false;
+};
+
+/// @memberof ShaderProgramParser
+class ShaderProgramParserInput
+{
+	friend ShaderProgramParser;
+	friend ShaderProgramParserVariant;
+
+public:
+	ShaderProgramParserInput(GenericMemoryPoolAllocator<U8> alloc)
+		: m_name(alloc)
+	{
+	}
+
+	CString getName() const
+	{
+		return m_name.toCString();
+	}
+
+	ShaderVariableDataType getDataType() const
+	{
+		return m_dataType;
+	}
+
+	Bool isInstanced() const
+	{
+		return m_instanced;
+	}
+
+	/// @param constantId It's the vulkan spec const index.
+	Bool isConstant(U32* constantId = nullptr) const
+	{
+		if(constantId)
+		{
+			*constantId = m_specConstId;
+		}
+		return m_specConstId != MAX_U32;
+	}
+
+	Bool isTexture() const
+	{
+		return m_dataType >= ShaderVariableDataType::TEXTURE_FIRST
+			   && m_dataType <= ShaderVariableDataType::TEXTURE_LAST;
+	}
+
+	Bool isSampler() const
+	{
+		return m_dataType == ShaderVariableDataType::SAMPLER;
+	}
+
+	Bool inUbo() const
+	{
+		return !isConstant() && !isTexture() && !isSampler();
+	}
+
+private:
+	StringAuto m_name;
+	U32 m_idx = MAX_U32; ///< Index inside an array.
+	U32 m_specConstId = MAX_U32;
+	Bool m_instanced = false;
+	ShaderVariableDataType m_dataType = ShaderVariableDataType::NONE;
+};
+
+/// @memberof ShaderProgramParser
+class ShaderProgramParserVariant
+{
+	friend class ShaderProgramParser;
+
+public:
+	~ShaderProgramParserVariant()
+	{
+		for(String& s : m_sources)
+		{
+			s.destroy(m_alloc);
+		}
+		m_blockInfos.destroy(m_alloc);
+		m_bindings.destroy(m_alloc);
+	}
+
+	CString getSource(ShaderType type) const
+	{
+		return m_sources[type];
+	}
+
+	Bool isInputActive(const ShaderProgramParserInput& in) const
+	{
+		return m_activeInputVarsMask.get(in.m_idx);
+	}
+
+	const ShaderVariableBlockInfo& getBlockInfo(const ShaderProgramParserInput& in) const
+	{
+		ANKI_ASSERT(in.inUbo() && isInputActive(in));
+		return m_blockInfos[in.m_idx];
+	}
+
+	U32 getBinding(const ShaderProgramParserInput& in) const
+	{
+		ANKI_ASSERT((in.isSampler() || in.isTexture()) && m_bindings[in.m_idx] >= 0);
+		return U32(m_bindings[in.m_idx]);
+	}
+
+	U32 getBlockSize() const
+	{
+		return m_uniBlockSize;
+	}
+
+	Bool usesPushConstants() const
+	{
+		return m_usesPushConstants;
+	}
+
+private:
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	Array<String, U(ShaderType::COUNT)> m_sources;
+	DynamicArray<ShaderVariableBlockInfo> m_blockInfos;
+	DynamicArray<I16> m_bindings;
+	U32 m_uniBlockSize = 0;
+	Bool m_usesPushConstants = false;
+	BitSet<MAX_SHADER_PROGRAM_INPUT_VARIABLES> m_activeInputVarsMask = {false};
+};
+
+/// This is a special preprocessor that run before the usual preprocessor. Its purpose is to add some meta information
+/// in the shader programs.
+///
+/// It supports the following expressions:
+/// #include {<> | ""}
+/// #pragma once
+/// #pragma anki mutator [instanced] NAME VALUE0 [VALUE1 [VALUE2] ...]
+/// #pragma anki rewrite_mutation NAME_A VALUE0 NAME_B VALUE1 [NAME_C VALUE3...] to
+///                               NAME_A VALUE4 NAME_B VALUE5 [NAME_C VALUE6...]
+/// #pragma anki input [const | instanced] TYPE NAME
+/// #pragma anki start {vert | tessc | tesse | geom | frag | comp}
+/// #pragma anki end
+/// #pragma anki descriptor_set <number>
+///
+/// Only the "anki input" should be in an ifdef-like guard. For everything else it's ignored.
+class ShaderProgramParser : public NonCopyable
+{
+public:
+	ShaderProgramParser(CString fname,
+		ShaderProgramFilesystemInterface* fsystem,
+		GenericMemoryPoolAllocator<U8> alloc,
+		U32 pushConstantsSize,
+		U32 backendMinor,
+		U32 backendMajor,
+		GpuVendor gpuVendor);
+
+	~ShaderProgramParser();
+
+	/// Parse the file and its includes.
+	ANKI_USE_RESULT Error parse();
+
+	/// Given a mutation convert it to something acceptable. This will reduce the variants.
+	/// @return true if the mutation was rewritten.
+	Bool rewriteMutation(WeakArray<MutatorValue> mutation) const;
+
+	/// Get the source (and a few more things) given a list of mutators.
+	ANKI_USE_RESULT Error generateVariant(
+		ConstWeakArray<MutatorValue> mutation, ShaderProgramParserVariant& variant) const;
+
+	ConstWeakArray<ShaderProgramParserMutator> getMutators() const
+	{
+		return m_mutators;
+	}
+
+	ConstWeakArray<ShaderProgramParserInput> getInputs() const
+	{
+		return m_inputs;
+	}
+
+	ShaderTypeBit getShaderTypes() const
+	{
+		return m_shaderTypes;
+	}
+
+	U32 getDescritproSet() const
+	{
+		return m_set;
+	}
+
+private:
+	using Mutator = ShaderProgramParserMutator;
+	using Input = ShaderProgramParserInput;
+
+	class MutationRewrite
+	{
+	public:
+		class Record
+		{
+		public:
+			U32 m_mutatorIndex = MAX_U32;
+			MutatorValue m_valueFrom = getMaxNumericLimit<MutatorValue>();
+			MutatorValue m_valueTo = getMaxNumericLimit<MutatorValue>();
+
+			Bool operator!=(const Record& b) const
+			{
+				return !(
+					m_mutatorIndex == b.m_mutatorIndex && m_valueFrom == b.m_valueFrom && m_valueTo == b.m_valueTo);
+			}
+		};
+
+		DynamicArrayAuto<Record> m_records;
+
+		MutationRewrite(GenericMemoryPoolAllocator<U8> alloc)
+			: m_records(alloc)
+		{
+		}
+	};
+
+	static const U32 MAX_INCLUDE_DEPTH = 8;
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	StringAuto m_fname;
+	ShaderProgramFilesystemInterface* m_fsystem = nullptr;
+
+	StringListAuto m_codeLines = {m_alloc}; ///< The code.
+	StringListAuto m_globalsLines = {m_alloc};
+	StringListAuto m_uboStructLines = {m_alloc};
+	StringAuto m_codeSource = {m_alloc};
+	StringAuto m_globalsSource = {m_alloc};
+	StringAuto m_uboSource = {m_alloc};
+
+	DynamicArrayAuto<Mutator> m_mutators = {m_alloc};
+	DynamicArrayAuto<Input> m_inputs = {m_alloc};
+	DynamicArrayAuto<MutationRewrite> m_mutationRewrites = {m_alloc};
+
+	ShaderTypeBit m_shaderTypes = ShaderTypeBit::NONE;
+	Bool m_insideShader = false;
+	U32 m_set = 0;
+	U32 m_instancedMutatorIdx = MAX_U32;
+	U32 m_specConstIdx = 0;
+	const U32 m_pushConstSize = 0;
+	const U32 m_backendMinor = 1;
+	const U32 m_backendMajor = 1;
+	const GpuVendor m_gpuVendor = GpuVendor::AMD;
+	Bool m_foundAtLeastOneInstancedInput = false;
+
+	ANKI_USE_RESULT Error parseFile(CString fname, U32 depth);
+	ANKI_USE_RESULT Error parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth);
+	ANKI_USE_RESULT Error parseInclude(
+		const StringAuto* begin, const StringAuto* end, CString line, CString fname, U32 depth);
+	ANKI_USE_RESULT Error parsePragmaMutator(
+		const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaInput(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaStart(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaEnd(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaDescriptorSet(
+		const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaRewriteMutation(
+		const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+
+	ANKI_USE_RESULT Error findActiveInputVars(CString source, BitSet<MAX_SHADER_PROGRAM_INPUT_VARIABLES>& active) const;
+	void tokenizeLine(CString line, DynamicArrayAuto<StringAuto>& tokens) const;
+
+	static Bool tokenIsComment(CString token)
+	{
+		return token.getLength() >= 2 && token[0] == '/' && (token[1] == '/' || token[1] == '*');
+	}
+
+	static Bool mutatorHasValue(const ShaderProgramParserMutator& mutator, MutatorValue value);
+};
+/// @}
+
+} // end namespace anki

+ 18 - 12
src/anki/util/BitSet.h

@@ -5,7 +5,7 @@
 
 #pragma once
 
-#include <anki/util/StdTypes.h>
+#include <anki/util/Array.h>
 #include <initializer_list>
 #include <cstring>
 
@@ -21,6 +21,15 @@ namespace anki
 template<U32 N, typename TChunkType = U8>
 class BitSet
 {
+protected:
+	using ChunkType = TChunkType;
+
+	/// Number of bits a chunk holds.
+	static constexpr U32 CHUNK_BIT_COUNT = sizeof(ChunkType) * 8;
+
+	/// Number of chunks.
+	static constexpr U32 CHUNK_COUNT = (N + (CHUNK_BIT_COUNT - 1)) / CHUNK_BIT_COUNT;
+
 public:
 	/// Constructor. It will set all the bits or unset them.
 	BitSet(Bool set)
@@ -158,7 +167,7 @@ public:
 	/// Set all bits.
 	void setAll()
 	{
-		memset(m_chunks, 0xFF, sizeof(m_chunks));
+		memset(&m_chunks[0], 0xFF, sizeof(m_chunks));
 		zeroUnusedBits();
 	}
 
@@ -179,7 +188,7 @@ public:
 	/// Unset all bits.
 	void unsetAll()
 	{
-		memset(m_chunks, 0, sizeof(m_chunks));
+		memset(&m_chunks[0], 0, sizeof(m_chunks));
 	}
 
 	/// Flip the bits at the given position. It will go from 1 to 0 or from 0 to 1.
@@ -237,16 +246,13 @@ public:
 		return MAX_U32;
 	}
 
-protected:
-	using ChunkType = TChunkType;
-
-	/// Number of bits a chunk holds.
-	static const U32 CHUNK_BIT_COUNT = sizeof(ChunkType) * 8;
-
-	/// Number of chunks.
-	static const U32 CHUNK_COUNT = (N + (CHUNK_BIT_COUNT - 1)) / CHUNK_BIT_COUNT;
+	Array<TChunkType, CHUNK_COUNT> getData() const
+	{
+		return m_chunks;
+	}
 
-	ChunkType m_chunks[CHUNK_COUNT];
+protected:
+	Array<ChunkType, CHUNK_COUNT> m_chunks;
 
 	BitSet()
 	{

+ 1 - 1
src/anki/util/CMakeLists.txt

@@ -1,5 +1,5 @@
 set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.cpp HighRezTimer.cpp ThreadPool.cpp
-	ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp)
+	ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp Serializer.cpp)
 
 if(LINUX OR ANDROID OR MACOS)
 	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)

+ 19 - 0
src/anki/util/DynamicArray.h

@@ -222,6 +222,16 @@ public:
 		return &m_data[m_size - 1];
 	}
 
+	/// Remove the last value.
+	template<typename TAllocator>
+	void popBack(TAllocator alloc)
+	{
+		if(m_size > 0)
+		{
+			resizeStorage(alloc, m_size - 1);
+		}
+	}
+
 	/// Emplace a new element at a specific position. @a T needs to be movable and default constructible.
 	/// @param alloc The allocator.
 	/// @param where Points to the position to emplace. Should be less or equal to what getEnd() returns.
@@ -314,6 +324,15 @@ public:
 		resize(size);
 	}
 
+	/// With default value
+	template<typename TAllocator>
+	DynamicArrayAuto(TAllocator alloc, PtrSize size, const T& v)
+		: Base()
+		, m_alloc(alloc)
+	{
+		create(size, v);
+	}
+
 	/// Move.
 	DynamicArrayAuto(DynamicArrayAuto&& b)
 		: Base()

+ 23 - 0
src/anki/util/File.cpp

@@ -452,6 +452,29 @@ Error File::seek(PtrSize offset, FileSeekOrigin origin)
 	return err;
 }
 
+PtrSize File::tell()
+{
+	ANKI_ASSERT(m_file);
+	ANKI_ASSERT(m_flags != FileOpenFlag::NONE);
+
+	if(m_type == Type::C)
+	{
+		return ftell(ANKI_CFILE);
+	}
+#if ANKI_OS_ANDROID
+	else if(m_type == Type::SPECIAL)
+	{
+		ANKI_ASSERT(0);
+	}
+#endif
+	else
+	{
+		ANKI_ASSERT(0);
+	}
+
+	return 0;
+}
+
 Error File::identifyFile(const CString& filename,
 	char* archiveFilename,
 	PtrSize archiveFilenameLength,

+ 3 - 0
src/anki/util/File.h

@@ -106,6 +106,9 @@ public:
 	/// @param origin Position used as reference for the offset
 	ANKI_USE_RESULT Error seek(PtrSize offset, FileSeekOrigin origin);
 
+	/// Return the position indicator inside the file.
+	PtrSize tell();
+
 	/// The the size of the file.
 	PtrSize getSize() const;
 

+ 24 - 7
src/anki/util/Functions.h

@@ -33,7 +33,30 @@ namespace anki
 /// Make a preprocessor token a string.
 #define ANKI_STRINGIZE(a) _ANKI_STRINGIZE(a)
 
+/// Format to print bits
+#define ANKI_PRIb8 "c%c%c%c%c%c%c%c"
+#define ANKI_PRIb16 ANKI_PRIb8 "%c%c%c%c%c%c%c%c"
+#define ANKI_PRIb32 ANKI_PRIb16 "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c"
+#define ANKI_PRIb64 ANKI_PRIb32 "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c"
+
+#define _ANKI_FORMAT_HELPER(byte, bit) (U64(byte) & (U64(1) << U64(bit))) ? '1' : '0'
+
+#define ANKI_FORMAT_U8(byte) \
+	_ANKI_FORMAT_HELPER((byte), 7), _ANKI_FORMAT_HELPER((byte), 6), _ANKI_FORMAT_HELPER((byte), 5), \
+		_ANKI_FORMAT_HELPER((byte), 4), _ANKI_FORMAT_HELPER((byte), 3), _ANKI_FORMAT_HELPER((byte), 2), \
+		_ANKI_FORMAT_HELPER((byte), 1), _ANKI_FORMAT_HELPER((byte), 0)
+
+#define ANKI_FORMAT_U16(u16) ANKI_FORMAT_U8(u16 >> 8), ANKI_FORMAT_U8(u16)
+#define ANKI_FORMAT_U32(u32) ANKI_FORMAT_U16(u32 >> 16), ANKI_FORMAT_U16(u32)
+#define ANKI_FORMAT_U64(u64) ANKI_FORMAT_U32(u64 >> 32), ANKI_FORMAT_U32(u64)
+
 // ANKI_ENABLE_METHOD & ANKI_ENABLE_ARG trickery copied from Tick library
+template<typename T, int N>
+struct DummyType
+{
+};
+
+#if defined(_MSC_VER)
 template<bool B>
 struct RequiresBool
 {
@@ -54,12 +77,6 @@ struct PrivateEnum
 	};
 };
 
-template<typename T, int N>
-struct DummyType
-{
-};
-
-#if defined(_MSC_VER)
 #	define ANKI_REQUIRES_BOOL(line, ...) RequiresUnwrap<decltype(RequiresBool<(__VA_ARGS__)>{}), line>::VALUE
 
 #	define ANKI_ENABLE_INTERNAL(line, ...) \
@@ -223,7 +240,7 @@ inline void alignRoundDown(TAlignment alignment, TValue& value)
 template<typename Type>
 inline Bool isAligned(PtrSize alignment, Type value)
 {
-	return ((PtrSize)value % alignment) == 0;
+	return (PtrSize(value) % alignment) == 0;
 }
 
 template<typename T>

+ 1 - 1
src/anki/util/HighRezTimerWindows.cpp

@@ -23,7 +23,7 @@ public:
 
 	DummyInitTimer()
 	{
-		if (!QueryPerformanceFrequency(&m_ticksPerSec))
+		if(!QueryPerformanceFrequency(&m_ticksPerSec))
 		{
 			fprintf(stderr, "QueryPerformanceFrequency() failed\n");
 			abort();

+ 11 - 39
src/anki/util/Memory.cpp

@@ -147,45 +147,6 @@ Bool BaseMemoryPool::isCreated() const
 	return m_allocCb != nullptr;
 }
 
-void* BaseMemoryPool::allocate(PtrSize size, PtrSize alignmentBytes)
-{
-	void* out = nullptr;
-	switch(m_type)
-	{
-	case Type::HEAP:
-		out = static_cast<HeapMemoryPool*>(this)->allocate(size, alignmentBytes);
-		break;
-	case Type::STACK:
-		out = static_cast<StackMemoryPool*>(this)->allocate(size, alignmentBytes);
-		break;
-	case Type::CHAIN:
-		out = static_cast<ChainMemoryPool*>(this)->allocate(size, alignmentBytes);
-		break;
-	default:
-		ANKI_ASSERT(0);
-	}
-
-	return out;
-}
-
-void BaseMemoryPool::free(void* ptr)
-{
-	switch(m_type)
-	{
-	case Type::HEAP:
-		static_cast<HeapMemoryPool*>(this)->free(ptr);
-		break;
-	case Type::STACK:
-		static_cast<StackMemoryPool*>(this)->free(ptr);
-		break;
-	case Type::CHAIN:
-		static_cast<ChainMemoryPool*>(this)->free(ptr);
-		break;
-	default:
-		ANKI_ASSERT(0);
-	}
-}
-
 HeapMemoryPool::HeapMemoryPool()
 	: BaseMemoryPool(Type::HEAP)
 {
@@ -250,6 +211,11 @@ void HeapMemoryPool::free(void* ptr)
 {
 	ANKI_ASSERT(isCreated());
 
+	if(ANKI_UNLIKELY(ptr == nullptr))
+	{
+		return;
+	}
+
 #if ANKI_MEM_SIGNATURES
 	U8* memU8 = static_cast<U8*>(ptr);
 	memU8 -= m_headerSize;
@@ -433,6 +399,11 @@ void StackMemoryPool::free(void* ptr)
 {
 	ANKI_ASSERT(isCreated());
 
+	if(ANKI_UNLIKELY(ptr == nullptr))
+	{
+		return;
+	}
+
 	// ptr shouldn't be null or not aligned. If not aligned it was not
 	// allocated by this class
 	ANKI_ASSERT(ptr != nullptr && isAligned(m_alignmentBytes, ptr));
@@ -592,6 +563,7 @@ void* ChainMemoryPool::allocate(PtrSize size, PtrSize alignment)
 void ChainMemoryPool::free(void* ptr)
 {
 	ANKI_ASSERT(isCreated());
+
 	if(ANKI_UNLIKELY(ptr == nullptr))
 	{
 		return;

+ 52 - 17
src/anki/util/Memory.h

@@ -53,20 +53,6 @@ void* allocAligned(void* userData, void* ptr, PtrSize size, PtrSize alignment);
 class BaseMemoryPool : public NonCopyable
 {
 public:
-	/// Pool type.
-	enum class Type : U8
-	{
-		NONE,
-		HEAP,
-		STACK,
-		CHAIN
-	};
-
-	BaseMemoryPool(Type type)
-		: m_type(type)
-	{
-	}
-
 	virtual ~BaseMemoryPool();
 
 	/// Allocate memory. This operation MAY be thread safe
@@ -110,6 +96,15 @@ public:
 	}
 
 protected:
+	/// Pool type.
+	enum class Type : U8
+	{
+		NONE,
+		HEAP,
+		STACK,
+		CHAIN
+	};
+
 	/// User allocation function.
 	AllocAlignedCallback m_allocCb = nullptr;
 
@@ -119,15 +114,20 @@ protected:
 	/// Allocations count.
 	Atomic<U32> m_allocationsCount = {0};
 
+	BaseMemoryPool(Type type)
+		: m_type(type)
+	{
+	}
+
 	/// Check if already created.
 	Bool isCreated() const;
 
 private:
-	/// Type.
-	Type m_type = Type::NONE;
-
 	/// Refcount.
 	Atomic<U32> m_refcount = {0};
+
+	/// Type.
+	Type m_type = Type::NONE;
 };
 
 /// A dummy interface to match the StackMemoryPool and ChainMemoryPool interfaces in order to be used by the same
@@ -370,6 +370,41 @@ private:
 	/// Destroy a chunk.
 	void destroyChunk(Chunk* ch);
 };
+
+inline void* BaseMemoryPool::allocate(PtrSize size, PtrSize alignmentBytes)
+{
+	void* out = nullptr;
+	switch(m_type)
+	{
+	case Type::HEAP:
+		out = static_cast<HeapMemoryPool*>(this)->allocate(size, alignmentBytes);
+		break;
+	case Type::STACK:
+		out = static_cast<StackMemoryPool*>(this)->allocate(size, alignmentBytes);
+		break;
+	default:
+		ANKI_ASSERT(m_type == Type::CHAIN);
+		out = static_cast<ChainMemoryPool*>(this)->allocate(size, alignmentBytes);
+	}
+
+	return out;
+}
+
+inline void BaseMemoryPool::free(void* ptr)
+{
+	switch(m_type)
+	{
+	case Type::HEAP:
+		static_cast<HeapMemoryPool*>(this)->free(ptr);
+		break;
+	case Type::STACK:
+		static_cast<StackMemoryPool*>(this)->free(ptr);
+		break;
+	default:
+		ANKI_ASSERT(m_type == Type::CHAIN);
+		static_cast<ChainMemoryPool*>(this)->free(ptr);
+	}
+}
 /// @}
 
 } // end namespace anki

+ 45 - 0
src/anki/util/Serializer.cpp

@@ -0,0 +1,45 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/Serializer.h>
+
+namespace anki
+{
+
+Error BinarySerializer::doDynamicArrayBasicType(const void* arr, PtrSize size, U32 alignment, PtrSize memberOffset)
+{
+	ANKI_ASSERT(arr);
+	check();
+
+	if(size == 0)
+	{
+		ANKI_ASSERT(arr == nullptr);
+		// Do nothing
+	}
+	else
+	{
+		ANKI_ASSERT(arr);
+
+		// Move file pos to the end of the file (allocate space)
+		PtrSize arrayFilePos = getAlignedRoundUp(alignment, m_eofPos);
+		m_eofPos = arrayFilePos + size;
+
+		// Store the pointer for later
+		const PtrSize structFilePos = m_structureFilePos.getBack();
+		PointerInfo pinfo;
+		pinfo.m_filePos = structFilePos + memberOffset;
+		pinfo.m_value = arrayFilePos - m_beginOfDataFilePos;
+		m_pointerFilePositions.emplaceBack(m_alloc, pinfo);
+
+		// Write the array
+		ANKI_CHECK(m_file->seek(arrayFilePos, FileSeekOrigin::BEGINNING));
+		ANKI_CHECK(m_file->write(arr, size));
+	}
+
+	check();
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 198 - 0
src/anki/util/Serializer.h

@@ -0,0 +1,198 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/File.h>
+
+#pragma once
+
+namespace anki
+{
+
+/// @addtogroup util_file
+/// @{
+
+#define _ANKI_SIMPLE_TYPE (std::is_integral<T>::value || std::is_floating_point<T>::value || std::is_enum<T>::value)
+
+/// Serialize functor. Used to add serialization code to classes that you can't add a serialize() method.
+template<typename T>
+class SerializeFunctor
+{
+public:
+	template<typename TSerializer>
+	void operator()(const T& x, TSerializer& serializer)
+	{
+		x.serialize(serializer);
+	}
+};
+
+/// Deserialize functor. Used to add deserialization code to classes that you can't add a serialize() method.
+template<typename T>
+class DeserializeFunctor
+{
+public:
+	template<typename TDeserializer>
+	void operator()(T& x, TDeserializer& deserializer)
+	{
+		x.deserialize(deserializer);
+	}
+};
+
+/// Serializes to binary files.
+class BinarySerializer : public NonCopyable
+{
+public:
+	/// Serialize a class.
+	/// @param x What to serialize.
+	/// @param tmpAllocator A temp allocator for some memory needed.
+	/// @param file The file to populate.
+	template<typename T>
+	ANKI_USE_RESULT Error serialize(const T& x, GenericMemoryPoolAllocator<U8> tmpAllocator, File& file)
+	{
+		const Error err = serializeInternal(x, tmpAllocator, file);
+		if(err)
+		{
+			ANKI_UTIL_LOGE("There was a serialization error");
+		}
+		return err;
+	}
+
+	/// Write a single value. Can't call this directly.
+	template<typename T>
+	void doValue(CString varName, PtrSize memberOffset, const T& x)
+	{
+		doArray(varName, memberOffset, &x, 1);
+	}
+
+	/// Write an array of complex values. Can't call this directly.
+	template<typename T, ANKI_ENABLE(!_ANKI_SIMPLE_TYPE)>
+	void doArray(CString varName, PtrSize memberOffset, const T* arr, PtrSize size)
+	{
+		if(!m_err)
+		{
+			m_err = doArrayComplexType(arr, size);
+		}
+	}
+
+	/// Write an array of int or float types. Can't call this directly.
+	template<typename T, ANKI_ENABLE(_ANKI_SIMPLE_TYPE)>
+	void doArray(CString varName, PtrSize memberOffset, const T* arr, PtrSize size)
+	{
+		// Do nothing, it's already copied
+	}
+
+	/// Write a pointer. Can't call this directly.
+	template<typename T>
+	void doPointer(CString varName, PtrSize memberOffset, const T* ptr)
+	{
+		doDynamicArray(varName, memberOffset, ptr, 1);
+	}
+
+	/// Write a dynamic array of complex types. Can't call this directly.
+	template<typename T, ANKI_ENABLE(!_ANKI_SIMPLE_TYPE)>
+	void doDynamicArray(CString varName, PtrSize memberOffset, const T* arr, PtrSize size)
+	{
+		if(!m_err)
+		{
+			m_err = doDynamicArrayComplexType(arr, size, memberOffset);
+		}
+	}
+
+	/// Write a dynamic array of int and float values. Can't call this directly.
+	template<typename T, ANKI_ENABLE(_ANKI_SIMPLE_TYPE)>
+	void doDynamicArray(CString varName, PtrSize memberOffset, const T* arr, PtrSize size)
+	{
+		if(!m_err)
+		{
+			m_err = doDynamicArrayBasicType(arr, size * sizeof(T), alignof(T), memberOffset);
+		}
+	}
+
+private:
+	class PointerInfo
+	{
+	public:
+		PtrSize m_filePos;
+		PtrSize m_value;
+	};
+
+	File* m_file = nullptr;
+	PtrSize m_eofPos; ///< A logical end of the file. Used for allocations.
+	PtrSize m_beginOfDataFilePos; ///< Where the data are located in the file.
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	DynamicArray<PointerInfo> m_pointerFilePositions; ///< Array of file positions that contain pointers.
+	DynamicArray<PtrSize> m_structureFilePos;
+	Error m_err = Error::NONE;
+
+	template<typename T>
+	ANKI_USE_RESULT Error doArrayComplexType(const T* arr, PtrSize size);
+
+	template<typename T>
+	ANKI_USE_RESULT Error doDynamicArrayComplexType(const T* arr, PtrSize size, PtrSize memberOffset);
+
+	ANKI_USE_RESULT Error doDynamicArrayBasicType(const void* arr, PtrSize size, U32 alignment, PtrSize memberOffset);
+
+	template<typename T>
+	ANKI_USE_RESULT Error serializeInternal(const T& x, GenericMemoryPoolAllocator<U8> tmpAllocator, File& file);
+
+	void check()
+	{
+		ANKI_ASSERT(m_file && "Can't call this function");
+		ANKI_ASSERT(m_file->tell() <= m_eofPos);
+	}
+
+	template<typename T>
+	static void checkStruct()
+	{
+		static_assert(!std::is_polymorphic<T>::value, "Only PODs are supported in this serializer");
+		static_assert(alignof(T) <= ANKI_SAFE_ALIGNMENT, "Alignments can't exceed ANKI_SAFE_ALIGNMENT");
+	}
+};
+
+/// Deserializes binary files.
+class BinaryDeserializer : public NonCopyable
+{
+public:
+	/// Serialize a class.
+	/// @param x The struct to read.
+	/// @param allocator The allocator to use to allocate the new structures.
+	/// @param file The file to read from.
+	template<typename T>
+	static ANKI_USE_RESULT Error deserialize(T*& x, GenericMemoryPoolAllocator<U8> allocator, File& file);
+
+	/// Read a single value. Can't call this directly.
+	template<typename T>
+	void doValue(CString varName, PtrSize memberOffset, T& x)
+	{
+		// Do nothing
+	}
+
+	/// Read an array. Can't call this directly.
+	template<typename T>
+	void doArray(CString varName, PtrSize memberOffset, T* arr, PtrSize size)
+	{
+		// Do nothing
+	}
+
+	/// Read a pointer. Can't call this directly.
+	template<typename T>
+	void doPointer(CString varName, PtrSize memberOffset, T* ptr)
+	{
+		// Do nothing
+	}
+
+	/// Read a dynamic array of complex types. Can't call this directly.
+	template<typename T>
+	void doDynamicArray(CString varName, PtrSize memberOffset, T* arr, PtrSize size)
+	{
+		// Do nothing
+	}
+};
+/// @}
+
+#undef _ANKI_SIMPLE_TYPE
+
+} // end namespace anki
+
+#include <anki/util/Serializer.inl.h>

+ 234 - 0
src/anki/util/Serializer.inl.h

@@ -0,0 +1,234 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/Serializer.h>
+
+namespace anki
+{
+
+namespace detail
+{
+
+class alignas(ANKI_SAFE_ALIGNMENT) BinarySerializerHeader
+{
+public:
+	Array<U8, 8> m_magic;
+	PtrSize m_dataSize;
+	PtrSize m_pointerArrayFilePosition; ///< Points to an array of file positions that contain pointers.
+	PtrSize m_pointerCount; ///< The size of the above.
+};
+
+static constexpr const char* BINARY_SERIALIZER_MAGIC = "ANKIBIN1";
+
+} // end namespace detail
+
+template<typename T>
+Error BinarySerializer::serializeInternal(const T& x, GenericMemoryPoolAllocator<U8> tmpAllocator, File& file)
+{
+	checkStruct<T>();
+
+	// Misc
+	m_file = &file;
+	ANKI_ASSERT(m_file->tell() == 0);
+	m_err = Error::NONE;
+	m_alloc = tmpAllocator;
+
+	// Write the empty header (will be filled later)
+	detail::BinarySerializerHeader header = {};
+	const PtrSize headerFilePos = m_file->tell();
+	ANKI_CHECK(m_file->write(&header, sizeof(header)));
+
+	// Set EOF file pos
+	const PtrSize dataFilePos = headerFilePos + sizeof(header);
+	ANKI_ASSERT(isAligned(ANKI_SAFE_ALIGNMENT, dataFilePos));
+	m_eofPos = dataFilePos + sizeof(T);
+	m_beginOfDataFilePos = dataFilePos;
+
+	// Finaly, serialize
+	ANKI_CHECK(m_file->write(&x, sizeof(T)));
+	m_structureFilePos.emplaceBack(m_alloc, dataFilePos);
+	doValue("root", 0, x);
+	m_structureFilePos.popBack(m_alloc);
+	if(m_err)
+	{
+		return m_err;
+	}
+
+	// Write all pointers
+	DynamicArrayAuto<PtrSize> pointerFilePositions(m_alloc);
+	for(const PointerInfo& pointer : m_pointerFilePositions)
+	{
+		ANKI_CHECK(m_file->seek(pointer.m_filePos, FileSeekOrigin::BEGINNING));
+		ANKI_CHECK(m_file->write(&pointer.m_value, sizeof(pointer.m_value)));
+
+		pointerFilePositions.emplaceBack(pointer.m_filePos - m_beginOfDataFilePos);
+	}
+
+	// Write the pointer offsets
+	if(pointerFilePositions.getSize() > 0)
+	{
+		ANKI_CHECK(m_file->seek(m_eofPos, FileSeekOrigin::BEGINNING));
+		ANKI_CHECK(m_file->write(&pointerFilePositions[0], pointerFilePositions.getSizeInBytes()));
+		header.m_pointerCount = pointerFilePositions.getSize();
+		header.m_pointerArrayFilePosition = m_eofPos;
+	}
+
+	// Write the header
+	memcpy(&header.m_magic[0], detail::BINARY_SERIALIZER_MAGIC, sizeof(header.m_magic));
+	header.m_dataSize = m_eofPos - dataFilePos;
+	ANKI_CHECK(m_file->seek(headerFilePos, FileSeekOrigin::BEGINNING));
+	ANKI_CHECK(m_file->write(&header, sizeof(header)));
+
+	// Done
+	m_file = nullptr;
+	m_pointerFilePositions.destroy(m_alloc);
+	m_alloc = GenericMemoryPoolAllocator<U8>();
+	return Error::NONE;
+}
+
+template<typename T>
+Error BinarySerializer::doArrayComplexType(const T* arr, PtrSize size)
+{
+	ANKI_ASSERT(arr && size > 0);
+	check();
+	checkStruct<T>();
+
+	// Serialize pointers
+	PtrSize structFilePos = m_structureFilePos.getBack();
+	for(PtrSize i = 0; i < size; ++i)
+	{
+		m_structureFilePos.emplaceBack(m_alloc, structFilePos);
+
+		// Serialize the pointers
+		SerializeFunctor<T>()(arr[i], *this);
+		ANKI_CHECK(m_err);
+
+		// Advance file pos
+		m_structureFilePos.popBack(m_alloc);
+		structFilePos += sizeof(T);
+	}
+
+	check();
+	return Error::NONE;
+}
+
+template<typename T>
+Error BinarySerializer::doDynamicArrayComplexType(const T* arr, PtrSize size, PtrSize memberOffset)
+{
+	check();
+	checkStruct<T>();
+
+	if(size == 0)
+	{
+		ANKI_ASSERT(arr == nullptr);
+		// Do nothing
+	}
+	else
+	{
+		ANKI_ASSERT(arr);
+
+		// Move file pos to the end of the file (allocate space)
+		PtrSize arrayFilePos = getAlignedRoundUp(alignof(T), m_eofPos);
+		m_eofPos = arrayFilePos + size * sizeof(T);
+
+		// Store the pointer for later
+		const PtrSize structFilePos = m_structureFilePos.getBack();
+		PointerInfo pinfo;
+		pinfo.m_filePos = structFilePos + memberOffset;
+		pinfo.m_value = arrayFilePos - m_beginOfDataFilePos;
+		m_pointerFilePositions.emplaceBack(m_alloc, pinfo);
+
+		// Write the structures
+		ANKI_CHECK(m_file->seek(arrayFilePos, FileSeekOrigin::BEGINNING));
+		ANKI_CHECK(m_file->write(&arr[0], sizeof(T) * size));
+
+		// Basically serialize pointers
+		for(PtrSize i = 0; i < size && !m_err; ++i)
+		{
+			m_structureFilePos.emplaceBack(m_alloc, arrayFilePos);
+
+			SerializeFunctor<T>()(arr[i], *this);
+
+			m_structureFilePos.popBack(m_alloc);
+			arrayFilePos += sizeof(T);
+		}
+		ANKI_CHECK(m_err);
+	}
+
+	check();
+	return Error::NONE;
+}
+
+template<typename T>
+Error BinaryDeserializer::deserialize(T*& x, GenericMemoryPoolAllocator<U8> allocator, File& file)
+{
+	x = nullptr;
+
+	detail::BinarySerializerHeader header;
+	ANKI_CHECK(file.read(&header, sizeof(header)));
+	const PtrSize dataFilePos = file.tell();
+
+	// Sanity checks
+	{
+		if(memcmp(&header.m_magic[0], detail::BINARY_SERIALIZER_MAGIC, 8) != 0)
+		{
+			ANKI_UTIL_LOGE("Wrong magic work in header");
+			return Error::USER_DATA;
+		}
+
+		if(header.m_dataSize < sizeof(T))
+		{
+			ANKI_UTIL_LOGE("Wrong data size");
+			return Error::USER_DATA;
+		}
+
+		const PtrSize expectedSizeAfterHeader = header.m_dataSize + header.m_pointerCount * sizeof(void*);
+		const PtrSize actualSizeAfterHeader = file.getSize() - dataFilePos;
+		if(expectedSizeAfterHeader > actualSizeAfterHeader)
+		{
+			ANKI_UTIL_LOGE("File size doesn't match expectations");
+			return Error::USER_DATA;
+		}
+	}
+
+	// Allocate & read data
+	U8* const baseAddress =
+		static_cast<U8*>(allocator.getMemoryPool().allocate(header.m_dataSize, ANKI_SAFE_ALIGNMENT));
+	ANKI_CHECK(file.read(baseAddress, header.m_dataSize));
+
+	// Fix pointers
+	if(header.m_pointerCount)
+	{
+		ANKI_CHECK(file.seek(header.m_pointerArrayFilePosition, FileSeekOrigin::BEGINNING));
+		for(PtrSize i = 0; i < header.m_pointerCount; ++i)
+		{
+			// Read the location of the pointer
+			PtrSize offsetFromBeginOfData;
+			ANKI_CHECK(file.read(&offsetFromBeginOfData, sizeof(offsetFromBeginOfData)));
+			if(offsetFromBeginOfData >= header.m_dataSize)
+			{
+				ANKI_UTIL_LOGE("Corrupt pointer");
+				return Error::USER_DATA;
+			}
+
+			// Add to the location the actual base address
+			U8* ptrLocation = baseAddress + offsetFromBeginOfData;
+			PtrSize& ptrValue = *reinterpret_cast<PtrSize*>(ptrLocation);
+			if(ptrValue >= header.m_dataSize)
+			{
+				ANKI_UTIL_LOGE("Corrupt pointer");
+				return Error::USER_DATA;
+			}
+
+			ptrValue += ptrToNumber(baseAddress);
+		}
+	}
+
+	// Done
+	x = reinterpret_cast<T*>(baseAddress);
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 50 - 6
src/anki/util/Singleton.h

@@ -6,6 +6,7 @@
 #pragma once
 
 #include <anki/util/Assert.h>
+#include <anki/util/Thread.h>
 #include <utility>
 
 namespace anki
@@ -99,18 +100,18 @@ typename SingletonInit<T>::Value* SingletonInit<T>::m_instance = nullptr;
 
 /// This template makes a class singleton with thread local instance
 template<typename T>
-class SingletonThreadSafe
+class SingletonThreadLocal
 {
 public:
 	typedef T Value;
 
 	// Non copyable
-	SingletonThreadSafe(const SingletonThreadSafe&) = delete;
-	SingletonThreadSafe& operator=(const SingletonThreadSafe&) = delete;
+	SingletonThreadLocal(const SingletonThreadLocal&) = delete;
+	SingletonThreadLocal& operator=(const SingletonThreadLocal&) = delete;
 
 	// Non constructable
-	SingletonThreadSafe() = delete;
-	~SingletonThreadSafe() = delete;
+	SingletonThreadLocal() = delete;
+	~SingletonThreadLocal() = delete;
 
 	/// Get instance
 	static Value& get()
@@ -132,7 +133,50 @@ private:
 };
 
 template<typename T>
-thread_local typename SingletonThreadSafe<T>::Value* SingletonThreadSafe<T>::m_instance = nullptr;
+thread_local typename SingletonThreadLocal<T>::Value* SingletonThreadLocal<T>::m_instance = nullptr;
+
+/// This template makes a class with a destructor with arguments singleton
+template<typename T>
+class SingletonThreadsafe
+{
+public:
+	typedef T Value;
+
+	// Non copyable
+	SingletonThreadsafe(const SingletonThreadsafe&) = delete;
+	SingletonThreadsafe& operator=(const SingletonThreadsafe&) = delete;
+
+	// Non constructable
+	SingletonThreadsafe() = delete;
+	~SingletonThreadsafe() = delete;
+
+	/// Get instance
+	static Value& get()
+	{
+		LockGuard<Mutex> lock(m_mtx);
+		return *(m_instance ? m_instance : (m_instance = new Value));
+	}
+
+	/// Cleanup
+	static void destroy()
+	{
+		LockGuard<Mutex> lock(m_mtx);
+		if(m_instance)
+		{
+			delete m_instance;
+		}
+	}
+
+private:
+	static Value* m_instance;
+	static Mutex m_mtx;
+};
+
+template<typename T>
+typename SingletonThreadsafe<T>::Value* SingletonThreadsafe<T>::m_instance = nullptr;
+
+template<typename T>
+Mutex SingletonThreadsafe<T>::m_mtx;
 /// @}
 
 } // end namespace anki

+ 2 - 0
src/anki/util/StdTypes.h

@@ -73,6 +73,8 @@ constexpr F64 MIN_F64 = -std::numeric_limits<F64>::max();
 using Bool = bool; ///< 1 byte boolean type. The same as C++'s bool.
 static_assert(sizeof(bool) == 1, "Wrong size for bool");
 
+using Bool32 = I32;
+
 using Second = F64; ///< The base time unit is second.
 constexpr Second MAX_SECOND = MAX_F64;
 constexpr Second MIN_SECOND = MIN_F64;

+ 38 - 40
src/anki/util/String.cpp

@@ -16,12 +16,13 @@ Error CString::toNumber(F64& out) const
 {
 	checkInit();
 	errno = 0;
-	out = std::strtod(m_ptr, nullptr);
+	char* endPtr;
+	out = std::strtod(m_ptr, &endPtr);
 
-	if(errno)
+	if(errno || endPtr != m_ptr + getLength())
 	{
 		errno = 0;
-		ANKI_UTIL_LOGE("Conversion failed");
+		ANKI_UTIL_LOGE("Conversion failed: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
@@ -36,53 +37,51 @@ Error CString::toNumber(F32& out) const
 	return Error::NONE;
 }
 
-Error CString::toNumber(I8& out) const
+Error CString::toNumber(I64& out) const
 {
-	I64 i64 = 0;
-	ANKI_CHECK(toNumber(i64));
+	checkInit();
+	errno = 0;
+	char* endPtr;
+	static_assert(sizeof(long long) == sizeof(I64), "See file");
+	out = std::strtoll(m_ptr, &endPtr, 10);
 
-	if(i64 < MIN_I8 || i64 > MAX_I8)
+	if(errno || endPtr != m_ptr + getLength())
 	{
-		ANKI_UTIL_LOGE("Conversion failed. Our of range");
+		errno = 0;
+		ANKI_UTIL_LOGE("Conversion failed: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
-	out = I8(i64);
 	return Error::NONE;
 }
 
-Error CString::toNumber(I64& out) const
+Error CString::toNumber(I8& out) const
 {
-	checkInit();
-	errno = 0;
-	static_assert(sizeof(long long) == sizeof(I64), "See file");
-	out = std::strtoll(m_ptr, nullptr, 10);
+	I64 i64 = 0;
+	ANKI_CHECK(toNumber(i64));
 
-	if(errno)
+	if(i64 < MIN_I8 || i64 > MAX_I8)
 	{
-		errno = 0;
-		ANKI_UTIL_LOGE("Conversion failed");
+		ANKI_UTIL_LOGE("Conversion failed. Our of range: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
+	out = I8(i64);
 	return Error::NONE;
 }
 
 Error CString::toNumber(I32& out) const
 {
-	checkInit();
-	errno = 0;
-	long long i = std::strtoll(m_ptr, nullptr, 10);
+	I64 i64 = 0;
+	ANKI_CHECK(toNumber(i64));
 
-	if(errno || i < MIN_I32 || i > MAX_I32)
+	if(i64 < MIN_I32 || i64 > MAX_I32)
 	{
-		errno = 0;
-		ANKI_UTIL_LOGE("Conversion failed");
+		ANKI_UTIL_LOGE("Conversion failed. Our of range: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
-	out = I32(i);
-
+	out = I32(i64);
 	return Error::NONE;
 }
 
@@ -90,13 +89,14 @@ Error CString::toNumber(U64& out) const
 {
 	checkInit();
 	errno = 0;
+	char* endPtr;
 	static_assert(sizeof(unsigned long long) == sizeof(U64), "See file");
-	out = std::strtoull(m_ptr, nullptr, 10);
+	out = std::strtoull(m_ptr, &endPtr, 10);
 
-	if(errno)
+	if(errno || endPtr != m_ptr + getLength())
 	{
 		errno = 0;
-		ANKI_UTIL_LOGE("Conversion failed");
+		ANKI_UTIL_LOGE("Conversion failed: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
@@ -105,33 +105,31 @@ Error CString::toNumber(U64& out) const
 
 Error CString::toNumber(U32& out) const
 {
-	checkInit();
-	errno = 0;
-	unsigned long long i = std::strtoull(m_ptr, nullptr, 10);
+	U64 u64;
+	ANKI_CHECK(toNumber(u64));
 
-	if(errno || i > MAX_U32)
+	if(u64 > MAX_U32)
 	{
-		errno = 0;
-		ANKI_UTIL_LOGE("Conversion failed");
+		ANKI_UTIL_LOGE("Conversion failed. Our of range: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
-	out = U32(i);
+	out = U32(u64);
 	return Error::NONE;
 }
 
 Error CString::toNumber(U8& out) const
 {
-	U64 i64 = 0;
-	ANKI_CHECK(toNumber(i64));
+	U64 u64 = 0;
+	ANKI_CHECK(toNumber(u64));
 
-	if(i64 > MAX_U8)
+	if(u64 > MAX_U8)
 	{
-		ANKI_UTIL_LOGE("Conversion failed. Our of range");
+		ANKI_UTIL_LOGE("Conversion failed. Our of range: %s", m_ptr);
 		return Error::USER_DATA;
 	}
 
-	out = U8(i64);
+	out = U8(u64);
 	return Error::NONE;
 }
 

+ 19 - 3
src/anki/util/String.h

@@ -228,15 +228,15 @@ public:
 	/// Convert to I32.
 	ANKI_USE_RESULT Error toNumber(I32& out) const;
 
-	/// Convert to U8.
-	ANKI_USE_RESULT Error toNumber(U8& out) const;
-
 	/// Convert to U64.
 	ANKI_USE_RESULT Error toNumber(U64& out) const;
 
 	/// Convert to U32.
 	ANKI_USE_RESULT Error toNumber(U32& out) const;
 
+	/// Convert to U8.
+	ANKI_USE_RESULT Error toNumber(U8& out) const;
+
 	/// Convert to Bool.
 	ANKI_USE_RESULT Error toNumber(Bool& out) const;
 
@@ -688,6 +688,17 @@ public:
 		return *this;
 	}
 
+	/// Copy from string.
+	StringAuto& operator=(const CString& b)
+	{
+		destroy();
+		if(!b.isEmpty())
+		{
+			create(b.getBegin(), b.getEnd());
+		}
+		return *this;
+	}
+
 	/// Move one string to this one.
 	StringAuto& operator=(StringAuto&& b)
 	{
@@ -695,6 +706,11 @@ public:
 		return *this;
 	}
 
+	GenericMemoryPoolAllocator<Char> getAllocator() const
+	{
+		return m_alloc;
+	}
+
 	/// Initialize using a const string.
 	void create(const CStringType& cstr)
 	{

+ 1 - 1
src/anki/util/WeakArray.h

@@ -41,7 +41,7 @@ public:
 	}
 
 	template<PtrSize S>
-	explicit WeakArray(Array<T, S>& arr)
+	WeakArray(Array<T, S>& arr)
 		: WeakArray(&arr[0], S)
 	{
 	}

+ 243 - 0
src/anki/util/serializer.py

@@ -0,0 +1,243 @@
+#!/usr/bin/python3
+
+# Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+# All rights reserved.
+# Code licensed under the BSD License.
+# http://www.anki3d.org/LICENSE
+
+import optparse
+import xml.etree.ElementTree as et
+import os
+import copy
+
+
+class Context:
+    __slots__ = ["input_filenames", "output_filename", "output_file", "identation_level"]
+
+    def __init__(self):
+        self.input_filenames = None
+        self.output_filename = None
+        self.output_file = None
+        self.identation_level = 0
+
+
+ctx = Context()
+
+
+class MemberInfo:
+    __slots__ = ["name", "base_type", "array_size", "comment", "pointer", "constructor"]
+
+    def __init__(self):
+        self.name = None
+        self.base_type = None
+        self.array_size = 1
+        self.comment = None
+        self.pointer = False
+        self.constructor = None
+
+    def is_dynamic_array(self, member_arr):
+        if not self.pointer:
+            return False
+
+        for member in member_arr:
+            if member.name == str(member.array_size):
+                return False
+
+        return True
+
+    def is_pointer(self, member_arr):
+        return self.pointer and not self.is_dynamic_array(member_arr)
+
+
+def parse_commandline():
+    """ Parse the command line arguments """
+
+    parser = optparse.OptionParser(usage="usage: %prog [options]", description="Create serialization code using XML")
+
+    parser.add_option(
+        "-i", "--input", dest="inp", type="string", help="specify the XML files to parse. Seperate with :")
+
+    parser.add_option("-o", "--output", dest="out", type="string", help="specify the .h file to populate")
+
+    (options, args) = parser.parse_args()
+
+    if not options.inp or not options.out:
+        parser.error("argument is missing")
+
+    global ctx
+    ctx.input_filenames = options.inp.split(":")
+    ctx.output_filename = options.out
+
+
+def writeln(txt):
+    """ Write generated code to the output """
+    global ctx
+    ctx.output_file.write("%s%s\n" % ("\t" * ctx.identation_level, txt))
+
+
+def ident(number):
+    """ Increase or recrease identation for the writeln """
+    global ctx
+    ctx.identation_level += number
+
+
+def gen_class(root_el):
+    """ Parse a "class" element and generate the code. """
+
+    name = root_el.get("name")
+
+    # Write doxygen
+    if not root_el.get("comment"):
+        writeln("/// %s class." % name)
+    else:
+        writeln("/// %s." % root_el.get("comment"))
+
+    # Body start
+    writeln("class %s" % name)
+    writeln("{")
+    writeln("public:")
+
+    # Parse members
+    member_arr = []
+    members_el = root_el.find("members")
+    for member_el in members_el:
+        member = MemberInfo()
+
+        member.name = member_el.get("name")
+        member.base_type = member_el.get("type")
+
+        member.array_size = member_el.get("array_size")
+        if not member.array_size:
+            member.array_size = "1"
+
+        if member_el.get("pointer") and member_el.get("pointer") == "true":
+            member.pointer = True
+        else:
+            member.pointer = False
+
+        if member_el.get("comment"):
+            member.comment = member_el.get("comment")
+
+        if member_el.get("constructor"):
+            member.constructor = member_el.get("constructor")
+
+        member_arr.append(member)
+
+    # Write members
+    ident(1)
+    for member in member_arr:
+        if member.comment:
+            comment = "///< %s." % member.comment
+        else:
+            comment = ""
+
+        if member.constructor:
+            constructor = "= {%s}" % member.constructor
+        else:
+            constructor = ""
+
+        if member.pointer:
+            writeln("%s* %s%s; %s" % (member.base_type, member.name, constructor, comment))
+        elif member.array_size != "1":
+            writeln("Array<%s, %s> %s%s; %s" % (member.base_type, member.array_size, member.name, constructor, comment))
+        else:
+            writeln("%s %s%s; %s" % (member.base_type, member.name, constructor, comment))
+    ident(-1)
+
+    # Before serialize make sure the dynamic arrays are last
+    member_arr_copy = member_arr.copy()
+    member_arr.sort(key=lambda x: x.is_dynamic_array(member_arr_copy))
+
+    # Write the serialization and deserialization code
+    writeln("")
+    ident(1)
+    writeln("template<typename TSerializer, typename TClass>")
+    writeln("static void serializeCommon(TSerializer& s, TClass self)")
+    writeln("{")
+    ident(1)
+
+    for member in member_arr:
+        if member.is_pointer(member_arr_copy):
+            writeln("s.doPointer(\"%s\", offsetof(%s, %s), self.%s);" % (member.name, name, member.name, member.name))
+        elif member.is_dynamic_array(member_arr_copy):
+            writeln("s.doDynamicArray(\"%s\", offsetof(%s, %s), self.%s, self.%s);" % (member.name, name, member.name,
+                                                                                       member.name, member.array_size))
+        elif member.array_size != "1":
+            writeln("s.doArray(\"%s\", offsetof(%s, %s), &self.%s[0], %s);" % (member.name, name, member.name,
+                                                                               member.name, member.array_size))
+        else:
+            writeln("s.doValue(\"%s\", offsetof(%s, %s), self.%s);" % (member.name, name, member.name, member.name))
+
+    ident(-1)
+    writeln("}")
+    ident(-1)
+
+    # Write the methods
+    writeln("")
+    ident(1)
+    writeln("template<typename TDeserializer>")
+    writeln("void deserialize(TDeserializer& deserializer)")
+    writeln("{")
+    ident(1)
+    writeln("serializeCommon<TDeserializer, %s&>(deserializer, *this);" % name)
+    ident(-1)
+    writeln("}")
+    writeln("")
+
+    writeln("template<typename TSerializer>")
+    writeln("void serialize(TSerializer& serializer) const")
+    writeln("{")
+    ident(1)
+    writeln("serializeCommon<TSerializer, const %s&>(serializer, *this);" % name)
+    ident(-1)
+    writeln("}")
+    ident(-1)
+
+    # Body end
+    writeln("};")
+    writeln("")
+
+
+def gen_file(filename):
+    """ Parse an XML file and generate the code. """
+
+    tree = et.parse(filename)
+    root = tree.getroot()
+
+    for incs in root.iter("includes"):
+        for inc in incs.iter("include"):
+            writeln("#include %s" % inc.get("file"))
+
+    writeln("")
+    writeln("namespace anki")
+    writeln("{")
+    writeln("")
+
+    for cls in root.iter("classes"):
+        for cl in cls.iter("class"):
+            gen_class(cl)
+
+    writeln("} // end namespace anki")
+
+
+def main():
+    parse_commandline()
+
+    ctx.output_file = open(ctx.output_filename, "w")
+    ctx.output_file.write("""// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// WARNING: This file is auto generated.
+
+#pragma once
+
+""")
+
+    for filename in ctx.input_filenames:
+        gen_file(filename)
+
+
+if __name__ == "__main__":
+    main()

+ 1 - 1
tests/gr/ClassGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/ClassGpuAllocator.h>
+#include <anki/gr/utils/ClassGpuAllocator.h>
 #include <tests/framework/Framework.h>
 #include <random>
 #include <algorithm>

+ 1 - 1
tests/gr/StackGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/StackGpuAllocator.h>
+#include <anki/gr/utils/StackGpuAllocator.h>
 #include <anki/util/ThreadHive.h>
 #include <tests/framework/Framework.h>
 #include <algorithm>

+ 156 - 0
tests/shader_compiler/ShaderProgramCompiler.cpp

@@ -0,0 +1,156 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/shader_compiler/ShaderProgramCompiler.h>
+
+ANKI_TEST(ShaderCompiler, ShaderProgramCompiler)
+{
+	const CString sourceCode = R"(
+#pragma anki mutator instanceCount INSTANCE_COUNT 1 2 4 8 16 32 64
+#pragma anki mutator LOD 0 1 2
+#pragma anki mutator PASS 0 1 2 3
+#pragma anki mutator DIFFUSE_TEX 0 1
+#pragma anki mutator SPECULAR_TEX 0 1
+#pragma anki mutator ROUGHNESS_TEX 0 1
+#pragma anki mutator METAL_TEX 0 1
+#pragma anki mutator NORMAL_TEX 0 1
+#pragma anki mutator PARALLAX 0 1
+#pragma anki mutator EMISSIVE_TEX 0 1
+#pragma anki mutator BONES 0 1
+#pragma anki mutator VELOCITY 0 1
+
+#pragma anki rewrite_mutation PASS 1 DIFFUSE_TEX 1 to PASS 1 DIFFUSE_TEX 0
+#pragma anki rewrite_mutation PASS 2 DIFFUSE_TEX 1 to PASS 2 DIFFUSE_TEX 0
+#pragma anki rewrite_mutation PASS 3 DIFFUSE_TEX 1 to PASS 2 DIFFUSE_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 SPECULAR_TEX 1 to PASS 1 SPECULAR_TEX 0
+#pragma anki rewrite_mutation PASS 2 SPECULAR_TEX 1 to PASS 2 SPECULAR_TEX 0
+#pragma anki rewrite_mutation PASS 3 SPECULAR_TEX 1 to PASS 2 SPECULAR_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 ROUGHNESS_TEX 1 to PASS 1 ROUGHNESS_TEX 0
+#pragma anki rewrite_mutation PASS 2 ROUGHNESS_TEX 1 to PASS 2 ROUGHNESS_TEX 0
+#pragma anki rewrite_mutation PASS 3 ROUGHNESS_TEX 1 to PASS 2 ROUGHNESS_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 METAL_TEX 1 to PASS 1 METAL_TEX 0
+#pragma anki rewrite_mutation PASS 2 METAL_TEX 1 to PASS 2 METAL_TEX 0
+#pragma anki rewrite_mutation PASS 3 METAL_TEX 1 to PASS 2 METAL_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 NORMAL_TEX 1 to PASS 1 NORMAL_TEX 0
+#pragma anki rewrite_mutation PASS 2 NORMAL_TEX 1 to PASS 2 NORMAL_TEX 0
+#pragma anki rewrite_mutation PASS 3 NORMAL_TEX 1 to PASS 2 NORMAL_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 EMISSIVE_TEX 1 to PASS 1 EMISSIVE_TEX 0
+#pragma anki rewrite_mutation PASS 2 EMISSIVE_TEX 1 to PASS 2 EMISSIVE_TEX 0
+#pragma anki rewrite_mutation PASS 3 EMISSIVE_TEX 1 to PASS 2 EMISSIVE_TEX 0
+
+#pragma anki rewrite_mutation PASS 1 VELOCITY 1 to PASS 1 VELOCITY 0
+#pragma anki rewrite_mutation PASS 2 VELOCITY 1 to PASS 2 VELOCITY 0
+#pragma anki rewrite_mutation PASS 3 VELOCITY 1 to PASS 2 VELOCITY 0
+
+#pragma anki input instanced Mat4 mvp
+#if PASS == 0
+#	pragma anki input instanced Mat3 rotationMat
+#endif
+#if PASS == 0 && PARALLAX == 1
+#	pragma anki input instanced Mat4 modelViewMat
+#endif
+#if PASS == 0 && VELOCITY == 1
+#	pragma anki input instanced Mat4 prevMvp
+#endif
+
+#if DIFFUSE_TEX == 0 && PASS == 0
+#	pragma anki input const Vec3 diffColor
+#endif
+#if SPECULAR_TEX == 0 && PASS == 0
+#	pragma anki input const Vec3 specColor
+#endif
+#if ROUGHNESS_TEX == 0 && PASS == 0
+#	pragma anki input const F32 roughness
+#endif
+#if METAL_TEX == 0 && PASS == 0
+#	pragma anki input const F32 metallic
+#endif
+#if EMISSIVE_TEX == 0 && PASS == 0
+#	pragma anki input const Vec3 emission
+#endif
+#if PARALLAX == 1 && PASS == 0 && LOD == 0
+#	pragma anki input const F32 heightMapScale
+#endif
+#if PASS == 0
+#	pragma anki input const F32 subsurface
+#endif
+#pragma anki input sampler globalSampler
+#if DIFFUSE_TEX == 1 && PASS == 0
+#	pragma anki input texture2D diffTex
+#endif
+#if SPECULAR_TEX == 1 && PASS == 0
+#	pragma anki input texture2D specTex
+#endif
+#if ROUGHNESS_TEX == 1 && PASS == 0
+#	pragma anki input texture2D roughnessTex
+#endif
+#if METAL_TEX == 1 && PASS == 0
+#	pragma anki input texture2D metallicTex
+#endif
+#if NORMAL_TEX == 1 && PASS == 0 && LOD < 2
+#	pragma anki input texture2D normalTex
+#endif
+#if PARALLAX == 1 && PASS == 0 && LOD == 0
+#	pragma anki input texture2D heightTex
+#endif
+#if EMISSIVE_TEX == 1 && PASS == 0
+#	pragma anki input texture2D emissiveTex
+#endif
+
+#pragma anki start vert
+out gl_PerVertex
+{
+	Vec4 gl_Position;
+};
+
+void main()
+{
+	gl_Position = Vec4(gl_VertexID);
+}
+#pragma anki end
+
+#pragma anki start frag
+layout(location = 0) out Vec3 out_color;
+
+void main()
+{
+	out_color = Vec3(0.0);
+}
+#pragma anki end
+	)";
+
+	// Write the file
+	{
+		File file;
+		ANKI_TEST_EXPECT_NO_ERR(file.open("test.glslp", FileOpenFlag::WRITE));
+		ANKI_TEST_EXPECT_NO_ERR(file.writeText(sourceCode));
+	}
+
+	class Fsystem : public ShaderProgramFilesystemInterface
+	{
+	public:
+		Error readAllText(CString filename, StringAuto& txt) final
+		{
+			File file;
+			ANKI_CHECK(file.open(filename, FileOpenFlag::READ));
+			ANKI_CHECK(file.readAllText(txt));
+			return Error::NONE;
+		}
+	} fsystem;
+
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+	ShaderProgramBinaryWrapper binary(alloc);
+	ANKI_TEST_EXPECT_NO_ERR(compileShaderProgram("test.glslp", fsystem, alloc, 128, 1, 1, GpuVendor::AMD, binary));
+
+	/*StringAuto dis(alloc);
+	disassembleShaderProgramBinary(binary.getBinary(), dis);
+	ANKI_LOGI("Binary disassembly:\n%s\n", dis.cstr());*/
+}

+ 76 - 0
tests/shader_compiler/ShaderProgramParser.cpp

@@ -0,0 +1,76 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/shader_compiler/ShaderProgramParser.h>
+
+ANKI_TEST(ShaderCompiler, ShaderCompilerParser)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	class FilesystemInterface : public ShaderProgramFilesystemInterface
+	{
+	public:
+		U32 count = 0;
+
+		Error readAllText(CString filename, StringAuto& txt) final
+		{
+			if(count == 0)
+			{
+				txt = R"(
+#pragma anki mutator M0 1 2
+#pragma anki mutator M1 3 4
+
+#pragma anki rewrite_mutation M0 2 M1 4 to M0 1 M1 3
+
+#if M0 == 1
+#pragma anki input Vec3 var0
+#endif
+
+#if M1 == 4
+#pragma anki input const Vec3 var1
+#endif
+
+#pragma anki start vert
+
+// vert
+#pragma anki end
+
+#pragma anki start frag
+// frag
+#pragma anki end
+				)";
+			}
+			else
+			{
+				return Error::FUNCTION_FAILED;
+			}
+
+			++count;
+			return Error::NONE;
+		}
+	} interface;
+
+	ShaderProgramParser parser("filename0", &interface, alloc, 128, 1, 1, GpuVendor::AMD);
+	ANKI_TEST_EXPECT_NO_ERR(parser.parse());
+
+	// Check inputs
+	ANKI_TEST_EXPECT_EQ(parser.getInputs().getSize(), 2);
+
+	// Test a variant
+	Array<MutatorValue, 2> mutation = {{2, 4}};
+
+	ShaderProgramParserVariant variant;
+	ANKI_TEST_EXPECT_NO_ERR(parser.generateVariant(mutation, variant));
+	ANKI_TEST_EXPECT_EQ(variant.isInputActive(parser.getInputs()[0]), false);
+	ANKI_TEST_EXPECT_EQ(variant.isInputActive(parser.getInputs()[1]), true);
+
+	// Test rewrite
+	ANKI_TEST_EXPECT_EQ(parser.rewriteMutation(mutation), true);
+	ANKI_TEST_EXPECT_EQ(mutation[0], 1);
+	ANKI_TEST_EXPECT_EQ(mutation[1], 3);
+
+	// printf("%s\n", variant.getSource(ShaderType::VERTEX).cstr());
+}

+ 1 - 1
tests/util/HashMap.cpp

@@ -145,7 +145,7 @@ ANKI_TEST(Util, HashMap)
 		using AkMap = HashMap<int, int, Hasher>;
 		AkMap akMap(128, 32, 0.9f);
 		using StlMap =
-			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<int, int>>>;
+			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<const int, int>>>;
 		StlMap stdMap(10, std::hash<int>(), std::equal_to<int>(), alloc);
 
 		std::unordered_map<int, int> tmpMap;

+ 2 - 2
tests/util/HighRezTimer.cpp

@@ -3,8 +3,8 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include "tests/framework/Framework.h"
-#include "anki/util/HighRezTimer.h"
+#include <tests/framework/Framework.h>
+#include <anki/util/HighRezTimer.h>
 #include <chrono>
 #include <thread>
 

+ 71 - 0
tests/util/Serializer.cpp

@@ -0,0 +1,71 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/util/Serializer.h>
+#include <tests/util/SerializerTest.h>
+
+ANKI_TEST(Util, BinarySerializer)
+{
+	Array<ClassB, 2> b = {};
+
+	b[0].m_array[0] = 2;
+	b[0].m_array[1] = 3;
+	b[0].m_array[2] = 4;
+	Array<U32, 3> bDarr = {{0xFF12EE34, 0xAA12BB34, 0xCC12DD34}};
+	b[0].m_darraySize = bDarr.getSize();
+	b[0].m_darray = &bDarr[0];
+
+	b[1].m_array[0] = 255;
+	b[1].m_array[1] = 127;
+	b[1].m_array[2] = 55;
+	Array<U32, 1> bDarr2 = {{0x12345678}};
+	b[1].m_darraySize = bDarr2.getSize();
+	b[1].m_darray = &bDarr2[0];
+
+	ClassA a = {};
+	a.m_array[0] = 123;
+	a.m_array[1] = 56;
+	a.m_u32 = b.getSize();
+	a.m_u64 = 0x123456789ABCDEFF;
+	a.m_darray = &b[0];
+
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Serialize
+	{
+		File file;
+		ANKI_TEST_EXPECT_NO_ERR(file.open("serialized.bin", FileOpenFlag::WRITE | FileOpenFlag::BINARY));
+		BinarySerializer serializer;
+
+		ANKI_TEST_EXPECT_NO_ERR(serializer.serialize(a, alloc, file));
+	}
+
+	// Deserialize
+	{
+		File file;
+		ANKI_TEST_EXPECT_NO_ERR(file.open("serialized.bin", FileOpenFlag::READ | FileOpenFlag::BINARY));
+
+		BinaryDeserializer deserializer;
+		ClassA* pa;
+		ANKI_TEST_EXPECT_NO_ERR(deserializer.deserialize(pa, alloc, file));
+
+		ANKI_TEST_EXPECT_EQ(pa->m_array[0], a.m_array[0]);
+		ANKI_TEST_EXPECT_EQ(pa->m_u32, a.m_u32);
+
+		for(U32 i = 0; i < pa->m_u32; ++i)
+		{
+			ANKI_TEST_EXPECT_EQ(pa->m_darray[i].m_array[1], b[i].m_array[1]);
+			ANKI_TEST_EXPECT_EQ(pa->m_darray[i].m_darraySize, b[i].m_darraySize);
+
+			for(U32 j = 0; j < pa->m_darray[i].m_darraySize; ++j)
+			{
+				ANKI_TEST_EXPECT_EQ(pa->m_darray[i].m_darray[j], b[i].m_darray[j]);
+			}
+		}
+
+		alloc.deleteInstance(pa);
+	}
+}

+ 75 - 0
tests/util/SerializerTest.h

@@ -0,0 +1,75 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// WARNING: This file is auto generated.
+
+#pragma once
+
+#include <anki/util/Array.h>
+
+namespace anki
+{
+
+/// ClassB class.
+class ClassB
+{
+public:
+	Array<U8, 3> m_array;
+	U32* m_darray;
+	U8 m_darraySize;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& serializer, TClass self)
+	{
+		serializer.doArray("m_array", offsetof(ClassB, m_array), &self.m_array[0], 3);
+		serializer.doValue("m_darraySize", offsetof(ClassB, m_darraySize), self.m_darraySize);
+		serializer.doDynamicArray("m_darray", offsetof(ClassB, m_darray), self.m_darray, self.m_darraySize);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ClassB&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ClassB&>(serializer, *this);
+	}
+};
+
+/// ClassA class.
+class ClassA
+{
+public:
+	Array<U8, 2> m_array;
+	U32 m_u32;
+	U64 m_u64;
+	ClassB* m_darray;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& serializer, TClass self)
+	{
+		serializer.doArray("m_array", offsetof(ClassA, m_array), &self.m_array[0], 2);
+		serializer.doValue("m_u32", offsetof(ClassA, m_u32), self.m_u32);
+		serializer.doValue("m_u64", offsetof(ClassA, m_u64), self.m_u64);
+		serializer.doDynamicArray("m_darray", offsetof(ClassA, m_darray), self.m_darray, self.m_u32);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, ClassA&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const ClassA&>(serializer, *this);
+	}
+};
+
+} // end namespace anki

+ 24 - 0
tests/util/SerializerTest.xml

@@ -0,0 +1,24 @@
+<serializer>
+	<includes>
+		<include file="&lt;anki/util/Array.h&gt;"/>
+	</includes>
+
+	<classes>
+		<class name="ClassB">
+			<members>
+				<member name="m_array" type="U8" array_size="3" />
+				<member name="m_darray" type="U32" pointer="true" array_size="m_darraySize" />
+				<member name="m_darraySize" type="U8" />
+			</members>
+		</class>
+
+		<class name="ClassA">
+			<members>
+				<member name="m_array" type="U8" array_size="2" />
+				<member name="m_u32" type="U32" />
+				<member name="m_u64" type="U64" />
+				<member name="m_darray" type="ClassB" pointer="true" array_size="m_u32" />
+			</members>
+		</class>
+	</classes>
+</serializer>

+ 3 - 2
tests/util/SparseArray.cpp

@@ -202,7 +202,7 @@ ANKI_TEST(Util, SparseArray)
 		const U MAX = 10000;
 		SparseArray<SAFoo, U64> arr;
 		using StlMap =
-			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<int, int>>>;
+			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<const int, int>>>;
 		StlMap map(10, std::hash<int>(), std::equal_to<int>(), alloc);
 
 		for(U i = 0; i < MAX; ++i)
@@ -320,7 +320,8 @@ ANKI_TEST(Util, SparseArrayBench)
 	HeapAllocator<U8> allocStl(allocAlignedStl, nullptr);
 	HeapAllocator<U8> allocTml(allocAligned, nullptr);
 
-	using StlMap = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<int, int>>>;
+	using StlMap =
+		std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<const int, int>>>;
 	StlMap stdMap(10, std::hash<int>(), std::equal_to<int>(), allocStl);
 
 	using AkMap = SparseArray<int, U32>;

+ 1 - 1
tools/count_lines.sh

@@ -1 +1 @@
-wc -l `find ./src ./tests ./sandbox ./tools ./shaders ./samples -name '*.h' -o -name '*.hpp' -o -name '*.c' -o -name '*.cpp' -o -name '*.glsl' -o -name '*.py' -o -name '*.glslp'`
+wc -l `find ./src ./tests ./sandbox ./tools ./shaders ./samples -name '*.h' -o -name '*.hpp' -o -name '*.c' -o -name '*.cpp' -o -name '*.glsl' -o -name '*.py' -o -name '*.glslp' -o -name '*.xml' `