Panagiotis Christopoulos Charitos 3 лет назад
Родитель
Сommit
5b013dea70

+ 1 - 1
AnKi/Resource/CpuMeshResource.cpp

@@ -30,7 +30,7 @@ Error CpuMeshResource::load(const ResourceFilename& filename, [[maybe_unused]] B
 	DynamicArrayRaii<Vec3> tempPositions(&getMemoryPool());
 	DynamicArrayRaii<U32> tempIndices(&getMemoryPool());
 
-	ANKI_CHECK(loader.storeIndicesAndPosition(tempIndices, tempPositions));
+	ANKI_CHECK(loader.storeIndicesAndPosition(0, tempIndices, tempPositions));
 
 	m_indices = std::move(tempIndices);
 	m_positions = std::move(tempPositions);

+ 23 - 20
AnKi/Resource/MeshBinary.h

@@ -9,13 +9,14 @@
 
 #include <AnKi/Resource/Common.h>
 #include <AnKi/Math.h>
+#include <AnKi/Gr/Common.h>
 
 namespace anki {
 
 /// @addtogroup resource
 /// @{
 
-inline constexpr const char* kMeshMagic = "ANKIMES5";
+inline constexpr const char* kMeshMagic = "ANKIMES6";
 
 constexpr U32 kMeshBinaryBufferAlignment = 16;
 
@@ -29,12 +30,11 @@ enum class MeshBinaryFlag : U32
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
 
-/// Vertex buffer info. The size of the buffer is m_vertexStride*MeshBinaryHeader::m_totalVertexCount aligned to
-/// kMeshBinaryBufferAlignment.
+/// Vertex buffer info.
 class MeshBinaryVertexBuffer
 {
 public:
-	/// The size of the vertex.
+	/// The size of the vertex. It's zero if the buffer is not present.
 	U32 m_vertexStride;
 
 	template<typename TSerializer, typename TClass>
@@ -60,7 +60,7 @@ public:
 class MeshBinaryVertexAttribute
 {
 public:
-	U32 m_bufferBinding;
+	U32 m_bufferIndex;
 
 	/// If the format is kNone then the attribute is not present.
 	Format m_format;
@@ -71,7 +71,7 @@ public:
 	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_bufferIndex", offsetof(MeshBinaryVertexAttribute, m_bufferIndex), self.m_bufferIndex);
 		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);
@@ -94,8 +94,8 @@ public:
 class MeshBinarySubMesh
 {
 public:
-	U32 m_firstIndex;
-	U32 m_indexCount;
+	Array<U32, kMaxLodCount> m_firstIndices;
+	Array<U32, kMaxLodCount> m_indexCounts;
 
 	/// Bounding box min.
 	Vec3 m_aabbMin;
@@ -106,8 +106,10 @@ public:
 	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.doArray("m_firstIndices", offsetof(MeshBinarySubMesh, m_firstIndices), &self.m_firstIndices[0],
+				  self.m_firstIndices.getSize());
+		s.doArray("m_indexCounts", offsetof(MeshBinarySubMesh, m_indexCounts), &self.m_indexCounts[0],
+				  self.m_indexCounts.getSize());
 		s.doValue("m_aabbMin", offsetof(MeshBinarySubMesh, m_aabbMin), self.m_aabbMin);
 		s.doValue("m_aabbMax", offsetof(MeshBinarySubMesh, m_aabbMax), self.m_aabbMax);
 	}
@@ -125,21 +127,20 @@ public:
 	}
 };
 
-/// The 1st things that appears in a mesh binary. @note The index and vertex buffers are aligned to
-/// kMeshBinaryBufferAlignment bytes.
+/// The 1st things that appears in a mesh binary.
 class MeshBinaryHeader
 {
 public:
 	Array<U8, 8> m_magic;
 	MeshBinaryFlag m_flags;
-	Array<MeshBinaryVertexBuffer, U32(VertexAttributeId::kCount)> m_vertexBuffers;
-	U32 m_vertexBufferCount;
-	Array<MeshBinaryVertexAttribute, U32(VertexAttributeId::kCount)> m_vertexAttributes;
+	Array<MeshBinaryVertexBuffer, kMaxVertexAttributes> m_vertexBuffers;
+	Array<MeshBinaryVertexAttribute, kMaxVertexAttributes> m_vertexAttributes;
 	IndexType m_indexType;
 	Array<U8, 3> m_padding;
-	U32 m_totalIndexCount;
-	U32 m_totalVertexCount;
+	Array<U32, kMaxLodCount> m_totalIndexCounts;
+	Array<U32, kMaxLodCount> m_totalVertexCounts;
 	U32 m_subMeshCount;
+	U32 m_lodCount;
 
 	/// Bounding box min.
 	Vec3 m_aabbMin;
@@ -154,14 +155,16 @@ public:
 		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.doArray("m_totalIndexCounts", offsetof(MeshBinaryHeader, m_totalIndexCounts), &self.m_totalIndexCounts[0],
+				  self.m_totalIndexCounts.getSize());
+		s.doArray("m_totalVertexCounts", offsetof(MeshBinaryHeader, m_totalVertexCounts), &self.m_totalVertexCounts[0],
+				  self.m_totalVertexCounts.getSize());
 		s.doValue("m_subMeshCount", offsetof(MeshBinaryHeader, m_subMeshCount), self.m_subMeshCount);
+		s.doValue("m_lodCount", offsetof(MeshBinaryHeader, m_lodCount), self.m_lodCount);
 		s.doValue("m_aabbMin", offsetof(MeshBinaryHeader, m_aabbMin), self.m_aabbMin);
 		s.doValue("m_aabbMax", offsetof(MeshBinaryHeader, m_aabbMax), self.m_aabbMax);
 	}

+ 13 - 12
AnKi/Resource/MeshBinary.xml

@@ -2,12 +2,13 @@
 	<includes>
 		<include file="&lt;AnKi/Resource/Common.h&gt;"/>
 		<include file="&lt;AnKi/Math.h&gt;"/>
+		<include file="&lt;AnKi/Gr/Common.h&gt;"/>
 	</includes>
 
 	<doxygen_group name="resource"/>
 
 	<prefix_code><![CDATA[
-inline constexpr const char* kMeshMagic = "ANKIMES5";
+inline constexpr const char* kMeshMagic = "ANKIMES6";
 
 constexpr U32 kMeshBinaryBufferAlignment = 16;
 
@@ -23,15 +24,15 @@ 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 kMeshBinaryBufferAlignment">
+		<class name="MeshBinaryVertexBuffer" comment="Vertex buffer info">
 			<members>
-				<member name="m_vertexStride" type="U32" comment="The size of the vertex"/>
+				<member name="m_vertexStride" type="U32" comment="The size of the vertex. It's zero if the buffer is not present"/>
 			</members>
 		</class>
 
 		<class name="MeshBinaryVertexAttribute" comment="Vertex attribute">
 			<members>
-				<member name="m_bufferBinding" type="U32"/>
+				<member name="m_bufferIndex" type="U32"/>
 				<member name="m_format" type="Format" comment="If the format is kNone then the attribute is not present"/>
 				<member name="m_relativeOffset" type="U32"/>
 				<member name="m_scale" type="F32"/>
@@ -40,25 +41,25 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MeshBinaryFlag)
 
 		<class name="MeshBinarySubMesh">
 			<members>
-				<member name="m_firstIndex" type="U32"/>
-				<member name="m_indexCount" type="U32"/>
+				<member name="m_firstIndices" type="U32" array_size="kMaxLodCount"/>
+				<member name="m_indexCounts" type="U32" array_size="kMaxLodCount"/>
 				<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 kMeshBinaryBufferAlignment bytes">
+		<class name="MeshBinaryHeader" comment="The 1st things that appears in a mesh binary">
 			<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(VertexAttributeId::kCount)"/>
-				<member name="m_vertexBufferCount" type="U32"/>
-				<member name="m_vertexAttributes" type="MeshBinaryVertexAttribute" array_size="U32(VertexAttributeId::kCount)"/>
+				<member name="m_vertexBuffers" type="MeshBinaryVertexBuffer" array_size="kMaxVertexAttributes"/>
+				<member name="m_vertexAttributes" type="MeshBinaryVertexAttribute" array_size="kMaxVertexAttributes"/>
 				<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_totalIndexCounts" type="U32" array_size="kMaxLodCount"/>
+				<member name="m_totalVertexCounts" type="U32" array_size="kMaxLodCount"/>
 				<member name="m_subMeshCount" type="U32"/>
+				<member name="m_lodCount" type="U32"/>
 				<member name="m_aabbMin" type="Vec3" comment="Bounding box min"/>
 				<member name="m_aabbMax" type="Vec3" comment="Bounding box max"/>
 			</members>

+ 119 - 80
AnKi/Resource/MeshBinaryLoader.cpp

@@ -20,25 +20,30 @@ MeshBinaryLoader::~MeshBinaryLoader()
 
 Error MeshBinaryLoader::load(const ResourceFilename& filename)
 {
-	BaseMemoryPool& pool = *m_pool;
-
-	// Load header
+	// Load header + submeshes
 	ANKI_CHECK(m_manager->getFilesystem().openFile(filename, m_file));
 	ANKI_CHECK(m_file->read(&m_header, sizeof(m_header)));
 	ANKI_CHECK(checkHeader());
+	ANKI_CHECK(loadSubmeshes());
 
-	// Read submesh info
-	{
-		m_subMeshes.create(pool, m_header.m_subMeshCount);
-		ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
+	return Error::kNone;
+}
+
+Error MeshBinaryLoader::loadSubmeshes()
+{
+	m_subMeshes.create(*m_pool, m_header.m_subMeshCount);
+	ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
 
-		// Checks
-		const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+	// Checks
+	const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+
+	for(U32 lod = 0; lod < m_header.m_lodCount; ++lod)
+	{
 		U idxSum = 0;
 		for(U32 i = 0; i < m_subMeshes.getSize(); i++)
 		{
 			const MeshBinarySubMesh& sm = m_subMeshes[i];
-			if(sm.m_firstIndex != idxSum || (sm.m_indexCount % indicesPerFace) != 0)
+			if(sm.m_firstIndices[lod] != idxSum || (sm.m_indexCounts[lod] % indicesPerFace) != 0)
 			{
 				ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
 				return Error::kUserData;
@@ -48,17 +53,17 @@ Error MeshBinaryLoader::load(const ResourceFilename& filename)
 			{
 				if(sm.m_aabbMin[d] >= sm.m_aabbMax[d])
 				{
-					ANKI_RESOURCE_LOGE("Wrong bounding box");
+					ANKI_RESOURCE_LOGE("Wrong submesh bounding box");
 					return Error::kUserData;
 				}
 			}
 
-			idxSum += sm.m_indexCount;
+			idxSum += sm.m_indexCounts[lod];
 		}
 
-		if(idxSum != m_header.m_totalIndexCount)
+		if(idxSum != m_header.m_totalIndexCounts[lod])
 		{
-			ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+			ANKI_RESOURCE_LOGE("Submesh index count doesn't add up to the total");
 			return Error::kUserData;
 		}
 	}
@@ -66,52 +71,51 @@ Error MeshBinaryLoader::load(const ResourceFilename& filename)
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::checkFormat(VertexAttributeId type, ConstWeakArray<Format> supportedFormats,
-									U32 vertexBufferIdx, U32 relativeOffset) const
+Error MeshBinaryLoader::checkFormat(VertexStreamId stream, Bool isOptional) const
 {
-	const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[type];
+	const U32 vertexAttribIdx = U32(stream);
+	const U32 vertexBufferIdx = U32(stream);
+	const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[vertexAttribIdx];
 
 	// Check format
-	Bool found = false;
-	for(Format fmt : supportedFormats)
+	if(isOptional && attrib.m_format == Format::kNone)
 	{
-		if(fmt == attrib.m_format)
-		{
-			found = true;
-			break;
-		}
+		// Attrib is not in use, no more checks
+		return Error::kNone;
 	}
 
-	if(!found)
+	if(attrib.m_format != kMeshRelatedVertexStreamFormats[stream])
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u has unsupported format %u", U32(type),
-						   U32(m_header.m_vertexAttributes[type].m_format));
+		ANKI_RESOURCE_LOGE("Vertex attribute %u has unsupported format %s", vertexAttribIdx,
+						   getFormatInfo(attrib.m_format).m_name);
 		return Error::kUserData;
 	}
 
-	if(!attrib.m_format)
+	if(attrib.m_bufferIndex != vertexBufferIdx)
 	{
-		// Attrib is not in use, no more checks
-		return Error::kNone;
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should belong to the %u vertex buffer", vertexAttribIdx,
+						   vertexBufferIdx);
+		return Error::kUserData;
 	}
 
-	if(attrib.m_bufferBinding != vertexBufferIdx)
+	if(attrib.m_relativeOffset != 0)
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should belong to the %u vertex buffer", U32(type), vertexBufferIdx);
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have relative vertex offset equal to 0", vertexAttribIdx);
 		return Error::kUserData;
 	}
 
-	if(attrib.m_relativeOffset != relativeOffset)
+	// Scale should be 1.0 for now
+	if(attrib.m_scale != 1.0f)
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should have relative vertex offset equal to %u", U32(type),
-						   relativeOffset);
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", vertexAttribIdx);
 		return Error::kUserData;
 	}
 
