浏览代码

Cluster based deferred shading is feature complete

Panagiotis Christopoulos Charitos 10 年之前
父节点
当前提交
a133bcc373

+ 3 - 5
include/anki/collision/CollisionShape.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_COLLISION_COLLISION_SHAPE_H
-#define ANKI_COLLISION_COLLISION_SHAPE_H
+#pragma once
 
 #include "anki/collision/Forward.h"
 #include "anki/collision/Common.h"
@@ -71,11 +70,11 @@ public:
 	};
 
 	CollisionShape(Type cid)
-	:	m_cid(cid)
+		: m_cid(cid)
 	{}
 
 	CollisionShape(const CollisionShape& b)
-	:	m_cid(b.m_cid)
+		: m_cid(b.m_cid)
 	{
 		operator=(b);
 	}
@@ -146,4 +145,3 @@ private:
 
 } // end namespace anki
 
-#endif

+ 7 - 12
include/anki/collision/ConvexHullShape.h

@@ -3,11 +3,11 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_COLLISION_CONVEX_HULL_SHAPE_H
-#define ANKI_COLLISION_CONVEX_HULL_SHAPE_H
+#pragma once
 
 #include "anki/collision/ConvexShape.h"
 #include "anki/Math.h"
+#include "anki/util/NonCopyable.h"
 
 namespace anki {
 
@@ -15,7 +15,7 @@ namespace anki {
 /// @{
 
 /// Convex hull collision shape
-class ConvexHullShape: public ConvexShape
+class ConvexHullShape: public NonCopyable, public ConvexShape
 {
 public:
 	using Base = ConvexShape;
@@ -26,24 +26,20 @@ public:
 	}
 
 	ConvexHullShape()
-	:	Base(Type::CONVEX_HULL)
+		: Base(Type::CONVEX_HULL)
 	{}
 
 	ConvexHullShape(ConvexHullShape&& b)
-	:	Base(Type::CONVEX_HULL)
+		: Base(Type::CONVEX_HULL)
 	{
 		move(b);
 	}
 
-	ConvexHullShape(const ConvexHullShape& b) = delete;
-
 	~ConvexHullShape()
 	{
 		destroy();
 	}
 
-	ConvexHullShape& operator=(const ConvexHullShape& b) = delete;
-
 	ConvexHullShape& operator=(ConvexHullShape&& b)
 	{
 		move(b);
@@ -74,14 +70,14 @@ public:
 		const Vec3* buff, U count, PtrSize stride, PtrSize buffSize);
 
 	/// This function initializes the storage that holds the point cloud. This
-	/// method allocates a storage and the owner is the convex hull. 
+	/// method allocates a storage and the owner is the convex hull.
 	/// @param alloc The allocator to use for the point cloud
 	/// @param pointCount The number of points
 	void initStorage(CollisionAllocator<U8>& alloc, U pointCount);
 
 	/// This function initializes the storage that holds the point cloud using
 	/// a predefined buffer. The convex hull is not the owner of the storage.
-	/// @param buffer The base of the storage buffer. Size should be 
+	/// @param buffer The base of the storage buffer. Size should be
 	///               @a pointCount * sizeof(Vec4)
 	/// @param pointCount The number of points
 	void initStorage(void* buffer, U pointCount);
@@ -126,4 +122,3 @@ private:
 
 } // end namespace anki
 
-#endif

+ 24 - 26
include/anki/math/Transform.h

@@ -3,15 +3,12 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_MATH_TRANSFORM_H
-#define ANKI_MATH_TRANSFORM_H
+#pragma once
 
 #include "anki/math/CommonIncludes.h"
 
 namespace anki {
 
-#define ANKI_CHECK_W() ANKI_ASSERT(m_origin.w() == 0.0)
-
 /// @addtogroup math
 /// @{
 
@@ -26,11 +23,11 @@ public:
 	{}
 
 	TTransform(const TTransform& b)
-	:	m_origin(b.m_origin), 
-		m_rotation(b.m_rotation), 
-		m_scale(b.m_scale)
+		: m_origin(b.m_origin)
+		, m_rotation(b.m_rotation)
+		, m_scale(b.m_scale)
 	{
-		ANKI_CHECK_W();
+		checkW();
 	}
 
 	explicit TTransform(const Mat4& m4)
@@ -38,16 +35,16 @@ public:
 		m_rotation = TMat3x4<T>(m4.getRotationPart());
 		m_origin = m4.getTranslationPart().xyz0();
 		m_scale = 1.0;
-		ANKI_CHECK_W();
+		checkW();
 	}
 
-	explicit TTransform(const TVec4<T>& origin, const TMat3x4<T>& rotation,
+	TTransform(const TVec4<T>& origin, const TMat3x4<T>& rotation,
 		const T scale)
-	:	m_origin(origin), 
-		m_rotation(rotation), 
-		m_scale(scale)
+		: m_origin(origin)
+		, m_rotation(rotation)
+		, m_scale(scale)
 	{
-		ANKI_CHECK_W();
+		checkW();
 	}
 	/// @}
 
@@ -66,7 +63,7 @@ public:
 	void setOrigin(const TVec4<T>& o)
 	{
 		m_origin = o;
-		ANKI_CHECK_W();
+		checkW();
 	}
 
 	const TMat3x4<T>& getRotation() const
@@ -107,13 +104,13 @@ public:
 		m_origin = b.m_origin;
 		m_rotation = b.m_rotation;
 		m_scale = b.m_scale;
-		ANKI_CHECK_W();
+		checkW();
 		return *this;
 	}
 
 	Bool operator==(const TTransform& b) const
 	{
-		return m_origin == b.m_origin && m_rotation == b.m_rotation 
+		return m_origin == b.m_origin && m_rotation == b.m_rotation
 			&& m_scale == b.m_scale;
 	}
 
@@ -140,11 +137,11 @@ public:
 	/// @copybrief combineTTransformations
 	TTransform combineTransformations(const TTransform& b) const
 	{
-		ANKI_CHECK_W();
+		checkW();
 		const TTransform& a = *this;
 		TTransform out;
 
-		out.m_origin = 
+		out.m_origin =
 			TVec4<T>(a.m_rotation * (b.m_origin * a.m_scale), 0.0) + a.m_origin;
 
 		out.m_rotation = a.m_rotation.combineTransformations(b.m_rotation);
@@ -156,7 +153,7 @@ public:
 	/// Get the inverse transformation. Its faster that inverting a Mat4
 	TTransform getInverse() const
 	{
-		ANKI_CHECK_W();
+		checkW();
 		TTransform o;
 		o.m_rotation = m_rotation;
 		o.m_rotation.transposeRotationPart();
@@ -175,14 +172,14 @@ public:
 	/// Transform a TVec3
 	TVec3<T> transform(const TVec3<T>& b) const
 	{
-		ANKI_CHECK_W();
+		checkW();
 		return (m_rotation.getRotationPart() * (b * m_scale)) + m_origin.xyz();
 	}
 
 	/// Transform a TVec4. SIMD optimized
 	TVec4<T> transform(const TVec4<T>& b) const
 	{
-		ANKI_CHECK_W();
+		checkW();
 		TVec4<T> out = TVec4<T>(m_rotation * (b * m_scale), 0.0) + m_origin;
 		return out;
 	}
@@ -202,15 +199,16 @@ private:
 	TMat3x4<T> m_rotation; ///< The translation
 	T m_scale; ///< The uniform scaling
 	/// @}
+
+	void checkW() const
+	{
+		ANKI_ASSERT(m_origin.w() == 0.0);
+	}
 };
 
 /// F32 transformation
 typedef TTransform<F32> Transform;
-
 /// @}
 
-#undef ANKI_CHECK_W
-
 } // end namespace anki
 
-#endif

+ 4 - 10
include/anki/renderer/Is.h

@@ -28,6 +28,7 @@ class MoveComponent;
 class SpatialComponent;
 class FrustumComponent;
 class TaskCommonData;
+class ClustererTestResult;
 
 /// @addtogroup renderer
 /// @{
@@ -99,12 +100,8 @@ private:
 	Array<BufferPtr, MAX_FRAMES_IN_FLIGHT> m_sLightsBuffs;
 	U32 m_sLightsBuffSize = 0;
 
-	/// Contains all the textured spot lights
-	Array<BufferPtr, MAX_FRAMES_IN_FLIGHT> m_stLightsBuffs;
-	U32 m_stLightsBuffSize = 0;
-
-	/// Contains the number of lights per tile
-	Array<BufferPtr, MAX_FRAMES_IN_FLIGHT> m_tilesBuffers;
+	/// Contains the cluster info
+	Array<BufferPtr, MAX_FRAMES_IN_FLIGHT> m_clusterBuffers;
 
 	/// Contains light indices.
 	Array<BufferPtr, MAX_FRAMES_IN_FLIGHT> m_lightIdsBuffers;
@@ -150,9 +147,6 @@ private:
 	/// Prepare GL for rendering
 	void setState(CommandBufferPtr& cmdBuff);
 
-	/// Calculate the size of the tile
-	PtrSize calcTileSize() const;
-
 	void updateCommonBlock(CommandBufferPtr& cmdBuff, FrustumComponent& frc);
 
 	// Binning
@@ -164,7 +158,7 @@ private:
 		const MoveComponent& camMove, const FrustumComponent& camFrc,
 		TaskCommonData& task);
 	void binLight(SpatialComponent& sp, U pos, U lightType,
-		TaskCommonData& task);
+		TaskCommonData& task, ClustererTestResult& testResult);
 };
 
 /// @}

