瀏覽代碼

Fixes and optimizations

Panagiotis Christopoulos Charitos 6 年之前
父節點
當前提交
e5b1d804ab

+ 4 - 4
shaders/GBufferGeneric.glslp

@@ -32,7 +32,7 @@
 #pragma anki input texture2D diffTex "DIFFUSE_TEX == 1 && PASS == 0"
 #pragma anki input texture2D specTex "SPECULAR_TEX == 1 && PASS == 0"
 #pragma anki input texture2D roughnessTex "ROUGHNESS_TEX == 1 && PASS == 0"
-#pragma anki input texture2D metalTex "METAL_TEX == 1 && PASS == 0"
+#pragma anki input texture2D metallicTex "METAL_TEX == 1 && PASS == 0"
 #pragma anki input texture2D normalTex "NORMAL_TEX == 1 && PASS == 0 && LOD < 2"
 #pragma anki input texture2D heightTex "PARALLAX == 1 && PASS == 0 && LOD == 0"
 #pragma anki input texture2D emissiveTex "EMISSIVE_TEX == 1 && PASS == 0"
@@ -83,11 +83,11 @@ void main()
 #	endif
 
 #	if roughnessTex_DEFINED
-	const F32 roughness = texture(roughnessTex, globalSampler, uv).r;
+	const F32 roughness = texture(roughnessTex, globalSampler, uv).g;
 #	endif
 
-#	if metalTex_DEFINED
-	const F32 metallic = texture(metalTex, globalSampler, uv).r;
+#	if metallicTex_DEFINED
+	const F32 metallic = texture(metallicTex, globalSampler, uv).b;
 #	endif
 
 #	if normalTex_DEFINED

+ 1 - 1
src/anki/gr/common/FrameGpuAllocator.cpp

@@ -83,7 +83,7 @@ PtrSize FrameGpuAllocator::getUnallocatedMemorySize() const
 {
 	PtrSize perFrameSize = m_size / MAX_FRAMES_IN_FLIGHT;
 	PtrSize crntFrameStartOffset = perFrameSize * (m_frame % MAX_FRAMES_IN_FLIGHT);
-	PtrSize usedSize = m_offset.get() - crntFrameStartOffset + m_lastAllocatedSize.get();
+	PtrSize usedSize = m_offset.getNonAtomically() - crntFrameStartOffset + m_lastAllocatedSize.getNonAtomically();
 
 	PtrSize remaining = (perFrameSize >= usedSize) ? (perFrameSize - usedSize) : 0;
 	return remaining;

+ 11 - 24
src/anki/resource/ModelResource.cpp

@@ -31,15 +31,6 @@ static Bool attributeIsRequired(VertexAttributeLocation loc, Pass pass, Bool has
 	}
 }
 
-ModelPatch::ModelPatch(ModelResource* model)
-	: m_model(model)
-{
-}
-
-ModelPatch::~ModelPatch()
-{
-}
-
 void ModelPatch::getRenderingDataSub(
 	const RenderingKey& key, WeakArray<U8> subMeshIndicesArray, ModelRenderingInfo& inf) const
 {
@@ -118,10 +109,14 @@ U ModelPatch::getLodCount() const
 	return max<U>(m_meshCount, getMaterial()->getLodCount());
 }
 
