Browse Source

Fix bugs & optimize the tile allocation

Panagiotis Christopoulos Charitos 7 years ago
parent
commit
21b4cdc8ef

+ 4 - 4
shaders/ForwardShadingCommonFrag.glsl

@@ -55,7 +55,7 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 	{
 		PointLight light = u_pointLights[idx];
 
-		Vec3 diffC = diffCol * light.m_diffuseColorAtlasTileScale.rgb;
+		Vec3 diffC = diffCol * light.m_diffuseColorShadowAtlasTileScale.rgb;
 
 		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
 		F32 att = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
@@ -64,12 +64,12 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 		const F32 shadow = 1.0;
 #else
 		F32 shadow = 1.0;
-		if(light.m_diffuseColorAtlasTileScale.w >= 0.0)
+		if(light.m_diffuseColorShadowAtlasTileScale.w >= 0.0)
 		{
 			shadow = computeShadowFactorOmni(frag2Light,
 				light.m_radiusPad3.x,
-				light.m_atlasTileOffets,
-				light.m_diffuseColorAtlasTileScale.w,
+				light.m_shadowAtlasTileOffsets,
+				light.m_diffuseColorShadowAtlasTileScale.w,
 				u_shadowTex);
 		}
 #endif

+ 4 - 4
shaders/LightShading.glslp

@@ -154,18 +154,18 @@ void main()
 
 		LIGHTING_COMMON_BRDF();
 
-		ANKI_BRANCH if(light.m_diffuseColorAtlasTileScale.w >= 0.0)
+		ANKI_BRANCH if(light.m_diffuseColorShadowAtlasTileScale.w >= 0.0)
 		{
 			F32 shadow = computeShadowFactorOmni(frag2Light,
 				light.m_radiusPad3.x,
-				light.m_atlasTileOffets,
-				light.m_diffuseColorAtlasTileScale.w,
+				light.m_shadowAtlasTileOffsets,
+				light.m_diffuseColorShadowAtlasTileScale.w,
 				u_shadowTex);
 			lambert *= shadow;
 		}
 
 		out_color +=
-			(diffC + specC) * light.m_diffuseColorAtlasTileScale.rgb * (att * max(gbuffer.m_subsurface, lambert));
+			(diffC + specC) * light.m_diffuseColorShadowAtlasTileScale.rgb * (att * max(gbuffer.m_subsurface, lambert));
 	}
 
 	// Spot lights

+ 4 - 4
shaders/VolumetricLightingAccumulation.glslp

@@ -111,17 +111,17 @@ Vec4 accumulateLightsAndFog(U32 clusterIdx, Vec3 worldPos)
 		factor *= phaseFunction(viewDir, normalize(worldPos - light.m_posRadius.xyz), PHASE_FUNCTION_ANISOTROPY);
 
 #if ENABLE_SHADOWS
-		if(light.m_diffuseColorAtlasTileScale.w >= 0.0)
+		if(light.m_diffuseColorShadowAtlasTileScale.w >= 0.0)
 		{
 			factor *= computeShadowFactorOmni(frag2Light,
 				light.m_radiusPad3.x,
-				light.m_atlasTileOffets,
-				light.m_diffuseColorAtlasTileScale.w,
+				light.m_shadowAtlasTileOffsets,
+				light.m_diffuseColorShadowAtlasTileScale.w,
 				u_shadowTex);
 		}
 #endif
 
-		color += light.m_diffuseColorAtlasTileScale.rgb * factor;
+		color += light.m_diffuseColorShadowAtlasTileScale.rgb * factor;
 	}
 
 	// Spot lights

+ 16 - 3
shaders/glsl_cpp_common/ClusteredShading.h

@@ -13,6 +13,7 @@ ANKI_BEGIN_NAMESPACE
 const U32 TYPED_OBJECT_COUNT = 5u;
 const F32 INVALID_TEXTURE_INDEX = -1.0;
 const F32 LIGHT_FRUSTUM_NEAR_PLANE = 0.1 / 4.0; // The near plane on the shadow map frustums.
+const U32 MAX_SHADOW_CASCADES = 4u;
 
 // See the documentation in the ClustererBin class.
 struct ClustererMagicValues