+ 18 - 9
include/anki/renderer/Renderer.h

@@ -10,6 +10,7 @@
 #include "anki/Math.h"
 #include "anki/Gr.h"
 #include "anki/scene/Forward.h"
+#include "anki/scene/Clusterer.h"
 #include "anki/resource/Forward.h"
 #include "anki/resource/ShaderResource.h"
 #include "anki/core/Timestamp.h"
@@ -120,7 +121,8 @@ public:
 		Array<CommandBufferPtr, RENDERER_COMMAND_BUFFERS_COUNT>& cmdBuff);
 
 anki_internal:
-	const U TILE_SIZE = 64;
+	static const U TILE_SIZE = 64;
+	static const U CLUSTER_SPLIT_COUNT = 12;
 
 	void getOutputFramebuffer(FramebufferPtr& outputFb, U32& width, U32& height)
 	{
@@ -177,19 +179,24 @@ anki_internal:
 		return m_tessellation;
 	}
 
-	const UVec2& getTilesCount() const
+	U getTileCount() const
 	{
-		return m_tilesCount;
+		return m_tileCount;
 	}
 
-	U getTilesCountXY() const
+	const UVec2& getTileCountXY() const
 	{
-		return m_tilesCountXY;
+		return m_tileCountXY;
 	}
 
-	UVec2 getTileSize() const
+	U getClusterCount() const
 	{
-		return UVec2(TILE_SIZE, TILE_SIZE);
+		return m_clusterer.getClusterCount();
+	}
+
+	const Clusterer& getClusterer() const
+	{
+		return m_clusterer;
 	}
 
 	const ShaderPtr& getDrawQuadVertexShader() const
@@ -283,6 +290,8 @@ private:
 	StackAllocator<U8> m_frameAlloc;
 	const Timestamp* m_globalTimestamp = nullptr;
 
+	Clusterer m_clusterer;
+
 	/// @name Rendering stages
 	/// @{
 	UniquePtr<Ms> m_ms; ///< Material rendering stage
@@ -301,8 +310,8 @@ private:
 	U8 m_samples; ///< Number of sample in multisampling
 	Bool8 m_isOffscreen; ///< Is offscreen renderer?
 	Bool8 m_tessellation;
-	UVec2 m_tilesCount;
-	U32 m_tilesCountXY;
+	U32 m_tileCount;
+	UVec2 m_tileCountXY;
 
 	ShaderResourcePtr m_drawQuadVert;
 

+ 16 - 8
include/anki/scene/Clusterer.h

@@ -51,10 +51,7 @@ private:
 class Clusterer
 {
 public:
-	static constexpr U TILE_SIZE = 64;
-
-	Clusterer(const GenericMemoryPoolAllocator<U8>& alloc)
-		: m_alloc(alloc)
+	Clusterer()
 	{}
 
 	~Clusterer()
@@ -63,23 +60,34 @@ public:
 		m_splitInfo.destroy(m_alloc);
 	}
 
-	void init(U clusterCountX, U clusterCountY, U clusterCountZ)
+	void init(const GenericMemoryPoolAllocator<U8>& alloc, U clusterCountX,
+		U clusterCountY, U clusterCountZ)
 	{
+		m_alloc = alloc;
 		m_counts[0] = clusterCountX;
 		m_counts[1] = clusterCountY;
 		m_counts[2] = clusterCountZ;
 	}
 
-	void prepare(
-		const PerspectiveFrustum& fr, const SArray<Vec2>& minMaxTileDepth);
+	U getClusterCount() const
+	{
+		return U(m_counts[0]) * U(m_counts[1]) * U(m_counts[2]);
+	}
+
+	void prepare(const PerspectiveFrustum& fr);
 
-	void initTempTestResults(const GenericMemoryPoolAllocator<U8>& alloc,
+	void initTestResults(const GenericMemoryPoolAllocator<U8>& alloc,
 		ClustererTestResult& rez) const;
 
 	/// Bin collision shape.
 	/// @param[in] cs The collision shape should be in view space.
 	void bin(const CollisionShape& cs, ClustererTestResult& rez) const;
 
+	void fillShaderParams(Vec4& params) const
+	{
+		params = Vec4(m_near, m_calcNearOpt, 0.0, 0.0);
+	}
+
 public:
 	GenericMemoryPoolAllocator<U8> m_alloc;
 

+ 1 - 0
shaders/IsCommon.glsl

@@ -13,5 +13,6 @@ layout(std140, row_major, binding = 0) uniform _0
 	vec4 u_projectionParams;
 	vec4 u_sceneAmbientColor;
 	vec4 u_groundLightDir;
+	vec4 u_clustererParams;
 	mat4 u_viewMat;
 };

+ 32 - 43
shaders/IsLp.frag.glsl

@@ -16,11 +16,7 @@
 #	define BRDF 1
 #endif
 
-// Representation of a tile
-struct Tile
-{
-	uvec4 offsetCounts; // x:offset y:points_count z:spots_count w:stex_count
-};
+const uint CLUSTER_COUNT_Z = CLUSTER_COUNT / (TILES_X_COUNT * TILES_Y_COUNT);
 
 // The base of all lights
 struct Light
@@ -36,7 +32,7 @@ struct Light
 // Spot light
 struct SpotLight
 {
-	Light lightBase;
+	Light base;
 	vec4 lightDir;
 	vec4 outerCosInnerCos;
 	mat4 texProjectionMat;
@@ -52,14 +48,9 @@ layout(std140, binding = 1) readonly buffer _s1
 	SpotLight u_spotLights[MAX_SPOT_LIGHTS];
 };
 
-layout(std140, binding = 2) readonly buffer _s2
-{
-	SpotLight u_spotTexLights[MAX_SPOT_TEX_LIGHTS];
-};
-
-layout(std140, binding = 3) readonly buffer _s3
+layout(std430, binding = 3) readonly buffer _s3
 {
-	Tile u_tiles[TILES_COUNT];
+	uint u_clusters[CLUSTER_COUNT];
 };
 
 layout(std430, binding = 4) readonly buffer _s5
@@ -93,6 +84,17 @@ vec3 getFragPosVSpace()
 	return fragPos;
 }
 
+//==============================================================================
+/// Calculate the cluster split
+uint calcK(float zVspace)
+{
+	zVspace = -zVspace;
+	float fk = sqrt((zVspace - u_clustererParams.x) / u_clustererParams.y);
+	uint k = uint(fk);
+	k = min(k, CLUSTER_COUNT_Z);
+	return k;
+}
+
 //==============================================================================
 float computeAttenuationFactor(
 	in float lightRadius,
@@ -298,7 +300,7 @@ void main()
 		in_texCoord, diffCol, normal, specCol, specPower);
 
 #if BRDF
-	float a2 = pow(max(EPSILON, specPower), 2.5);
+	float a2 = pow(max(EPSILON, specPower), 2.5); // XXX ?
 #else
 	specPower = max(EPSILON, specPower) * 128.0;
 #endif
@@ -306,12 +308,14 @@ void main()
 	// Ambient color
 	out_color = diffCol * u_sceneAmbientColor.rgb;
 