-Error ModelPatch::create(
-	ConstWeakArray<CString> meshFNames, const CString& mtlFName, Bool async, ResourceManager* manager)
+Error ModelPatch::init(ModelResource* model,
+	ConstWeakArray<CString> meshFNames,
+	const CString& mtlFName,
+	Bool async,
+	ResourceManager* manager)
 {
 	ANKI_ASSERT(meshFNames.getSize() > 0);
+	m_model = model;
 
 	// Load material
 	ANKI_CHECK(manager->loadResource(mtlFName, m_mtl, async));
@@ -153,12 +148,6 @@ ModelResource::ModelResource(ResourceManager* manager)
 ModelResource::~ModelResource()
 {
 	auto alloc = getAllocator();
-
-	for(ModelPatch* patch : m_modelPatches)
-	{
-		alloc.deleteInstance(patch);
-	}
-
 	m_modelPatches.destroy(alloc);
 }
 
@@ -236,11 +225,9 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 
 		CString cstr;
 		ANKI_CHECK(materialEl.getText(cstr));
-		ModelPatch* mpatch = alloc.newInstance<ModelPatch>(this);
-
-		ANKI_CHECK(mpatch->create(ConstWeakArray<CString>(&meshesFnames[0], meshesCount), cstr, async, &getManager()));
 
-		m_modelPatches[count++] = mpatch;
+		ANKI_CHECK(m_modelPatches[count].init(
+			this, ConstWeakArray<CString>(&meshesFnames[0], meshesCount), cstr, async, &getManager()));
 
 		// Move to next
 		ANKI_CHECK(modelPatchEl.getNextSiblingElement("modelPatch", modelPatchEl));
@@ -259,11 +246,11 @@ Error ModelResource::load(const ResourceFilename& filename, Bool async)
 	// Calculate compound bounding volume
 	RenderingKey key;
 	key.m_lod = 0;
-	m_visibilityShape = m_modelPatches[0]->getMesh(key).getBoundingShape();
+	m_visibilityShape = m_modelPatches[0].getMesh(key).getBoundingShape();
 
-	for(auto it = m_modelPatches.begin() + 1; it != m_modelPatches.end(); ++it)
+	for(auto it = m_modelPatches.getBegin() + 1; it != m_modelPatches.getEnd(); ++it)
 	{
-		m_visibilityShape = m_visibilityShape.getCompoundShape((*it)->getMesh(key).getBoundingShape());
+		m_visibilityShape = m_visibilityShape.getCompoundShape((*it).getMesh(key).getBoundingShape());
 	}
 
 	return Error::NONE;

+ 10 - 9
src/anki/resource/ModelResource.h

@@ -85,11 +85,9 @@ public:
 /// Model patch interface class. Its very important class and it binds the material with the mesh
 class ModelPatch
 {
-public:
-	ModelPatch(ModelResource* model);
-
-	~ModelPatch();
+	friend class ModelResource;
 
+public:
 	MaterialResourcePtr getMaterial() const
 	{
 		return m_mtl;
@@ -124,9 +122,6 @@ public:
 		return m_meshes[0]->getSubMeshCount();
 	}
 
-	ANKI_USE_RESULT Error create(
-		ConstWeakArray<CString> meshFNames, const CString& mtlFName, Bool async, ResourceManager* resources);
-
 	/// Get information for multiDraw rendering. Given an array of submeshes that are visible return the correct indices
 	/// offsets and counts.
 	void getRenderingDataSub(const RenderingKey& key, WeakArray<U8> subMeshIndicesArray, ModelRenderingInfo& inf) const;
@@ -140,6 +135,12 @@ private:
 
 	/// Return the maximum number of LODs
 	U getLodCount() const;
+
+	ANKI_USE_RESULT Error init(ModelResource* model,
+		ConstWeakArray<CString> meshFNames,
+		const CString& mtlFName,
+		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.
@@ -176,7 +177,7 @@ public:
 
 	~ModelResource();
 
-	const DynamicArray<ModelPatch*>& getModelPatches() const
+	const DynamicArray<ModelPatch>& getModelPatches() const
 	{
 		return m_modelPatches;
 	}
@@ -194,7 +195,7 @@ public:
 	ANKI_USE_RESULT Error load(const ResourceFilename& filename, Bool async);
 
 private:
-	DynamicArray<ModelPatch*> m_modelPatches;
+	DynamicArray<ModelPatch> m_modelPatches;
 	Obb m_visibilityShape;
 	SkeletonResourcePtr m_skeleton;
 	DynamicArray<AnimationResourcePtr> m_animations;

+ 6 - 6
src/anki/scene/ModelNode.cpp

@@ -73,7 +73,7 @@ Error ModelNode::init(ModelResourcePtr resource, U32 modelPatchIdx)
 	newComponent<MoveFeedbackComponent>();
 	newComponent<SpatialComponent>(this, &m_obb);
 	MaterialRenderComponent* rcomp =
-		newComponent<MaterialRenderComponent>(this, m_model->getModelPatches()[m_modelPatchIdx]->getMaterial());
+		newComponent<MaterialRenderComponent>(this, m_model->getModelPatches()[m_modelPatchIdx].getMaterial());
 	rcomp->setup(
 		[](RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData) {
 			const ModelNode& self = *static_cast<const ModelNode*>(userData[0]);
@@ -107,7 +107,7 @@ Error ModelNode::init(const CString& modelFname)
 
 void ModelNode::onMoveComponentUpdate(const MoveComponent& move)
 {
-	m_obb = m_model->getModelPatches()[m_modelPatchIdx]->getBoundingShape().getTransformed(move.getWorldTransform());
+	m_obb = m_model->getModelPatches()[m_modelPatchIdx].getBoundingShape().getTransformed(move.getWorldTransform());
 
 	SpatialComponent& sp = getComponent<SpatialComponent>();
 	sp.markForUpdate();
@@ -123,11 +123,11 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 
 	if(ANKI_LIKELY(!ctx.m_debugDraw))
 	{
-		const ModelPatch* patch = m_model->getModelPatches()[m_modelPatchIdx];
+		const ModelPatch& patch = m_model->getModelPatches()[m_modelPatchIdx];
 
 		// That will not work on multi-draw and instanced at the same time. Make sure that there is no multi-draw
 		// anywhere
-		ANKI_ASSERT(patch->getSubMeshCount() == 1);
+		ANKI_ASSERT(patch.getSubMeshCount() == 1);
 
 		// Transforms
 		Array<Mat4, MAX_INSTANCES> trfs;
@@ -148,7 +148,7 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 
 		ctx.m_key.m_velocity = moved && ctx.m_key.m_pass == Pass::GB;
 		ModelRenderingInfo modelInf;
-		patch->getRenderingDataSub(ctx.m_key, WeakArray<U8>(), modelInf);
+		patch.getRenderingDataSub(ctx.m_key, WeakArray<U8>(), modelInf);
 
 		// Bones storage
 		if(m_model->getSkeleton())
@@ -167,7 +167,7 @@ void ModelNode::draw(RenderQueueDrawContext& ctx, ConstWeakArray<void*> userData
 
 		// Uniforms
 		static_cast<const MaterialRenderComponent&>(getComponent<RenderComponent>())
-			.allocateAndSetupUniforms(patch->getMaterial()->getDescriptorSetIndex(),
+			.allocateAndSetupUniforms(patch.getMaterial()->getDescriptorSetIndex(),
 				ctx,
 				ConstWeakArray<Mat4>(&trfs[0], userData.getSize()),
 				ConstWeakArray<Mat4>(&prevTrfs[0], userData.getSize()),

+ 2 - 1
src/anki/scene/SceneGraph.cpp

@@ -86,7 +86,8 @@ Error SceneGraph::init(AllocAlignedCallback allocCb,
 
 	// Init the default main camera
 	ANKI_CHECK(newSceneNode<PerspectiveCameraNode>("mainCamera", m_defaultMainCam));
-	m_defaultMainCam->getComponent<FrustumComponent>().setPerspective(0.1f, 1000.0f, toRad(60.0f), toRad(60.0f));
+	m_defaultMainCam->getComponent<FrustumComponent>().setPerspective(
+		0.1f, 1000.0f, toRad(60.0f), (1920.0f / 1080.0f) * toRad(60.0f));
 	m_mainCam = m_defaultMainCam;
 
 	// Create a special node for debugging the physics world

+ 2 - 2
src/anki/scene/StaticGeometryNode.cpp

@@ -55,10 +55,10 @@ Error StaticGeometryNode::init(const CString& filename)
 	ANKI_CHECK(getResourceManager().loadResource(filename, m_model));
 
 	U i = 0;
-	for(const ModelPatch* patch : m_model->getModelPatches())
+	for(const ModelPatch& patch : m_model->getModelPatches())
 	{
 		StaticGeometryPatchNode* node;
-		ANKI_CHECK(getSceneGraph().newSceneNode<StaticGeometryPatchNode>(CString(), node, patch));
+		ANKI_CHECK(getSceneGraph().newSceneNode<StaticGeometryPatchNode>(CString(), node, &patch));
 
 		++i;
 	}

+ 3 - 6
src/anki/util/String.cpp

@@ -152,12 +152,9 @@ String& String::operator=(StringAuto&& b)
 void String::create(Allocator alloc, const CStringType& cstr)
 {
 	auto len = cstr.getLength();
-	if(len > 0)
-	{
-		auto size = len + 1;
-		m_data.create(alloc, size);
-		std::memcpy(&m_data[0], &cstr[0], sizeof(Char) * size);
-	}
+	auto size = len + 1;
+	m_data.create(alloc, size);
+	std::memcpy(&m_data[0], &cstr[0], sizeof(Char) * size);
 }
 
 void String::create(Allocator alloc, ConstIterator first, ConstIterator last)

+ 2 - 2
src/anki/util/String.h

@@ -120,7 +120,7 @@ public:
 	/// Return true if the string is not initialized.
 	Bool isEmpty() const
 	{
-		return m_ptr == nullptr || getLength() == 0;
+		return m_ptr == nullptr;
 	}
 
 	Bool operator==(const CString& b) const
@@ -597,7 +597,7 @@ protected:
 
 	void checkInit() const
 	{
-		ANKI_ASSERT(m_data.getSize() > 1);
+		ANKI_ASSERT(m_data.getSize() > 0);
 	}
 
 	/// Append to this string.

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit 1b6ed99944f741db13dedc24f3a0c31c6ce3e318
+Subproject commit 096fcc88ed28039d0aae809f550599f86e8955e9

+ 23 - 11
tools/gltf_importer/Importer.cpp

@@ -245,7 +245,7 @@ Error Importer::getExtras(const cgltf_extras& extras, HashMapAuto<CString, Strin
 	StringListAuto tokenStrings(m_alloc);
 	for(const jsmntok_t& token : tokens)
 	{
-		if(token.type != JSMN_STRING)
+		if(token.type != JSMN_STRING && token.type != JSMN_PRIMITIVE)
 		{
 			continue;
 		}
@@ -391,10 +391,9 @@ Error Importer::visitNode(
 				gpuParticles = true;
 			}
 
-			ANKI_CHECK(m_sceneFile.writeText("\nnode = scene:new%sModelNode(\"%s\", \"%s%s\")\n",
+			ANKI_CHECK(m_sceneFile.writeText("\nnode = scene:new%sParticleEmitterNode(\"%s\", \"%s\")\n",
 				(gpuParticles) ? "Gpu" : "",
 				getNodeName(node).cstr(),
-				m_rpath.cstr(),
 				fname.cstr()));
 		}
 		else if((it = extras.find("collision")) != extras.getEnd() && *it == "true")
@@ -563,16 +562,36 @@ Error Importer::visitNode(
 			{
 				Importer* m_importer;
 				cgltf_mesh* m_mesh;
+				cgltf_material* m_mtl;
+				cgltf_skin* m_skin;
 			};
 			Ctx* ctx = m_alloc.newInstance<Ctx>();
 			ctx->m_importer = this;
 			ctx->m_mesh = node.mesh;
+			ctx->m_mtl = node.mesh->primitives[0].material;
+			ctx->m_skin = node.skin;
 
 			m_hive->submitTask(
 				[](void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore) {
 					Ctx& self = *static_cast<Ctx*>(userData);
 
-					const Error err = self.m_importer->writeMesh(*self.m_mesh);
+					Error err = self.m_importer->writeMesh(*self.m_mesh);
+
+					if(!err)
+					{
+						err = self.m_importer->writeMaterial(*self.m_mtl);
+					}
+
+					if(!err)
+					{
+						err = self.m_importer->writeModel(*self.m_mesh, (self.m_skin) ? self.m_skin->name : CString());
+					}
+
+					if(!err && self.m_skin)
+					{
+						err = self.m_importer->writeSkeleton(*self.m_skin);
+					}
+
 					if(err)
 					{
 						self.m_importer->m_errorInThread.store(err._getCode());
@@ -582,13 +601,6 @@ Error Importer::visitNode(
 				},
 				ctx);
 
-			ANKI_CHECK(writeMaterial(*node.mesh->primitives[0].material));
-			ANKI_CHECK(writeModel(*node.mesh, (node.skin) ? node.skin->name : CString()));
-			if(node.skin)
-			{
-				ANKI_CHECK(writeSkeleton(*node.skin));
-			}
-
 			ANKI_CHECK(writeModelNode(node, parentExtras));
 		}
 	}

+ 28 - 2
tools/gltf_importer/Importer.h

@@ -83,14 +83,15 @@ private:
 		readAccessor(accessor, out, [](const T&) {});
 	}
 
+	template<typename T, typename TFunc>
+	static void visitAccessor(const cgltf_accessor& accessor, TFunc func);
+
 	// Resources
 	ANKI_USE_RESULT Error writeMesh(const cgltf_mesh& mesh);
 	ANKI_USE_RESULT Error writeMaterial(const cgltf_material& mtl);
 	ANKI_USE_RESULT Error writeModel(const cgltf_mesh& mesh, CString skinName);
 	ANKI_USE_RESULT Error writeAnimation(const cgltf_animation& anim);
 	ANKI_USE_RESULT Error writeSkeleton(const cgltf_skin& skin);
-	template<typename T, typename TFunc>
-	static Error appendAttribute(const cgltf_attribute& attrib, DynamicArrayAuto<T>& out, TFunc func);
 
 	// Scene
 	ANKI_USE_RESULT Error writeTransform(const Transform& trf);
@@ -127,4 +128,29 @@ void Importer::readAccessor(const cgltf_accessor& accessor, DynamicArrayAuto<T>&
 	}
 }
 
+template<typename T, typename TFunc>
+void Importer::visitAccessor(const cgltf_accessor& accessor, TFunc func)
+{
+	const U8* base =
+		static_cast<const U8*>(accessor.buffer_view->buffer->data) + accessor.offset + accessor.buffer_view->offset;
+
+	PtrSize stride = accessor.buffer_view->stride;
+	if(stride == 0)
+	{
+		stride = accessor.stride;
+	}
+	ANKI_ASSERT(stride);
+	ANKI_ASSERT(stride >= sizeof(T));
+
+	const U count = accessor.count;
+
+	for(U i = 0; i < count; ++i)
+	{
+		const U8* ptr = base + stride * i;
+		T val;
+		memcpy(&val, ptr, sizeof(T)); // Memcpy because it might not be aligned
+		func(val);
+	}
+}
+
 } // end namespace anki

+ 119 - 20
tools/gltf_importer/ImporterMaterial.cpp

@@ -5,12 +5,15 @@
 
 #include "Importer.h"
 
+#define STB_IMAGE_IMPLEMENTATION
+#define STBI_ASSERT(x) ANKI_ASSERT(x)
+#include <stb/stb_image.h>
+
 namespace anki
 {
 
-const char* MATERIAL_TEMPLATE = R"(<!-- This file is auto generated by ExporterMaterial.cpp -->
+const char* MATERIAL_TEMPLATE = R"(<!-- This file is auto generated by ImporterMaterial.cpp -->
 <material shaderProgram="shaders/GBufferGeneric.glslp">
-
 	<mutators>
 		<mutator name="DIFFUSE_TEX" value="%diffTexMutator%"/>
 		<mutator name="SPECULAR_TEX" value="%specTexMutator%"/>
@@ -66,6 +69,52 @@ static CString getTextureUri(const cgltf_texture_view& view)
 	return view.texture->image->uri;
 }
 
+/// Read the texture and find out if
+static Error identifyMetallicRoughnessTexture(CString fname, F32& constantMetalines, F32& constantRoughness)
+{
+	int width, height, comp;
+	U8Vec4* data = reinterpret_cast<U8Vec4*>(stbi_load(fname.cstr(), &width, &height, &comp, 4));
+	if(!data)
+	{
+		ANKI_GLTF_LOGE("Failed to read: %s", fname.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	const F32 epsilon = 1.0f / 255.0f;
+	for(int y = 0; y < height; ++y)
+	{
+		for(int x = 0; x < width; ++x)
+		{
+			const U8Vec4& pixel = *(data + y * width + x);
+			const F32 m = F32(pixel.z()) / 255.0f;
+			const F32 r = F32(pixel.y()) / 255.0f;
+
+			if(x == 0 && y == 0)
+			{
+				// Initialize
+				constantMetalines = m;
+				constantRoughness = r;
+			}
+			else
+			{
+				if(constantMetalines < 0.0f || absolute(m - constantMetalines) > epsilon)
+				{
+					constantMetalines = -1.0f;
+				}
+
+				if(constantRoughness < 0.0f || absolute(r - constantRoughness) > epsilon)
+				{
+					constantRoughness = -1.0f;
+				}
+			}
+		}
+	}
+
+	stbi_image_free(data);
+
+	return Error::NONE;
+}
+
 Error Importer::writeMaterial(const cgltf_material& mtl)
 {
 	StringAuto fname(m_alloc);
@@ -78,7 +127,10 @@ Error Importer::writeMaterial(const cgltf_material& mtl)
 		return Error::USER_DATA;
 	}
 
-	std::string xml = MATERIAL_TEMPLATE;
+	HashMapAuto<CString, StringAuto> extras(m_alloc);
+	ANKI_CHECK(getExtras(mtl.extras, extras));
+
+	std::string xml = XML_HEADER + std::string("\n") + MATERIAL_TEMPLATE;
 
 	// Diffuse
 	if(mtl.pbr_metallic_roughness.base_color_texture.texture)
@@ -103,14 +155,49 @@ Error Importer::writeMaterial(const cgltf_material& mtl)
 	}
 
 	// Specular color (freshnel)
-	// TODO
 	{
-		xml = replaceAllString(xml, "%spec%", "<input shaderInput=\"specColor\" value=\"0.04 0.04 0.04\"/>");
+		Vec3 specular;
+		auto it = extras.find("specular");
+		if(it != extras.getEnd())
+		{
+			StringListAuto tokens(m_alloc);
+			tokens.splitString(it->toCString(), ' ');
+			if(tokens.getSize() != 3)
+			{
+				ANKI_GLTF_LOGE("Wrong specular: %s", it->cstr());
+				return Error::USER_DATA;
+			}
+
+			auto token = tokens.getBegin();
+			ANKI_CHECK(token->toNumber(specular.x()));
+			++token;
+			ANKI_CHECK(token->toNumber(specular.y()));
+			++token;
+			ANKI_CHECK(token->toNumber(specular.z()));
+		}
+		else
+		{
+			specular = Vec3(0.04f);
+		}
+
+		xml = replaceAllString(xml,
+			"%spec%",
+			"<input shaderInput=\"specColor\" value=\"" + std::to_string(specular.x()) + " "
+				+ std::to_string(specular.y()) + " " + std::to_string(specular.z()) + "\"/>");
 		xml = replaceAllString(xml, "%specTexMutator%", "0");
 	}
 
-	// Roughness
+	// Identify metallic/roughness texture
+	F32 constantMetaliness = -1.0f, constantRoughness = -1.0f;
 	if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture)
+	{
+		const CString fname = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture);
+
+		ANKI_CHECK(identifyMetallicRoughnessTexture(fname, constantMetaliness, constantRoughness));
+	}
+
+	// Roughness
+	if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && constantRoughness < 0.0f)
 	{
 		StringAuto uri(m_alloc);
 		uri.sprintf(
@@ -123,16 +210,18 @@ Error Importer::writeMaterial(const cgltf_material& mtl)
 	}
 	else
 	{
-		xml = replaceAllString(xml,
-			"%roughness%",
-			"<input shaderInput=\"roughness\" value=\"" + std::to_string(mtl.pbr_metallic_roughness.roughness_factor)
-				+ "\" />");
+		const F32 roughness = (constantRoughness >= 0.0f)
+								  ? constantRoughness * mtl.pbr_metallic_roughness.roughness_factor
+								  : mtl.pbr_metallic_roughness.roughness_factor;
+
+		xml = replaceAllString(
+			xml, "%roughness%", "<input shaderInput=\"roughness\" value=\"" + std::to_string(roughness) + "\" />");
 
 		xml = replaceAllString(xml, "%roughnessTexMutator%", "0");
 	}
 
 	// Metallic
-	if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture)
+	if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && constantMetaliness < 0.0f)
 	{
 		StringAuto uri(m_alloc);
 		uri.sprintf(
@@ -145,10 +234,12 @@ Error Importer::writeMaterial(const cgltf_material& mtl)
 	}
 	else
 	{
-		xml = replaceAllString(xml,
-			"%metallic%",
-			"<input shaderInput=\"metallic\" value=\"" + std::to_string(mtl.pbr_metallic_roughness.metallic_factor)
-				+ "\" />");
+		const F32 metalines = (constantMetaliness >= 0.0f)
+								  ? constantMetaliness * mtl.pbr_metallic_roughness.metallic_factor
+								  : mtl.pbr_metallic_roughness.metallic_factor;
+
+		xml = replaceAllString(
+			xml, "%metallic%", "<input shaderInput=\"metallic\" value=\"" + std::to_string(metalines) + "\" />");
 
 		xml = replaceAllString(xml, "%metalTexMutator%", "0");
 	}
@@ -194,20 +285,28 @@ Error Importer::writeMaterial(const cgltf_material& mtl)
 	}
 
 	// Subsurface
-	// TODO
 	{
-		F32 subsurface = 0.0f;
+		F32 subsurface;
+		auto it = extras.find("subsurface");
+		if(it != extras.getEnd())
+		{
+			ANKI_CHECK(it->toNumber(subsurface));
+		}
+		else
+		{
+			subsurface = 0.0f;
+		}
 
 		xml = replaceAllString(
 			xml, "%subsurface%", "<input shaderInput=\"subsurface\" value=\"" + std::to_string(subsurface) + "\"/>");
 	}
 
 	// Height texture
-	// TODO Add native support and not use occlusion map
-	if(mtl.occlusion_texture.texture)
+	auto it = extras.find("height_map");
+	if(it != extras.getEnd())
 	{
 		StringAuto uri(m_alloc);
-		uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.occlusion_texture).cstr());
+		uri.sprintf("%s%s", m_texrpath.cstr(), it->cstr());
 
 		xml = replaceAllString(xml,
 			"%height%",

+ 102 - 96
tools/gltf_importer/ImporterMesh.cpp

@@ -59,8 +59,8 @@ static U calcImplicitStride(const cgltf_attribute& attrib)
 }
 #endif
 
-template<typename T, typename TFunc>
-Error Importer::appendAttribute(const cgltf_attribute& attrib, DynamicArrayAuto<T>& out, TFunc func)
+template<typename T>
+Error checkAttribute(const cgltf_attribute& attrib)
 {
 	if(cgltfComponentCount(attrib.data->type) != T::COMPONENT_COUNT)
 	{
@@ -82,20 +82,31 @@ Error Importer::appendAttribute(const cgltf_attribute& attrib, DynamicArrayAuto<
 		return Error::USER_DATA;
 	}
 
-	readAccessor(*attrib.data, out, func);
-
 	return Error::NONE;
 }
 
+class TempVertex
+{
+public:
+	Vec4 m_tangent;
+	Vec4 m_boneWeights;
+	Vec3 m_position;
+	Vec3 m_normal;
+	Vec2 m_uv;
+	U16Vec4 m_boneIds;
+	F32 m_padding[2];
+
+	TempVertex()
+	{
+		zeroMemory(*this);
+	}
+};
+static_assert(sizeof(TempVertex) == 5 * sizeof(Vec4), "Will be hashed");
+
 class SubMesh
 {
 public:
-	DynamicArrayAuto<Vec3> m_positions;
-	DynamicArrayAuto<Vec3> m_normals;
-	DynamicArrayAuto<Vec4> m_tangents;
-	DynamicArrayAuto<Vec2> m_uvs;
-	DynamicArrayAuto<U16Vec4> m_boneIds;
-	DynamicArrayAuto<Vec4> m_boneWeights;
+	DynamicArrayAuto<TempVertex> m_verts;
 	DynamicArrayAuto<U16> m_indices;
 
 	Vec3 m_aabbMin{MAX_F32};
@@ -105,12 +116,7 @@ public:
 	U32 m_idxCount = MAX_U32;
 
 	SubMesh(HeapAllocator<U8>& alloc)
-		: m_positions(alloc)
-		, m_normals(alloc)
-		, m_tangents(alloc)
-		, m_uvs(alloc)
-		, m_boneIds(alloc)
-		, m_boneWeights(alloc)
+		: m_verts(alloc)
 		, m_indices(alloc)
 	{
 	}
@@ -149,6 +155,25 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 
 		SubMesh& submesh = *submeshes.emplaceBack(m_alloc);
 
+		U minVertCount = MAX_U;
+		U maxVertCount = MIN_U;
+		for(const cgltf_attribute* attrib = primitive->attributes;
+			attrib < primitive->attributes + primitive->attributes_count;
+			++attrib)
+		{
+			minVertCount = min(minVertCount, U(attrib->data->count));
+			maxVertCount = max(maxVertCount, U(attrib->data->count));
+		}
+
+		if(maxVertCount == 0 || minVertCount != maxVertCount)
+		{
+			ANKI_GLTF_LOGE("Wrong number of vertices");
+			return Error::USER_DATA;
+		}
+
+		const U vertCount = primitive->attributes[0].data->count;
+		submesh.m_verts.create(vertCount);
+
 		//
 		// Gather positions + normals + UVs
 		//
@@ -158,30 +183,45 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		{
 			if(attrib->type == cgltf_attribute_type_position)
 			{
-				appendAttribute(*attrib, submesh.m_positions, [&](const Vec3& pos) {
+				U count = 0;
+				ANKI_CHECK(checkAttribute<Vec3>(*attrib));
+				visitAccessor<Vec3>(*attrib->data, [&](const Vec3& pos) {
 					submesh.m_aabbMin = submesh.m_aabbMin.min(pos);
 					submesh.m_aabbMax = submesh.m_aabbMax.max(pos);
+					submesh.m_verts[count++].m_position = pos;
 				});
 			}
 			else if(attrib->type == cgltf_attribute_type_normal)
 			{
-				appendAttribute(*attrib, submesh.m_normals, [](const Vec3&) {});
+				U count = 0;
+				ANKI_CHECK(checkAttribute<Vec3>(*attrib));
+				visitAccessor<Vec3>(
+					*attrib->data, [&](const Vec3& normal) { submesh.m_verts[count++].m_normal = normal; });
 			}
 			else if(attrib->type == cgltf_attribute_type_texcoord)
 			{
-				appendAttribute(*attrib, submesh.m_uvs, [&](const Vec2& uv) {
+				U count = 0;
+				ANKI_CHECK(checkAttribute<Vec2>(*attrib));
+				visitAccessor<Vec2>(*attrib->data, [&](const Vec2& uv) {
 					maxUvDistance = max(maxUvDistance, max(uv.x(), uv.y()));
 					minUvDistance = min(minUvDistance, min(uv.x(), uv.y()));
+					submesh.m_verts[count++].m_uv = uv;
 				});
 			}
 			else if(attrib->type == cgltf_attribute_type_joints)
 			{
-				appendAttribute(*attrib, submesh.m_boneIds, [](const U16Vec4&) {});
+				U count = 0;
+				ANKI_CHECK(checkAttribute<U16Vec4>(*attrib));
+				visitAccessor<U16Vec4>(
+					*attrib->data, [&](const U16Vec4& x) { submesh.m_verts[count++].m_boneIds = x; });
 				hasBoneWeights = true;
 			}
 			else if(attrib->type == cgltf_attribute_type_weights)
 			{
-				appendAttribute(*attrib, submesh.m_boneWeights, [](const Vec4&) {});
+				U count = 0;
+				ANKI_CHECK(checkAttribute<Vec4>(*attrib));
+				visitAccessor<Vec4>(
+					*attrib->data, [&](const Vec4& bw) { submesh.m_verts[count++].m_boneWeights = bw; });
 			}
 			else
 			{
@@ -192,49 +232,17 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		aabbMin = aabbMin.min(submesh.m_aabbMin);
 		aabbMax = aabbMax.max(submesh.m_aabbMax);
 
-		const U vertCount = submesh.m_positions.getSize();
-		if(submesh.m_positions.getSize() == 0)
-		{
-			ANKI_GLTF_LOGE("Zero size of positions");
-			return Error::USER_DATA;
-		}
-
-		if(submesh.m_normals.getSize() != vertCount)
-		{
-			ANKI_GLTF_LOGE("Normal count is incorrect. Is %u, should be %u", submesh.m_normals.getSize(), vertCount);
-			return Error::USER_DATA;
-		}
-
-		if(submesh.m_uvs.getSize() != vertCount)
-		{
-			ANKI_GLTF_LOGE("UV count is incorrect. Is %u, should be %u", submesh.m_uvs.getSize(), vertCount);
-			return Error::USER_DATA;
-		}
-
-		if(submesh.m_boneIds.getSize() != 0 && submesh.m_boneIds.getSize() != vertCount)
-		{
-			ANKI_GLTF_LOGE("Bone IDs count is incorrect. Is %u, should be %u", submesh.m_boneIds.getSize(), vertCount);
-			return Error::USER_DATA;
-		}
-
-		if(submesh.m_boneWeights.getSize() != 0 && submesh.m_boneWeights.getSize() != vertCount)
-		{
-			ANKI_GLTF_LOGE(
-				"Bone weights count is incorrect. Is %u, should be %u", submesh.m_boneWeights.getSize(), vertCount);
-			return Error::USER_DATA;
-		}
-
 		//
 		// Fix normals. If normal A and normal B have the same position then try to merge them
 		//
 		for(U v = 0; v < vertCount; ++v)
 		{
-			const Vec3& pos = submesh.m_positions[v];
-			Vec3& normal = submesh.m_normals[v];
+			const Vec3& pos = submesh.m_verts[v].m_position;
+			Vec3& normal = submesh.m_verts[v].m_normal;
 
 			for(U prevV = 0; prevV < v; ++prevV)
 			{
-				const Vec3& otherPos = submesh.m_positions[prevV];
+				const Vec3& otherPos = submesh.m_verts[prevV].m_position;
 
 				// Check the positions dist
 				const F32 posDist = (otherPos - pos).getLengthSquared();
@@ -244,7 +252,7 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 				}
 
 				// Check angle of the normals
-				Vec3& otherNormal = submesh.m_normals[prevV];
+				Vec3& otherNormal = submesh.m_verts[prevV].m_normal;
 				const F32 ang = acos(clamp(otherNormal.dot(normal), -1.0f, 1.0f));
 				if(ang > m_normalsMergeAngle)
 				{
@@ -301,8 +309,6 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		//
 		{
 			DynamicArrayAuto<Vec3> bitangents(m_alloc);
-
-			submesh.m_tangents.create(vertCount, Vec4(0.0f));
 			bitangents.create(vertCount, Vec3(0.0f));
 
 			for(U i = 0; i < submesh.m_indices.getSize(); i += 3)
@@ -311,14 +317,14 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 				const U i1 = submesh.m_indices[i + 1];
 				const U i2 = submesh.m_indices[i + 2];
 
-				const Vec3& v0 = submesh.m_positions[i0];
-				const Vec3& v1 = submesh.m_positions[i1];
-				const Vec3& v2 = submesh.m_positions[i2];
+				const Vec3& v0 = submesh.m_verts[i0].m_position;
+				const Vec3& v1 = submesh.m_verts[i1].m_position;
+				const Vec3& v2 = submesh.m_verts[i2].m_position;
 				const Vec3 edge01 = v1 - v0;
 				const Vec3 edge02 = v2 - v0;
 
-				const Vec2 uvedge01 = submesh.m_uvs[i1] - submesh.m_uvs[i0];
-				const Vec2 uvedge02 = submesh.m_uvs[i2] - submesh.m_uvs[i0];
+				const Vec2 uvedge01 = submesh.m_verts[i1].m_uv - submesh.m_verts[i0].m_uv;
+				const Vec2 uvedge02 = submesh.m_verts[i2].m_uv - submesh.m_verts[i0].m_uv;
 
 				F32 det = (uvedge01.y() * uvedge02.x()) - (uvedge01.x() * uvedge02.y());
 				det = (isZero(det)) ? 0.0001f : (1.0f / det);
@@ -344,9 +350,9 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 					b.normalize();
 				}
 
-				submesh.m_tangents[i0] += Vec4(t, 0.0f);
-				submesh.m_tangents[i1] += Vec4(t, 0.0f);
-				submesh.m_tangents[i2] += Vec4(t, 0.0f);
+				submesh.m_verts[i0].m_tangent += Vec4(t, 0.0f);
+				submesh.m_verts[i1].m_tangent += Vec4(t, 0.0f);
+				submesh.m_verts[i2].m_tangent += Vec4(t, 0.0f);
 
 				bitangents[i0] += b;
 				bitangents[i1] += b;
@@ -355,15 +361,15 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 
 			for(U i = 0; i < vertCount; ++i)
 			{
-				Vec3 t = Vec3(submesh.m_tangents[i].xyz());
-				const Vec3& n = submesh.m_normals[i];
+				Vec3 t = Vec3(submesh.m_verts[i].m_tangent.xyz());
+				const Vec3& n = submesh.m_verts[i].m_normal;
 				Vec3& b = bitangents[i];
 
 				t.normalize();
 				b.normalize();
 
 				const F32 w = ((n.cross(t)).dot(b) < 0.0f) ? 1.0f : -1.0f;
-				submesh.m_tangents[i] = Vec4(t, w);
+				submesh.m_verts[i].m_tangent = Vec4(t, w);
 			}
 		}
 
@@ -381,9 +387,9 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 			const U i1 = submesh.m_indices[i + 1];
 			const U i2 = submesh.m_indices[i + 2];
 
-			const Vec3& v0 = submesh.m_positions[i0];
-			const Vec3& v1 = submesh.m_positions[i1];
-			const Vec3& v2 = submesh.m_positions[i2];
+			const Vec3& v0 = submesh.m_verts[i0].m_position;
+			const Vec3& v1 = submesh.m_verts[i1].m_position;
+			const Vec3& v2 = submesh.m_verts[i2].m_position;
 
 			if(computeTriangleArea(v0, v1, v2) <= EPSILON)
 			{
@@ -395,9 +401,9 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 
 			for(const SubMesh& submeshB : submeshes)
 			{
-				for(const Vec3& posB : submeshB.m_positions)
+				for(const TempVertex& vertB : submeshB.m_verts)
 				{
-					const F32 test = testPlane(plane, posB.xyz0());
+					const F32 test = testPlane(plane, vertB.m_position.xyz0());
 					if(test > EPSILON)
 					{
 						convex = false;
@@ -552,25 +558,25 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		const MeshBinaryFile::VertexAttribute& posa = header.m_vertexAttributes[VertexAttributeLocation::POSITION];
 		if(posa.m_format == Format::R32G32B32_SFLOAT)
 		{
-			ANKI_CHECK(file.write(&submesh.m_positions[0], submesh.m_positions.getSizeInBytes()));
+			DynamicArrayAuto<Vec3> positions(m_alloc);
+			positions.create(submesh.m_verts.getSize());
+			for(U v = 0; v < submesh.m_verts.getSize(); ++v)
+			{
+				positions[v] = submesh.m_verts[v].m_position;
+			}
+			ANKI_CHECK(file.write(&positions[0], positions.getSizeInBytes()));
 		}
 		else if(posa.m_format == Format::R16G16B16A16_SFLOAT)
 		{
-			DynamicArrayAuto<F16> pos16(m_alloc);
-			pos16.create(submesh.m_positions.getSize() * 4);
+			DynamicArrayAuto<HVec4> pos16(m_alloc);
+			pos16.create(submesh.m_verts.getSize());
 
-			const Vec3* p32 = &submesh.m_positions[0];
-			const Vec3* p32end = p32 + submesh.m_positions.getSize();
-			F16* p16 = &pos16[0];
-			while(p32 != p32end)
+			for(U v = 0; v < submesh.m_verts.getSize(); ++v)
 			{
-				p16[0] = F16(p32->x());
-				p16[1] = F16(p32->y());
-				p16[2] = F16(p32->z());
-				p16[3] = F16(0.0f);
-
-				p32 += 1;
-				p16 += 4;
+				pos16[v] = HVec4(F16(submesh.m_verts[v].m_position.x()),
+					F16(submesh.m_verts[v].m_position.y()),
+					F16(submesh.m_verts[v].m_position.z()),
+					F16(0.0f));
 			}
 
 			ANKI_CHECK(file.write(&pos16[0], pos16.getSizeInBytes()));
@@ -592,13 +598,13 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		};
 
 		DynamicArrayAuto<Vert> verts(m_alloc);
-		verts.create(submesh.m_positions.getSize());
+		verts.create(submesh.m_verts.getSize());
 
 		for(U i = 0; i < verts.getSize(); ++i)
 		{
-			const Vec3& normal = submesh.m_normals[i];
-			const Vec4& tangent = submesh.m_tangents[i];
-			const Vec2& uv = submesh.m_uvs[i];
+			const Vec3& normal = submesh.m_verts[i].m_normal;
+			const Vec4& tangent = submesh.m_verts[i].m_tangent;
+			const Vec2& uv = submesh.m_verts[i].m_uv;
 
 			verts[i].m_n = packColorToR10G10B10A2SNorm(normal.x(), normal.y(), normal.z(), 0.0f);
 			verts[i].m_t = packColorToR10G10B10A2SNorm(tangent.x(), tangent.y(), tangent.z(), tangent.w());
@@ -630,7 +636,7 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 		for(const SubMesh& submesh : submeshes)
 		{
 			DynamicArrayAuto<WeightVertex> verts(m_alloc);
-			verts.create(submesh.m_boneIds.getSize());
+			verts.create(submesh.m_verts.getSize());
 
 			for(U i = 0; i < verts.getSize(); ++i)
 			{
@@ -638,8 +644,8 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 
 				for(U c = 0; c < 4; ++c)
 				{
-					vert.m_boneIndices[c] = submesh.m_boneIds[i][c];
-					vert.m_weights[c] = submesh.m_boneWeights[i][c];
+					vert.m_boneIndices[c] = submesh.m_verts[i].m_boneIds[c];
+					vert.m_weights[c] = submesh.m_verts[i].m_boneWeights[c];
 				}
 
 				verts[i] = vert;

+ 8 - 0
tools/gltf_importer/Main.cpp

@@ -50,6 +50,10 @@ static Error parseCommandLineArgs(int argc, char** argv, CmdLineArgs& info)
 				{
 					info.m_texRpath.sprintf("%s/", argv[i]);
 				}
+				else
+				{
+					info.m_texRpath.sprintf("");
+				}
 			}
 			else
 			{
@@ -67,6 +71,10 @@ static Error parseCommandLineArgs(int argc, char** argv, CmdLineArgs& info)
 				{
 					info.m_rpath.sprintf("%s/", argv[i]);
 				}
+				else
+				{
+					info.m_rpath.sprintf("");
+				}
 			}
 			else
 			{