@@ -25,11 +26,11 @@ struct ClustererMagicValues
 struct PointLight
 {
 	Vec4 m_posRadius; // xyz: Light pos in world space. w: The 1/(radius^2)
-	Vec4 m_diffuseColorAtlasTileScale; // xyz: diff color, w: UV scale for all tiles
+	Vec4 m_diffuseColorShadowAtlasTileScale; // xyz: diff color, w: UV scale for all tiles
 	Vec4 m_radiusPad3; // x: radius
-	Vec4 m_atlasTileOffets[3u]; // It's a Vec4 because of the std140 limitations
+	Vec4 m_shadowAtlasTileOffsets[3u]; // It's a Vec4 because of the std140 limitations
 };
-const U32 SIZEOF_POINT_LIGHT = 2 * SIZEOF_VEC4 + 8 * SIZEOF_VEC2;
+const U32 SIZEOF_POINT_LIGHT = 6 * SIZEOF_VEC4;
 ANKI_SHADER_STATIC_ASSERT(sizeof(PointLight) == SIZEOF_POINT_LIGHT)
 
 // Spot light
@@ -44,6 +45,18 @@ struct SpotLight
 const U32 SIZEOF_SPOT_LIGHT = 4 * SIZEOF_VEC4 + SIZEOF_MAT4;
 ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLight) == SIZEOF_SPOT_LIGHT)
 