-	// Scale should be 1.0 for now
-	if(attrib.m_scale != 1.0f)
+	const U32 vertexBufferStride = getFormatInfo(attrib.m_format).m_texelSize;
+	if(m_header.m_vertexBuffers[vertexBufferIdx].m_vertexStride != vertexBufferStride)
 	{
-		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", U32(type));
+		ANKI_RESOURCE_LOGE("Vertex buffer %u doesn't have the expected stride of %u", vertexBufferIdx,
+						   vertexBufferStride);
 		return Error::kUserData;
 	}
 
@@ -137,27 +141,27 @@ Error MeshBinaryLoader::checkHeader() const
 	}
 
 	// Attributes
-	ANKI_CHECK(checkFormat(VertexAttributeId::kPosition, Array<Format, 1>{{Format::kR32G32B32_Sfloat}}, 0, 0));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kNormal, Array<Format, 1>{{Format::kA2B10G10R10_Snorm_Pack32}}, 1, 0));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kTangent, Array<Format, 1>{{Format::kA2B10G10R10_Snorm_Pack32}}, 1, 4));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kUv0, Array<Format, 1>{{Format::kR32G32_Sfloat}}, 1, 8));
-	ANKI_CHECK(checkFormat(VertexAttributeId::kUv1, Array<Format, 1>{{Format::kNone}}, 1, 0));
-	ANKI_CHECK(
-		checkFormat(VertexAttributeId::kBoneIndices, Array<Format, 2>{{Format::kNone, Format::kR8G8B8A8_Uint}}, 2, 0));
-	ANKI_CHECK(
-		checkFormat(VertexAttributeId::kBoneWeights, Array<Format, 2>{{Format::kNone, Format::kR8G8B8A8_Unorm}}, 2, 4));
+	ANKI_CHECK(checkFormat(VertexStreamId::kPosition, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kNormal, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kTangent, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kUv, false));
+	ANKI_CHECK(checkFormat(VertexStreamId::kBoneIds, true));
+	ANKI_CHECK(checkFormat(VertexStreamId::kBoneWeights, true));
 
 	// Vertex buffers
-	if(m_header.m_vertexBufferCount != 2 + U32(hasBoneInfo()))
+	const Format boneIdxFormat = m_header.m_vertexAttributes[VertexStreamId::kBoneIds].m_format;
+	const Format boneWeightsFormat = m_header.m_vertexAttributes[VertexStreamId::kBoneWeights].m_format;
+	if((boneIdxFormat == Format::kNone && boneWeightsFormat != Format::kNone)
+	   || (boneWeightsFormat == Format::kNone && boneIdxFormat != Format::kNone))
 	{
-		ANKI_RESOURCE_LOGE("Wrong number of vertex buffers");
+		ANKI_RESOURCE_LOGE("Bone buffers are partially present");
 		return Error::kUserData;
 	}
 
-	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 != 8))
+	// LOD
+	if(h.m_lodCount == 0 || h.m_lodCount >= kMaxLodCount)
 	{
-		ANKI_RESOURCE_LOGE("Some of the vertex buffers have incorrect vertex stride");
+		ANKI_RESOURCE_LOGE("Wrong LOD count");
 		return Error::kUserData;
 	}
 
@@ -169,18 +173,24 @@ Error MeshBinaryLoader::checkHeader() const
 	}
 
 	// m_totalIndexCount
-	const U indicesPerFace = !!(h.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
-	if(h.m_totalIndexCount == 0 || (h.m_totalIndexCount % indicesPerFace) != 0)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		ANKI_RESOURCE_LOGE("Wrong index count");
-		return Error::kUserData;
+		const U indicesPerFace = !!(h.m_flags & MeshBinaryFlag::kQuad) ? 4 : 3;
+		if(h.m_totalIndexCounts[lod] == 0 || (h.m_totalIndexCounts[lod] % indicesPerFace) != 0)
+		{
+			ANKI_RESOURCE_LOGE("Wrong index count");
+			return Error::kUserData;
+		}
 	}
 
 	// m_totalVertexCount
-	if(h.m_totalVertexCount == 0)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		ANKI_RESOURCE_LOGE("Wrong vertex count");
-		return Error::kUserData;
+		if(h.m_totalVertexCounts[lod] == 0)
+		{
+			ANKI_RESOURCE_LOGE("Wrong vertex count");
+			return Error::kUserData;
+		}
 	}
 
 	// m_subMeshCount
@@ -202,13 +212,11 @@ Error MeshBinaryLoader::checkHeader() const
 
 	// Check the file size
 	PtrSize totalSize = sizeof(m_header);
-
 	totalSize += sizeof(MeshBinarySubMesh) * m_header.m_subMeshCount;
-	totalSize += getAlignedIndexBufferSize();
 
-	for(U32 i = 0; i < m_header.m_vertexBufferCount; ++i)
+	for(U32 lod = 0; lod < h.m_lodCount; ++lod)
 	{
-		totalSize += getAlignedVertexBufferSize(i);
+		totalSize += getLodBuffersSize(lod);
 	}
 
 	if(totalSize != m_file->getSize())
@@ -220,30 +228,44 @@ Error MeshBinaryLoader::checkHeader() const
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeIndexBuffer(void* ptr, PtrSize size)
+Error MeshBinaryLoader::storeIndexBuffer(U32 lod, void* ptr, PtrSize size)
 {
 	ANKI_ASSERT(ptr);
 	ANKI_ASSERT(isLoaded());
-	ANKI_ASSERT(size == getIndexBufferSize());
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+	ANKI_ASSERT(size == getIndexBufferSize(lod));
+
+	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
+	for(U32 l = lod + 1; l < m_header.m_lodCount; ++l)
+	{
+		seek += getLodBuffersSize(l);
+	}
 
-	const PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
 	ANKI_CHECK(m_file->seek(seek, FileSeekOrigin::kBeginning));
 	ANKI_CHECK(m_file->read(ptr, size));
 
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size)
+Error MeshBinaryLoader::storeVertexBuffer(U32 lod, U32 bufferIdx, void* ptr, PtrSize size)
 {
 	ANKI_ASSERT(ptr);
 	ANKI_ASSERT(isLoaded());
-	ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-	ANKI_ASSERT(size == getVertexBufferSize(bufferIdx));
+	ANKI_ASSERT(size == getVertexBufferSize(lod, bufferIdx));
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+
+	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes();
+
+	for(U32 l = lod + 1; l < m_header.m_lodCount; ++l)
+	{
+		seek += getLodBuffersSize(l);
+	}
+
+	seek += getIndexBufferSize(lod);
 
-	PtrSize seek = sizeof(m_header) + m_subMeshes.getSizeInBytes() + getAlignedIndexBufferSize();
 	for(U32 i = 0; i < bufferIdx; ++i)
 	{
-		seek += getAlignedVertexBufferSize(i);
+		seek += getVertexBufferSize(lod, i);
 	}
 
 	ANKI_CHECK(m_file->seek(seek, FileSeekOrigin::kBeginning));
@@ -252,22 +274,24 @@ Error MeshBinaryLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size
 	return Error::kNone;
 }
 