-	//Tile tile = u_tiles[in_instanceId];
-	#define tile u_tiles[in_instanceId]
+	// Get counts and offsets
+	uint k = calcK(fragPos.z);
+	uint cluster = u_clusters[in_instanceId + k * CLUSTER_COUNT_Z];
+	uint lightOffset = cluster >> 16u;
+	uint pointLightsCount = (cluster >> 8u) & 0xFFu;
+	uint spotLightsCount = cluster & 0xFFu;
 
 	// Point lights
-	uint lightOffset = tile.offsetCounts.x;
-	uint pointLightsCount = tile.offsetCounts.y;
 	for(uint i = 0U; i < pointLightsCount; ++i)
 	{
 		uint lightId = u_lightIndices[lightOffset++];
@@ -320,11 +324,11 @@ void main()
 		LIGHTING_COMMON();
 
 		float shadow = 1.0;
+		float shadowmapLayerIdx = light.diffuseColorShadowmapId.w;
 		if(light.diffuseColorShadowmapId.w < 128.0)
 		{
 			shadow = computeShadowFactorOmni(frag2Light,
-				light.diffuseColorShadowmapId.w,
-				-1.0 / light.posRadius.w);
+				shadowmapLayerIdx, -1.0 / light.posRadius.w);
 		}
 
 		out_color += (specC + diffC)
@@ -332,30 +336,11 @@ void main()
 	}
 
 	// Spot lights
-	uint spotLightsCount = tile.offsetCounts.z;
 	for(uint i = 0U; i < spotLightsCount; ++i)
 	{
 		uint lightId = u_lightIndices[lightOffset++];
 		SpotLight slight = u_spotLights[lightId];
-		Light light = slight.lightBase;
-
-		LIGHTING_COMMON();
-
-		float spot = computeSpotFactor(
-			l, slight.outerCosInnerCos.x,
-			slight.outerCosInnerCos.y,
-			slight.lightDir.xyz);
-
-		out_color += (diffC + specC) * (att * max(subsurface, lambert) * spot);
-	}
-
-	// Spot lights with shadow
-	uint spotTexLightsCount = tile.offsetCounts.w;
-	for(uint i = 0U; i < spotTexLightsCount; ++i)
-	{
-		uint lightId = u_lightIndices[lightOffset++];
-		SpotLight slight = u_spotTexLights[lightId];
-		Light light = slight.lightBase;
+		Light light = slight.base;
 
 		LIGHTING_COMMON();
 
@@ -364,9 +349,13 @@ void main()
 			slight.outerCosInnerCos.y,
 			slight.lightDir.xyz);
 
-		float shadowmapLayerId = light.diffuseColorShadowmapId.w;
-		float shadow = computeShadowFactor(slight.texProjectionMat,
-			fragPos, u_spotMapArr, shadowmapLayerId);
+		float shadow = 1.0;
+		float shadowmapLayerIdx = light.diffuseColorShadowmapId.w;
+		if(shadowmapLayerIdx < 128.0)
+		{
+			shadow = computeShadowFactor(slight.texProjectionMat,
+				fragPos, u_spotMapArr, shadowmapLayerIdx);
+		}
 
 		out_color += (diffC + specC)
 			* (att * spot * max(subsurface, lambert * shadow));

+ 1 - 1
src/core/Config.cpp

@@ -29,7 +29,7 @@ Config::Config()
 	newOption("is.maxPointLights", 384);
 	newOption("is.maxSpotLights", 16);
 	newOption("is.maxSpotTexLights", 8);
-	newOption("is.maxLightsPerTile", 16);
+	newOption("is.maxLightsPerCluster", 8);
 
 	newOption("lf.maxSpritesPerFlare", 8);
 	newOption("lf.maxFlares", 16);

+ 193 - 212
src/renderer/Is.cpp

@@ -21,93 +21,85 @@ namespace anki {
 // Misc                                                                        =
 //==============================================================================
 
-//==============================================================================
 // Shader structs and block representations. All positions and directions in
 // viewspace
 // For documentation see the shaders
 
-namespace shader {
-
-struct Tile
+struct ShaderCluster
 {
-	U32 m_offset;
-	U32 m_pointLightsCount;
-	U32 m_spotLightsCount;
-	U32 m_spotTexLightsCount;
+	/// If m_combo = 0xFFFFAABB then FFFF is the light index offset, AA the
+	/// number of point lights and BB the number of spot lights
+	U32 m_combo;
 };
 
-struct Light
+struct ShaderLight
 {
 	Vec4 m_posRadius;
 	Vec4 m_diffuseColorShadowmapId;
 	Vec4 m_specularColorTexId;
 };
 
-struct PointLight: Light
+struct ShaderPointLight: ShaderLight
 {};
 
-struct SpotLight: Light
+struct ShaderSpotLight: ShaderLight
 {
 	Vec4 m_lightDir;
 	Vec4 m_outerCosInnerCos;
 	Mat4 m_texProjectionMat; ///< Texture projection matrix
 };
 
-struct CommonUniforms
+struct ShaderCommonUniforms
 {
 	Vec4 m_projectionParams;
 	Vec4 m_sceneAmbientColor;
 	Vec4 m_groundLightDir;
+	Vec4 m_clustererParams;
 	Mat4 m_viewMat;
 };
 
-} // end namespace shader
-
-//==============================================================================
-
-using Lid = U32; ///< Light ID type
-static const U MAX_TYPED_LIGHTS_PER_TILE = 16;
+using Lid = U32; ///< Light ID
+static const U MAX_TYPED_LIGHTS_PER_CLUSTER = 16;
+static const F32 INVALID_TEXTURE_INDEX = 128.0;
 
-class TileData
+class ClusterData
 {
 public:
 	Atomic<U32> m_pointCount;
 	Atomic<U32> m_spotCount;
-	Atomic<U32> m_spotTexCount;
 
-	Array<Lid, MAX_TYPED_LIGHTS_PER_TILE> m_pointIds;
-	Array<Lid, MAX_TYPED_LIGHTS_PER_TILE> m_spotIds;
-	Array<Lid, MAX_TYPED_LIGHTS_PER_TILE> m_spotTexIds;
+	Array<Lid, MAX_TYPED_LIGHTS_PER_CLUSTER> m_pointIds;
+	Array<Lid, MAX_TYPED_LIGHTS_PER_CLUSTER> m_spotIds;
 };
 
 /// Common data for all tasks.
 class TaskCommonData
 {
 public:
+	TaskCommonData(StackAllocator<U8> alloc)
+		: m_tempClusters(alloc)
+	{}
+
 	// To fill the light buffers
-	SArray<shader::PointLight> m_pointLights;
-	SArray<shader::SpotLight> m_spotLights;
-	SArray<shader::SpotLight> m_spotTexLights;
+	SArray<ShaderPointLight> m_pointLights;
+	SArray<ShaderSpotLight> m_spotLights;
 
 	SArray<Lid> m_lightIds;
-	SArray<shader::Tile> m_tiles;
+	SArray<ShaderCluster> m_clusters;
 
-	Atomic<U32> m_pointLightsCount;
-	Atomic<U32> m_spotLightsCount;
-	Atomic<U32> m_spotTexLightsCount;
+	Atomic<U32> m_pointLightsCount = {0};
+	Atomic<U32> m_spotLightsCount = {0};
 
 	// To fill the tile buffers
-	Array2d<TileData, ANKI_RENDERER_MAX_TILES_Y, ANKI_RENDERER_MAX_TILES_X>
-		m_tempTiles;
+	DArrayAuto<ClusterData> m_tempClusters;
 
 	// To fill the light index buffer
-	Atomic<U32> m_lightIdsCount;
+	Atomic<U32> m_lightIdsCount = {0};
 
 	// Misc
 	VisibilityTestResults::Container::ConstIterator m_lightsBegin = nullptr;
 	VisibilityTestResults::Container::ConstIterator m_lightsEnd = nullptr;
 
-	WeakPtr<Barrier> m_barrier;
 	Is* m_is = nullptr;
 };
 
@@ -124,7 +116,64 @@ public:
 	}
 };
 