+// Directional light (sun)
+struct DirectionalLight
+{
+	Vec3 m_diffuseColor;
+	U32 m_present; // If it's been used or not
+	Vec3 m_dir;
+	U32 m_padding;
+	Mat4 m_textureMatrices[MAX_SHADOW_CASCADES];
+};
+const U32 SIZEOF_DIR_LIGHT = 2 * SIZEOF_VEC4 + MAX_SHADOW_CASCADES * SIZEOF_MAT4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(DirectionalLight) == SIZEOF_DIR_LIGHT)
+
 // Representation of a reflection probe
 struct ReflectionProbe
 {

+ 7 - 5
src/anki/renderer/ClusterBin.cpp

@@ -558,17 +558,19 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 			PointLight& out = gpuLights[i];
 
 			out.m_posRadius = Vec4(in.m_worldPosition.xyz(), 1.0f / (in.m_radius * in.m_radius));
-			out.m_diffuseColorAtlasTileScale = in.m_diffuseColor.xyz0();
+			out.m_diffuseColorShadowAtlasTileScale = in.m_diffuseColor.xyz0();
 
 			if(in.m_shadowRenderQueues[0] == nullptr || !ctx.m_in->m_shadowsEnabled)
 			{
-				out.m_diffuseColorAtlasTileScale.w() = INVALID_TEXTURE_INDEX;
+				out.m_diffuseColorShadowAtlasTileScale.w() = INVALID_TEXTURE_INDEX;
 			}
 			else
 			{
-				out.m_diffuseColorAtlasTileScale.w() = in.m_atlasTileScale;
-				ANKI_ASSERT(sizeof(out.m_atlasTileOffets) == sizeof(in.m_atlasTileOffsets));
-				memcpy(&out.m_atlasTileOffets[0], &in.m_atlasTileOffsets[0], sizeof(in.m_atlasTileOffsets));
+				out.m_diffuseColorShadowAtlasTileScale.w() = in.m_shadowAtlasTileSize;
+				ANKI_ASSERT(sizeof(out.m_shadowAtlasTileOffsets) == sizeof(in.m_shadowAtlasTileOffsets));
+				memcpy(&out.m_shadowAtlasTileOffsets[0],
+					&in.m_shadowAtlasTileOffsets[0],
+					sizeof(in.m_shadowAtlasTileOffsets));
 			}
 
 			out.m_radiusPad3 = Vec4(in.m_radius);

+ 3 - 4
src/anki/renderer/RenderQueue.h

@@ -8,6 +8,7 @@
 #include <anki/renderer/Common.h>
 #include <anki/resource/RenderingKey.h>
 #include <anki/ui/Canvas.h>
+#include <shaders/glsl_cpp_common/ClusteredShading.h>
 
 namespace anki
 {
@@ -76,8 +77,8 @@ public:
 	const void* m_userData;
 	RenderQueueDrawCallback m_drawCallback;
 
-	Array<Vec2, 6> m_atlasTileOffsets; ///< Renderer internal.
-	F32 m_atlasTileScale; ///< Renderer internal.
+	Array<Vec2, 6> m_shadowAtlasTileOffsets; ///< Renderer internal.
+	F32 m_shadowAtlasTileSize; ///< Renderer internal.
 
 	PointLightQueueElement()
 	{
@@ -119,8 +120,6 @@ public:
 
 static_assert(std::is_trivially_destructible<SpotLightQueueElement>::value == true, "Should be trivially destructible");
 
-const U32 MAX_SHADOW_CASCADES = 4;
-
 /// Directional light render queue element.
 class DirectionalLightQueueElement final
 {

+ 7 - 7
src/anki/renderer/ShadowMapping.cpp

@@ -422,7 +422,7 @@ TileAllocatorResult ShadowMapping::allocateTilesAndScratchTiles(U64 lightUuid,
 			continue;
 		}
 
-		ANKI_ASSERT(subResults[i] == TileAllocatorResult::ALLOCATION_SUCCEDDED);
+		ANKI_ASSERT(subResults[i] == TileAllocatorResult::ALLOCATION_SUCCEEDED);
 
 		res = m_scratchTileAlloc.allocate(m_r->getGlobalTimestamp(),
 			faceTimestamps[i],
@@ -485,7 +485,7 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 		static Bool firstRun = true;
 		if(firstRun)
 		{
-			ANKI_ASSERT(res == TileAllocatorResult::ALLOCATION_SUCCEDDED);
+			ANKI_ASSERT(res == TileAllocatorResult::ALLOCATION_SUCCEEDED);
 			firstRun = false;
 		}
 		else
@@ -603,7 +603,7 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 			F32 superTileSize = esmViewports[0][2]; // Should be the same for all tiles and faces
 			superTileSize -= 1.0f; // Remove 2 half texels to avoid bilinear filtering bleeding
 
-			light->m_atlasTileScale = superTileSize / atlasResolution;
+			light->m_shadowAtlasTileSize = superTileSize / atlasResolution;
 
 			numOfFacesThatHaveDrawcalls = 0;
 			for(U face = 0; face < 6; ++face)
@@ -616,8 +616,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 					const Viewport& scratchViewport = scratchViewports[numOfFacesThatHaveDrawcalls];
 
 					// Add a half texel to the viewport's start to avoid bilinear filtering bleeding
-					light->m_atlasTileOffsets[face].x() = (F32(esmViewport[0]) + 0.5f) / atlasResolution;
-					light->m_atlasTileOffsets[face].y() = (F32(esmViewport[1]) + 0.5f) / atlasResolution;
+					light->m_shadowAtlasTileOffsets[face].x() = (F32(esmViewport[0]) + 0.5f) / atlasResolution;
+					light->m_shadowAtlasTileOffsets[face].y() = (F32(esmViewport[1]) + 0.5f) / atlasResolution;
 
 					if(subResults[numOfFacesThatHaveDrawcalls] != TileAllocatorResult::CACHED)
 					{
@@ -640,8 +640,8 @@ void ShadowMapping::processLights(RenderingContext& ctx, U32& threadCountForScra
 					esmViewport[2] = superTileSize;
 					esmViewport[3] = superTileSize;
 
-					light->m_atlasTileOffsets[face].x() = (F32(esmViewport[0]) + 0.5f) / atlasResolution;
-					light->m_atlasTileOffsets[face].y() = (F32(esmViewport[1]) + 0.5f) / atlasResolution;
+					light->m_shadowAtlasTileOffsets[face].x() = (F32(esmViewport[0]) + 0.5f) / atlasResolution;
+					light->m_shadowAtlasTileOffsets[face].y() = (F32(esmViewport[1]) + 0.5f) / atlasResolution;
 				}
 			}
 		}

+ 87 - 15
src/anki/renderer/TileAllocator.cpp

@@ -147,6 +147,59 @@ void TileAllocator::updateSuperTiles(const Tile& updateFrom)
 	}
 }
 
+Bool TileAllocator::searchTileRecursively(U crntTileIdx,
+	U crntTileLod,
+	U allocationLod,
+	Timestamp crntTimestamp,
+	U& emptyTileIdx,
+	U& toKickTileIdx,
+	Timestamp& tileToKickMinTimestamp) const
+{
+	const Tile& tile = m_allTiles[crntTileIdx];
+
+	if(crntTileLod == allocationLod)
+	{
+		// We may have a candidate
+
+		if(tile.m_lastUsedTimestamp == 0)
+		{
+			// Found empty
+			emptyTileIdx = crntTileIdx;
+			return true;
+		}
+		else if(tile.m_lastUsedTimestamp != crntTimestamp && tile.m_lastUsedTimestamp < tileToKickMinTimestamp)
+		{
+			// Found one with low timestamp
+			toKickTileIdx = crntTileIdx;
+			tileToKickMinTimestamp = tile.m_lightTimestamp;
+		}
+	}
+	else if(tile.m_subTiles[0] != MAX_U16)
+	{
+		// Move down the hierarchy
+
+		ANKI_ASSERT(allocationLod < crntTileLod);
+
+		for(const U16 idx : tile.m_subTiles)
+		{
+			const Bool done = searchTileRecursively(idx,
+				crntTileLod >> 1,
+				allocationLod,
+				crntTimestamp,
+				emptyTileIdx,
+				toKickTileIdx,
+				tileToKickMinTimestamp);
+
+			if(done)
+			{
+				return done;
+			}
+		}
+	}
+
+	return false;
+}
+
 TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp,
 	Timestamp lightTimestamp,
 	U64 lightUuid,
@@ -198,32 +251,51 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp,
 
 				updateTileHierarchy(tile);
 
-				return (needsReRendering) ? TileAllocatorResult::ALLOCATION_SUCCEDDED : TileAllocatorResult::CACHED;
+				return (needsReRendering) ? TileAllocatorResult::ALLOCATION_SUCCEEDED : TileAllocatorResult::CACHED;
 			}
 		}
 	}
 