-Error MeshBinaryLoader::storeIndicesAndPosition(DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions)
+Error MeshBinaryLoader::storeIndicesAndPosition(U32 lod, DynamicArrayRaii<U32>& indices,
+												DynamicArrayRaii<Vec3>& positions)
 {
 	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(lod < m_header.m_lodCount);
 
 	// Store indices
 	{
-		indices.resize(m_header.m_totalIndexCount);
+		indices.resize(m_header.m_totalIndexCounts[lod]);
 
 		// Store to staging buff
 		DynamicArrayRaii<U8, PtrSize> staging(m_pool);
-		staging.create(getIndexBufferSize());
-		ANKI_CHECK(storeIndexBuffer(&staging[0], staging.getSizeInBytes()));
+		staging.create(getIndexBufferSize(lod));
+		ANKI_CHECK(storeIndexBuffer(lod, &staging[0], staging.getSizeInBytes()));
 
 		// Copy from staging
 		ANKI_ASSERT(m_header.m_indexType == IndexType::kU16);
-		for(U32 i = 0; i < m_header.m_totalIndexCount; ++i)
+		for(U32 i = 0; i < m_header.m_totalIndexCounts[lod]; ++i)
 		{
 			indices[i] = *reinterpret_cast<U16*>(&staging[PtrSize(i) * 2]);
 		}
@@ -275,13 +299,28 @@ Error MeshBinaryLoader::storeIndicesAndPosition(DynamicArrayRaii<U32>& indices,
 
 	// Store positions
 	{
-		positions.resize(m_header.m_totalVertexCount);
-		const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[VertexAttributeId::kPosition];
-		ANKI_ASSERT(attrib.m_format == Format::kR32G32B32_Sfloat);
-		ANKI_CHECK(storeVertexBuffer(attrib.m_bufferBinding, &positions[0], positions.getSizeInBytes()));
+		positions.resize(m_header.m_totalVertexCounts[lod]);
+		const MeshBinaryVertexAttribute& attrib = m_header.m_vertexAttributes[VertexStreamId::kPosition];
+		ANKI_CHECK(storeVertexBuffer(lod, attrib.m_bufferIndex, &positions[0], positions.getSizeInBytes()));
 	}
 
 	return Error::kNone;
 }
 
+PtrSize MeshBinaryLoader::getLodBuffersSize(U32 lod) const
+{
+	ANKI_ASSERT(lod < m_header.m_lodCount);
+
+	PtrSize size = getIndexBufferSize(lod);
+	for(U32 vertBufferIdx = 0; vertBufferIdx < m_header.m_vertexBuffers.getSize(); ++vertBufferIdx)
+	{
+		if(m_header.m_vertexBuffers[vertBufferIdx].m_vertexStride > 0)
+		{
+			size += getVertexBufferSize(lod, vertBufferIdx);
+		}
+	}
+
+	return size;
+}
+
 } // end namespace anki

+ 19 - 28
AnKi/Resource/MeshBinaryLoader.h

@@ -9,6 +9,7 @@
 #include <AnKi/Resource/ResourceFilesystem.h>
 #include <AnKi/Resource/MeshBinary.h>
 #include <AnKi/Util/WeakArray.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 
 namespace anki {
 
@@ -16,6 +17,12 @@ namespace anki {
 /// @{
 
 /// This class loads the mesh binary file. It only supports a subset of combinations of vertex formats and buffers.
+/// The file is layed out in memory:
+/// * Header
+/// * Submeshes
+/// * Index buffer of max LOD
+/// * Vertex buffer #0 of max LOD
+/// * etc...
 class MeshBinaryLoader
 {
 public:
@@ -32,12 +39,12 @@ public:
 
 	Error load(const ResourceFilename& filename);
 
-	Error storeIndexBuffer(void* ptr, PtrSize size);
+	Error storeIndexBuffer(U32 lod, void* ptr, PtrSize size);
 
-	Error storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size);
+	Error storeVertexBuffer(U32 lod, U32 bufferIdx, void* ptr, PtrSize size);
 
 	/// Instead of calling storeIndexBuffer and storeVertexBuffer use this method to get those buffers into the CPU.
-	Error storeIndicesAndPosition(DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions);
+	Error storeIndicesAndPosition(U32 lod, DynamicArrayRaii<U32>& indices, DynamicArrayRaii<Vec3>& positions);
 
 	const MeshBinaryHeader& getHeader() const
 	{
@@ -45,12 +52,6 @@ public:
 		return m_header;
 	}
 
-	Bool hasBoneInfo() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return m_header.m_vertexAttributes[VertexAttributeId::kBoneIndices].m_format != Format::kNone;
-	}
-
 	ConstWeakArray<MeshBinarySubMesh> getSubMeshes() const
 	{
 		return ConstWeakArray<MeshBinarySubMesh>(m_subMeshes);
@@ -70,35 +71,25 @@ private:
 		return m_file.get() != nullptr;
 	}
 
-	PtrSize getIndexBufferSize() const
+	PtrSize getIndexBufferSize(U32 lod) const
 	{
 		ANKI_ASSERT(isLoaded());
-		return PtrSize(m_header.m_totalIndexCount) * ((m_header.m_indexType == IndexType::kU16) ? 2 : 4);
+		ANKI_ASSERT(lod < m_header.m_lodCount);
+		return PtrSize(m_header.m_totalIndexCounts[lod]) * getIndexSize(m_header.m_indexType);
 	}
 
-	PtrSize getAlignedIndexBufferSize() const
+	PtrSize getVertexBufferSize(U32 lod, U32 bufferIdx) const
 	{
 		ANKI_ASSERT(isLoaded());
-		return getAlignedRoundUp(kMeshBinaryBufferAlignment, getIndexBufferSize());
+		ANKI_ASSERT(lod < m_header.m_lodCount);
+		return PtrSize(m_header.m_totalVertexCounts[lod]) * PtrSize(m_header.m_vertexBuffers[bufferIdx].m_vertexStride);
 	}
 
-	PtrSize getVertexBufferSize(U32 bufferIdx) const
-	{
-		ANKI_ASSERT(isLoaded());
-		ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-		return PtrSize(m_header.m_totalVertexCount) * PtrSize(m_header.m_vertexBuffers[bufferIdx].m_vertexStride);
-	}
-
-	PtrSize getAlignedVertexBufferSize(U32 bufferIdx) const
-	{
-		ANKI_ASSERT(isLoaded());
-		ANKI_ASSERT(bufferIdx < m_header.m_vertexBufferCount);
-		return getAlignedRoundUp(kMeshBinaryBufferAlignment, getVertexBufferSize(bufferIdx));
-	}
+	PtrSize getLodBuffersSize(U32 lod) const;
 
 	Error checkHeader() const;
-	Error checkFormat(VertexAttributeId type, ConstWeakArray<Format> supportedFormats, U32 vertexBufferIdx,
-					  U32 relativeOffset) const;
+	Error checkFormat(VertexStreamId stream, Bool isOptional) const;
+	Error loadSubmeshes();
 };
 /// @}
 

+ 180 - 186
AnKi/Resource/MeshResource.cpp

@@ -19,7 +19,7 @@ public:
 	MeshResourcePtr m_mesh;
 	MeshBinaryLoader m_loader;
 
-	LoadContext(const MeshResourcePtr& mesh, BaseMemoryPool* pool)
+	LoadContext(MeshResource* mesh, BaseMemoryPool* pool)
 		: m_mesh(mesh)
 		, m_loader(&mesh->getManager(), pool)
 	{
@@ -32,7 +32,7 @@ class MeshResource::LoadTask : public AsyncLoaderTask
 public:
 	MeshResource::LoadContext m_ctx;
 
-	LoadTask(const MeshResourcePtr& mesh)
+	LoadTask(MeshResource* mesh)
 		: m_ctx(mesh, &mesh->getManager().getAsyncLoader().getMemoryPool())
 	{
 	}
@@ -51,46 +51,52 @@ public:
 MeshResource::MeshResource(ResourceManager* manager)
 	: ResourceObject(manager)
 {
-	memset(&m_meshGpuDescriptor, 0, sizeof(m_meshGpuDescriptor));
 }
 
 MeshResource::~MeshResource()
 {
 	m_subMeshes.destroy(getMemoryPool());
-	m_vertexBufferInfos.destroy(getMemoryPool());
 
-	if(m_vertexBuffersOffset != kMaxPtrSize)
+	for(Lod& lod : m_lods)
 	{
-		getManager().getUnifiedGeometryMemoryPool().free(m_vertexBuffersSize, 4, m_vertexBuffersOffset);
-	}
+		if(lod.m_unifiedGeometryIndexBufferOffset != kMaxPtrSize)
+		{
+			const U32 alignment = getIndexSize(m_indexType);
+			const PtrSize size = lod.m_indexCount * PtrSize(alignment);
+			getManager().getUnifiedGeometryMemoryPool().free(size, alignment, lod.m_unifiedGeometryIndexBufferOffset);
+		}
 
-	if(m_indexBufferOffset != kMaxPtrSize)
-	{
-		const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
-		getManager().getUnifiedGeometryMemoryPool().free(indexBufferSize, getIndexSize(m_indexType),
-														 m_indexBufferOffset);
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+		{
+			if(lod.m_unifiedGeometryVertBufferOffsets[stream] != kMaxPtrSize)
+			{
+				const U32 alignment = getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+				const PtrSize size = PtrSize(alignment) * lod.m_vertexCount;
+
+				getManager().getUnifiedGeometryMemoryPool().free(size, alignment,
+																 lod.m_unifiedGeometryVertBufferOffsets[stream]);
+			}
+		}
 	}
-}
 
-Bool MeshResource::isCompatible(const MeshResource& other) const
-{
-	return hasBoneWeights() == other.hasBoneWeights() && getSubMeshCount() == other.getSubMeshCount();
+	m_lods.destroy(getMemoryPool());
 }
 
 Error MeshResource::load(const ResourceFilename& filename, Bool async)
 {
 	UniquePtr<LoadTask> task;
 	LoadContext* ctx;
-	LoadContext localCtx(MeshResourcePtr(this), &getTempMemoryPool());
+	LoadContext localCtx(this, &getTempMemoryPool());
 
 	StringRaii basename(&getTempMemoryPool());
 	getFilepathFilename(filename, basename);
 
 	const Bool rayTracingEnabled = getManager().getGrManager().getDeviceCapabilities().m_rayTracingEnabled;
+	BufferPtr unifiedGeometryBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
 
 	if(async)
 	{
-		task.reset(getManager().getAsyncLoader().newTask<LoadTask>(MeshResourcePtr(this)));
+		task.reset(getManager().getAsyncLoader().newTask<LoadTask>(this));
 		ctx = &task->m_ctx;
 	}
 	else
@@ -104,86 +110,102 @@ Error MeshResource::load(const ResourceFilename& filename, Bool async)
 	ANKI_CHECK(loader.load(filename));
 	const MeshBinaryHeader& header = loader.getHeader();
 
-	//
+	// Misc
+	m_indexType = header.m_indexType;
+	m_aabb.setMin(header.m_aabbMin);
+	m_aabb.setMax(header.m_aabbMax);
+
 	// Submeshes
-	//
 	m_subMeshes.create(getMemoryPool(), header.m_subMeshCount);
 	for(U32 i = 0; i < m_subMeshes.getSize(); ++i)
 	{
-		m_subMeshes[i].m_firstIndex = loader.getSubMeshes()[i].m_firstIndex;
-		m_subMeshes[i].m_indexCount = loader.getSubMeshes()[i].m_indexCount;
+		m_subMeshes[i].m_firstIndices = loader.getSubMeshes()[i].m_firstIndices;
+		m_subMeshes[i].m_indexCounts = loader.getSubMeshes()[i].m_indexCounts;
 		m_subMeshes[i].m_aabb.setMin(loader.getSubMeshes()[i].m_aabbMin);
 		m_subMeshes[i].m_aabb.setMax(loader.getSubMeshes()[i].m_aabbMax);
 	}
 
-	//
-	// Index stuff
-	//
-	m_indexCount = header.m_totalIndexCount;
-	ANKI_ASSERT((m_indexCount % 3) == 0 && "Expecting triangles");
-	m_indexType = header.m_indexType;
-
-	const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
-	ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(indexBufferSize, getIndexSize(m_indexType),
-																	m_indexBufferOffset));
-
-	//
-	// Vertex stuff
-	//
-	m_vertexCount = header.m_totalVertexCount;
-	m_vertexBufferInfos.create(getMemoryPool(), header.m_vertexBufferCount);
-
-	m_vertexBuffersSize = 0;
-	for(U32 i = 0; i < header.m_vertexBufferCount; ++i)
+	// LODs
+	m_lods.create(getMemoryPool(), header.m_lodCount);
+	for(U32 l = header.m_lodCount - 1; l >= 0; --l)
 	{
-		alignRoundUp(kMeshBinaryBufferAlignment, m_vertexBuffersSize);
-
-		m_vertexBufferInfos[i].m_offset = m_vertexBuffersSize;
-		m_vertexBufferInfos[i].m_stride = header.m_vertexBuffers[i].m_vertexStride;
-
-		m_vertexBuffersSize += m_vertexCount * m_vertexBufferInfos[i].m_stride;
-	}
+		Lod& lod = m_lods[l];
+
+		// Index stuff
+		lod.m_indexCount = header.m_totalIndexCounts[l];
+		ANKI_ASSERT((lod.m_indexCount % 3) == 0 && "Expecting triangles");
+		const PtrSize indexBufferSize = PtrSize(lod.m_indexCount) * getIndexSize(m_indexType);
+		ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(indexBufferSize, getIndexSize(m_indexType),
+																		lod.m_unifiedGeometryIndexBufferOffset));
+
+		// Vertex stuff
+		lod.m_vertexCount = header.m_totalVertexCounts[l];
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+		{
+			if(header.m_vertexAttributes[stream].m_format == Format::kNone)
+			{
+				lod.m_unifiedGeometryVertBufferOffsets[stream] = kMaxPtrSize;
+				continue;
+			}
 
-	ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(m_vertexBuffersSize, 4, m_vertexBuffersOffset));
+			m_presentVertStreams |= VertexStreamMask(1 << stream);
 
-	// Readjust the individual offset now that we have a global offset
-	for(U32 i = 0; i < header.m_vertexBufferCount; ++i)
-	{
-		m_vertexBufferInfos[i].m_offset += m_vertexBuffersOffset;
-	}
+			const U32 texelSize = getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+			const PtrSize vertexBufferSize = PtrSize(lod.m_vertexCount) * texelSize;
 
-	for(VertexAttributeId attrib = VertexAttributeId::kFirst; attrib < VertexAttributeId::kCount; ++attrib)
-	{
-		AttribInfo& out = m_attributes[attrib];
-		const MeshBinaryVertexAttribute& in = header.m_vertexAttributes[attrib];
+			ANKI_CHECK(getManager().getUnifiedGeometryMemoryPool().allocate(
+				vertexBufferSize, texelSize, lod.m_unifiedGeometryVertBufferOffsets[stream]));
+		}
 
-		if(!!in.m_format)
+		// BLAS
+		if(rayTracingEnabled)
 		{
-			out.m_format = in.m_format;
-			out.m_relativeOffset = in.m_relativeOffset;
-			out.m_buffIdx = U8(in.m_bufferBinding);
-			ANKI_ASSERT(in.m_scale == 1.0f && "Not supported ATM");
+			AccelerationStructureInitInfo inf(
+				StringRaii(&getTempMemoryPool()).sprintf("%s_%s", "Blas", basename.cstr()));
+			inf.m_type = AccelerationStructureType::kBottomLevel;
+
+			inf.m_bottomLevel.m_indexBuffer = unifiedGeometryBuffer;
+			inf.m_bottomLevel.m_indexBufferOffset = lod.m_unifiedGeometryIndexBufferOffset;
+			inf.m_bottomLevel.m_indexCount = lod.m_indexCount;
+			inf.m_bottomLevel.m_indexType = m_indexType;
+			inf.m_bottomLevel.m_positionBuffer = unifiedGeometryBuffer;
+			inf.m_bottomLevel.m_positionBufferOffset =
+				lod.m_unifiedGeometryVertBufferOffsets[VertexStreamId::kPosition];
+			inf.m_bottomLevel.m_positionStride =
+				getFormatInfo(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition]).m_texelSize;
+			inf.m_bottomLevel.m_positionsFormat = kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition];
+			inf.m_bottomLevel.m_positionCount = lod.m_vertexCount;
+
+			lod.m_blas = getManager().getGrManager().newAccelerationStructure(inf);
 		}
 	}
 