+/// Visitor that transforms a collision object.
+class ShapeTransformer final: public CollisionShape::ConstVisitor
+{
+public:
+	Sphere m_sphere;
+	ConvexHullShape m_hull;
+	Array<Vec4, 5> m_hullPoints;
+	CollisionShape* m_outShape = nullptr;
+	Transform* m_trf = nullptr;
+
+	void visit(const LineSegment&)
+	{
+		ANKI_ASSERT(0);
+	}
+
+	void visit(const Obb&)
+	{
+		ANKI_ASSERT(0);
+	}
+
+	void visit(const Plane&)
+	{
+		ANKI_ASSERT(0);
+	}
+
+	void visit(const Sphere& s)
+	{
+		m_sphere = s;
+		m_sphere.transform(*m_trf);
+		m_outShape = &m_sphere;
+	}
+
+	void visit(const Aabb&)
+	{
+		ANKI_ASSERT(0);
+	}
+
+	void visit(const CompoundShape&)
+	{
+	}
+
+	void visit(const ConvexHullShape& hull)
+	{
+		ANKI_ASSERT(hull.getPointsCount() == m_hullPoints.getSize());
+		memcpy(&m_hullPoints[0], hull.getPoints(), sizeof(m_hullPoints));
+		for(Vec4& p : m_hullPoints)
+		{
+			p = m_trf->transform(p);
+		}
+		m_hull.initStorage(&m_hullPoints[0], m_hullPoints.getSize());
+		m_outShape = &m_hull;
+	}
+};
+
+//==============================================================================
+// Is                                                                          =
 //==============================================================================
+
 const PixelFormat Is::RT_PIXEL_FORMAT(
 	ComponentFormat::R11G11B10, TransformFormat::FLOAT);
 
@@ -170,7 +219,7 @@ Error Is::initInternal(const ConfigSet& config)
 		return ErrorCode::USER_DATA;
 	}
 
-	m_maxLightIds = config.getNumber("is.maxLightsPerTile");
+	m_maxLightIds = config.getNumber("is.maxLightsPerCluster");
 
 	if(m_maxLightIds == 0)
 	{
@@ -178,7 +227,7 @@ Error Is::initInternal(const ConfigSet& config)
 		return ErrorCode::USER_DATA;
 	}
 
-	m_maxLightIds *= m_r->getTilesCountXY();
+	m_maxLightIds *= m_r->getClusterCount();
 
 	//
 	// Init the passes
@@ -193,28 +242,24 @@ Error Is::initInternal(const ConfigSet& config)
 	pps.sprintf(
 		"\n#define TILES_X_COUNT %u\n"
 		"#define TILES_Y_COUNT %u\n"
-		"#define TILES_COUNT %u\n"
+		"#define CLUSTER_COUNT %u\n"
 		"#define RENDERER_WIDTH %u\n"
 		"#define RENDERER_HEIGHT %u\n"
 		"#define MAX_POINT_LIGHTS %u\n"
 		"#define MAX_SPOT_LIGHTS %u\n"
-		"#define MAX_SPOT_TEX_LIGHTS %u\n"
 		"#define MAX_LIGHT_INDICES %u\n"
 		"#define GROUND_LIGHT %u\n"
-		"#define TILES_BLOCK_BINDING %u\n"
 		"#define POISSON %u\n"
 		"#define OMNI_LIGHT_FRUSTUM_NEAR_PLANE %f\n",
-		m_r->getTilesCount().x(),
-		m_r->getTilesCount().y(),
-		(m_r->getTilesCount().x() * m_r->getTilesCount().y()),
+		m_r->getTileCountXY().x(),
+		m_r->getTileCountXY().y(),
+		m_r->getClusterCount(),
 		m_r->getWidth(),
 		m_r->getHeight(),
 		m_maxPointLights,
 		m_maxSpotLights,
-		m_maxSpotTexLights,
 		m_maxLightIds,
 		m_groundLightEnabled,
-		TILES_BLOCK_BINDING,
 		m_sm.getPoissonEnabled(),
 		PointLight::FRUSTUM_NEAR_PLANE);
 
@@ -253,9 +298,8 @@ Error Is::initInternal(const ConfigSet& config)
 	//
 	// Create UBOs
 	//
-	m_pLightsBuffSize = m_maxPointLights * sizeof(shader::PointLight);
-	m_sLightsBuffSize = m_maxSpotLights * sizeof(shader::SpotLight);
-	m_stLightsBuffSize = m_maxSpotTexLights * sizeof(shader::SpotLight);
+	m_pLightsBuffSize = m_maxPointLights * sizeof(ShaderPointLight);
+	m_sLightsBuffSize = m_maxSpotLights * sizeof(ShaderSpotLight);
 
 	for(U i = 0; i < m_pLightsBuffs.getSize(); ++i)
 	{
@@ -269,20 +313,14 @@ Error Is::initInternal(const ConfigSet& config)
 			m_sLightsBuffSize, BufferUsageBit::STORAGE,
 			BufferAccessBit::CLIENT_MAP_WRITE);
 
-		// Spot tex lights
-		m_stLightsBuffs[i] = getGrManager().newInstance<Buffer>(
-			m_stLightsBuffSize, BufferUsageBit::STORAGE,
-			BufferAccessBit::CLIENT_MAP_WRITE);
-
-		// Tiles
-		m_tilesBuffers[i] = getGrManager().newInstance<Buffer>(
-			m_r->getTilesCountXY() * sizeof(shader::Tile),
+		// Clusters
+		m_clusterBuffers[i] = getGrManager().newInstance<Buffer>(
+			m_r->getClusterCount() * sizeof(ShaderCluster),
 			BufferUsageBit::STORAGE, BufferAccessBit::CLIENT_MAP_WRITE);
 
 		// Index
 		m_lightIdsBuffers[i] = getGrManager().newInstance<Buffer>(
-			(m_maxPointLights * m_maxSpotLights * m_maxSpotTexLights)
-			* sizeof(U32),
+			m_maxLightIds * sizeof(Lid),
 			BufferUsageBit::STORAGE, BufferAccessBit::CLIENT_MAP_WRITE);
 	}
 
@@ -301,8 +339,7 @@ Error Is::initInternal(const ConfigSet& config)
 
 		init.m_storageBuffers[0].m_buffer = m_pLightsBuffs[i];
 		init.m_storageBuffers[1].m_buffer = m_sLightsBuffs[i];
-		init.m_storageBuffers[2].m_buffer = m_stLightsBuffs[i];
-		init.m_storageBuffers[3].m_buffer = m_tilesBuffers[i];
+		init.m_storageBuffers[3].m_buffer = m_clusterBuffers[i];
 		init.m_storageBuffers[4].m_buffer = m_lightIdsBuffers[i];
 
 		m_rcGroups[i] = getGrManager().newInstance<ResourceGroup>(init);
@@ -328,16 +365,16 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 
 	m_currentFrame = getGlobalTimestamp() % MAX_FRAMES_IN_FLIGHT;
 
-	U tilesCount = m_r->getTilesCountXY();
+	U clusterCount = m_r->getClusterCount();
 
 	//
 	// Quickly get the lights
 	//
 	U visiblePointLightsCount = 0;
 	U visibleSpotLightsCount = 0;
-	U visibleSpotTexLightsCount = 0;
-	Array<SceneNode*, Sm::MAX_SHADOW_CASTERS> shadowCasters;
+	Array<SceneNode*, Sm::MAX_SHADOW_CASTERS> spotCasters;
 	Array<SceneNode*, Sm::MAX_SHADOW_CASTERS> omniCasters;
+	U spotCastersCount = 0;
 	U omniCastersCount = 0;
 
 	auto it = vi.getLightsBegin();
@@ -357,17 +394,13 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 			}
 			break;
 		case LightComponent::LightType::SPOT:
+			++visibleSpotLightsCount;
+
+			if(light.getShadowEnabled())
 			{
-				if(light.getShadowEnabled())
-				{
-					shadowCasters[visibleSpotTexLightsCount++] = node;
-				}
-				else
-				{
-					++visibleSpotLightsCount;
-				}
-				break;
+				spotCasters[spotCastersCount++] = node;
 			}
+			break;
 		default:
 			ANKI_ASSERT(0);
 			break;
@@ -377,18 +410,15 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 	// Sanitize the counters
 	visiblePointLightsCount = min<U>(visiblePointLightsCount, m_maxPointLights);
 	visibleSpotLightsCount = min<U>(visibleSpotLightsCount, m_maxSpotLights);
-	visibleSpotTexLightsCount =
-		min<U>(visibleSpotTexLightsCount, m_maxSpotTexLights);
 
 	ANKI_COUNTER_INC(RENDERER_LIGHTS_COUNT,
-		U64(visiblePointLightsCount + visibleSpotLightsCount
-		+ visibleSpotTexLightsCount));
+		U64(visiblePointLightsCount + visibleSpotLightsCount));
 
 	//
 	// Do shadows pass
 	//
 	ANKI_CHECK(m_sm.run(
-		{&shadowCasters[0], visibleSpotTexLightsCount},
+		{&spotCasters[0], spotCastersCount},
 		{&omniCasters[0], omniCastersCount},
 		cmdBuff));
 
