Browse Source

Start refactoring the mesh loader

Panagiotis Christopoulos Charitos 5 years ago
parent
commit
3e31acb639

+ 169 - 0
anki/resource/MeshBinary.h

@@ -0,0 +1,169 @@
+// Copyright (C) 2009-2020, 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/resource/Common.h>
+#include <anki/Math.h>
+
+namespace anki
+{
+
+/// @addtogroup resource
+/// @{
+
+static constexpr const char* MESH_MAGIC = "ANKIMES5";
+
+enum class MeshBinaryFlag : U32
+{
+	NONE = 0,
+	QUAD = 1 << 0,
+	CONVEX = 1 << 1,
+
+	ALL = QUAD | CONVEX,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
+
+/// Vertex buffer info. The size of the buffer is m_vertexStride*MeshBinaryHeader::m_totalVertexCount aligned to 16.
+class MeshBinaryVertexBuffer
+{
+public:
+	U32 m_vertexStride; ///< The size of the vertex.
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doValue("m_vertexStride", offsetof(MeshBinaryVertexBuffer, m_vertexStride), self.m_vertexStride);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, MeshBinaryVertexBuffer&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const MeshBinaryVertexBuffer&>(serializer, *this);
+	}
+};
+
+/// Vertex attribute.
+class MeshBinaryVertexAttribute
+{
+public:
+	U32 m_bufferBinding;
+	Format m_format; ///< If the format is NONE then the attribute is not present.
+	U32 m_relativeOffset;
+	F32 m_scale;
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doValue("m_bufferBinding", offsetof(MeshBinaryVertexAttribute, m_bufferBinding), self.m_bufferBinding);
+		s.doValue("m_format", offsetof(MeshBinaryVertexAttribute, m_format), self.m_format);
+		s.doValue("m_relativeOffset", offsetof(MeshBinaryVertexAttribute, m_relativeOffset), self.m_relativeOffset);
+		s.doValue("m_scale", offsetof(MeshBinaryVertexAttribute, m_scale), self.m_scale);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, MeshBinaryVertexAttribute&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const MeshBinaryVertexAttribute&>(serializer, *this);
+	}
+};
+
+/// MeshBinarySubMesh class.
+class MeshBinarySubMesh
+{
+public:
+	U32 m_firstIndex;
+	U32 m_indexCount;
+	Vec3 m_aabbMin; ///< Bounding box min.
+	Vec3 m_aabbMax; ///< Bounding box max.
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doValue("m_firstIndex", offsetof(MeshBinarySubMesh, m_firstIndex), self.m_firstIndex);
+		s.doValue("m_indexCount", offsetof(MeshBinarySubMesh, m_indexCount), self.m_indexCount);
+		s.doValue("m_aabbMin", offsetof(MeshBinarySubMesh, m_aabbMin), self.m_aabbMin);
+		s.doValue("m_aabbMax", offsetof(MeshBinarySubMesh, m_aabbMax), self.m_aabbMax);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, MeshBinarySubMesh&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const MeshBinarySubMesh&>(serializer, *this);
+	}
+};
+
+/// The 1st things that appears in a mesh binary. @note The index and vertex buffers are aligned to 16 bytes.
+class MeshBinaryHeader
+{
+public:
+	Array<U8, 8> m_magic;
+	MeshBinaryFlag m_flags;
+	Array<MeshBinaryVertexBuffer, U32(VertexAttributeLocation::COUNT)> m_vertexBuffers;
+	U32 m_vertexBufferCount;
+	Array<MeshBinaryVertexAttribute, U32(VertexAttributeLocation::COUNT)> m_vertexAttributes;
+	IndexType m_indexType;
+	Array<U8, 3> m_padding;
+	U32 m_totalIndexCount;
+	U32 m_totalVertexCount;
+	U32 m_subMeshCount;
+	Vec3 m_aabbMin; ///< Bounding box min.
+	Vec3 m_aabbMax; ///< Bounding box max.
+
+	template<typename TSerializer, typename TClass>
+	static void serializeCommon(TSerializer& s, TClass self)
+	{
+		s.doArray("m_magic", offsetof(MeshBinaryHeader, m_magic), &self.m_magic[0], self.m_magic.getSize());
+		s.doValue("m_flags", offsetof(MeshBinaryHeader, m_flags), self.m_flags);
+		s.doArray("m_vertexBuffers", offsetof(MeshBinaryHeader, m_vertexBuffers), &self.m_vertexBuffers[0],
+				  self.m_vertexBuffers.getSize());
+		s.doValue("m_vertexBufferCount", offsetof(MeshBinaryHeader, m_vertexBufferCount), self.m_vertexBufferCount);
+		s.doArray("m_vertexAttributes", offsetof(MeshBinaryHeader, m_vertexAttributes), &self.m_vertexAttributes[0],
+				  self.m_vertexAttributes.getSize());
+		s.doValue("m_indexType", offsetof(MeshBinaryHeader, m_indexType), self.m_indexType);
+		s.doArray("m_padding", offsetof(MeshBinaryHeader, m_padding), &self.m_padding[0], self.m_padding.getSize());
+		s.doValue("m_totalIndexCount", offsetof(MeshBinaryHeader, m_totalIndexCount), self.m_totalIndexCount);
+		s.doValue("m_totalVertexCount", offsetof(MeshBinaryHeader, m_totalVertexCount), self.m_totalVertexCount);
+		s.doValue("m_subMeshCount", offsetof(MeshBinaryHeader, m_subMeshCount), self.m_subMeshCount);
+		s.doValue("m_aabbMin", offsetof(MeshBinaryHeader, m_aabbMin), self.m_aabbMin);
+		s.doValue("m_aabbMax", offsetof(MeshBinaryHeader, m_aabbMax), self.m_aabbMax);
+	}
+
+	template<typename TDeserializer>
+	void deserialize(TDeserializer& deserializer)
+	{
+		serializeCommon<TDeserializer, MeshBinaryHeader&>(deserializer, *this);
+	}
+
+	template<typename TSerializer>
+	void serialize(TSerializer& serializer) const
+	{
+		serializeCommon<TSerializer, const MeshBinaryHeader&>(serializer, *this);
+	}
+};
+
+/// @}
+
+} // end namespace anki