-	// Start searching for a tile
+	// Start searching for a suitable tile. Do a hieratchical search to end up with better locality and not better
+	// utilization of the atlas' space
 	U emptyTileIdx = MAX_U;
 	U toKickTileIdx = MAX_U;
 	Timestamp tileToKickMinTimestamp = MAX_TIMESTAMP;
-	const U firstTileIdx = m_lodFirstTileIndex[lod];
-	const U lastTileIdx = m_lodFirstTileIndex[lod + 1];
-	for(U tileIdx = firstTileIdx; tileIdx <= lastTileIdx; ++tileIdx)
+	const U maxLod = m_lodCount - 1;
+	if(lod == maxLod)
 	{
-		const Tile& tile = m_allTiles[tileIdx];
+		// This search is simple, iterate the tiles of the max LOD
 
-		if(tile.m_lastUsedTimestamp == 0)
+		for(U tileIdx = m_lodFirstTileIndex[maxLod]; tileIdx <= m_lodFirstTileIndex[maxLod + 1]; ++tileIdx)
 		{
-			// Found empty
-			emptyTileIdx = tileIdx;
-			break;
+			const Tile& tile = m_allTiles[tileIdx];
+
+			if(tile.m_lastUsedTimestamp == 0)
+			{
+				// Found empty
+				emptyTileIdx = tileIdx;
+				break;
+			}
+			else if(tile.m_lastUsedTimestamp != crntTimestamp && tile.m_lastUsedTimestamp < tileToKickMinTimestamp)
+			{
+				// Found one with low timestamp
+				toKickTileIdx = tileIdx;
+				tileToKickMinTimestamp = tile.m_lightTimestamp;
+			}
 		}
-		else if(tile.m_lastUsedTimestamp != crntTimestamp && tile.m_lastUsedTimestamp < tileToKickMinTimestamp)
+	}
+	else
+	{
+		// Need to do a recursive search
+
+		for(U tileIdx = m_lodFirstTileIndex[maxLod]; tileIdx <= m_lodFirstTileIndex[maxLod + 1]; ++tileIdx)
 		{
-			// Found some with low timestamp
-			toKickTileIdx = tileIdx;
-			tileToKickMinTimestamp = tile.m_lightTimestamp;
+			const Bool done = searchTileRecursively(
+				tileIdx, maxLod, lod, crntTimestamp, emptyTileIdx, toKickTileIdx, tileToKickMinTimestamp);
+			if(done)
+			{
+				break;
+			}
 		}
 	}
 
@@ -267,7 +339,7 @@ TileAllocatorResult TileAllocator::allocate(Timestamp crntTimestamp,
 		allocatedTile.m_viewport[2],
 		allocatedTile.m_viewport[3]};
 
-	return TileAllocatorResult::ALLOCATION_SUCCEDDED;
+	return TileAllocatorResult::ALLOCATION_SUCCEEDED;
 }
 
 void TileAllocator::invalidateCache(U64 lightUuid, U32 lightFace)

