Browse Source

Merge pull request #33 from godlikepanos/octree

Octree (WIP)
Panagiotis Christopoulos Charitos 7 years ago
parent
commit
cbbb3497e8
56 changed files with 728 additions and 87 deletions
  1. BIN
      engine_data/Plight.ankimesh
  2. BIN
      engine_data/Slight.ankimesh
  3. 1 1
      programs/DeferredShading.ankiprog
  4. 1 1
      programs/FinalComposite.ankiprog
  5. 4 2
      programs/Ui.ankiprog
  6. 4 4
      programs/VolumetricFog.ankiprog
  7. BIN
      samples/sponza/assets/arch_support_big.ankimesh
  8. BIN
      samples/sponza/assets/arch_support_med.ankimesh
  9. BIN
      samples/sponza/assets/arch_support_tiny.ankimesh
  10. BIN
      samples/sponza/assets/column_a.001.ankimesh
  11. BIN
      samples/sponza/assets/column_a.ankimesh
  12. BIN
      samples/sponza/assets/column_b.ankimesh
  13. BIN
      samples/sponza/assets/column_b_top.ankimesh
  14. BIN
      samples/sponza/assets/column_c.ankimesh
  15. BIN
      samples/sponza/assets/column_c_small_top.ankimesh
  16. BIN
      samples/sponza/assets/column_c_square.ankimesh
  17. BIN
      samples/sponza/assets/flag_pole.ankimesh
  18. BIN
      samples/sponza/assets/hanging_vase.ankimesh
  19. BIN
      samples/sponza/assets/lion.ankimesh
  20. BIN
      samples/sponza/assets/rod_end.ankimesh
  21. BIN
      samples/sponza/assets/round_window.ankimesh
  22. BIN
      samples/sponza/assets/small_window_inner.ankimesh
  23. BIN
      samples/sponza/assets/small_window_outter.ankimesh
  24. BIN
      samples/sponza/assets/sponza_00.ankimesh
  25. BIN
      samples/sponza/assets/sponza_257.ankimesh
  26. BIN
      samples/sponza/assets/sponza_277.ankimesh
  27. BIN
      samples/sponza/assets/sponza_278.ankimesh
  28. BIN
      samples/sponza/assets/sponza_279.ankimesh
  29. BIN
      samples/sponza/assets/sponza_281.ankimesh
  30. BIN
      samples/sponza/assets/vase.ankimesh
  31. BIN
      samples/sponza/assets/vase_chains.ankimesh
  32. BIN
      samples/sponza/assets/vase_hanger.ankimesh
  33. BIN
      samples/sponza/assets/window.ankimesh
  34. 7 7
      shaders/ClusterLightCommon.glsl
  35. 11 3
      shaders/Common.glsl
  36. 1 0
      src/anki/Util.h
  37. 1 0
      src/anki/collision/Aabb.h
  38. 12 9
      src/anki/gr/Common.h
  39. 32 10
      src/anki/gr/RenderGraph.cpp
  40. 12 12
      src/anki/gr/ShaderCompiler.cpp
  41. 11 2
      src/anki/gr/ShaderCompiler.h
  42. 3 2
      src/anki/gr/gl/CommandBuffer.cpp
  43. 5 0
      src/anki/gr/gl/Common.cpp
  44. 3 3
      src/anki/gr/gl/GlState.cpp
  45. 11 9
      src/anki/gr/gl/ShaderProgramImpl.cpp
  46. 9 5
      src/anki/gr/vulkan/GrManagerImpl.cpp
  47. 1 0
      src/anki/renderer/DownscaleBlur.cpp
  48. 9 6
      src/anki/renderer/Indirect.cpp
  49. 6 3
      src/anki/resource/MeshLoader.cpp
  50. 141 0
      src/anki/scene/Octree.cpp
  51. 167 0
      src/anki/scene/Octree.h
  52. 2 2
      src/anki/ui/Canvas.cpp
  53. 14 0
      src/anki/util/Atomic.h
  54. 98 0
      src/anki/util/ObjectAllocator.h
  55. 142 0
      src/anki/util/ObjectAllocator.inl.h
  56. 20 6
      tools/scene/ExporterMesh.cpp

BIN
engine_data/Plight.ankimesh


BIN
engine_data/Slight.ankimesh


+ 1 - 1
programs/DeferredShading.ankiprog

@@ -38,7 +38,7 @@ void main()
 
 		<shader type="frag">
 			<inputs>
-				<input name="FB_SIZE" type="uvec2" const="1" />
+				<input name="FB_SIZE" type="uvec2" const="1"/>
 			</inputs>
 
 			<source><![CDATA[

+ 1 - 1
programs/FinalComposite.ankiprog

@@ -69,7 +69,7 @@ void main()
 	out_color = textureLod(u_isRt, uv, 0.0).rgb;
 #endif
 
-	out_color = tonemap(out_color, readFirstInvocationARB(u_exposureThreshold0));
+	out_color = tonemap(out_color, UNIFORM(u_exposureThreshold0));
 
 #if BLOOM_ENABLED
 	vec3 bloom = textureLod(u_ppsBloomLfRt, uv, 0.0).rgb;

+ 4 - 2
programs/Ui.ankiprog

@@ -15,8 +15,10 @@ http://www.anki3d.org/LICENSE
 #include "shaders/Common.glsl"
 
 layout(location = 0) in vec2 in_pos;
-layout(location = 1) in vec2 in_uv;
-layout(location = 2) in vec4 in_col;
+layout(location = 1) in vec4 in_col;
+#if TEXTURE_TYPE > 0
+layout(location = 2) in vec2 in_uv;
+#endif
 
 #if TEXTURE_TYPE > 0
 layout(location = 0) out vec2 out_uv;

+ 4 - 4
programs/VolumetricFog.ankiprog

@@ -49,10 +49,10 @@ layout(std140, ANKI_UBO_BINDING(0, 3), row_major) uniform ubo0_
 	mat4 u_prevViewProjMatMulInvViewProjMat2; // TODO Light common ubo has that. Maybe remove it
 };
 
-#define u_linearize readFirstInvocationARB(u_linearizeNoiseTexOffsetLayer.xy)
-#define u_noiseYOffset readFirstInvocationARB(u_linearizeNoiseTexOffsetLayer.z)
-#define u_noiseLayer readFirstInvocationARB(u_linearizeNoiseTexOffsetLayer.w)
-#define u_fogParticleColor readFirstInvocationARB(u_fogParticleColorPad1.rgb)
+#define u_linearize UNIFORM(u_linearizeNoiseTexOffsetLayer.xy)
+#define u_noiseYOffset UNIFORM(u_linearizeNoiseTexOffsetLayer.z)
+#define u_noiseLayer UNIFORM(u_linearizeNoiseTexOffsetLayer.w)
+#define u_fogParticleColor UNIFORM(u_fogParticleColorPad1.rgb)
 
 layout(location = 0) out vec3 out_color;
 

BIN
samples/sponza/assets/arch_support_big.ankimesh


BIN
samples/sponza/assets/arch_support_med.ankimesh


BIN
samples/sponza/assets/arch_support_tiny.ankimesh


BIN
samples/sponza/assets/column_a.001.ankimesh


BIN
samples/sponza/assets/column_a.ankimesh


BIN
samples/sponza/assets/column_b.ankimesh


BIN
samples/sponza/assets/column_b_top.ankimesh


BIN
samples/sponza/assets/column_c.ankimesh


BIN
samples/sponza/assets/column_c_small_top.ankimesh


BIN
samples/sponza/assets/column_c_square.ankimesh


BIN
samples/sponza/assets/flag_pole.ankimesh


BIN
samples/sponza/assets/hanging_vase.ankimesh


BIN
samples/sponza/assets/lion.ankimesh


BIN
samples/sponza/assets/rod_end.ankimesh


BIN
samples/sponza/assets/round_window.ankimesh


BIN
samples/sponza/assets/small_window_inner.ankimesh


BIN
samples/sponza/assets/small_window_outter.ankimesh


BIN
samples/sponza/assets/sponza_00.ankimesh


BIN
samples/sponza/assets/sponza_257.ankimesh


BIN
samples/sponza/assets/sponza_277.ankimesh


BIN
samples/sponza/assets/sponza_278.ankimesh


BIN
samples/sponza/assets/sponza_279.ankimesh


BIN
samples/sponza/assets/sponza_281.ankimesh


BIN
samples/sponza/assets/vase.ankimesh


BIN
samples/sponza/assets/vase_chains.ankimesh


BIN
samples/sponza/assets/vase_hanger.ankimesh


BIN
samples/sponza/assets/window.ankimesh


+ 7 - 7
shaders/ClusterLightCommon.glsl

@@ -81,14 +81,14 @@ layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING), std140, row_major) unifor
 	LightingUniforms u_lightingUniforms;
 };
 