-	// Other
-	m_aabb.setMin(header.m_aabbMin);
-	m_aabb.setMax(header.m_aabbMax);
-	m_vertexBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
-
-	//
 	// Clear the buffers
-	//
 	if(async)
 	{
-		CommandBufferInitInfo cmdbinit;
+		CommandBufferInitInfo cmdbinit("MeshResourceClear");
 		cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
 		CommandBufferPtr cmdb = getManager().getGrManager().newCommandBuffer(cmdbinit);
 
-		cmdb->fillBuffer(m_vertexBuffer, m_vertexBuffersOffset, m_vertexBuffersSize, 0);
-		cmdb->fillBuffer(m_vertexBuffer, m_indexBufferOffset, indexBufferSize, 0);
+		for(const Lod& lod : m_lods)
+		{
+			cmdb->fillBuffer(unifiedGeometryBuffer, lod.m_unifiedGeometryIndexBufferOffset,
+							 PtrSize(lod.m_indexCount) * getIndexSize(m_indexType), 0);
+
+			for(VertexStreamId stream :
+				EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
+			{
+				if(header.m_vertexAttributes[stream].m_format != Format::kNone)
+				{
+					cmdb->fillBuffer(unifiedGeometryBuffer, lod.m_unifiedGeometryVertBufferOffsets[stream],
+									 PtrSize(lod.m_vertexCount)
+										 * getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize,
+									 0);
+				}
+			}
+		}
 
-		const BufferBarrierInfo barrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
+		const BufferBarrierInfo barrier = {unifiedGeometryBuffer.get(), BufferUsageBit::kTransferDestination,
 										   BufferUsageBit::kVertex, 0, kMaxPtrSize};
 
 		cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
@@ -191,71 +213,6 @@ Error MeshResource::load(const ResourceFilename& filename, Bool async)
 		cmdb->flush();
 	}
 
-	//
-	// Create the BLAS
-	//
-	if(rayTracingEnabled)
-	{
-		AccelerationStructureInitInfo inf(StringRaii(&getTempMemoryPool()).sprintf("%s_%s", "Blas", basename.cstr()));
-		inf.m_type = AccelerationStructureType::kBottomLevel;
-
-		inf.m_bottomLevel.m_indexBuffer = m_vertexBuffer;
-		inf.m_bottomLevel.m_indexBufferOffset = m_indexBufferOffset;
-		inf.m_bottomLevel.m_indexCount = m_indexCount;
-		inf.m_bottomLevel.m_indexType = m_indexType;
-
-		U32 bufferIdx;
-		Format format;
-		U32 relativeOffset;
-		getVertexAttributeInfo(VertexAttributeId::kPosition, bufferIdx, format, relativeOffset);
-
-		BufferPtr buffer;
-		PtrSize offset;
-		PtrSize stride;
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-
-		inf.m_bottomLevel.m_positionBuffer = std::move(buffer);
-		inf.m_bottomLevel.m_positionBufferOffset = offset;
-		inf.m_bottomLevel.m_positionStride = U32(stride);
-		inf.m_bottomLevel.m_positionsFormat = format;
-		inf.m_bottomLevel.m_positionCount = m_vertexCount;
-
-		m_blas = getManager().getGrManager().newAccelerationStructure(inf);
-	}
-
-	// Fill the GPU descriptor
-	if(rayTracingEnabled)
-	{
-		m_meshGpuDescriptor.m_indexBufferPtr = m_vertexBuffer->getGpuAddress() + m_indexBufferOffset;
-
-		U32 bufferIdx;
-		Format format;
-		U32 relativeOffset;
-		getVertexAttributeInfo(VertexAttributeId::kPosition, bufferIdx, format, relativeOffset);
-		BufferPtr buffer;
-		PtrSize offset;
-		PtrSize stride;
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-		m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kPosition] = buffer->getGpuAddress() + offset;
-
-		getVertexAttributeInfo(VertexAttributeId::kNormal, bufferIdx, format, relativeOffset);
-		getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-		m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kNormalTangentUv0] =
-			buffer->getGpuAddress() + offset;
-
-		if(hasBoneWeights())
-		{
-			getVertexAttributeInfo(VertexAttributeId::kBoneWeights, bufferIdx, format, relativeOffset);
-			getVertexBufferInfo(bufferIdx, buffer, offset, stride);
-			m_meshGpuDescriptor.m_vertexBufferPtrs[VertexAttributeBufferId::kBone] = buffer->getGpuAddress() + offset;
-		}
-
-		m_meshGpuDescriptor.m_indexCount = m_indexCount;
-		m_meshGpuDescriptor.m_vertexCount = m_vertexCount;
-		m_meshGpuDescriptor.m_aabbMin = header.m_aabbMin;
-		m_meshGpuDescriptor.m_aabbMax = header.m_aabbMax;
-	}
-
 	// Submit the loading task
 	if(async)
 	{
@@ -275,88 +232,125 @@ Error MeshResource::loadAsync(MeshBinaryLoader& loader) const
 {
 	GrManager& gr = getManager().getGrManager();
 	TransferGpuAllocator& transferAlloc = getManager().getTransferGpuAllocator();
-	Array<TransferGpuAllocatorHandle, 2> handles;
+
+	Array<TransferGpuAllocatorHandle, kMaxLodCount*(U32(VertexStreamId::kMeshRelatedCount) + 1)> handles;
+	U32 handleCount = 0;
+
+	BufferPtr unifiedGeometryBuffer = getManager().getUnifiedGeometryMemoryPool().getVertexBuffer();
 
 	CommandBufferInitInfo cmdbinit;
 	cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
 	CommandBufferPtr cmdb = gr.newCommandBuffer(cmdbinit);
 
-	// Set barriers
-	const BufferBarrierInfo barrier = {m_vertexBuffer.get(), BufferUsageBit::kVertex,
+	// Set transfer to transfer barrier because of the clear that happened while sync loading
+	const BufferBarrierInfo barrier = {unifiedGeometryBuffer.get(), BufferUsageBit::kVertex,
 									   BufferUsageBit::kTransferDestination, 0, kMaxPtrSize};
 	cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
 
-	// Write index buffer
+	// Upload index and vertex buffers
+	for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
 	{
-		const PtrSize indexBufferSize = PtrSize(m_indexCount) * ((m_indexType == IndexType::kU32) ? 4 : 2);
+		const Lod& lod = m_lods[lodIdx];
 
-		ANKI_CHECK(transferAlloc.allocate(indexBufferSize, handles[1]));
-		void* data = handles[1].getMappedMemory();
-		ANKI_ASSERT(data);
+		// Upload index buffer
+		{
+			TransferGpuAllocatorHandle& handle = handles[handleCount++];
+			const PtrSize indexBufferSize = PtrSize(lod.m_indexCount) * getIndexSize(m_indexType);
 
-		ANKI_CHECK(loader.storeIndexBuffer(data, indexBufferSize));
+			ANKI_CHECK(transferAlloc.allocate(indexBufferSize, handle));
+			void* data = handle.getMappedMemory();
+			ANKI_ASSERT(data);
 
-		cmdb->copyBufferToBuffer(handles[1].getBuffer(), handles[1].getOffset(), m_vertexBuffer, m_indexBufferOffset,
-								 handles[1].getRange());
-	}
+			ANKI_CHECK(loader.storeIndexBuffer(lodIdx, data, indexBufferSize));
 
-	// Write vert buff
-	{
-		ANKI_CHECK(transferAlloc.allocate(m_vertexBuffersSize, handles[0]));
-		U8* data = static_cast<U8*>(handles[0].getMappedMemory());
-		ANKI_ASSERT(data);
+			cmdb->copyBufferToBuffer(handle.getBuffer(), handle.getOffset(), unifiedGeometryBuffer,
+									 lod.m_unifiedGeometryIndexBufferOffset, handle.getRange());
+		}
 
-		// Load to staging
-		PtrSize offset = 0;
-		for(U32 i = 0; i < m_vertexBufferInfos.getSize(); ++i)
+		// Upload vert buffers
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			alignRoundUp(kMeshBinaryBufferAlignment, offset);
-			ANKI_CHECK(
-				loader.storeVertexBuffer(i, data + offset, PtrSize(m_vertexBufferInfos[i].m_stride) * m_vertexCount));
+			if(!(m_presentVertStreams & VertexStreamMask(1 << stream)))
+			{
+				continue;
+			}
 
-			offset += PtrSize(m_vertexBufferInfos[i].m_stride) * m_vertexCount;
-		}
+			TransferGpuAllocatorHandle& handle = handles[handleCount++];
+			const PtrSize vertexBufferSize =
+				PtrSize(lod.m_vertexCount) * getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize;
+
+			ANKI_CHECK(transferAlloc.allocate(vertexBufferSize, handle));
+			U8* data = static_cast<U8*>(handle.getMappedMemory());
+			ANKI_ASSERT(data);
 
-		ANKI_ASSERT(offset == m_vertexBuffersSize);
+			// Load to staging
+			ANKI_CHECK(loader.storeVertexBuffer(lodIdx, U32(stream), data, vertexBufferSize));
 
-		// Copy
-		cmdb->copyBufferToBuffer(handles[0].getBuffer(), handles[0].getOffset(), m_vertexBuffer, m_vertexBuffersOffset,
-								 handles[0].getRange());
+			// Copy
+			cmdb->copyBufferToBuffer(handle.getBuffer(), handle.getOffset(), unifiedGeometryBuffer,
+									 lod.m_unifiedGeometryVertBufferOffsets[stream], handle.getRange());
+		}
 	}
 
-	// Build the BLAS
 	if(gr.getDeviceCapabilities().m_rayTracingEnabled)
 	{
-		const BufferBarrierInfo buffBarrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
-											   BufferUsageBit::kAccelerationStructureBuild | BufferUsageBit::kVertex
-												   | BufferUsageBit::kIndex,
-											   0, kMaxPtrSize};
-		const AccelerationStructureBarrierInfo asBarrier = {m_blas.get(), AccelerationStructureUsageBit::kNone,
-															AccelerationStructureUsageBit::kBuild};
+		// Build BLASes
+
+		// Set the barriers
+		BufferBarrierInfo bufferBarrier;
+		bufferBarrier.m_buffer = unifiedGeometryBuffer.get();
+		bufferBarrier.m_offset = 0;
+		bufferBarrier.m_size = kMaxPtrSize;
+		bufferBarrier.m_previousUsage = BufferUsageBit::kTransferDestination;
+		bufferBarrier.m_nextUsage = BufferUsageBit::kAllRead;
+
+		Array<AccelerationStructureBarrierInfo, kMaxLodCount> asBarriers;
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			asBarriers[lodIdx].m_as = m_lods[lodIdx].m_blas.get();
+			asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kNone;
+			asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kBuild;
+		}
 