@@ -396,54 +426,42 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 	// Write the lights and tiles UBOs
 	//
 	Array<WriteLightsTask, ThreadPool::MAX_THREADS> tasks;
-	TaskCommonData taskData;
-	memset(&taskData, 0, sizeof(taskData));
+	TaskCommonData taskData(getFrameAllocator());
+	taskData.m_tempClusters.create(m_r->getClusterCount());
 
 	if(visiblePointLightsCount)
 	{
 		void* data = m_pLightsBuffs[m_currentFrame]->map(
-			0, visiblePointLightsCount * sizeof(shader::PointLight),
+			0, visiblePointLightsCount * sizeof(ShaderPointLight),
 			BufferAccessBit::CLIENT_MAP_WRITE);
 
-		taskData.m_pointLights = SArray<shader::PointLight>(
-			static_cast<shader::PointLight*>(data), visiblePointLightsCount);
+		taskData.m_pointLights = SArray<ShaderPointLight>(
+			static_cast<ShaderPointLight*>(data), visiblePointLightsCount);
 	}
 
 	if(visibleSpotLightsCount)
 	{
 		void* data = m_sLightsBuffs[m_currentFrame]->map(
-			0, visibleSpotLightsCount * sizeof(shader::SpotLight),
-			BufferAccessBit::CLIENT_MAP_WRITE);
-
-		taskData.m_spotLights = SArray<shader::SpotLight>(
-			static_cast<shader::SpotLight*>(data), visibleSpotLightsCount);
-	}
-
-	if(visibleSpotTexLightsCount)
-	{
-		void* data = m_stLightsBuffs[m_currentFrame]->map(
-			0, visibleSpotTexLightsCount * sizeof(shader::SpotLight),
+			0, visibleSpotLightsCount * sizeof(ShaderSpotLight),
 			BufferAccessBit::CLIENT_MAP_WRITE);
 
-		taskData.m_spotTexLights = SArray<shader::SpotLight>(
-			static_cast<shader::SpotLight*>(data), visibleSpotTexLightsCount);
+		taskData.m_spotLights = SArray<ShaderSpotLight>(
+			static_cast<ShaderSpotLight*>(data), visibleSpotLightsCount);
 	}
 
 	taskData.m_lightsBegin = vi.getLightsBegin();
 	taskData.m_lightsEnd = vi.getLightsEnd();
 
-	taskData.m_barrier = m_barrier;
-
 	taskData.m_is = this;
 
-	// Map tiles
-	void* data = m_tilesBuffers[m_currentFrame]->map(
+	// Map clusters
+	void* data = m_clusterBuffers[m_currentFrame]->map(
 		0,
-		tilesCount * sizeof(shader::Tile),
+		clusterCount * sizeof(ShaderCluster),
 		BufferAccessBit::CLIENT_MAP_WRITE);
 
-	taskData.m_tiles = SArray<shader::Tile>(
-		static_cast<shader::Tile*>(data), tilesCount);
+	taskData.m_clusters = SArray<ShaderCluster>(
+		static_cast<ShaderCluster*>(data), clusterCount);
 
 	// Map light IDs
 	data = m_lightIdsBuffers[m_currentFrame]->map(
@@ -482,18 +500,13 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 		m_sLightsBuffs[m_currentFrame]->unmap();
 	}
 
-	if(visibleSpotTexLightsCount)
-	{
-		m_stLightsBuffs[m_currentFrame]->unmap();
-	}
-
-	m_tilesBuffers[m_currentFrame]->unmap();
+	m_clusterBuffers[m_currentFrame]->unmap();
 	m_lightIdsBuffers[m_currentFrame]->unmap();
 
 	//
 	// Draw
 	//
-	cmdBuff->drawArrays(4, m_r->getTilesCountXY());
+	cmdBuff->drawArrays(4, m_r->getTileCount());
 
 	return ErrorCode::NONE;
 }
@@ -501,14 +514,17 @@ Error Is::lightPass(CommandBufferPtr& cmdBuff)
 //==============================================================================
 void Is::binLights(U32 threadId, PtrSize threadsCount, TaskCommonData& task)
 {
-	U ligthsCount = task.m_lightsEnd - task.m_lightsBegin;
+	U lightsCount = task.m_lightsEnd - task.m_lightsBegin;
 	const FrustumComponent& camfrc = m_cam->getComponent<FrustumComponent>();
 	const MoveComponent& cammove = m_cam->getComponent<MoveComponent>();
 
 	// Iterate lights and bin them
 	PtrSize start, end;
 	ThreadPool::Task::choseStartEnd(
-		threadId, threadsCount, ligthsCount, start, end);
+		threadId, threadsCount, lightsCount, start, end);
+
+	ClustererTestResult testResult;
+	m_r->getClusterer().initTestResults(getFrameAllocator(), testResult);
 
 	for(U64 i = start; i < end; i++)
 	{
@@ -524,7 +540,7 @@ void Is::binLights(U32 threadId, PtrSize threadsCount, TaskCommonData& task)
 				I pos = writePointLight(light, move, camfrc, task);
 				if(pos != -1)
 				{
-					binLight(sp, pos, 0, task);
+					binLight(sp, pos, 0, task, testResult);
 				}
 			}
 			break;
@@ -535,7 +551,7 @@ void Is::binLights(U32 threadId, PtrSize threadsCount, TaskCommonData& task)
 				I pos = writeSpotLight(light, move, frc, cammove, camfrc, task);
 				if(pos != -1)
 				{
-					binLight(sp, pos, light.getShadowEnabled() ? 2 : 1, task);
+					binLight(sp, pos, 1, task, testResult);
 				}
 			}
 			break;
@@ -546,72 +562,53 @@ void Is::binLights(U32 threadId, PtrSize threadsCount, TaskCommonData& task)
 	}
 
 	//
-	// Last thing, update the real tiles
+	// Last thing, update the real clusters
 	//
-	task.m_barrier->wait();
+	m_barrier->wait();
 