+ 12 - 2
src/anki/renderer/TileAllocator.h

@@ -16,9 +16,9 @@ namespace anki
 /// The result of a tile allocation.
 enum class TileAllocatorResult : U32
 {
-	CACHED, ///< The tile is caches. No need to do anything.
+	CACHED, ///< The tile is cached. No need to re-render it.
 	ALLOCATION_FAILED, ///< No more available tiles.
-	ALLOCATION_SUCCEDDED ///< Allocation succeded but the tile needs update.
+	ALLOCATION_SUCCEEDED ///< Allocation succeded but the tile needs update.
 };
 
 /// Allocates tiles out of a tilemap suitable for shadow mapping.
@@ -71,11 +71,21 @@ private:
 
 	void updateSuperTiles(const Tile& updateFrom);
 
+	/// Given a tile move the hierarchy up and down to update the hierarchy this tile belongs to.
 	void updateTileHierarchy(const Tile& updateFrom)
 	{
 		updateSubTiles(updateFrom);
 		updateSuperTiles(updateFrom);
 	}
+
+	/// Search for a tile recursively.
+	Bool searchTileRecursively(U crntTileIdx,
+		U crntTileLod,
+		U allocationLod,
+		Timestamp crntTimestamp,
+		U& emptyTileIdx,
+		U& toKickTileIdx,
+		Timestamp& tileToKickMinTimestamp) const;
 };
 /// @}
 

+ 9 - 0
src/anki/scene/LightNode.cpp

@@ -313,12 +313,21 @@ public:
 			// Move updated
 			LightComponent& lightc = node.getComponent<LightComponent>();
 			lightc.updateWorldTransform(move.getWorldTransform());
+
+			SpatialComponent& spatialc = node.getComponent<SpatialComponent>();
+			spatialc.setSpatialOrigin(move.getWorldTransform().getOrigin());
+			spatialc.markForUpdate();
 		}
 
 		return Error::NONE;
 	}
 };
 