-#	define u_near readFirstInvocationARB(u_lightingUniforms.rendererSizeTimeNear.w)
-#	define u_far readFirstInvocationARB(u_lightingUniforms.cameraPosFar.w)
-#	define u_cameraPos readFirstInvocationARB(u_lightingUniforms.cameraPosFar.xyz)
-#	define u_clusterCountX readFirstInvocationARB(u_lightingUniforms.tileCount.x)
-#	define u_clusterCountY readFirstInvocationARB(u_lightingUniforms.tileCount.y)
+#	define u_near UNIFORM(u_lightingUniforms.rendererSizeTimeNear.w)
+#	define u_far UNIFORM(u_lightingUniforms.cameraPosFar.w)
+#	define u_cameraPos UNIFORM(u_lightingUniforms.cameraPosFar.xyz)
+#	define u_clusterCountX UNIFORM(u_lightingUniforms.tileCount.x)
+#	define u_clusterCountY UNIFORM(u_lightingUniforms.tileCount.y)
 #	define u_clustererMagic u_lightingUniforms.clustererMagicValues
-#	define u_time readFirstInvocationARB(u_lightingUniforms.rendererSizeTimeNear.z)
-#	define u_unprojectionParams readFirstInvocationARB(u_lightingUniforms.unprojectionParams)
+#	define u_time UNIFORM(u_lightingUniforms.rendererSizeTimeNear.z)
+#	define u_unprojectionParams UNIFORM(u_lightingUniforms.unprojectionParams)
 #	define u_rendererSize u_lightingUniforms.rendererSizeTimeNear.xy
 
 #	define u_viewMat u_lightingUniforms.viewMat

+ 11 - 3
shaders/Common.glsl

@@ -8,13 +8,19 @@
 #ifndef ANKI_SHADERS_COMMON_GLSL
 #define ANKI_SHADERS_COMMON_GLSL
 
-// WORKAROUND
+// WORKAROUNDS
 #if defined(ANKI_VENDOR_NVIDIA)
 #	define NVIDIA_LINK_ERROR_WORKAROUND 1
 #else
 #	define NVIDIA_LINK_ERROR_WORKAROUND 0
 #endif
 
+#if defined(ANKI_VENDOR_AMD) && defined(ANKI_BACKEND_VULKAN)
+#	define AMD_VK_READ_FIRST_INVOCATION_COMPILER_CRASH 1
+#else
+#	define AMD_VK_READ_FIRST_INVOCATION_COMPILER_CRASH 0
+#endif
+
 // Default precision
 #ifndef DEFAULT_FLOAT_PRECISION
 #	define DEFAULT_FLOAT_PRECISION highp
@@ -60,8 +66,10 @@ const uint UBO_MAX_SIZE = 16384u;
 #define PASS_EZ 2
 
 // Other
-#if !defined(ANKI_ARB_SHADER_BALLOT)
-#	define readFirstInvocationARB(x_) (x_)
+#if defined(ANKI_ARB_SHADER_BALLOT) && AMD_VK_READ_FIRST_INVOCATION_COMPILER_CRASH == 0
+#	define UNIFORM(x_) readFirstInvocationARB(x_)
+#else
+#	define UNIFORM(x_) x_
 #endif
 
 #define CALC_BITANGENT_IN_VERT 1

+ 1 - 0
src/anki/Util.h

@@ -35,6 +35,7 @@
 #include <anki/util/Visitor.h>
 #include <anki/util/INotify.h>
 #include <anki/util/SparseArray.h>
+#include <anki/util/ObjectAllocator.h>
 
 /// @defgroup util Utilities (like STL)
 

+ 1 - 0
src/anki/collision/Aabb.h

@@ -30,6 +30,7 @@ public:
 		, m_min(min)
 		, m_max(max)
 	{
+		ANKI_ASSERT(min.w() == 0.0f && max.w() == 0.0f);
 		ANKI_ASSERT(m_min.xyz() < m_max.xyz());
 	}
 

+ 12 - 9
src/anki/gr/Common.h

@@ -108,15 +108,6 @@ extern Array<CString, U(GpuVendor::COUNT)> GPU_VENDOR_STR;
 class GpuDeviceCapabilities
 {
 public:
-	/// Max push constant size.
-	U32 m_pushConstantsSize = 128;
-
-	/// GPU vendor.
-	GpuVendor m_gpuVendor = GpuVendor::UNKNOWN;
-
-	/// Device supports subgroup operations.
-	Bool8 m_shaderSubgroups = false;
-
 	/// The alignment of offsets when bounding uniform buffers.
 	PtrSize m_uniformBufferBindOffsetAlignment = MAX_U32;
 
@@ -134,6 +125,18 @@ public:
 
 	/// The max visible range of texture buffers inside the shaders.
 	PtrSize m_textureBufferMaxRange = 0;
+
+	/// Max push constant size.
+	U32 m_pushConstantsSize = 128;
+
+	/// GPU vendor.
+	GpuVendor m_gpuVendor = GpuVendor::UNKNOWN;
+
+	/// Device supports subgroup operations.
+	Bool8 m_shaderSubgroups = false;
+
+	/// Pad it because valgrind complains.
+	U8 _m_padding[2] = {};
 };
 
 /// The type of the allocator for heap allocations

+ 32 - 10
src/anki/gr/RenderGraph.cpp

@@ -104,6 +104,8 @@ public:
 	};
 	DynamicArray<ConsumedTextureInfo> m_consumedTextures;
 