+ 65 - 0
anki/resource/MeshBinary.xml

@@ -0,0 +1,65 @@
+<serializer>
+	<includes>
+		<include file="&lt;anki/resource/Common.h&gt;"/>
+		<include file="&lt;anki/Math.h&gt;"/>
+	</includes>
+
+	<doxygen_group name="resource"/>
+
+	<prefix_code><![CDATA[
+static constexpr const char* MESH_MAGIC = "ANKIMES5";
+
+enum class MeshBinaryFlag : U32
+{
+	NONE = 0,
+	QUAD = 1 << 0,
+	CONVEX = 1 << 1,
+
+	ALL = QUAD | CONVEX,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
+]]></prefix_code>
+
+	<classes>
+		<class name="MeshBinaryVertexBuffer" comment="Vertex buffer info. The size of the buffer is m_vertexStride*MeshBinaryHeader::m_totalVertexCount aligned to 16">
+			<members>
+				<member name="m_vertexStride" type="U32" comment="The size of the vertex"/>
+			</members>
+		</class>
+
+		<class name="MeshBinaryVertexAttribute" comment="Vertex attribute">
+			<members>
+				<member name="m_bufferBinding" type="U32"/>
+				<member name="m_format" type="Format" comment="If the format is NONE then the attribute is not present"/>
+				<member name="m_relativeOffset" type="U32"/>
+				<member name="m_scale" type="F32"/>
+			</members>
+		</class>
+
+		<class name="MeshBinarySubMesh">
+			<members>
+				<member name="m_firstIndex" type="U32"/>
+				<member name="m_indexCount" type="U32"/>
+				<member name="m_aabbMin" type="Vec3" comment="Bounding box min"/>
+				<member name="m_aabbMax" type="Vec3" comment="Bounding box max"/>
+			</members>
+		</class>
+
+		<class name="MeshBinaryHeader" comment="The 1st things that appears in a mesh binary. @note The index and vertex buffers are aligned to 16 bytes">
+			<members>
+				<member name="m_magic" type="U8" array_size="8"/>
+				<member name="m_flags" type="MeshBinaryFlag"/>
+				<member name="m_vertexBuffers" type="MeshBinaryVertexBuffer" array_size="U32(VertexAttributeLocation::COUNT)"/>
+				<member name="m_vertexBufferCount" type="U32"/>
+				<member name="m_vertexAttributes" type="MeshBinaryVertexAttribute" array_size="U32(VertexAttributeLocation::COUNT)"/>
+				<member name="m_indexType" type="IndexType"/>
+				<member name="m_padding" type="U8" array_size="3"/>
+				<member name="m_totalIndexCount" type="U32"/>
+				<member name="m_totalVertexCount" type="U32"/>
+				<member name="m_subMeshCount" type="U32"/>
+				<member name="m_aabbMin" type="Vec3" comment="Bounding box min"/>
+				<member name="m_aabbMax" type="Vec3" comment="Bounding box max"/>
+			</members>
+		</class>
+	</classes>
+</serializer>

+ 367 - 0
anki/resource/MeshBinaryLoader.cpp

@@ -0,0 +1,367 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/resource/MeshBinaryLoader.h>
+#include <anki/resource/ResourceManager.h>
+
+namespace anki
+{
+
+MeshBinaryLoader::MeshBinaryLoader(ResourceManager* manager)
+	: MeshBinaryLoader(manager, manager->getTempAllocator())
+{
+}
+
+MeshBinaryLoader::~MeshBinaryLoader()
+{
+	m_subMeshes.destroy(m_alloc);
+}
+
+Error MeshBinaryLoader::load(const ResourceFilename& filename)
+{
+	auto& alloc = m_alloc;
+
+	// Load header
+	ANKI_CHECK(m_manager->getFilesystem().openFile(filename, m_file));
+	ANKI_CHECK(m_file->read(&m_header, sizeof(m_header)));
+	ANKI_CHECK(checkHeader());
+
+	// Read submesh info
+	{
+		m_subMeshes.create(alloc, m_header.m_subMeshCount);
+		ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
+
+		// Checks
+		const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFlag::QUAD) ? 4 : 3;
+		U idxSum = 0;
+		for(U i = 0; i < m_subMeshes.getSize(); i++)
+		{
+			const MeshBinarySubMesh& sm = m_subMeshes[0];
+			if(sm.m_firstIndex != idxSum || (sm.m_indexCount % indicesPerFace) != 0)
+			{
+				ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+				return Error::USER_DATA;
+			}
+
+			for(U d = 0; d < 3; ++d)
+			{
+				if(sm.m_aabbMin[i] >= sm.m_aabbMax[i])
+				{
+					ANKI_RESOURCE_LOGE("Wrong bounding box");
+					return Error::USER_DATA;
+				}
+			}
+
+			idxSum += sm.m_indexCount;
+		}
+
+		if(idxSum != m_header.m_totalIndexCount)
+		{
+			ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+			return Error::USER_DATA;
+		}
+	}
+
+	// Check the vert buffers
+	{
+		U32 vertBufferMask = 0;
+		U32 vertBufferCount = 0;
+		for(const MeshBinaryVertexAttribute& attrib : m_header.m_vertexAttributes)
+		{
+			if(attrib.m_format == Format::NONE)
+			{
+				continue;
+			}
+
+			vertBufferCount = max(attrib.m_bufferBinding + 1, vertBufferCount);
+			vertBufferMask |= 1 << attrib.m_bufferBinding;
+		}
+
+		if(U(__builtin_popcount(vertBufferMask)) != vertBufferCount)
+		{
+			ANKI_RESOURCE_LOGE("Problem in vertex buffers");
+			return Error::USER_DATA;
+		}
+
+		if(vertBufferCount != m_header.m_vertexBufferCount)
+		{
+			ANKI_RESOURCE_LOGE("Wrong vertex buffer count in the header");
+			return Error::USER_DATA;
+		}
+	}
+
+	// Check the file size
+	{
+		U32 totalSize = sizeof(m_header);
+
+		totalSize += sizeof(MeshBinarySubMesh) * m_header.m_subMeshCount;
+		totalSize += U32(getIndexBufferSize());
+
+		for(U i = 0; i < m_header.m_vertexBufferCount; ++i)
+		{
+			totalSize +=
+				getAlignedRoundUp(16, m_header.m_vertexBuffers[i].m_vertexStride * m_header.m_totalVertexCount);
+		}
+
+		if(totalSize != m_file->getSize())
+		{
+			ANKI_RESOURCE_LOGE("Unexpected file size");
+			return Error::USER_DATA;
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error MeshBinaryLoader::checkFormat(VertexAttributeLocation type, ConstWeakArray<Format> supportedFormats,
+									U32 vertexBufferIdx, U32 relativeOffset) const
+{
+	const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[type];
+
+	// Check format
+	Bool found = false;
+	for(Format fmt : supportedFormats)
+	{
+		if(fmt == attrib.m_format)
+		{
+			found = true;
+			break;
+		}
+	}
+
+	if(!found)
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u has unsupported format %u", U32(type),
+						   U32(m_header.m_vertexAttributes[type].m_format));
+		return Error::USER_DATA;
+	}
+
+	if(!attrib.m_format)
+	{
+		// Attrib is not in use, no more checks
+		return Error::NONE;
+	}
+
+	if(attrib.m_bufferBinding != vertexBufferIdx)
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should belong to the %u vertex buffer", U32(type), vertexBufferIdx);
+		return Error::USER_DATA;
+	}
+
+	if(attrib.m_relativeOffset != relativeOffset)
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have relative vertex offset equal to %u", U32(type),
+						   relativeOffset);
+		return Error::USER_DATA;
+	}
+
+	// Scale should be 1.0 for now
+	if(attrib.m_scale != 1.0f)
+	{
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", U32(type));
+		return Error::USER_DATA;
+	}
+
+	return Error::NONE;
+}
+
+Error MeshBinaryLoader::checkHeader() const
+{
+	const MeshBinaryHeader& h = m_header;
+
+	// Header
+	if(memcmp(&h.m_magic[0], MESH_MAGIC, 8) != 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong magic word");
+		return Error::USER_DATA;
+	}
+
+	// Flags
+	if((h.m_flags & ~MeshBinaryFlag::ALL) != MeshBinaryFlag::NONE)
+	{
+		ANKI_RESOURCE_LOGE("Wrong header flags");
+		return Error::USER_DATA;
+	}
+
+	// Attributes
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::POSITION, Array<Format, 1>{{Format::R32G32B32_SFLOAT}}, 0, 0));
+	ANKI_CHECK(
+		checkFormat(VertexAttributeLocation::NORMAL, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}, 1, 0));
+	ANKI_CHECK(
+		checkFormat(VertexAttributeLocation::TANGENT, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}, 1, 4));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::UV, Array<Format, 1>{{Format::R32G32_SFLOAT}}, 1, 8));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::UV2, Array<Format, 1>{{Format::NONE}}, 1, 0));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::BONE_INDICES,
+						   Array<Format, 2>{{Format::NONE, Format::R16G16B16A16_UINT}}, 2, 0));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::BONE_WEIGHTS,
+						   Array<Format, 2>{{Format::NONE, Format::R8G8B8A8_UNORM}}, 2, 8));
+
+	// Vertex buffers
+	if(m_header.m_vertexBufferCount != 2 + hasBoneInfo())
+	{
+		ANKI_RESOURCE_LOGE("Wrong number of vertex buffers");
+		return Error::USER_DATA;
+	}
+
+	if(m_header.m_vertexBuffers[0].m_vertexStride != sizeof(Vec3) || m_header.m_vertexBuffers[1].m_vertexStride != 16
+	   || (hasBoneInfo() && m_header.m_vertexBuffers[2].m_vertexStride != 12))
+	{
+		ANKI_RESOURCE_LOGE("Some of the vertex buffers has an incorrect vertex stride");
+		return Error::USER_DATA;
+	}
+
+	// Indices format
+	if(h.m_indexType != IndexType::U16)
+	{
+		ANKI_RESOURCE_LOGE("Wrong format for indices");
+		return Error::USER_DATA;
+	}
+
+	// m_totalIndexCount
+	const U indicesPerFace = !!(h.m_flags & MeshBinaryFlag::QUAD) ? 4 : 3;
+	if(h.m_totalIndexCount == 0 || (h.m_totalIndexCount % indicesPerFace) != 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong index count");
+		return Error::USER_DATA;
+	}
+
+	// m_totalVertexCount
+	if(h.m_totalVertexCount == 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong vertex count");
+		return Error::USER_DATA;
+	}
+
+	// m_subMeshCount
+	if(h.m_subMeshCount == 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong submesh count");
+		return Error::USER_DATA;
+	}
+
+	// AABB
+	for(U d = 0; d < 3; ++d)
+	{
+		if(h.m_aabbMin[d] >= h.m_aabbMax[d])
+		{
+			ANKI_RESOURCE_LOGE("Wrong bounding box");
+			return Error::USER_DATA;
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error MeshBinaryLoader::storeIndexBuffer(void* ptr, PtrSize size)
+{
+	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(size == getIndexBufferSize());
+	ANKI_ASSERT(m_loadedChunk == 0);
+
+	if(ptr)
+	{
+		ANKI_CHECK(m_file->read(ptr, size));
+	}
+	else
+	{
+		ANKI_CHECK(m_file->seek(size, FileSeekOrigin::CURRENT));
+	}
+
+	++m_loadedChunk;
+	return Error::NONE;
+}
+
+Error MeshBinaryLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size)
+{
+	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
+	ANKI_ASSERT(size == m_header.m_vertexBuffers[bufferIdx].m_vertexStride * m_header.m_totalVertexCount);
+	ANKI_ASSERT(m_loadedChunk == bufferIdx + 1);
+
+	if(ptr)
+	{
+		ANKI_CHECK(m_file->read(ptr, size));
+	}
+	else
+	{
+		ANKI_CHECK(m_file->seek(size, FileSeekOrigin::CURRENT));
+	}
+
+	++m_loadedChunk;
+	return Error::NONE;
+}
+
+Error MeshBinaryLoader::storeIndicesAndPosition(DynamicArrayAuto<U32>& indices, DynamicArrayAuto<Vec3>& positions)
+{
+	// Store indices
+	{
+		indices.resize(m_header.m_totalIndexCount);
+
+		// Create staging buff
+		const PtrSize idxBufferSize = getIndexBufferSize();
+		DynamicArrayAuto<U8, PtrSize> staging(m_alloc);
+		staging.create(idxBufferSize);
+
+		// Store to staging buff
+		ANKI_CHECK(storeIndexBuffer(&staging[0], staging.getSizeInBytes()));
+
+		// Copy
+		for(U32 i = 0; i < m_header.m_totalIndexCount; ++i)
+		{
+			if(m_header.m_indexType == IndexType::U32)
+			{
+				indices[i] = *reinterpret_cast<U32*>(&staging[i * 4]);
+			}
+			else
+			{
+				indices[i] = *reinterpret_cast<U16*>(&staging[i * 2]);
+			}
+		}
+	}
+
+	// Store positions
+	{
+		positions.resize(m_header.m_totalVertexCount);
+
+		const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[VertexAttributeLocation::POSITION];
+		const MeshBinaryVertexBuffer& buffInfo = m_header.m_vertexBuffers[attrib.m_bufferBinding];
+
+		// Create staging buff
+		const PtrSize vertBuffSize = m_header.m_totalVertexCount * buffInfo.m_vertexStride;
+		DynamicArrayAuto<U8, PtrSize> staging(m_alloc);
+		staging.create(vertBuffSize);
+
+		// Store to staging buff
+		ANKI_CHECK(storeVertexBuffer(attrib.m_bufferBinding, &staging[0], staging.getSizeInBytes()));
+
+		// Copy
+		for(U32 i = 0; i < m_header.m_totalVertexCount; ++i)
+		{
+			Vec3 vert(0.0f);
+			if(attrib.m_format == Format::R32G32B32_SFLOAT)
+			{
+				vert = *reinterpret_cast<Vec3*>(&staging[i * buffInfo.m_vertexStride + attrib.m_relativeOffset]);
+			}
+			else if(attrib.m_format == Format::R16G16B16A16_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;
+		}
+	}
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 82 - 0
anki/resource/MeshBinaryLoader.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/resource/MeshBinary.h>
+#include <anki/resource/ResourceFilesystem.h>
+#include <anki/util/WeakArray.h>
+
+namespace anki
+{
+
+/// @addtogroup resource
+/// @{
+
+/// This class loads the mesh binary file. It only supports a subset of combinations of vertex formats and buffers.
+class MeshBinaryLoader
+{
+public:
+	MeshBinaryLoader(ResourceManager* manager);
+
+	MeshBinaryLoader(ResourceManager* manager, GenericMemoryPoolAllocator<U8> alloc)
+		: m_manager(manager)
+		, m_alloc(alloc)
+	{
+	}
+
+	~MeshBinaryLoader();
+
+	ANKI_USE_RESULT Error load(const ResourceFilename& filename);
+
+	ANKI_USE_RESULT Error storeIndexBuffer(void* ptr, PtrSize size);
+
+	ANKI_USE_RESULT Error storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size);
+
+	/// Instead of calling storeIndexBuffer and storeVertexBuffer use this method to get those buffers into the CPU.
+	ANKI_USE_RESULT Error storeIndicesAndPosition(DynamicArrayAuto<U32>& indices, DynamicArrayAuto<Vec3>& positions);
+
+	const MeshBinaryHeader& getHeader() const
+	{
+		ANKI_ASSERT(isLoaded());
+		return m_header;
+	}
+
+	Bool hasBoneInfo() const
+	{
+		ANKI_ASSERT(isLoaded());
+		return m_header.m_vertexAttributes[VertexAttributeLocation::BONE_INDICES].m_format != Format::NONE;
+	}
+
+private:
+	ResourceManager* m_manager;
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	ResourceFilePtr m_file;
+
+	MeshBinaryHeader m_header;
+
+	DynamicArray<MeshBinarySubMesh> m_subMeshes;
+
+	U32 m_loadedChunk = 0; ///< Because the store methods need to be called in sequence.
+
+	Bool isLoaded() const
+	{
+		return m_file.get() != nullptr;
+	}
+
+	PtrSize getIndexBufferSize() const
+	{
+		ANKI_ASSERT(isLoaded());
+		return getAlignedRoundUp(16, PtrSize(m_header.m_totalIndexCount)
+										 * ((m_header.m_indexType == IndexType::U16) ? 2 : 4));
+	}
+
+	ANKI_USE_RESULT Error checkHeader() const;
+	ANKI_USE_RESULT Error checkFormat(VertexAttributeLocation type, ConstWeakArray<Format> supportedFormats,
+									  U32 vertexBufferIdx, U32 relativeOffset) const;
+};
+/// @}
+
+} // end namespace anki

+ 22 - 4
anki/util/serializer.py

@@ -54,8 +54,11 @@ def parse_commandline():
 
 
     parser = optparse.OptionParser(usage="usage: %prog [options]", description="Create serialization code using XML")
     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("-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")
     parser.add_option("-o", "--output", dest="out", type="string", help="specify the .h file to populate")
 
 
@@ -160,8 +163,8 @@ def gen_class(root_el):
         if member.is_pointer(member_arr_copy):
         if member.is_pointer(member_arr_copy):
             writeln("s.doPointer(\"%s\", offsetof(%s, %s), self.%s);" % (member.name, name, member.name, member.name))
             writeln("s.doPointer(\"%s\", offsetof(%s, %s), self.%s);" % (member.name, name, member.name, member.name))
         elif member.is_dynamic_array(member_arr_copy):
         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))