-		cmdb->setPipelineBarrier({}, {&buffBarrier, 1}, {&asBarrier, 1});
+		cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {&asBarriers[0], m_lods.getSize()});
 
-		cmdb->buildAccelerationStructure(m_blas);
+		// Build BLASes
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			cmdb->buildAccelerationStructure(m_lods[lodIdx].m_blas);
+		}
 
-		const AccelerationStructureBarrierInfo asBarrier2 = {m_blas.get(), AccelerationStructureUsageBit::kBuild,
-															 AccelerationStructureUsageBit::kAllRead};
+		// Barriers again
+		for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
+		{
+			asBarriers[lodIdx].m_as = m_lods[lodIdx].m_blas.get();
+			asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kBuild;
+			asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kAllRead;
+		}
 
-		cmdb->setPipelineBarrier({}, {}, {&asBarrier2, 1});
+		cmdb->setPipelineBarrier({}, {}, {&asBarriers[0], m_lods.getSize()});
 	}
 	else
 	{
-		const BufferBarrierInfo buffBarrier = {m_vertexBuffer.get(), BufferUsageBit::kTransferDestination,
-											   BufferUsageBit::kVertex | BufferUsageBit::kIndex, 0, kMaxPtrSize};
-
-		cmdb->setPipelineBarrier({}, {&buffBarrier, 1}, {});
+		// Only set a barrier
+		BufferBarrierInfo bufferBarrier;
+		bufferBarrier.m_buffer = unifiedGeometryBuffer.get();
+		bufferBarrier.m_offset = 0;
+		bufferBarrier.m_size = kMaxPtrSize;
+		bufferBarrier.m_previousUsage = BufferUsageBit::kTransferDestination;
+		bufferBarrier.m_nextUsage = BufferUsageBit::kAllRead;
+
+		cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {});
 	}
 
 	// Finalize
 	FencePtr fence;
 	cmdb->flush({}, &fence);
 
-	transferAlloc.release(handles[0], fence);
-	transferAlloc.release(handles[1], fence);
+	for(U32 i = 0; i < handleCount; ++i)
+	{
+		transferAlloc.release(handles[i], fence);
+	}
 
 	return Error::kNone;
 }

+ 44 - 93
AnKi/Resource/MeshResource.h

@@ -9,7 +9,7 @@
 #include <AnKi/Math.h>
 #include <AnKi/Gr.h>
 #include <AnKi/Collision/Aabb.h>
-#include <AnKi/Shaders/Include/ModelTypes.h>
+#include <AnKi/Shaders/Include/MeshTypes.h>
 
 namespace anki {
 
@@ -28,9 +28,6 @@ public:
 
 	~MeshResource();
 
-	/// Helper function for correct loading
-	Bool isCompatible(const MeshResource& other) const;
-
 	/// Load from a mesh file
 	Error load(const ResourceFilename& filename, Bool async);
 
@@ -40,134 +37,88 @@ public:
 		return m_aabb;
 	}
 
-	/// Get submesh info.
-	void getSubMeshInfo(U32 subMeshId, U32& firstIndex, U32& indexCount, Aabb& aabb) const
-	{
-		const SubMesh& sm = m_subMeshes[subMeshId];
-		firstIndex = sm.m_firstIndex;
-		indexCount = sm.m_indexCount;
-		aabb = sm.m_aabb;
-	}
-
 	U32 getSubMeshCount() const
 	{
 		return m_subMeshes.getSize();
 	}
 
-	/// Get all info around vertex indices.
-	void getIndexBufferInfo(BufferPtr& buff, PtrSize& buffOffset, U32& indexCount, IndexType& indexType) const
+	/// Get submesh info.
+	void getSubMeshInfo(U32 lod, U32 subMeshId, U32& firstIndex, U32& indexCount, Aabb& aabb) const
 	{
-		buff = m_vertexBuffer;
-		buffOffset = m_indexBufferOffset;
-		indexCount = m_indexCount;
-		indexType = m_indexType;
+		const SubMesh& sm = m_subMeshes[subMeshId];
+		firstIndex = sm.m_firstIndices[lod];
+		indexCount = sm.m_indexCounts[lod];
+		aabb = sm.m_aabb;
 	}
 
-	/// Get the number of logical vertex buffers.
-	U32 getVertexBufferCount() const
+	/// Get all info around vertex indices.
+	void getIndexBufferInfo(U32 lod, PtrSize& buffOffset, U32& indexCount, IndexType& indexType) const
 	{
-		return m_vertexBufferInfos.getSize();
+		buffOffset = m_lods[lod].m_unifiedGeometryIndexBufferOffset;
+		ANKI_ASSERT(isAligned(getIndexSize(m_indexType), buffOffset));
+		indexCount = m_lods[lod].m_indexCount;
+		indexType = m_indexType;
 	}
 
 	/// Get vertex buffer info.