+	Bool8 m_drawsToDefaultFb = false;
+
 	FramebufferPtr& fb()
 	{
 		return m_secondLevelCmdbInitInfo.m_framebuffer;
@@ -116,11 +118,13 @@ public:
 };
 
 /// A batch of render passes. These passes can run in parallel.
+/// @warning It's POD. Destructor won't be called.
 class RenderGraph::Batch
 {
 public:
 	DynamicArray<U32> m_passIndices;
 	DynamicArray<Barrier> m_barriersBefore;
+	CommandBuffer* m_cmdb; ///< Someone else holds the ref already so have a ptr here.
 };
 
 /// The RenderGraph build context.
@@ -134,7 +138,7 @@ public:
 	DynamicArray<RT> m_rts;
 	DynamicArray<Buffer> m_buffers;
 
-	CommandBufferPtr m_cmdb;
+	DynamicArray<CommandBufferPtr> m_graphicsCmdbs;
 
 	BakeContext(const StackAllocator<U8>& alloc)
 		: m_alloc(alloc)
@@ -271,7 +275,7 @@ void RenderGraph::reset()
 		p.m_secondLevelCmdbs.destroy(m_ctx->m_alloc);
 	}
 
-	m_ctx->m_cmdb.reset(nullptr);
+	m_ctx->m_graphicsCmdbs.destroy(m_ctx->m_alloc);
 
 	m_ctx->m_alloc = StackAllocator<U8>();
 	m_ctx = nullptr;