-	const UVec2 tilesCount2d = m_r->getTilesCount();
-	U tilesCount = tilesCount2d.x() * tilesCount2d.y();
+	U clusterCount = m_r->getClusterCount();
 
 	ThreadPool::Task::choseStartEnd(
-		threadId, threadsCount, tilesCount, start, end);
+		threadId, threadsCount, clusterCount, start, end);
 
 	// Run per tile
 	for(U i = start; i < end; ++i)
 	{
-		U y = i / tilesCount2d.x();
-		U x = i % tilesCount2d.x();
-		const auto& tile = task.m_tempTiles[y][x];
+		const auto& cluster = task.m_tempClusters[i];
 
-		const U countP = tile.m_pointCount.load();
-		const U countS = tile.m_spotCount.load();
-		const U countST = tile.m_spotTexCount.load();
-		const U count = countP + countS + countST;
+		const U countP = cluster.m_pointCount.load();
+		const U countS = cluster.m_spotCount.load();
+		const U count = countP + countS;
 
 		const U offset = task.m_lightIdsCount.fetchAdd(count);
 
-		auto& t = task.m_tiles[i];
+		auto& c = task.m_clusters[i];
+		c.m_combo = 0;
 
 		if(offset + count <= m_maxLightIds)
 		{
-			t.m_offset = offset;
+			ANKI_ASSERT(offset <= 0xFFFF);
+			c.m_combo = offset << 16;
 
 			if(countP > 0)
 			{
-				t.m_pointLightsCount = countP;
-				memcpy(&task.m_lightIds[offset], &tile.m_pointIds[0],
+				ANKI_ASSERT(countP <= 0xFF);
+				c.m_combo |= countP << 8;
+				memcpy(&task.m_lightIds[offset], &cluster.m_pointIds[0],
 					countP * sizeof(Lid));
 			}
-			else
-			{
-				t.m_pointLightsCount = 0;
-			}
 
 			if(countS > 0)
 			{
-				t.m_spotLightsCount = countS;
-				memcpy(&task.m_lightIds[offset + countP], &tile.m_spotIds[0],
+				ANKI_ASSERT(countS <= 0xFF);
+				c.m_combo |= countS;
+				memcpy(&task.m_lightIds[offset + countP], &cluster.m_spotIds[0],
 					countS * sizeof(Lid));
 			}
-			else
-			{
-				t.m_spotLightsCount = 0;
-			}
-
-			if(countST > 0)
-			{
-				t.m_spotTexLightsCount = countST;
-				memcpy(&task.m_lightIds[offset + countP + countS],
-					&tile.m_spotTexIds[0], countST * sizeof(Lid));
-			}
-			else
-			{
-				t.m_spotTexLightsCount = 0;
-			}
 		}
 		else
 		{
-			memset(&t, 0, sizeof(t));
+			memset(&c, 0, sizeof(c));
 
 			ANKI_LOGW("Light IDs buffer too small");
 		}
@@ -630,7 +627,7 @@ I Is::writePointLight(const LightComponent& lightc,
 		return -1;
 	}
 
-	shader::PointLight& slight = task.m_pointLights[i];
+	ShaderPointLight& slight = task.m_pointLights[i];
 
 	Vec4 pos = camFrc.getViewMatrix()
 		* lightMove.getWorldTransform().getOrigin().xyz1();
@@ -640,7 +637,7 @@ I Is::writePointLight(const LightComponent& lightc,
 
 	if(!lightc.getShadowEnabled())
 	{
-		slight.m_diffuseColorShadowmapId.w() = 128.0;
+		slight.m_diffuseColorShadowmapId.w() = INVALID_TEXTURE_INDEX;
 	}
 	else
 	{
@@ -658,23 +655,17 @@ I Is::writeSpotLight(const LightComponent& lightc,
 	const MoveComponent& camMove, const FrustumComponent& camFrc,
 	TaskCommonData& task)
 {
-	Bool isTexLight = lightc.getShadowEnabled();
-	I i;
-	shader::SpotLight* baseslight = nullptr;
-
-	if(isTexLight)
+	I i = task.m_spotLightsCount.fetchAdd(1);
+	if(ANKI_UNLIKELY(i >= I(m_maxSpotTexLights)))
 	{
-		// Spot tex light
-
-		i = task.m_spotTexLightsCount.fetchAdd(1);
-		if(ANKI_UNLIKELY(i >= I(m_maxSpotTexLights)))
-		{
-			return -1;
-		}
+		return -1;
+	}
 
-		shader::SpotLight& slight = task.m_spotTexLights[i];
-		baseslight = &slight;
+	ShaderSpotLight& light = task.m_spotLights[i];
+	F32 shadowmapIndex = INVALID_TEXTURE_INDEX;
 
+	if(lightc.getShadowEnabled())
+	{
 		// Write matrix
 		static const Mat4 biasMat4(
 			0.5, 0.0, 0.0, 0.5,
@@ -682,51 +673,37 @@ I Is::writeSpotLight(const LightComponent& lightc,
 			0.0, 0.0, 0.5, 0.5,
 			0.0, 0.0, 0.0, 1.0);
 		// bias * proj_l * view_l * world_c
-		slight.m_texProjectionMat =
+		light.m_texProjectionMat =
 			biasMat4
 			* lightFrc->getViewProjectionMatrix()
 			* Mat4(camMove.getWorldTransform());
 
 		// Transpose because of driver bug
-		slight.m_texProjectionMat.transpose();
-	}
-	else
-	{
-		// Spot light without texture
-
-		i = task.m_spotLightsCount.fetchAdd(1);
-		if(ANKI_UNLIKELY(i >= I(m_maxSpotLights)))
-		{
-			return -1;
-		}
+		light.m_texProjectionMat.transpose();
 
-		shader::SpotLight& slight = task.m_spotLights[i];
-		baseslight = &slight;
+		shadowmapIndex = (F32)lightc.getShadowMapIndex();
 	}
 
-	// Write common stuff
-	ANKI_ASSERT(baseslight);
-
 	// Pos & dist
 	Vec4 pos =
 		camFrc.getViewMatrix()
 		* lightMove.getWorldTransform().getOrigin().xyz1();
-	baseslight->m_posRadius = Vec4(pos.xyz(), -1.0 / lightc.getDistance());
+	light.m_posRadius = Vec4(pos.xyz(), -1.0 / lightc.getDistance());
 
 	// Diff color and shadowmap ID now
-	baseslight->m_diffuseColorShadowmapId =
-		Vec4(lightc.getDiffuseColor().xyz(), (F32)lightc.getShadowMapIndex());
+	light.m_diffuseColorShadowmapId =
+		Vec4(lightc.getDiffuseColor().xyz(), shadowmapIndex);
 
 	// Spec color
-	baseslight->m_specularColorTexId = lightc.getSpecularColor();
+	light.m_specularColorTexId = lightc.getSpecularColor();
 
 	// Light dir
 	Vec3 lightDir = -lightMove.getWorldTransform().getRotation().getZAxis();
 	lightDir = camFrc.getViewMatrix().getRotationPart() * lightDir;
-	baseslight->m_lightDir = Vec4(lightDir, 0.0);
+	light.m_lightDir = Vec4(lightDir, 0.0);
 
 	// Angles
-	baseslight->m_outerCosInnerCos = Vec4(
+	light.m_outerCosInnerCos = Vec4(
 		lightc.getOuterAngleCos(),
 		lightc.getInnerAngleCos(),
 		1.0,
@@ -740,39 +717,42 @@ void Is::binLight(
 	SpatialComponent& sp,
 	U pos,
 	U lightType,
-	TaskCommonData& task)
+	TaskCommonData& task,
+	ClustererTestResult& testResult)
 {
-	// Do the tests
-	Tiler::TestResult visTiles;
-	Tiler::TestParameters params;
-	params.m_collisionShape = &sp.getSpatialCollisionShape();
-	params.m_collisionShapeBox = &sp.getAabb();
-	params.m_nearPlane = true;
-	params.m_output = &visTiles;
-	m_r->getTiler().test(params);
+	// Transform the spatial collision shape to view space for the cluster tests
+	FrustumComponent& frc =
+		m_r->getActiveCamera().getComponent<FrustumComponent>();
+	Transform viewTrf(frc.getViewMatrix());
+	ShapeTransformer transformer;
+	transformer.m_trf = &viewTrf;
+	sp.getSpatialCollisionShape().accept(transformer);
+
+	m_r->getClusterer().bin(*transformer.m_outShape, testResult);
 
 	// Bin to the correct tiles
-	for(U t = 0; t < visTiles.m_count; t++)
+	auto it = testResult.getClustersBegin();
+	auto end = testResult.getClustersEnd();
+	for(; it != end; ++it)
 	{
-		U x = visTiles.m_tileIds[t].m_x;
-		U y = visTiles.m_tileIds[t].m_y;
+		U x = (*it)[0];
+		U y = (*it)[1];
+		U z = (*it)[2];
 
-		auto& tile = task.m_tempTiles[y][x];
-		U idx;
+		U i =
+			m_r->getTileCountXY().x() * (z * m_r->getTileCountXY().y() + y) + x;
+
+		auto& cluster = task.m_tempClusters[i];
 
 		switch(lightType)
 		{
 		case 0:
-			idx = tile.m_pointCount.fetchAdd(1) % MAX_TYPED_LIGHTS_PER_TILE;
-			tile.m_pointIds[idx] = pos;
+			i = cluster.m_pointCount.fetchAdd(1) % MAX_TYPED_LIGHTS_PER_CLUSTER;
+			cluster.m_pointIds[i] = pos;
 			break;
 		case 1:
-			idx = tile.m_spotCount.fetchAdd(1) % MAX_TYPED_LIGHTS_PER_TILE;
-			tile.m_spotIds[idx] = pos;
-			break;
-		case 2:
-			idx = tile.m_spotTexCount.fetchAdd(1) % MAX_TYPED_LIGHTS_PER_TILE;
-			tile.m_spotTexIds[idx] = pos;
+			i = cluster.m_spotCount.fetchAdd(1) % MAX_TYPED_LIGHTS_PER_CLUSTER;
+			cluster.m_spotIds[i] = pos;
 			break;
 		default:
 			ANKI_ASSERT(0);
@@ -799,13 +779,14 @@ Error Is::run(CommandBufferPtr& cmdBuff)
 //==============================================================================
 void Is::updateCommonBlock(CommandBufferPtr& cmdb, FrustumComponent& fr)
 {
-	shader::CommonUniforms* blk;
+	ShaderCommonUniforms* blk;
 	cmdb->updateDynamicUniforms(sizeof(*blk), blk);
 
 	// Start writing
 	blk->m_projectionParams = m_r->getProjectionParameters();
 	blk->m_sceneAmbientColor = m_ambientColor;
 	blk->m_viewMat = fr.getViewMatrix().getTransposed();
+	m_r->getClusterer().fillShaderParams(blk->m_clustererParams);
 
 	Vec3 groundLightDir;
 	if(m_groundLightEnabled)

+ 2 - 1
src/renderer/MainRenderer.cpp

@@ -44,7 +44,8 @@ Error MainRenderer::create(
 	ANKI_LOGI("Initializing main renderer");
 
 	m_alloc = HeapAllocator<U8>(allocCb, allocCbUserData);
-	m_frameAlloc = StackAllocator<U8>(allocCb, allocCbUserData, 1024 * 1024);
+	m_frameAlloc = StackAllocator<U8>(allocCb, allocCbUserData,
+		1024 * 1024 * 5);
 
 	// Init default FB
 	m_width = config.getNumber("width");

+ 12 - 5
src/renderer/Renderer.cpp

@@ -63,9 +63,12 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_lodDistance = config.getNumber("lodDistance");
 	m_framesNum = 0;
 	m_samples = config.getNumber("samples");
-	m_tilesCount.x() = m_width / TILE_SIZE;
-	m_tilesCount.y() = m_height / TILE_SIZE;
-	m_tilesCountXY = m_tilesCount.x() * m_tilesCount.y();
+	m_tileCountXY.x() = m_width / TILE_SIZE;
+	m_tileCountXY.y() = m_height / TILE_SIZE;
+	m_tileCount = m_tileCountXY.x() * m_tileCountXY.y();
+
+	m_clusterer.init(getAllocator(), m_tileCountXY.x(), m_tileCountXY.y(),
+		CLUSTER_SPLIT_COUNT);
 
 	m_tessellation = config.getNumber("tessellation");
 
@@ -83,13 +86,13 @@ Error Renderer::initInternal(const ConfigSet& config)
 		return ErrorCode::USER_DATA;
 	}
 
-	if(m_width % m_tilesCount.x() != 0)
+	if(m_width % m_tileCountXY.x() != 0)
 	{
 		ANKI_LOGE("Width is not multiple of tile width");
 		return ErrorCode::USER_DATA;
 	}
 
-	if(m_height % m_tilesCount.y() != 0)
+	if(m_height % m_tileCountXY.y() != 0)
 	{
 		ANKI_LOGE("Height is not multiple of tile height");
 		return ErrorCode::USER_DATA;
@@ -141,6 +144,10 @@ Error Renderer::render(SceneNode& frustumableNode,
 		m_projectionParamsUpdateTimestamp = getGlobalTimestamp();
 	}
 
+	ANKI_ASSERT(frc.getFrustum().getType() == Frustum::Type::PERSPECTIVE);
+	m_clusterer.prepare(
+		static_cast<const PerspectiveFrustum&>(frc.getFrustum()));
+
 	ANKI_COUNTER_START_TIMER(RENDERER_MS_TIME);
 	ANKI_CHECK(m_ms->run(cmdb[0]));
 	ANKI_COUNTER_STOP_TIMER_INC(RENDERER_MS_TIME);

+ 37 - 37
src/renderer/Tiler.cpp

@@ -89,10 +89,10 @@ Error Tiler::initInternal()
 		"#define TILE_SIZE_Y %u\n"
 		"#define TILES_COUNT_X %u\n"
 		"#define TILES_COUNT_Y %u\n",
-		m_r->getTileSize().x(),
-		m_r->getTileSize().y(),
-		m_r->getTilesCount().x(),
-		m_r->getTilesCount().y());
+		Renderer::TILE_SIZE,
+		Renderer::TILE_SIZE,
+		m_r->getTileCountXY().x(),
+		m_r->getTileCountXY().y());
 
 	ANKI_CHECK(getResourceManager().loadResourceToCache(
 		m_shader, "shaders/TilerMinMax.comp.glsl", pps.toCString(), "r_"));
@@ -104,9 +104,9 @@ Error Tiler::initInternal()
 	// Init planes. One plane for each direction, plus near/far plus the world
 	// space of those
 	U planesCount =
-		(m_r->getTilesCount().x() - 1) * 2 // planes J
-		+ (m_r->getTilesCount().y() - 1) * 2  // planes I
-		+ (m_r->getTilesCount().x() * m_r->getTilesCount().y() * 2); // near+far
+		(m_r->getTileCountXY().x() - 1) * 2 // planes J
+		+ (m_r->getTileCountXY().y() - 1) * 2  // planes I
+		+ (m_r->getTileCountXY().x() * m_r->getTileCountXY().y() * 2); // near+far
 
 	m_allPlanes.create(getAllocator(), planesCount);
 
@@ -114,31 +114,31 @@ Error Tiler::initInternal()
 	U count = 0;
 	using S = SArray<Plane>;
 
-	m_planesX = std::move(S(base + count, m_r->getTilesCount().x() - 1));
+	m_planesX = std::move(S(base + count, m_r->getTileCountXY().x() - 1));
 	count += m_planesX.getSize();
 
-	m_planesY = std::move(S(base + count, m_r->getTilesCount().y() - 1));
+	m_planesY = std::move(S(base + count, m_r->getTileCountXY().y() - 1));
 	count += m_planesY.getSize();
 
-	m_planesXW = std::move(S(base + count, m_r->getTilesCount().x() - 1));
+	m_planesXW = std::move(S(base + count, m_r->getTileCountXY().x() - 1));
 	count += m_planesXW.getSize();
 
-	m_planesYW = std::move(S(base + count, m_r->getTilesCount().y() - 1));
+	m_planesYW = std::move(S(base + count, m_r->getTileCountXY().y() - 1));
 	count += m_planesYW.getSize();
 
 	m_nearPlanesW = std::move(S(base + count,
-		m_r->getTilesCount().x() * m_r->getTilesCount().y()));
+		m_r->getTileCountXY().x() * m_r->getTileCountXY().y()));
 	count += m_nearPlanesW.getSize();
 
 	m_farPlanesW = std::move(S(base + count,
-		m_r->getTilesCount().x() * m_r->getTilesCount().y()));
+		m_r->getTileCountXY().x() * m_r->getTileCountXY().y()));
 
 	ANKI_ASSERT(count + m_farPlanesW.getSize() == m_allPlanes.getSize());
 
 	ANKI_CHECK(initPbos());
 
 	m_prevMinMaxDepth.create(getAllocator(),
-		m_r->getTilesCount().x() * m_r->getTilesCount().y(), Vec2(0.0, 1.0));
+		m_r->getTileCountXY().x() * m_r->getTileCountXY().y(), Vec2(0.0, 1.0));
 
 	return ErrorCode::NONE;
 }
@@ -147,7 +147,7 @@ Error Tiler::initInternal()
 Error Tiler::initPbos()
 {
 	// Allocate the buffers
-	U pboSize = m_r->getTilesCount().x() * m_r->getTilesCount().y();
+	U pboSize = m_r->getTileCountXY().x() * m_r->getTileCountXY().y();
 	pboSize *= sizeof(Vec2); // The pixel size
 
 	for(U i = 0; i < m_pbos.getSize(); ++i)
@@ -180,7 +180,7 @@ void Tiler::runMinMax(CommandBufferPtr& cmd)
 		cmd->bindResourceGroup(m_rcGroups[pboIdx]);
 
 		cmd->dispatchCompute(
-			m_r->getTilesCount().x(), m_r->getTilesCount().y(), 1);
+			m_r->getTileCountXY().x(), m_r->getTileCountXY().y(), 1);
 
 		m_r->drawQuad(cmd);
 	}
@@ -255,7 +255,7 @@ Bool Tiler::test(TestParameters& params) const
 		testRange(
 			*params.m_collisionShape,
 			params.m_nearPlane,
-			0, m_r->getTilesCount().y(), 0, m_r->getTilesCount().x(),
+			0, m_r->getTileCountXY().y(), 0, m_r->getTileCountXY().x(),
 			params.m_output,
 			count);
 	}
@@ -282,7 +282,7 @@ void Tiler::testRange(const CollisionShape& cs, Bool nearPlane,
 
 		if(m_enableGpuTests && getGlobalTimestamp() >= m_pbos.getSize())
 		{
-			U tileId = m_r->getTilesCount().x() * yFrom + xFrom;
+			U tileId = m_r->getTileCountXY().x() * yFrom + xFrom;
 
 			if(cs.testPlane(m_farPlanesW[tileId]) >= 0.0)
 			{
@@ -467,9 +467,9 @@ void Tiler::testFastSphere(const Sphere& s, const Aabb& aabb,
 	if(ANKI_UNLIKELY(eye.getLengthSquared() <= srad * srad))
 	{
 		// Camera totaly inside the sphere
-		for(U y = 0; y < m_r->getTilesCount().y(); ++y)
+		for(U y = 0; y < m_r->getTileCountXY().y(); ++y)
 		{
-			for(U x = 0; x < m_r->getTilesCount().x(); ++x)
+			for(U x = 0; x < m_r->getTileCountXY().x(); ++x)
 			{
 				visible->pushBack(x, y);
 				++count;
@@ -507,20 +507,20 @@ void Tiler::testFastSphere(const Sphere& s, const Aabb& aabb,
 	max2 = max2 * 0.5 + 0.5;
 
 	// Do a box test
-	F32 tcountX = m_r->getTilesCount().x();
-	F32 tcountY = m_r->getTilesCount().y();
+	F32 tcountX = m_r->getTileCountXY().x();
+	F32 tcountY = m_r->getTileCountXY().y();
 
 	I xFrom = floor(tcountX * min2.x());
-	xFrom = clamp<I>(xFrom, 0, m_r->getTilesCount().x());
+	xFrom = clamp<I>(xFrom, 0, m_r->getTileCountXY().x());
 
 	I xTo = ceil(tcountX * max2.x());
-	xTo = min<U>(xTo, m_r->getTilesCount().x());
+	xTo = min<U>(xTo, m_r->getTileCountXY().x());
 
 	I yFrom = floor(tcountY * min2.y());
-	yFrom = clamp<I>(yFrom, 0, m_r->getTilesCount().y());
+	yFrom = clamp<I>(yFrom, 0, m_r->getTileCountXY().y());
 
 	I yTo = ceil(tcountY * max2.y());
-	yTo = min<I>(yTo, m_r->getTilesCount().y());
+	yTo = min<I>(yTo, m_r->getTileCountXY().y());
 
 
 	ANKI_ASSERT(xFrom >= 0 && xFrom <= tcountX
@@ -601,7 +601,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 
 		// First the top looking planes
 		ThreadPool::Task::choseStartEnd(
-			threadId, threadsCount, m_r->getTilesCount().y() - 1,
+			threadId, threadsCount, m_r->getTileCountXY().y() - 1,
 			start, end);
 
 		for(U i = start; i < end; i++)
@@ -613,7 +613,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 
 		// Then the right looking planes
 		ThreadPool::Task::choseStartEnd(
-			threadId, threadsCount, m_r->getTilesCount().x() - 1,
+			threadId, threadsCount, m_r->getTileCountXY().x() - 1,
 			start, end);
 
 		for(U j = start; j < end; j++)
@@ -629,7 +629,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 
 		// First the top looking planes
 		ThreadPool::Task::choseStartEnd(
-			threadId, threadsCount, m_r->getTilesCount().y() - 1,
+			threadId, threadsCount, m_r->getTileCountXY().y() - 1,
 			start, end);
 
 		for(U i = start; i < end; i++)
@@ -639,7 +639,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 
 		// Then the right looking planes
 		ThreadPool::Task::choseStartEnd(
-			threadId, threadsCount, m_r->getTilesCount().x() - 1,
+			threadId, threadsCount, m_r->getTileCountXY().x() - 1,
 			start, end);
 
 		for(U j = start; j < end; j++)
@@ -652,7 +652,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 	if(m_enableGpuTests && getGlobalTimestamp() >= m_pbos.getSize())
 	{
 		const U tilesCount =
-			m_r->getTilesCount().x() * m_r->getTilesCount().y();
+			m_r->getTileCountXY().x() * m_r->getTileCountXY().y();
 
 		ThreadPool::Task::choseStartEnd(
 			threadId, threadsCount, tilesCount, start, end);
@@ -674,9 +674,9 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 			// p1 = CrntCamTrf * p0
 			// n1 = project(p1, CamProj)
 			// Using n1 get the tile depth and use that to set the planes
-			U x = k % m_r->getTilesCount().x();
-			U y = k / m_r->getTilesCount().x();
-			Vec2 tileCountf(m_r->getTilesCount().x(), m_r->getTilesCount().y());
+			U x = k % m_r->getTileCountXY().x();
+			U y = k / m_r->getTileCountXY().x();
+			Vec2 tileCountf(m_r->getTileCountXY().x(), m_r->getTileCountXY().y());
 			Vec2 ndc0 = Vec2(x, y) / tileCountf * 2.0 - 1.0;
 			for(U i = 0; i < 2; ++i)
 			{
@@ -706,7 +706,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 				else
 				{
 					// Inside view
-					U tileid = U(t.y()) * m_r->getTilesCount().x() + U(t.x());
+					U tileid = U(t.y()) * m_r->getTileCountXY().x() + U(t.x());
 					minMax[i] = pixels[tileid][i];
 				}
 			}
@@ -731,7 +731,7 @@ void Tiler::update(U32 threadId, PtrSize threadsCount,
 void Tiler::calcPlaneY(U i, const Vec4& projParams)
 {
 	Plane& plane = m_planesY[i];
-	F32 y = F32(i + 1) / m_r->getTilesCount().y() * 2.0 - 1.0;
+	F32 y = F32(i + 1) / m_r->getTileCountXY().y() * 2.0 - 1.0;
 
 	Vec4 viewA = unproject(1.0, Vec2(-1.0, y), projParams);
 	Vec4 viewB = unproject(1.0, Vec2(1.0, y), projParams);
@@ -746,7 +746,7 @@ void Tiler::calcPlaneY(U i, const Vec4& projParams)
 void Tiler::calcPlaneX(U j, const Vec4& projParams)
 {
 	Plane& plane = m_planesX[j];
-	F32 x = F32(j + 1) / m_r->getTilesCount().x() * 2.0 - 1.0;
+	F32 x = F32(j + 1) / m_r->getTileCountXY().x() * 2.0 - 1.0;
 
 	Vec4 viewA = unproject(1.0, Vec2(x, -1.0), projParams);
 	Vec4 viewB = unproject(1.0, Vec2(x, 1.0), projParams);

+ 2 - 3
src/scene/Clusterer.cpp

@@ -10,7 +10,7 @@
 namespace anki {
 
 //==============================================================================
-void Clusterer::initTempTestResults(const GenericMemoryPoolAllocator<U8>& alloc,
+void Clusterer::initTestResults(const GenericMemoryPoolAllocator<U8>& alloc,
 	ClustererTestResult& rez) const
 {
 	rez.m_clusterIds.create(alloc, m_clusters.getSize());
@@ -38,8 +38,7 @@ U Clusterer::calcK(F32 zVspace) const
 }
 
 //==============================================================================
-void Clusterer::prepare(const PerspectiveFrustum& fr,
-	const SArray<Vec2>& minMaxTileDepth)
+void Clusterer::prepare(const PerspectiveFrustum& fr)
 {
 	// Get some things
 	F32 near = fr.getNear();

+ 3 - 4
testapp/Main.cpp

@@ -43,7 +43,7 @@ App* app;
 ModelNode* horse;
 PerspectiveCamera* cam;
 
-#define PLAYER 1
+#define PLAYER 0
 #define MOUSE 1
 
 Bool profile = false;
@@ -88,8 +88,7 @@ Error init()
 #if !PLAYER
 	cam->getComponent<MoveComponent>().
 		setLocalTransform(Transform(
-		//Vec4(147.392776, -10.132728, 16.607138, 0.0),
-		Vec4(0.0),
+		Vec4(147.392776, -10.132728, 16.607138, 0.0),
 		Mat3x4(Euler(toRad(0.0), toRad(90.0), toRad(0.0))),
 		1.0));
 #endif
@@ -492,7 +491,7 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("samples", 1);
 	config.set("tessellation", true);
 	//config.set("maxTextureSize", 256);
-	config.set("fullscreenDesktopResolution", true);
+	config.set("fullscreenDesktopResolution", false);
 	config.set("debugContext", false);
 	if(getenv("ANKI_DATA_PATH"))
 	{