+            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":
         elif member.array_size != "1":
             writeln("s.doArray(\"%s\", offsetof(%s, %s), &self.%s[0], self.%s.getSize());" %
             writeln("s.doArray(\"%s\", offsetof(%s, %s), &self.%s[0], self.%s.getSize());" %
                     (member.name, name, member.name, member.name, member.name))
                     (member.name, name, member.name, member.name, member.name))
@@ -213,10 +216,25 @@ def gen_file(filename):
     writeln("{")
     writeln("{")
     writeln("")
     writeln("")
 
 
+    doxygen_group_el = root.find("doxygen_group")
+    if doxygen_group_el is not None:
+        doxygen_group = doxygen_group_el.get("name")
+        writeln("/// @addtogroup %s" % doxygen_group)
+        writeln("/// @{")
+        writeln("")
+
+    prefix_code = root.find("prefix_code")
+    if prefix_code is not None:
+        writeln("%s" % prefix_code.text)
+
     for cls in root.iter("classes"):
     for cls in root.iter("classes"):
         for cl in cls.iter("class"):
         for cl in cls.iter("class"):
             gen_class(cl)
             gen_class(cl)
 
 
+    if doxygen_group_el is not None:
+        writeln("/// @}")
+        writeln("")
+
     writeln("} // end namespace anki")
     writeln("} // end namespace anki")