@@ -617,6 +621,7 @@ void RenderGraph::initRenderPassesAndSetDeps(const RenderGraphDescription& descr
 			if(graphicsPass.hasFramebuffer())
 			{
 				outPass.fb() = getOrCreateFramebuffer(graphicsPass.m_fbDescr, &graphicsPass.m_rtHandles[0]);
+				outPass.m_drawsToDefaultFb = graphicsPass.m_fbDescr.m_defaultFb;
 
 				outPass.m_fbRenderArea = graphicsPass.m_fbRenderArea;
 
@@ -691,6 +696,7 @@ void RenderGraph::initBatches()
 	while(passesInBatchCount < passCount)
 	{
 		Batch batch;
+		Bool drawsToDefaultFb = false;
 
 		for(U i = 0; i < passCount; ++i)
 		{
@@ -699,9 +705,26 @@ void RenderGraph::initBatches()
 				// Add to the batch
 				++passesInBatchCount;
 				batch.m_passIndices.emplaceBack(m_ctx->m_alloc, i);
+
+				// Will batch draw to default FB?
+				drawsToDefaultFb = drawsToDefaultFb || m_ctx->m_passes[i].m_drawsToDefaultFb;
 			}
 		}
 
+		// Get or create cmdb for the batch.
+		// Create a new cmdb if the batch is writing to default FB. This will help Vulkan to have a dependency of the
+		// swap chain image acquire to the 2nd command buffer instead of adding it to a single big cmdb.
+		if(m_ctx->m_graphicsCmdbs.isEmpty() || drawsToDefaultFb)
+		{
+			CommandBufferInitInfo cmdbInit;
+			cmdbInit.m_flags = CommandBufferFlag::COMPUTE_WORK | CommandBufferFlag::GRAPHICS_WORK;
+			CommandBufferPtr cmdb = getManager().newCommandBuffer(cmdbInit);
+
+			m_ctx->m_graphicsCmdbs.emplaceBack(m_ctx->m_alloc, cmdb);
+
+			batch.m_cmdb = cmdb.get();
+		}
+
 		// Push back batch
 		m_ctx->m_batches.emplaceBack(m_ctx->m_alloc, std::move(batch));
 
@@ -905,11 +928,6 @@ void RenderGraph::compileNewGraph(const RenderGraphDescription& descr, StackAllo
 	// Create barriers between batches
 	setBatchBarriers(descr);
 
-	// Create main command buffer
-	CommandBufferInitInfo cmdbInit;
-	cmdbInit.m_flags = CommandBufferFlag::GRAPHICS_WORK | CommandBufferFlag::COMPUTE_WORK;
-	m_ctx->m_cmdb = getManager().newCommandBuffer(cmdbInit);
-
 #if ANKI_DBG_RENDER_GRAPH
 	if(dumpDependencyDotFile(descr, ctx, "./"))
 	{
@@ -964,16 +982,17 @@ void RenderGraph::run() const
 {
 	ANKI_TRACE_SCOPED_EVENT(GR_RENDER_GRAPH);
 	ANKI_ASSERT(m_ctx);
-	CommandBufferPtr& cmdb = m_ctx->m_cmdb;
 
 	RenderPassWorkContext ctx;
 	ctx.m_rgraph = this;
 	ctx.m_currentSecondLevelCommandBufferIndex = 0;
 	ctx.m_secondLevelCommandBufferCount = 0;
-	ctx.m_commandBuffer = cmdb;
 
 	for(const Batch& batch : m_ctx->m_batches)
 	{
+		ctx.m_commandBuffer.reset(batch.m_cmdb);
+		CommandBufferPtr& cmdb = ctx.m_commandBuffer;
+
 		// Set the barriers
 		for(const Barrier& barrier : batch.m_barriersBefore)
 		{
@@ -1036,7 +1055,10 @@ void RenderGraph::run() const
 
 void RenderGraph::flush()
 {
-	m_ctx->m_cmdb->flush();
+	for(CommandBufferPtr& cmdb : m_ctx->m_graphicsCmdbs)
+	{
+		cmdb->flush();
+	}
 }
 
 void RenderGraph::getCrntUsage(

+ 12 - 12
src/anki/gr/ShaderCompiler.cpp

@@ -35,20 +35,20 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #define ANKI_%s_SHADER 1
 
 #if defined(ANKI_BACKEND_GL) 
-#	define ANKI_UBO_BINDING(set_, binding_) binding = set_ * %u + binding_
-#	define ANKI_SS_BINDING(set_, binding_) binding = set_ * %u + binding_
-#	define ANKI_TEX_BINDING(set_, binding_) binding = set_ * %u + binding_
-#	define ANKI_IMAGE_BINDING(set_, binding_) binding = set_ * %u + binding_
+#	define ANKI_UBO_BINDING(set_, binding_) binding = (set_) * (%u) + (binding_)
+#	define ANKI_SS_BINDING(set_, binding_) binding = (set_) * (%u) + (binding_)
+#	define ANKI_TEX_BINDING(set_, binding_) binding = (set_) * (%u) + (binding_)
+#	define ANKI_IMAGE_BINDING(set_, binding_) binding = (set_) * (%u) + (binding_)
 #	define ANKI_SPEC_CONST(binding_, type_, name_) const type_ name_ = _anki_spec_const_ ## binding_
-#	define ANKI_PUSH_CONSTANTS(struct_, name_) layout(location = %u, row_major) uniform struct_ name_
+#	define ANKI_PUSH_CONSTANTS(struct_, name_) layout(location = (%u)) uniform struct_ name_
 #else
 #	define gl_VertexID gl_VertexIndex
 #	define gl_InstanceID gl_InstanceIndex
-#	define ANKI_TEX_BINDING(set_, binding_) set = set_, binding = %u + binding_
-#	define ANKI_UBO_BINDING(set_, binding_) set = set_, binding = %u + binding_
-#	define ANKI_SS_BINDING(set_, binding_) set = set_, binding = %u + binding_
-#	define ANKI_IMAGE_BINDING(set_, binding_) set = set_, binding = %u + binding_
-#	define ANKI_SPEC_CONST(binding_, type_, name_) layout(constant_id = binding_) const type_ name_ = type_(0)
+#	define ANKI_TEX_BINDING(set_, binding_) set = (set_), binding = (%u) + (binding_)
+#	define ANKI_UBO_BINDING(set_, binding_) set = (set_), binding = (%u) + (binding_)
+#	define ANKI_SS_BINDING(set_, binding_) set = (set_), binding = (%u) + (binding_)
+#	define ANKI_IMAGE_BINDING(set_, binding_) set = (set_), binding = (%u) + (binding_)
+#	define ANKI_SPEC_CONST(binding_, type_, name_) layout(constant_id = (binding_)) const type_ name_ = type_(0)
 #	define ANKI_PUSH_CONSTANTS(struct_, name_) layout(push_constant, row_major, std140) \
 		uniform pushConst_ {struct_ name_;}
 #endif
@@ -295,8 +295,8 @@ Error ShaderCompiler::compile(CString source, const ShaderCompilerOptions& optio
 		MAX_UNIFORM_BUFFER_BINDINGS,
 		MAX_STORAGE_BUFFER_BINDINGS,
 		MAX_TEXTURE_BINDINGS,
-		MAX_IMAGE_BINDINGS,
-		MAX_TEXTURE_BINDINGS * MAX_DESCRIPTOR_SETS, // Push constant location
+		MAX_IMAGE_BINDINGS, // Images
+		(MAX_TEXTURE_BINDINGS + MAX_IMAGE_BINDINGS) * MAX_DESCRIPTOR_SETS, // Push constant location
 		// VK bindings
 		0,
 		MAX_TEXTURE_BINDINGS,

+ 11 - 2
src/anki/gr/ShaderCompiler.h

@@ -25,10 +25,19 @@ enum class ShaderLanguage : U8
 class ShaderCompilerOptions
 {
 public:
-	ShaderLanguage m_outLanguage = ShaderLanguage::COUNT;
-	ShaderType m_shaderType = ShaderType::COUNT;
+	ShaderLanguage m_outLanguage;
+	ShaderType m_shaderType;
 	GpuDeviceCapabilities m_gpuCapabilities;
 
+	ShaderCompilerOptions()
+	{
+		// Zero it because it will be hashed
+		zeroMemory(*this);
+		m_outLanguage = ShaderLanguage::COUNT;
+		m_shaderType = ShaderType::COUNT;
+		::new(&m_gpuCapabilities) GpuDeviceCapabilities();
+	}
+
 	void setFromGrManager(const GrManager& gr);
 };
 

+ 3 - 2
src/anki/gr/gl/CommandBuffer.cpp

@@ -1507,6 +1507,7 @@ void CommandBuffer::setPushConstants(const void* data, U32 dataSize)
 				static_cast<ShaderProgramImpl&>(*state.m_crntProg).getReflection();
 			ANKI_ASSERT(refl.m_uniformDataSize == m_data.getSizeInBytes());
 
+			const Bool transpose = true;
 			for(const ShaderProgramImplReflection::Uniform& uni : refl.m_uniforms)
 			{
 				const U8* data = reinterpret_cast<const U8*>(&m_data[0]) + uni.m_pushConstantOffset;
@@ -1525,7 +1526,7 @@ void CommandBuffer::setPushConstants(const void* data, U32 dataSize)
 					glUniform4uiv(loc, count, reinterpret_cast<const GLuint*>(data));
 					break;
 				case ShaderVariableDataType::MAT4:
-					glUniformMatrix4fv(loc, count, false, reinterpret_cast<const GLfloat*>(data));
+					glUniformMatrix4fv(loc, count, transpose, reinterpret_cast<const GLfloat*>(data));
 					break;
 				case ShaderVariableDataType::MAT3:
 				{
@@ -1533,7 +1534,7 @@ void CommandBuffer::setPushConstants(const void* data, U32 dataSize)
 					ANKI_ASSERT(count == 1 && "TODO");
 					const Mat3x4* m34 = reinterpret_cast<const Mat3x4*>(data);
 					Mat3 m3(m34->getRotationPart());
-					glUniformMatrix3fv(loc, count, false, reinterpret_cast<const GLfloat*>(&m3));
+					glUniformMatrix3fv(loc, count, transpose, reinterpret_cast<const GLfloat*>(&m3));
 					break;
 				}
 				default:

+ 5 - 0
src/anki/gr/gl/Common.cpp

@@ -170,6 +170,11 @@ void convertVertexFormat(Format fmt, U& compCount, GLenum& type, Bool& normalize
 		type = GL_UNSIGNED_SHORT;
 		normalized = true;
 		break;
+	case Format::R16G16B16A16_SFLOAT:
+		compCount = 4;
+		type = GL_HALF_FLOAT;
+		normalized = false;
+		break;
 	case Format::A2B10G10R10_SNORM_PACK32:
 		compCount = 4;
 		type = GL_INT_2_10_10_10_REV;

+ 3 - 3
src/anki/gr/gl/GlState.cpp

@@ -155,13 +155,13 @@ void GlState::initRenderThread()
 
 	I64 val;
 	glGetInteger64v(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &val);
-	m_uboAlignment = val;
+	m_uboAlignment = max<U32>(ANKI_SAFE_ALIGNMENT, val); // Some GPU have really low requirements
 
 	glGetInteger64v(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &val);
-	m_ssboAlignment = val;
+	m_ssboAlignment = max<U32>(ANKI_SAFE_ALIGNMENT, val); // Some GPU have really low requirements
 
 	glGetInteger64v(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &val);
-	m_tboAlignment = val;
+	m_tboAlignment = max<U32>(ANKI_SAFE_ALIGNMENT, val); // Some GPU have really low requirements
 
 	glGetInteger64v(GL_MAX_UNIFORM_BLOCK_SIZE, &val);
 	m_uniBlockMaxSize = val;

+ 11 - 9
src/anki/gr/gl/ShaderProgramImpl.cpp

@@ -122,14 +122,7 @@ const ShaderProgramImplReflection& ShaderProgramImpl::getReflection()
 				continue;
 			}
 
-			GLint location = glGetUniformLocation(getGlName(), &name[0]);
-			if(location < I(MAX_TEXTURE_BINDINGS * MAX_DESCRIPTOR_SETS))
-			{
-				// It must be a sampled image, skip it
-				continue;
-			}
-
-			// Store those info
+			// Set type
 			ShaderVariableDataType akType = ShaderVariableDataType::NONE;
 			switch(type)
 			{
@@ -149,9 +142,18 @@ const ShaderProgramImplReflection& ShaderProgramImpl::getReflection()
 				akType = ShaderVariableDataType::MAT3;
 				break;
 			default:
-				ANKI_ASSERT(!"Unsupported type");
+				// Unsupported type, skip as well
+				continue;
+			}
+
+			const GLint location = glGetUniformLocation(getGlName(), &name[0]);
+			if(location < 0)
+			{
+				// Uniform block maybe, skip
+				continue;
 			}
 
+			// Store
 			ShaderProgramImplReflection::Uniform uni;
 			uni.m_location = location;
 			uni.m_type = akType;

+ 9 - 5
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -9,7 +9,7 @@
 #include <anki/gr/CommandBuffer.h>
 #include <anki/gr/Fence.h>
 #include <anki/gr/vulkan/FenceImpl.h>
-
+#include <anki/util/Functions.h>
 #include <anki/core/Config.h>
 #include <glslang/Public/ShaderLang.h>
 
@@ -402,16 +402,20 @@ Error GrManagerImpl::initInstance(const GrManagerInitInfo& init)
 	default:
 		m_capabilities.m_gpuVendor = GpuVendor::UNKNOWN;
 	}
-	ANKI_VK_LOGI("GPU vendor is %s", &GPU_VENDOR_STR[m_capabilities.m_gpuVendor][0]);
+	ANKI_VK_LOGI(
+		"GPU is %s. Vendor identified a %s", m_devProps.deviceName, &GPU_VENDOR_STR[m_capabilities.m_gpuVendor][0]);
 
 	vkGetPhysicalDeviceFeatures(m_physicalDevice, &m_devFeatures);
 
 	// Set limits
-	m_capabilities.m_uniformBufferBindOffsetAlignment = m_devProps.limits.minUniformBufferOffsetAlignment;
+	m_capabilities.m_uniformBufferBindOffsetAlignment =
+		max<U32>(ANKI_SAFE_ALIGNMENT, m_devProps.limits.minUniformBufferOffsetAlignment);
 	m_capabilities.m_uniformBufferMaxRange = m_devProps.limits.maxUniformBufferRange;
-	m_capabilities.m_storageBufferBindOffsetAlignment = m_devProps.limits.minStorageBufferOffsetAlignment;
+	m_capabilities.m_storageBufferBindOffsetAlignment =
+		max<U32>(ANKI_SAFE_ALIGNMENT, m_devProps.limits.minStorageBufferOffsetAlignment);
 	m_capabilities.m_storageBufferMaxRange = m_devProps.limits.maxStorageBufferRange;
-	m_capabilities.m_textureBufferBindOffsetAlignment = m_devProps.limits.minTexelBufferOffsetAlignment;
+	m_capabilities.m_textureBufferBindOffsetAlignment =
+		max<U32>(ANKI_SAFE_ALIGNMENT, m_devProps.limits.minTexelBufferOffsetAlignment);
 	m_capabilities.m_textureBufferMaxRange = MAX_U32;
 
 	return Error::NONE;

+ 1 - 0
src/anki/renderer/DownscaleBlur.cpp

@@ -39,6 +39,7 @@ Error DownscaleBlur::initInternal(const ConfigSet&)
 			| TextureUsageBit::SAMPLED_COMPUTE,
 		"DownscaleBlur");
 	texinit.m_mipmapCount = m_passCount;
+	texinit.m_initialUsage = TextureUsageBit::SAMPLED_COMPUTE;
 	m_rtTex = m_r->createAndClearRenderTarget(texinit);
 
 	// FB descr

+ 9 - 6
src/anki/renderer/Indirect.cpp

@@ -366,12 +366,15 @@ void Indirect::runGBuffer(CommandBufferPtr& cmdb)
 		ANKI_ASSERT(probe.m_renderQueues[faceIdx]);
 		const RenderQueue& rqueue = *probe.m_renderQueues[faceIdx];
 
-		m_r->getSceneDrawer().drawRange(Pass::GB_FS,
-			rqueue.m_viewMatrix,
-			rqueue.m_viewProjectionMatrix,
-			cmdb,
-			rqueue.m_renderables.getBegin(),
-			rqueue.m_renderables.getEnd());
+		if(!rqueue.m_renderables.isEmpty())
+		{
+			m_r->getSceneDrawer().drawRange(Pass::GB_FS,
+				rqueue.m_viewMatrix,
+				rqueue.m_viewProjectionMatrix,
+				cmdb,
+				rqueue.m_renderables.getBegin(),
+				rqueue.m_renderables.getEnd());
+		}
 	}
 
 	// Restore state

+ 6 - 3
src/anki/resource/MeshLoader.cpp

@@ -173,7 +173,7 @@ Error MeshLoader::checkHeader() const
 
 	// Attributes
 	ANKI_CHECK(checkFormat(
-		VertexAttributeLocation::POSITION, Array<Format, 2>{{Format::R16G16B16_SFLOAT, Format::R32G32B32_SFLOAT}}));
+		VertexAttributeLocation::POSITION, Array<Format, 2>{{Format::R16G16B16A16_SFLOAT, Format::R32G32B32_SFLOAT}}));
 	ANKI_CHECK(checkFormat(VertexAttributeLocation::NORMAL, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}));
 	ANKI_CHECK(checkFormat(VertexAttributeLocation::TANGENT, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}));
 	ANKI_CHECK(
@@ -315,15 +315,18 @@ Error MeshLoader::storeIndicesAndPosition(DynamicArrayAuto<U32>& indices, Dynami
 			{
 				vert = *reinterpret_cast<Vec3*>(&staging[i * buffInfo.m_vertexStride + attrib.m_relativeOffset]);
 			}
-			else
+			else if(attrib.m_format == Format::R16G16B16A16_SFLOAT)
 			{
-				ANKI_ASSERT(attrib.m_format == Format::R16G16B16_SFLOAT);
 				F16* f16 = reinterpret_cast<F16*>(&staging[i * buffInfo.m_vertexStride + attrib.m_relativeOffset]);
 
 				vert[0] = f16[0].toF32();
 				vert[1] = f16[1].toF32();
 				vert[2] = f16[2].toF32();
 			}
+			else
+			{
+				ANKI_ASSERT(0);
+			}
 
 			positions[i] = vert;
 		}

+ 141 - 0
src/anki/scene/Octree.cpp

@@ -0,0 +1,141 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/Octree.h>
+#include <anki/collision/Tests.h>
+#include <anki/collision/Aabb.h>
+
+namespace anki
+{
+
+void Octree::init(const Vec3& sceneAabbMin, const Vec3& sceneAabbMax, U32 maxDepth)
+{
+	ANKI_ASSERT(sceneAabbMin < sceneAabbMax);
+	ANKI_ASSERT(maxDepth > 0);
+
+	m_maxDepth = maxDepth;
+	m_sceneAabbMin = sceneAabbMin;
+	m_sceneAabbMax = sceneAabbMax;
+
+	m_rootLeaf = newLeaf();
+}
+
+void Octree::place(const Aabb& volume, OctreeHandle* handle)
+{
+	ANKI_ASSERT(m_rootLeaf);
+	ANKI_ASSERT(handle);
+
+	// Remove the handle from the Octree...
+	// TOOD
+
+	// .. and re-place it
+	placeRecursive(volume, handle, m_rootLeaf, m_sceneAabbMin, m_sceneAabbMax, 0);
+}
+
+void Octree::placeRecursive(
+	const Aabb& volume, OctreeHandle* handle, Leaf* parent, const Vec3& aabbMin, const Vec3& aabbMax, U32 depth)
+{
+	ANKI_ASSERT(handle);
+	ANKI_ASSERT(parent);
+	ANKI_ASSERT(testCollisionShapes(volume, Aabb(Vec4(aabbMin, 0.0f), Vec4(aabbMax, 0.0f))) && "Should be inside");
+
+	if(depth == m_maxDepth)
+	{
+		// Need to stop and bin the handle to the leaf
+
+		// Checks
+		for(const LeafNode& node : handle->m_leafs)
+		{
+			ANKI_ASSERT(node.m_leaf != parent && "Already binned. That's wrong");
+		}
+
+		// Connect handle and leaf
+		handle->m_leafs.pushBack(newLeafNode(parent));
+		parent->m_handles.pushBack(newHandle(handle));
+
+		return;
+	}
+
+	const Vec4& vMin = volume.getMin();
+	const Vec4& vMax = volume.getMax();
+	const Vec3 center = (aabbMax + aabbMin) / 2.0f;
+
+	LeafMask maskX;
+	if(vMin.x() > center.x())
+	{
+		// Only right
+		maskX = LeafMask::RIGHT;
+	}
+	else if(vMax.x() < center.x())
+	{
+		// Only left
+		maskX = LeafMask::LEFT;
+	}
+	else
+	{
+		maskX = LeafMask::ALL;
+	}
+
+	LeafMask maskY;
+	if(vMin.y() > center.y())
+	{
+		// Only top
+		maskY = LeafMask::TOP;
+	}
+	else if(vMax.y() < center.y())
+	{
+		// Only bottom
+		maskY = LeafMask::BOTTOM;
+	}
+	else
+	{
+		maskY = LeafMask::ALL;
+	}
+
+	LeafMask maskZ;
+	if(vMin.z() > center.z())
+	{
+		// Only front
+		maskZ = LeafMask::FRONT;
+	}
+	else if(vMax.z() < center.z())
+	{
+		// Only back
+		maskZ = LeafMask::BACK;
+	}
+	else
+	{
+		maskZ = LeafMask::ALL;
+	}
+
+	LeafMask maskUnion = maskX & maskY & maskZ;
+	ANKI_ASSERT(!!maskUnion && "Should be inside at least one leaf");
+
+	for(U i = 0; i < 8; ++i)
+	{
+		const LeafMask crntBit = LeafMask(1 << i);
+
+		if(!!(maskUnion & crntBit))
+		{
+			// Inside the leaf, move deeper
+
+			// Compute AABB
+			Vec3 aabbMin, aabbMax;
+			if(!!(crntBit & LeafMask::RIGHT))
+			{
+				// TODO
+			}
+
+			if(parent->m_leafs[i] == nullptr)
+			{
+				parent->m_leafs[i] = newLeaf();
+			}
+
+			// TODO
+		}
+	}
+}
+
+} // end namespace anki

+ 167 - 0
src/anki/scene/Octree.h

@@ -0,0 +1,167 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/Common.h>
+#include <anki/Math.h>
+#include <anki/collision/Forward.h>
+#include <anki/util/WeakArray.h>
+#include <anki/util/Enum.h>
+#include <anki/util/ObjectAllocator.h>
+#include <anki/util/List.h>
+
+namespace anki
+{
+
+// Forward
+class OctreeHandle;
+
+/// @addtogroup scene
+/// @{
+
+/// Octree for visibility tests.
+class Octree
+{
+	friend class OctreeHandle;
+
+public:
+	Octree(SceneAllocator<U8> alloc)
+		: m_alloc(alloc)
+	{
+	}
+
+	~Octree();
+
+	void init(const Vec3& sceneAabbMin, const Vec3& sceneAabbMax, U32 maxDepth);
+
+	/// Place or re-place an element in the tree.
+	/// @note It's thread-safe.
+	void place(const Aabb& volume, OctreeHandle* handle);
+
+	/// Remove an element from the tree.
+	/// @note It's thread-safe.
+	void remove(OctreeHandle& handle);
+
+	/// Gather visible handles.
+	/// @note It's thread-safe.
+	DynamicArray<OctreeHandle*> gatherVisible(GenericMemoryPoolAllocator<U8> alloc, U32 testId);
+
+private:
+	/// XXX
+	class Handle : public IntrusiveListEnabled<Handle>
+	{
+	public:
+		OctreeHandle* m_handle = nullptr;
+	};
+
+	/// XXX
+	/// @warning Keept its size as small as possible.
+	class Leaf
+	{
+	public:
+		IntrusiveList<Handle> m_handles;
+		Array<Leaf*, 8> m_leafs = {};
+	};
+
+	/// XXX
+	class LeafNode : public IntrusiveListEnabled<LeafNode>
+	{
+	public:
+		Leaf* m_leaf = nullptr;
+	};
+
+	/// P: Stands for positive and N: Negative
+	enum class LeafMask : U8
+	{
+		PX_PY_PZ = 1 << 0,
+		PX_PY_NZ = 1 << 1,
+		PX_NY_PZ = 1 << 2,
+		PX_NY_NZ = 1 << 3,
+		NX_PY_PZ = 1 << 4,
+		NX_PY_NZ = 1 << 5,
+		NX_NY_PZ = 1 << 6,
+		NX_NY_NZ = 1 << 7,
+
+		NONE = 0,
+		ALL = PX_PY_PZ | PX_PY_NZ | PX_NY_PZ | PX_NY_NZ | NX_PY_PZ | NX_PY_NZ | NX_NY_PZ | NX_NY_NZ,
+		RIGHT = PX_PY_PZ | PX_PY_NZ | PX_NY_PZ | PX_NY_NZ,
+		LEFT = NX_PY_PZ | NX_PY_NZ | NX_NY_PZ | NX_NY_NZ,
+		TOP = PX_PY_PZ | PX_PY_NZ | NX_PY_PZ | NX_PY_NZ,
+		BOTTOM = PX_NY_PZ | PX_NY_NZ | NX_NY_PZ | NX_NY_NZ,
+		FRONT = PX_PY_PZ | PX_NY_PZ | NX_PY_PZ | NX_NY_PZ,
+		BACK = PX_PY_NZ | PX_NY_NZ | NX_PY_NZ | NX_NY_NZ,
+	};
+	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(LeafMask, friend)
+
+	SceneAllocator<U8> m_alloc;
+	U32 m_maxDepth = 0;
+	Vec3 m_sceneAabbMin = Vec3(0.0f);
+	Vec3 m_sceneAabbMax = Vec3(0.0f);
+
+	ObjectAllocatorSameType<Leaf, 256> m_leafAlloc;
+	ObjectAllocatorSameType<LeafNode, 128> m_leafNodeAlloc;
+	ObjectAllocatorSameType<Handle, 256> m_handleAlloc;
+
+	Leaf* m_rootLeaf = nullptr;
+
+	Leaf* newLeaf()
+	{
+		return m_leafAlloc.newInstance(m_alloc);
+	}
+
+	void releaseLeaf(Leaf* leaf)
+	{
+		m_leafAlloc.deleteInstance(m_alloc, leaf);
+	}
+
+	Handle* newHandle(OctreeHandle* handle)
+	{
+		ANKI_ASSERT(handle);
+		Handle* out = m_handleAlloc.newInstance(m_alloc);
+		out->m_handle = handle;
+		return out;
+	}
+
+	void releaseHandle(Handle* handle)
+	{
+		m_handleAlloc.deleteInstance(m_alloc, handle);
+	}
+
+	LeafNode* newLeafNode(Leaf* leaf)
+	{
+		ANKI_ASSERT(leaf);
+		LeafNode* out = m_leafNodeAlloc.newInstance(m_alloc);
+		out->m_leaf = leaf;
+		return out;
+	}
+
+	void releaseLeafNode(LeafNode* node)
+	{
+		m_leafNodeAlloc.deleteInstance(m_alloc, node);
+	}
+
+	void placeRecursive(
+		const Aabb& volume, OctreeHandle* handle, Leaf* parent, const Vec3& aabbMin, const Vec3& aabbMax, U32 depth);
+};
+
+/// XXX
+class OctreeHandle
+{
+	friend class Octree;
+
+public:
+	void reset()
+	{
+		m_visitedMask.set(0);
+	}
+
+private:
+	Atomic<U64> m_visitedMask = {0u};
+	IntrusiveList<Octree::LeafNode> m_leafs; ///< A list of leafs this handle belongs.
+};
+/// @}
+
+} // end namespace anki

+ 2 - 2
src/anki/ui/Canvas.cpp

@@ -205,8 +205,8 @@ void Canvas::appendToCommandBuffer(CommandBufferPtr cmdb)
 	// Vert & idx buffers
 	cmdb->bindVertexBuffer(0, vertCtx.m_token.m_buffer, vertCtx.m_token.m_offset, sizeof(Vert));
 	cmdb->setVertexAttribute(0, 0, Format::R32G32_SFLOAT, 0);
-	cmdb->setVertexAttribute(1, 0, Format::R32G32_SFLOAT, sizeof(Vec2));
-	cmdb->setVertexAttribute(2, 0, Format::R8G8B8A8_UNORM, sizeof(Vec2) * 2);
+	cmdb->setVertexAttribute(1, 0, Format::R8G8B8A8_UNORM, sizeof(Vec2) * 2);
+	cmdb->setVertexAttribute(2, 0, Format::R32G32_SFLOAT, sizeof(Vec2));
 
 	cmdb->bindIndexBuffer(idxCtx.m_token.m_buffer, idxCtx.m_token.m_offset, IndexType::U16);
 

+ 14 - 0
src/anki/util/Atomic.h

@@ -81,6 +81,20 @@ public:
 		return m_att.fetch_sub(a, static_cast<std::memory_order>(memOrd));
 	}
 
+	/// Fetch and do bitwise or.
+	template<typename Y>
+	Value fetchOr(const Y& a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
+	{
+		return m_att.fetch_or(a, static_cast<std::memory_order>(memOrd));
+	}
+
+	/// Fetch and do bitwise and.
+	template<typename Y>
+	Value fetchAnd(const Y& a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
+	{
+		return m_att.fetch_and(a, static_cast<std::memory_order>(memOrd));
+	}
+
 	/// @code
 	/// if(m_val == expected) {
 	/// 	m_val = desired;

+ 98 - 0
src/anki/util/ObjectAllocator.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/util/Array.h>
+
+namespace anki
+{
+
+/// @addtogroup util
+/// @{
+
+/// A simple allocator for objects of similar types.
+/// @tparam T_OBJECT_SIZE       The maximum size of the objects.
+/// @tparam T_OBJECT_ALIGNMENT  The maximum alignment of the objects.
+/// @tparam T_OBJECTS_PER_CHUNK How much memory (in objects) will be allocated at once.
+/// @tparam TIndexType          If T_OBJECTS_PER_CHUNK>0xFF make it U16. If T_OBJECTS_PER_CHUNK>0xFFFF make it U32.
+template<PtrSize T_OBJECT_SIZE, U32 T_OBJECT_ALIGNMENT, U32 T_OBJECTS_PER_CHUNK = 64, typename TIndexType = U8>
+class ObjectAllocator
+{
+public:
+	static constexpr PtrSize OBJECT_SIZE = T_OBJECT_SIZE;
+	static constexpr U32 OBJECT_ALIGNMENT = T_OBJECT_ALIGNMENT;
+	static constexpr U32 OBJECTS_PER_CHUNK = T_OBJECTS_PER_CHUNK;
+
+	ObjectAllocator()
+	{
+	}
+
+	~ObjectAllocator()
+	{
+		ANKI_ASSERT(m_chunksHead == nullptr && m_chunksTail == nullptr && "Forgot to deallocate");
+	}
+
+	/// Allocate and construct a new object instance.
+	/// @note Not thread-safe.
+	template<typename T, typename TAlloc, typename... TArgs>
+	T* newInstance(TAlloc& alloc, TArgs&&... args);
+
+	/// Delete an object.
+	/// @note Not thread-safe.
+	template<typename T, typename TAlloc>
+	void deleteInstance(TAlloc& alloc, T* obj);
+
+private:
+	/// Storage with equal properties as the object.
+	struct alignas(OBJECT_ALIGNMENT) Object
+	{
+		U8 m_storage[OBJECT_SIZE];
+	};
+
+	/// A  single allocation.
+	class Chunk
+	{
+	public:
+		Array<Object, OBJECTS_PER_CHUNK> m_objects;
+		Array<U32, OBJECTS_PER_CHUNK> m_unusedStack;
+		U32 m_unusedCount;
+
+		Chunk* m_next = nullptr;
+		Chunk* m_prev = nullptr;
+	};
+
+	Chunk* m_chunksHead = nullptr;
+	Chunk* m_chunksTail = nullptr;
+};
+
+/// Convenience wrapper for ObjectAllocator.
+template<typename T, U32 T_OBJECTS_PER_CHUNK = 64, typename TIndexType = U8>
+class ObjectAllocatorSameType : public ObjectAllocator<sizeof(T), alignof(T), T_OBJECTS_PER_CHUNK, TIndexType>
+{
+public:
+	using Base = ObjectAllocator<sizeof(T), alignof(T), T_OBJECTS_PER_CHUNK, TIndexType>;
+
+	/// Allocate and construct a new object instance.
+	/// @note Not thread-safe.
+	template<typename TAlloc, typename... TArgs>
+	T* newInstance(TAlloc& alloc, TArgs&&... args)
+	{
+		return Base::template newInstance<T>(alloc, std::forward(args)...);
+	}
+
+	/// Delete an object.
+	/// @note Not thread-safe.
+	template<typename TAlloc>
+	void deleteInstance(TAlloc& alloc, T* obj)
+	{
+		Base::deleteInstance(alloc, obj);
+	}
+};
+/// @}
+
+} // end namespace anki
+
+#include <anki/util/ObjectAllocator.inl.h>

+ 142 - 0
src/anki/util/ObjectAllocator.inl.h

@@ -0,0 +1,142 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/ObjectAllocator.h>
+
+namespace anki
+{
+
+template<PtrSize T_OBJECT_SIZE, U32 T_OBJECT_ALIGNMENT, U32 T_OBJECTS_PER_CHUNK, typename TIndexType>
+template<typename T, typename TAlloc, typename... TArgs>
+T* ObjectAllocator<T_OBJECT_SIZE, T_OBJECT_ALIGNMENT, T_OBJECTS_PER_CHUNK, TIndexType>::newInstance(
+	TAlloc& alloc, TArgs&&... args)
+{
+	static_assert(alignof(T) <= OBJECT_ALIGNMENT, "Wrong object alignment");
+	static_assert(sizeof(T) <= OBJECT_SIZE, "Wrong object size");
+
+	T* out = nullptr;
+
+	// Try find one in the chunks
+	Chunk* chunk = m_chunksHead;
+	while(chunk)
+	{
+		if(chunk->m_unusedCount > 0)
+		{
+			// Pop an element
+			--chunk->m_unusedCount;
+			out = reinterpret_cast<T*>(&chunk->m_objects[chunk->m_unusedStack[chunk->m_unusedCount]]);
+			break;
+		}
+
+		chunk = chunk->m_next;
+	}
+
+	if(out == nullptr)
+	{
+		// Need to create a new chunk
+
+		// Create the chunk
+		Chunk* newChunk = alloc.template newInstance<Chunk>();
+		newChunk->m_unusedCount = OBJECTS_PER_CHUNK;
+
+		for(U i = 0; i < OBJECTS_PER_CHUNK; ++i)
+		{
+			newChunk->m_unusedStack[i] = OBJECTS_PER_CHUNK - (i + 1);
+		}
+
+		if(m_chunksTail)
+		{
+			ANKI_ASSERT(m_chunksHead);
+			newChunk->m_prev = m_chunksTail;
+			m_chunksTail->m_next = newChunk;
+			m_chunksTail = newChunk;
+		}
+		else
+		{
+			m_chunksTail = m_chunksHead = newChunk;
+		}
+
+		// Allocate one object
+		out = reinterpret_cast<T*>(&chunk->m_objects[0]);
+		--chunk->m_unusedCount;
+	}
+
+	ANKI_ASSERT(out);
+
+	// Construct it
+	::new(out) T(std::forward<TArgs>(args)...);
+
+	return out;
+}
+
+template<PtrSize T_OBJECT_SIZE, U32 T_OBJECT_ALIGNMENT, U32 T_OBJECTS_PER_CHUNK, typename TIndexType>
+template<typename T, typename TAlloc>
+void ObjectAllocator<T_OBJECT_SIZE, T_OBJECT_ALIGNMENT, T_OBJECTS_PER_CHUNK, TIndexType>::deleteInstance(
+	TAlloc& alloc, T* obj)
+{
+	static_assert(alignof(T) <= OBJECT_ALIGNMENT, "Wrong object alignment");
+	static_assert(sizeof(T) <= OBJECT_SIZE, "Wrong object size");
+
+	ANKI_ASSERT(obj);
+
+	// Find the chunk the obj is in
+	const Object* const mem = reinterpret_cast<Object*>(obj);
+	Chunk* chunk = m_chunksHead;
+	while(chunk)
+	{
+		const Object* const begin = chunk->m_objects.getBegin();
+		const Object* const end = chunk->m_objects.getEnd();
+		if(mem >= begin && mem < end)
+		{
+			// Found it, remove it from the chunk and maybe delete the chunk
+
+			ANKI_ASSERT(chunk->m_unusedCount < OBJECTS_PER_CHUNK);
+			const U idx = mem - begin;
+
+			// Destroy the object
+			obj->~T();
+
+			// Remove from the chunk
+			chunk->m_unusedStack[chunk->m_unusedCount] = idx;
+			++chunk->m_unusedCount;
+
+			// Delete the chunk if it's empty
+			if(chunk->m_unusedCount == OBJECTS_PER_CHUNK)
+			{
+				if(chunk == m_chunksTail)
+				{
+					m_chunksTail = chunk->m_prev;
+				}
+
+				if(chunk == m_chunksHead)
+				{
+					m_chunksHead = chunk->m_next;
+				}
+
+				if(chunk->m_prev)
+				{
+					ANKI_ASSERT(chunk->m_prev->m_next == chunk);
+					chunk->m_prev->m_next = chunk->m_next;
+				}
+
+				if(chunk->m_next)
+				{
+					ANKI_ASSERT(chunk->m_next->m_prev == chunk);
+					chunk->m_next->m_prev = chunk->m_prev;
+				}
+
+				alloc.deleteInstance(chunk);
+			}
+
+			break;
+		}
+
+		chunk = chunk->m_next;
+	}
+
+	ANKI_ASSERT(chunk != nullptr);
+}
+
+} // end namespace anki

+ 20 - 6
tools/scene/ExporterMesh.cpp

@@ -185,7 +185,8 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		// Positions
 		auto& posa = header.m_vertexAttributes[anki::VertexAttributeLocation::POSITION];
 		posa.m_bufferBinding = 0;
-		posa.m_format = (maxPositionDistance < 2.0) ? anki::Format::R16G16B16_SFLOAT : anki::Format::R32G32B32_SFLOAT;
+		posa.m_format =
+			(maxPositionDistance < 2.0) ? anki::Format::R16G16B16A16_SFLOAT : anki::Format::R32G32B32_SFLOAT;
 		posa.m_relativeOffset = 0;
 		posa.m_scale = 1.0;
 
@@ -244,9 +245,13 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		{
 			header.m_vertexBuffers[0].m_vertexStride = sizeof(float) * 3;
 		}
+		else if(posa.m_format == anki::Format::R16G16B16A16_SFLOAT)
+		{
+			header.m_vertexBuffers[0].m_vertexStride = sizeof(uint16_t) * 4;
+		}
 		else
 		{
-			header.m_vertexBuffers[0].m_vertexStride = sizeof(uint16_t) * 3;
+			assert(0);
 		}
 
 		// 2nd buff has normal + tangent + texcoords
@@ -322,14 +327,23 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		{
 			file.write(reinterpret_cast<char*>(&positions[0]), positions.size() * sizeof(positions[0]));
 		}
-		else if(posa.m_format == anki::Format::R16G16B16_SFLOAT)
+		else if(posa.m_format == anki::Format::R16G16B16A16_SFLOAT)
 		{
 			std::vector<uint16_t> pos16;
-			pos16.resize(positions.size());
+			pos16.resize(mesh.mNumVertices * 4);
 
-			for(unsigned i = 0; i < positions.size(); ++i)
+			const float* p32 = &positions[0];
+			const float* p32end = p32 + positions.size();
+			uint16_t* p16 = &pos16[0];
+			while(p32 != p32end)
 			{
-				pos16[i] = anki::F16(positions[i]).toU16();
+				p16[0] = anki::F16(p32[0]).toU16();
+				p16[1] = anki::F16(p32[1]).toU16();
+				p16[2] = anki::F16(p32[2]).toU16();
+				p16[3] = anki::F16(0.0f).toU16();
+
+				p32 += 3;
+				p16 += 4;
 			}
 
 			file.write(reinterpret_cast<char*>(&pos16[0]), pos16.size() * sizeof(pos16[0]));