+DirectionalLightNode::DirectionalLightNode(SceneGraph* scene, CString name)
+	: SceneNode(scene, name)
+{
+}
+
 Error DirectionalLightNode::init()
 {
 	newComponent<MoveComponent>();

+ 8 - 0
src/anki/scene/Visibility.cpp

@@ -588,6 +588,14 @@ void CombineResultsTask::combine()
 	ANKI_VIS_COMBINE(DecalQueueElement, m_decals);
 	ANKI_VIS_COMBINE(FogDensityQueueElement, m_fogDensityVolumes);
 
+	for(U i = 0; i < threadCount; ++i)
+	{
+		if(m_frcCtx->m_queueViews[i].m_directionalLight.m_shadowCascadeCount > 0)
+		{
+			results.m_directionalLight = m_frcCtx->m_queueViews[i].m_directionalLight;
+		}
+	}
+
 #undef ANKI_VIS_COMBINE
 #undef ANKI_VIS_COMBINE_AND_PTR
 

+ 4 - 2
src/anki/scene/components/LightComponent.cpp

@@ -70,8 +70,10 @@ void LightComponent::setupDirectionalLightQueueElement(
 
 	el.m_userData = this;
 	el.m_drawCallback = derectionalLightDebugDrawCallback;
+	el.m_uuid = m_uuid;
 	el.m_diffuseColor = m_diffColor.xyz();
 	el.m_direction = m_trf.getRotation().getZAxis().xyz();
+	el.m_shadowCascadeCount = m_dir.m_cascadeCount;
 
 	// Compute sub frustum edges
 	const Mat4 lightTrf(m_trf);
@@ -122,7 +124,7 @@ void LightComponent::setupDirectionalLightQueueElement(
 			for(U j = i * 4; j < i * 4 + 8; ++j)
 			{
 				aabbMin = aabbMin.min(edgesLightSpace[j].xyz());
-				aabbMax = aabbMax.min(edgesLightSpace[j].xyz());
+				aabbMax = aabbMax.max(edgesLightSpace[j].xyz());
 			}
 
 			aabbMax.z() = min(0.0f, aabbMax.z()); // Max can't go behind the light
@@ -153,7 +155,7 @@ void LightComponent::setupDirectionalLightQueueElement(
 			eye = lightTrf * eye;
 
 			Transform cascadeTransform = m_trf;
-			cascadeTransform.setOrigin(eye);
+			cascadeTransform.setOrigin(eye.xyz0());
 			const Mat4 cascadeViewMat = Mat4(cascadeTransform.getInverse());
 
 			static const Mat4 biasMat4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);

+ 63 - 0
src/anki/script/Scene.cpp

@@ -3564,6 +3564,68 @@ static int wrapSceneGraphnewSpotLightNode(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneGraph::newDirectionalLightNode.
+static inline int pwrapSceneGraphnewDirectionalLightNode(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSceneGraph, ud))
+	{
+		return -1;
+	}
+
+	SceneGraph* self = ud->getData<SceneGraph>();
+
+	// Pop arguments
+	const char* arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkString(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	DirectionalLightNode* ret = newSceneNode<DirectionalLightNode>(self, arg0);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "DirectionalLightNode");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoDirectionalLightNode;
+	ud->initPointed(&luaUserDataTypeInfoDirectionalLightNode, const_cast<DirectionalLightNode*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneGraph::newDirectionalLightNode.
+static int wrapSceneGraphnewDirectionalLightNode(lua_State* l)
+{
+	int res = pwrapSceneGraphnewDirectionalLightNode(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Pre-wrap method SceneGraph::newStaticCollisionNode.
 static inline int pwrapSceneGraphnewStaticCollisionNode(lua_State* l)
 {
@@ -4047,6 +4109,7 @@ static inline void wrapSceneGraph(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "newModelNode", wrapSceneGraphnewModelNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newPointLightNode", wrapSceneGraphnewPointLightNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newSpotLightNode", wrapSceneGraphnewSpotLightNode);
+	LuaBinder::pushLuaCFuncMethod(l, "newDirectionalLightNode", wrapSceneGraphnewDirectionalLightNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newStaticCollisionNode", wrapSceneGraphnewStaticCollisionNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newParticleEmitterNode", wrapSceneGraphnewParticleEmitterNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newReflectionProbeNode", wrapSceneGraphnewReflectionProbeNode);

+ 7 - 0
src/anki/script/Scene.xml

@@ -411,6 +411,13 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 					</args>
 					<return>SpotLightNode*</return>
 				</method>
+				<method name="newDirectionalLightNode">
+					<overrideCall><![CDATA[DirectionalLightNode* ret = newSceneNode<DirectionalLightNode>(self, arg0);]]></overrideCall>
+					<args>
+						<arg>const CString&amp;</arg>
+					</args>
+					<return>DirectionalLightNode*</return>
+				</method>
 				<method name="newStaticCollisionNode">
 					<overrideCall><![CDATA[StaticCollisionNode* ret = newSceneNode<StaticCollisionNode>(self, arg0, arg1, arg2);]]></overrideCall>
 					<args>

+ 10 - 10
tests/renderer/TileAllocator.cpp

@@ -26,15 +26,15 @@ ANKI_TEST(Renderer, TileAllocator)
 
 	// Allocate 1 med
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 1, 0, dcCount, 1, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 
 	// Allocate 3 big
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 2, 0, dcCount, 2, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 3, 0, dcCount, 2, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 4, 0, dcCount, 2, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 
 	// Fail to allocate 1 big
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 5, 0, dcCount, 2, viewport);
@@ -42,11 +42,11 @@ ANKI_TEST(Renderer, TileAllocator)
 
 	// Allocate 3 med
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 1, 1, dcCount, 1, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 1, 2, dcCount, 1, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 1, 3, dcCount, 1, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 
 	// Fail to allocate a small
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 6, 0, dcCount, 0, viewport);
@@ -57,17 +57,17 @@ ANKI_TEST(Renderer, TileAllocator)
 
 	// Allocate 3 big again
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 2, 0, dcCount + 1, 2, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 3, 0, dcCount, 2, viewport);
 	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::CACHED);
 	res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 4, 0, dcCount + 1, 2, viewport);
-	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+	ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 
 	// Allocate 16 small
 	for(U i = 0; i < 16; ++i)
 	{
 		res = talloc.allocate(crntTimestamp, lightTimestamp, lightUuid + 6 + i, 0, dcCount, 0, viewport);
-		ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEDDED);
+		ANKI_TEST_EXPECT_EQ(res, TileAllocatorResult::ALLOCATION_SUCCEEDED);
 	}
 }