-	void getVertexBufferInfo(const U32 buffIdx, BufferPtr& buff, PtrSize& offset, PtrSize& stride) const
-	{
-		buff = m_vertexBuffer;
-		offset = m_vertexBufferInfos[buffIdx].m_offset;
-		stride = m_vertexBufferInfos[buffIdx].m_stride;
-	}
-
-	/// Get attribute info. You need to check if the attribute is preset first (isVertexAttributePresent)
-	void getVertexAttributeInfo(const VertexAttributeId attrib, U32& bufferIdx, Format& format,
-								U32& relativeOffset) const
-	{
-		ANKI_ASSERT(isVertexAttributePresent(attrib));
-		bufferIdx = m_attributes[attrib].m_buffIdx;
-		format = m_attributes[attrib].m_format;
-		relativeOffset = m_attributes[attrib].m_relativeOffset;
-	}
-
-	/// Check if a vertex attribute is present.
-	Bool isVertexAttributePresent(const VertexAttributeId attrib) const
-	{
-		return !!m_attributes[attrib].m_format;
-	}
-
-	/// Return true if it has bone weights.
-	Bool hasBoneWeights() const
-	{
-		return isVertexAttributePresent(VertexAttributeId::kBoneWeights);
-	}
-
-	AccelerationStructurePtr getBottomLevelAccelerationStructure() const
+	void getVertexStreamInfo(U32 lod, VertexStreamId stream, PtrSize& bufferOffset, U32& vertexCount) const
 	{
-		ANKI_ASSERT(m_blas.isCreated());
-		return m_blas;
+		bufferOffset = m_lods[lod].m_unifiedGeometryVertBufferOffsets[stream];
+		vertexCount = m_lods[lod].m_vertexCount;
 	}
 
-	const MeshGpuDescriptor& getMeshGpuDescriptor() const
+	const AccelerationStructurePtr& getBottomLevelAccelerationStructure(U32 lod) const
 	{
-		return m_meshGpuDescriptor;
+		ANKI_ASSERT(m_lods[lod].m_blas);
+		return m_lods[lod].m_blas;
 	}
 
-	/// Get the buffer that contains all the indices of all submesses.
-	BufferPtr getIndexBuffer() const
+	/// Check if a vertex stream is present.
+	Bool isVertexStreamPresent(const VertexStreamId stream) const
 	{
-		return m_vertexBuffer;
+		return !!(m_presentVertStreams & VertexStreamMask(1 << stream));
 	}
 
-	/// Get the buffer that contains all the vertices of all submesses.
-	BufferPtr getVertexBuffer() const
+	Bool getLodCount() const
 	{
-		return m_vertexBuffer;
+		return m_lods.getSize();
 	}
 
 private:
 	class LoadTask;
 	class LoadContext;
 
-	class SubMesh
+	class Lod
 	{
 	public:
-		U32 m_firstIndex;
-		U32 m_indexCount;
-		Aabb m_aabb;
-	};
+		PtrSize m_unifiedGeometryIndexBufferOffset = kMaxPtrSize;
+		Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_unifiedGeometryVertBufferOffsets;
 
-	class VertBuffInfo
-	{
-	public:
-		PtrSize m_offset; ///< Offset from the base of m_vertexBuffer.
-		U32 m_stride;
+		U32 m_indexCount = 0;
+		U32 m_vertexCount = 0;
+
+		AccelerationStructurePtr m_blas;
+
+		Lod()
+		{
+			m_unifiedGeometryVertBufferOffsets.fill(m_unifiedGeometryVertBufferOffsets.getBegin(),
+													m_unifiedGeometryVertBufferOffsets.getEnd(), kMaxPtrSize);
+		}
 	};
 
-	class AttribInfo
+	class SubMesh
 	{
 	public:
-		Format m_format = Format::kNone;
-		U32 m_relativeOffset = 0;
-		U32 m_buffIdx = 0;
+		Array<U32, kMaxLodCount> m_firstIndices;
+		Array<U32, kMaxLodCount> m_indexCounts;
+		Aabb m_aabb;
 	};
 
 	DynamicArray<SubMesh> m_subMeshes;
-	DynamicArray<VertBuffInfo> m_vertexBufferInfos;
-	Array<AttribInfo, U(VertexAttributeId::kCount)> m_attributes;
-
-	BufferPtr m_vertexBuffer; ///< Contains all data (vertices and indices).
-
-	PtrSize m_vertexBuffersOffset = kMaxPtrSize; ///< Used for deallocation.
-	PtrSize m_vertexBuffersSize = 0; ///< Used for deallocation.
-	U32 m_vertexCount = 0;
-
-	PtrSize m_indexBufferOffset = kMaxPtrSize; ///< The offset from the base of m_vertexBuffer.
-	U32 m_indexCount = 0; ///< Total index count as if all submeshes are a single submesh.
-	IndexType m_indexType;
-
+	DynamicArray<Lod> m_lods;
 	Aabb m_aabb;
-
-	// RT
-	AccelerationStructurePtr m_blas;
-	MeshGpuDescriptor m_meshGpuDescriptor;
+	IndexType m_indexType;
+	VertexStreamMask m_presentVertStreams = VertexStreamMask::kNone;
 
 	Error loadAsync(MeshBinaryLoader& loader) const;
 };

+ 43 - 201
AnKi/Resource/ModelResource.cpp

@@ -11,23 +11,6 @@
 
 namespace anki {
 
-static Bool attributeIsRequired(VertexAttributeId loc, RenderingTechnique technique, Bool hasSkin)
-{
-	if(technique == RenderingTechnique::kGBuffer || technique == RenderingTechnique::kForward)
-	{
-		return true;
-	}
-	else if(!hasSkin)
-	{
-		return loc == VertexAttributeId::kPosition || loc == VertexAttributeId::kUv0;
-	}
-	else
-	{
-		return loc == VertexAttributeId::kPosition || loc == VertexAttributeId::kBoneIndices
-			   || loc == VertexAttributeId::kBoneWeights || loc == VertexAttributeId::kUv0;
-	}
-}
-
 void ModelPatch::getRenderingInfo(const RenderingKey& key, ModelRenderingInfo& inf) const
 {
 	ANKI_ASSERT(!(!supportsSkinning() && key.getSkinned()));
@@ -35,59 +18,17 @@ void ModelPatch::getRenderingInfo(const RenderingKey& key, ModelRenderingInfo& i
 
 	// Vertex attributes & bindings
 	{
-		U32 bufferBindingVisitedMask = 0;
-		Array<U32, kMaxVertexAttributes> realBufferBindingToVirtual;
-
-		inf.m_vertexAttributeCount = 0;
-		inf.m_vertexBufferBindingCount = 0;
+		inf.m_indexBufferOffset = m_lodInfos[meshLod].m_indexBufferOffset;
+		inf.m_indexType = IndexType::kU16;
+		inf.m_firstIndex = m_lodInfos[meshLod].m_firstIndex;
+		inf.m_indexCount = m_lodInfos[meshLod].m_indexCount;
 
-		for(VertexAttributeId loc : EnumIterable<VertexAttributeId>())
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			if(!m_presentVertexAttributes.get(loc)
-			   || !attributeIsRequired(loc, key.getRenderingTechnique(), key.getSkinned()))
-			{
-				continue;
-			}
-
-			// Attribute
-			ModelVertexAttribute& outAttribInfo = inf.m_vertexAttributes[inf.m_vertexAttributeCount++];
-			outAttribInfo.m_location = loc;
-			outAttribInfo.m_bufferBinding = m_vertexAttributeInfos[loc].m_bufferBinding;
-			outAttribInfo.m_relativeOffset = m_vertexAttributeInfos[loc].m_relativeOffset;
-			outAttribInfo.m_format = m_vertexAttributeInfos[loc].m_format;
-
-			// Binding. Also, remove any holes in the bindings
-			if(!(bufferBindingVisitedMask & (1 << outAttribInfo.m_bufferBinding)))
-			{
-				bufferBindingVisitedMask |= 1 << outAttribInfo.m_bufferBinding;
-
-				ModelVertexBufferBinding& outBinding = inf.m_vertexBufferBindings[inf.m_vertexBufferBindingCount];
-				const VertexBufferInfo& inBinding = m_vertexBufferInfos[meshLod][outAttribInfo.m_bufferBinding];
-				outBinding.m_buffer = inBinding.m_buffer;
-				ANKI_ASSERT(outBinding.m_buffer.isCreated());
-				outBinding.m_offset = inBinding.m_offset;
-				ANKI_ASSERT(outBinding.m_offset != kMaxPtrSize);
-				outBinding.m_stride = inBinding.m_stride;
-				ANKI_ASSERT(outBinding.m_stride != kMaxPtrSize);
-
-				realBufferBindingToVirtual[outAttribInfo.m_bufferBinding] = inf.m_vertexBufferBindingCount;
-				++inf.m_vertexBufferBindingCount;
-			}
-
-			// Change the binding of the attrib
-			outAttribInfo.m_bufferBinding = realBufferBindingToVirtual[outAttribInfo.m_bufferBinding];
+			inf.m_vertexBufferOffsets[stream] = m_lodInfos[meshLod].m_vertexBufferOffsets[stream];
 		}
-
-		ANKI_ASSERT(inf.m_vertexAttributeCount != 0 && inf.m_vertexBufferBindingCount != 0);
 	}
 
-	// Index buff
-	inf.m_indexBuffer = m_indexBufferInfos[meshLod].m_buffer;
-	inf.m_indexBufferOffset = m_indexBufferInfos[meshLod].m_offset;
-	inf.m_indexCount = m_indexBufferInfos[meshLod].m_indexCount;
-	inf.m_firstIndex = m_indexBufferInfos[meshLod].m_firstIndex;
-	inf.m_indexType = m_indexType;
-
 	// Get program
 	const MaterialVariant& variant = m_mtl->getOrCreateVariant(key);
 	inf.m_program = variant.getShaderProgram();
@@ -98,21 +39,17 @@ void ModelPatch::getRayTracingInfo(const RenderingKey& key, ModelRayTracingInfo&
 	ANKI_ASSERT(!!(m_mtl->getRenderingTechniques() & RenderingTechniqueBit(1 << key.getRenderingTechnique())));
 
 	// Mesh
-	const MeshResourcePtr& mesh = m_meshes[min(U32(m_meshLodCount - 1), key.getLod())];
-	info.m_bottomLevelAccelerationStructure = mesh->getBottomLevelAccelerationStructure();
+	const U32 meshLod = min<U32>(key.getLod(), m_meshLodCount - 1);
+	info.m_bottomLevelAccelerationStructure = m_mesh->getBottomLevelAccelerationStructure(meshLod);
 
 	// Material
 	const MaterialVariant& variant = m_mtl->getOrCreateVariant(key);
 	info.m_shaderGroupHandleIndex = variant.getRtShaderGroupHandleIndex();
-
-	// Misc
-	info.m_grObjectReferences = m_grObjectRefs;
 }
 
-Error ModelPatch::init(ModelResource* model, ConstWeakArray<CString> meshFNames, const CString& mtlFName,
+Error ModelPatch::init([[maybe_unused]] ModelResource* model, CString meshFName, const CString& mtlFName,
 					   U32 subMeshIndex, Bool async, ResourceManager* manager)
 {
-	ANKI_ASSERT(meshFNames.getSize() > 0);
 #if ANKI_ENABLE_ASSERTIONS
 	m_model = model;
 #endif
@@ -120,115 +57,49 @@ Error ModelPatch::init(ModelResource* model, ConstWeakArray<CString> meshFNames,
 	// Load material
 	ANKI_CHECK(manager->loadResource(mtlFName, m_mtl, async));
 
-	// Gather the material refs
-	if(m_mtl->getAllTextures().getSize())
-	{
-		m_grObjectRefs.resizeStorage(model->getMemoryPool(), m_mtl->getAllTextures().getSize());
+	// Load mesh
+	ANKI_CHECK(manager->loadResource(meshFName, m_mesh, async));
 
-		for(U32 i = 0; i < m_mtl->getAllTextures().getSize(); ++i)
-		{
-			m_grObjectRefs.emplaceBack(model->getMemoryPool(), m_mtl->getAllTextures()[i]);
-		}
+	if(subMeshIndex != kMaxU32 && subMeshIndex >= m_mesh->getSubMeshCount())
+	{
+		ANKI_RESOURCE_LOGE("Wrong subMeshIndex given");
+		return Error::kUserData;
 	}
 
-	// Load meshes
-	m_meshLodCount = 0;
-	for(U32 lod = 0; lod < meshFNames.getSize(); lod++)
+	// Init cached data
+	if(subMeshIndex == kMaxU32)
 	{
-		ANKI_CHECK(manager->loadResource(meshFNames[lod], m_meshes[lod], async));
-
-		// Sanity check
-		if(lod > 0 && !m_meshes[lod]->isCompatible(*m_meshes[lod - 1]))
-		{
-			ANKI_RESOURCE_LOGE("Meshes not compatible");
-			return Error::kUserData;
-		}
-
-		// Submesh index
-		if(subMeshIndex != kMaxU32 && subMeshIndex >= m_meshes[lod]->getSubMeshCount())
-		{
-			ANKI_RESOURCE_LOGE("Wrong subMeshIndex given");
-			return Error::kUserData;
-		}
-
-		++m_meshLodCount;
+		m_aabb = m_mesh->getBoundingShape();
 	}
-
-	// Create the cached items
+	else
 	{
-		// Vertex attributes
-		for(VertexAttributeId attrib : EnumIterable<VertexAttributeId>())
-		{
-			const MeshResource& mesh = *m_meshes[0].get();
-
-			const Bool enabled = mesh.isVertexAttributePresent(attrib);
-			m_presentVertexAttributes.set(U32(attrib), enabled);
-
-			if(!enabled)
-			{
-				continue;
-			}
+		U32 firstIndex, indexCount;
+		m_mesh->getSubMeshInfo(0, subMeshIndex, firstIndex, indexCount, m_aabb);
+	}
 
-			VertexAttributeInfo& outAttribInfo = m_vertexAttributeInfos[attrib];
-			U32 bufferBinding, relativeOffset;
-			mesh.getVertexAttributeInfo(attrib, bufferBinding, outAttribInfo.m_format, relativeOffset);
-			outAttribInfo.m_bufferBinding = bufferBinding & 0xFu;
-			outAttribInfo.m_relativeOffset = relativeOffset & 0xFFFFFFu;
-		}
+	m_meshLodCount = m_mesh->getLodCount();
 
-		// Vertex buffers
-		for(U32 lod = 0; lod < m_meshLodCount; ++lod)
-		{
-			const MeshResource& mesh = *m_meshes[lod].get();
+	for(U32 l = 0; l < m_meshLodCount; ++l)
+	{
+		Lod& lod = m_lodInfos[l];
+		Aabb aabb;
+		m_mesh->getSubMeshInfo(l, (subMeshIndex == kMaxU32) ? 0 : subMeshIndex, lod.m_firstIndex, lod.m_indexCount,
+							   aabb);
 
-			for(VertexAttributeId attrib : EnumIterable<VertexAttributeId>())
-			{
-				if(!m_presentVertexAttributes.get(attrib))
-				{
-					continue;
-				}
-
-				VertexBufferInfo& outVertBufferInfo =
-					m_vertexBufferInfos[lod][m_vertexAttributeInfos[attrib].m_bufferBinding];
-				if(!outVertBufferInfo.m_buffer.isCreated())
-				{
-					PtrSize offset, stride;
-					mesh.getVertexBufferInfo(m_vertexAttributeInfos[attrib].m_bufferBinding, outVertBufferInfo.m_buffer,
-											 offset, stride);
-					outVertBufferInfo.m_offset = offset & 0xFFFFFFFFFFFF;
-					outVertBufferInfo.m_stride = stride & 0xFFFF;
-				}
-			}
-		}
+		U32 totalIndexCount;
+		IndexType indexType;
+		m_mesh->getIndexBufferInfo(l, lod.m_indexBufferOffset, totalIndexCount, indexType);
 
-		// Index buffer
-		for(U32 lod = 0; lod < m_meshLodCount; ++lod)
+		for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
 		{
-			const MeshResource& mesh = *m_meshes[lod].get();
-			IndexBufferInfo& outIndexBufferInfo = m_indexBufferInfos[lod];
-
-			if(subMeshIndex == kMaxU32)
+			if(m_mesh->isVertexStreamPresent(stream))
 			{
-				IndexType indexType;
-				PtrSize offset;
-				mesh.getIndexBufferInfo(outIndexBufferInfo.m_buffer, offset, outIndexBufferInfo.m_indexCount,
-										indexType);
-				outIndexBufferInfo.m_offset = offset;
-				outIndexBufferInfo.m_firstIndex = 0;
-				m_indexType = indexType;
+				U32 vertCount;
+				m_mesh->getVertexStreamInfo(l, stream, lod.m_vertexBufferOffsets[stream], vertCount);
 			}
 			else
 			{
-				IndexType indexType;
-				PtrSize offset;
-				mesh.getIndexBufferInfo(outIndexBufferInfo.m_buffer, offset, outIndexBufferInfo.m_indexCount,
-										indexType);
-				outIndexBufferInfo.m_offset = offset;
-				m_indexType = indexType;
-
-				Aabb aabb;
-				mesh.getSubMeshInfo(subMeshIndex, outIndexBufferInfo.m_firstIndex, outIndexBufferInfo.m_indexCount,
-									aabb);
+				lod.m_vertexBufferOffsets[stream] = kMaxPtrSize;
 			}
 		}
 	}
@@ -243,18 +114,11 @@ ModelResource::ModelResource(ResourceManager* manager)
 
 ModelResource::~ModelResource()
 {
-	for(ModelPatch& patch : m_modelPatches)
-	{
-		patch.m_grObjectRefs.destroy(getMemoryPool());
-	}
-
 	m_modelPatches.destroy(getMemoryPool());
 }
 
 Error ModelResource::load(const ResourceFilename& filename, Bool async)
 {
-	HeapMemoryPool& pool = getMemoryPool();
-
 	// Load
 	//
 	XmlElement el;
@@ -287,7 +151,7 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 		return Error::kUserData;
 	}
 
-	m_modelPatches.create(pool, count);
+	m_modelPatches.create(getMemoryPool(), count);
 
 	count = 0;
 	ANKI_CHECK(modelPatchesEl.getChildElement("modelPatch", modelPatchEl));
@@ -304,37 +168,15 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 		XmlElement materialEl;
 		ANKI_CHECK(modelPatchEl.getChildElement("material", materialEl));
 
-		Array<CString, 3> meshesFnames;
-		U32 meshesCount = 1;
-
 		XmlElement meshEl;
 		ANKI_CHECK(modelPatchEl.getChildElement("mesh", meshEl));
-
-		XmlElement meshEl1;
-		ANKI_CHECK(modelPatchEl.getChildElementOptional("mesh1", meshEl1));
-
-		XmlElement meshEl2;
-		ANKI_CHECK(modelPatchEl.getChildElementOptional("mesh2", meshEl2));
-
-		ANKI_CHECK(meshEl.getText(meshesFnames[0]));
-
-		if(meshEl1)
-		{
-			++meshesCount;
-			ANKI_CHECK(meshEl1.getText(meshesFnames[1]));
-		}
-
-		if(meshEl2)
-		{
-			++meshesCount;
-			ANKI_CHECK(meshEl2.getText(meshesFnames[2]));
-		}
+		CString meshFname;
+		ANKI_CHECK(meshEl.getText(meshFname));
 
 		CString cstr;
 		ANKI_CHECK(materialEl.getText(cstr));
 
-		ANKI_CHECK(m_modelPatches[count].init(this, ConstWeakArray<CString>(&meshesFnames[0], meshesCount), cstr,
-											  subMeshIndex, async, &getManager()));
+		ANKI_CHECK(m_modelPatches[count].init(this, meshFname, cstr, subMeshIndex, async, &getManager()));
 
 		if(count > 0 && m_modelPatches[count].supportsSkinning() != m_modelPatches[count - 1].supportsSkinning())
 		{
@@ -349,10 +191,10 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 	ANKI_ASSERT(count == m_modelPatches.getSize());
 
 	// Calculate compound bounding volume
-	m_boundingVolume = m_modelPatches[0].m_meshes[0]->getBoundingShape();
+	m_boundingVolume = m_modelPatches[0].m_aabb;
 	for(auto it = m_modelPatches.getBegin() + 1; it != m_modelPatches.getEnd(); ++it)
 	{
-		m_boundingVolume = m_boundingVolume.getCompoundShape((*it).m_meshes[0]->getBoundingShape());
+		m_boundingVolume = m_boundingVolume.getCompoundShape((*it).m_aabb);
 	}
 
 	return Error::kNone;

+ 25 - 97
AnKi/Resource/ModelResource.h

@@ -17,46 +17,6 @@ namespace anki {
 /// @addtogroup resource
 /// @{
 
-/// @memberof ModelResource
-class ModelVertexBufferBinding
-{
-public:
-	BufferPtr m_buffer;
-	PtrSize m_offset;
-	PtrSize m_stride;
-
-	Bool operator==(const ModelVertexBufferBinding& b) const
-	{
-		return m_buffer == b.m_buffer && m_offset == b.m_offset && m_stride == b.m_stride;
-	}
-
-	Bool operator!=(const ModelVertexBufferBinding& b) const
-	{
-		return !(*this == b);
-	}
-};
-
-/// @memberof ModelResource
-class ModelVertexAttribute
-{
-public:
-	VertexAttributeId m_location;
-	Format m_format;
-	U32 m_bufferBinding;
-	U32 m_relativeOffset;
-
-	Bool operator==(const ModelVertexAttribute& b) const
-	{
-		return m_bufferBinding == b.m_bufferBinding && m_format == b.m_format && m_relativeOffset == b.m_relativeOffset
-			   && m_location == b.m_location;
-	}
-
-	Bool operator!=(const ModelVertexAttribute& b) const
-	{
-		return !(*this == b);
-	}
-};
-
 /// @memberof ModelResource
 /// Part of the information required render the model.
 class ModelRenderingInfo
@@ -64,16 +24,13 @@ class ModelRenderingInfo
 public:
 	ShaderProgramPtr m_program;
 
-	Array<ModelVertexBufferBinding, kMaxVertexAttributes> m_vertexBufferBindings;
-	U32 m_vertexBufferBindingCount;
-	Array<ModelVertexAttribute, kMaxVertexAttributes> m_vertexAttributes;
-	U32 m_vertexAttributeCount;
-
-	BufferPtr m_indexBuffer;
 	PtrSize m_indexBufferOffset;
 	IndexType m_indexType;
 	U32 m_firstIndex;
 	U32 m_indexCount;
+
+	/// Offset to the vertex buffer or kMaxPtrSize if stream is not present.
+	Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_vertexBufferOffsets;
 };
 
 /// Part of the information required to create a TLAS and a SBT.
@@ -83,12 +40,9 @@ class ModelRayTracingInfo
 public:
 	AccelerationStructurePtr m_bottomLevelAccelerationStructure;
 	U32 m_shaderGroupHandleIndex;
-
-	/// Get some pointers to pass to the command buffer for refcounting.
-	ConstWeakArray<GrObjectPtr> m_grObjectReferences;
 };
 
-/// Model patch class. Its very important class and it binds a material with a few mesh (one for each LOD).
+/// Model patch class. Its very important class and it binds a material with a mesh.
 class ModelPatch
 {
 	friend class ModelResource;
@@ -99,14 +53,14 @@ public:
 		return m_mtl;
 	}
 
-	const MeshResourcePtr& getMesh(U32 lod) const
+	const MeshResourcePtr& getMesh() const
 	{
-		return m_meshes[lod];
+		return m_mesh;
 	}
 
 	const Aabb& getBoundingShape() const
 	{
-		return m_meshes[0]->getBoundingShape();
+		return m_aabb;
 	}
 
 	/// Get information for rendering.
@@ -116,57 +70,33 @@ public:
 	void getRayTracingInfo(const RenderingKey& key, ModelRayTracingInfo& info) const;
 
 private:
-#if ANKI_ENABLE_ASSERTIONS
-	ModelResource* m_model = nullptr;
-#endif
-	MaterialResourcePtr m_mtl;
-	Array<MeshResourcePtr, kMaxLodCount> m_meshes; ///< Just keep the references.
-	DynamicArray<GrObjectPtr> m_grObjectRefs;
-
-	// Begin cached data
-	class VertexAttributeInfo
+	class Lod
 	{
 	public:
-		U32 m_bufferBinding : 8;
-		U32 m_relativeOffset : 24;
-		Format m_format = Format::kNone;
-	};
-
-	Array<VertexAttributeInfo, U(VertexAttributeId::kCount)> m_vertexAttributeInfos;
-
-	class VertexBufferInfo
-	{
-	public:
-		BufferPtr m_buffer;
-		PtrSize m_stride : 16;
-		PtrSize m_offset : 48;
-	};
-
-	Array2d<VertexBufferInfo, kMaxLodCount, U(VertexAttributeBufferId::kCount)> m_vertexBufferInfos;
-
-	class IndexBufferInfo
-	{
-	public:
-		BufferPtr m_buffer;
-		PtrSize m_offset;
+		PtrSize m_indexBufferOffset = kMaxPtrSize;
 		U32 m_firstIndex = kMaxU32;
 		U32 m_indexCount = kMaxU32;
-	};
 
-	Array<IndexBufferInfo, kMaxLodCount> m_indexBufferInfos;
-	BitSet<U(VertexAttributeId::kCount)> m_presentVertexAttributes = {false};
-	IndexType m_indexType : 2;
-	// End cached data
+		Array<PtrSize, U32(VertexStreamId::kMeshRelatedCount)> m_vertexBufferOffsets = {};
+	};
 
-	U8 m_meshLodCount : 6;
+#if ANKI_ENABLE_ASSERTIONS
+	ModelResource* m_model = nullptr;
+#endif
+	MaterialResourcePtr m_mtl;
+	MeshResourcePtr m_mesh; ///< Just keep the references.
 
-	Error init(ModelResource* model, ConstWeakArray<CString> meshFNames, const CString& mtlFName, U32 subMeshIndex,
-			   Bool async, ResourceManager* resources);
+	Array<Lod, kMaxLodCount> m_lodInfos;
+	Aabb m_aabb;
+	U32 m_meshLodCount = 0;
 
 	[[nodiscard]] Bool supportsSkinning() const
 	{
-		return m_meshes[0]->hasBoneWeights() && m_mtl->supportsSkinning();
+		return m_mesh->isVertexStreamPresent(VertexStreamId::kBoneIds) && m_mtl->supportsSkinning();
 	}
+
+	Error init(ModelResource* model, CString meshFName, const CString& mtlFName, U32 subMeshIndex, Bool async,
+			   ResourceManager* resources);
 };
 
 /// Model is an entity that acts as a container for other resources. Models are all the non static objects in a map.
@@ -175,10 +105,8 @@ private:
 /// @code
 /// <model>
 /// 	<modelPatches>
-/// 		<modelPatch [subMeshIndex=int]>
-/// 			<mesh>path/to/mesh.mesh</mesh>
-///				[<mesh1>path/to/mesh_lod_1.ankimesh</mesh1>]
-///				[<mesh2>path/to/mesh_lod_2.ankimesh</mesh2>]
+/// 		<modelPatch>
+/// 			<mesh [subMeshIndex=int]>path/to/mesh.ankimesh</mesh>
 /// 			<material>path/to/material.ankimtl</material>
 /// 		</modelPatch>
 /// 		...

+ 52 - 7
AnKi/Shaders/Include/MeshTypes.h

@@ -9,6 +9,56 @@
 
 ANKI_BEGIN_NAMESPACE
 
+#if __cplusplus
+enum class VertexStreamId : U8
+{
+	// For regular geometry
+	kPosition,
+	kNormal,
+	kTangent,
+	kUv,
+	kBoneIds,
+	kBoneWeights,
+
+	kMeshRelatedCount,
+	kMeshRelatedFirst = 0,
+
+	// For particles
+	kParticlePosition = 0,
+	kParticleScale,
+	kParticleAlpha,
+	kParticleLife,
+	kParticleStartingLife,
+	kParticlePreviousPosition,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexStreamId)
+
+enum class VertexStreamMask : U8
+{
+	kNone,
+
+	kPosition = 1 << 0,
+	kNormal = 1 << 1,
+	kTangent = 1 << 2,
+	kUv = 1 << 3,
+	kBoneIds = 1 << 4,
+	kBoneWeights = 1 << 5,
+
+	kParticlePosition = 1 << 0,
+	kParticleScale = 1 << 1,
+	kParticleAlpha = 1 << 2,
+	kParticleLife = 1 << 3,
+	kParticleStartingLife = 1 << 4,
+	kParticlePreviousPosition = 1 << 5,
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexStreamMask)
+
+inline constexpr Array<Format, U32(VertexStreamId::kMeshRelatedCount)> kMeshRelatedVertexStreamFormats = {
+	Format::kR32G32B32A32_Sfloat, Format::kR8G8B8A8_Snorm, Format::kR8G8B8A8_Snorm,
+	Format::kR32G32_Sfloat,       Format::kR8G8B8A8_Uint,  Format::kR8G8B8A8_Snorm};
+
+#else
+
 // For regular geometry
 const U32 kVertexStreamPosition = 0u;
 const U32 kVertexStreamNormal = 1u;
@@ -17,13 +67,7 @@ const U32 kVertexStreamUv = 3u;
 const U32 kVertexStreamBoneIds = 4u;
 const U32 kVertexStreamBoneWeights = 5u;
 
-const U32 kRegularVertexStreamCount = 6u;
-
-#if __cplusplus
-inline constexpr Array<Format, kRegularVertexStreamCount> kRegularVertexStreamFormats = {
-	Format::kR32R32G32B32_Sfloat, Format::kR8G8B8A8_Snorm, Format::kR8G8B8A8_Snorm,
-	Format::kR32G32_Sfloat,       Format::kR8G8B8A8_Uint,  Format::kR8G8B8A8_Snorm};
-#endif
+const U32 kVertexStreamRegularCount = 6u;
 
 // For particles
 const U32 kVertexStreamParticlePosition = 0u;
@@ -32,5 +76,6 @@ const U32 kVertexStreamParticleAlpha = 2u;
 const U32 kVertexStreamParticleLife = 3u;
 const U32 kVertexStreamParticleStartingLife = 4u;
 const U32 kVertexStreamParticlePreviousPosition = 5u;
+#endif
 
 ANKI_END_NAMESPACE

+ 23 - 5
AnKi/Util/Enum.h

@@ -140,7 +140,7 @@ class EnumIterableIterator
 public:
 	using Type = typename std::underlying_type<TEnum>::type;
 
-	EnumIterableIterator(TEnum val)
+	constexpr EnumIterableIterator(TEnum val)
 		: m_val(static_cast<Type>(val))
 	{
 	}
@@ -177,15 +177,33 @@ class EnumIterable
 public:
 	using Iterator = EnumIterableIterator<TEnum>;
 
-	static Iterator begin()
+	constexpr EnumIterable()
+		: m_begin(TEnum::kFirst)
+		, m_end(TEnum::kCount)
 	{
-		return Iterator(TEnum::kFirst);
+		ANKI_ASSERT(m_begin <= m_end);
 	}
 
-	static Iterator end()
+	constexpr EnumIterable(TEnum begin, TEnum end)
+		: m_begin(begin)
+		, m_end(end)
 	{
-		return Iterator(TEnum::kCount);
+		ANKI_ASSERT(m_begin <= m_end);
 	}
+
+	Iterator begin() const
+	{
+		return Iterator(m_begin);
+	}
+
+	Iterator end() const
+	{
+		return Iterator(m_end);
+	}
+
+public:
+	TEnum m_begin;
+	TEnum m_end;
 };
 /// @}