Browse Source

Occluder support. Some scene refactoring. Moving the condition variable notify inside the mutex lock

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
a16b0ae36e
59 changed files with 889 additions and 161 deletions
  1. 2 0
      include/anki/Scene.h
  2. 3 0
      include/anki/core/Trace.h
  3. 1 1
      include/anki/math/Vec.h
  4. 3 3
      include/anki/resource/AsyncLoader.h
  5. 1 1
      include/anki/resource/Model.h
  6. 3 6
      include/anki/scene/BodyComponent.h
  7. 2 5
      include/anki/scene/FrustumComponent.h
  8. 2 5
      include/anki/scene/LensFlareComponent.h
  9. 1 4
      include/anki/scene/LightComponent.h
  10. 1 2
      include/anki/scene/ModelNode.h
  11. 1 4
      include/anki/scene/MoveComponent.h
  12. 56 0
      include/anki/scene/OccluderComponent.h
  13. 40 0
      include/anki/scene/OccluderNode.h
  14. 4 6
      include/anki/scene/PlayerControllerComponent.h
  15. 4 6
      include/anki/scene/ReflectionProbeComponent.h
  16. 4 6
      include/anki/scene/ReflectionProxyComponent.h
  17. 2 5
      include/anki/scene/RenderComponent.h
  18. 67 29
      include/anki/scene/SceneComponent.h
  19. 7 0
      include/anki/scene/SceneGraph.h
  20. 8 6
      include/anki/scene/SceneNode.h
  21. 11 12
      include/anki/scene/Sector.h
  22. 3 0
      include/anki/scene/SoftwareRasterizer.h
  23. 1 4
      include/anki/scene/SpatialComponent.h
  24. 19 2
      include/anki/scene/Visibility.h
  25. 53 1
      include/anki/scene/VisibilityInternal.h
  26. 2 0
      include/anki/util/ThreadHive.h
  27. 6 3
      src/core/Trace.cpp
  28. 4 2
      src/gr/gl/RenderingThread.cpp
  29. 3 8
      src/renderer/Dbg.cpp
  30. 6 6
      src/renderer/Is.cpp
  31. 2 1
      src/resource/AsyncLoader.cpp
  32. 1 1
      src/resource/Model.cpp
  33. 1 1
      src/scene/BodyNode.cpp
  34. 4 3
      src/scene/Camera.cpp
  35. 1 1
      src/scene/FrustumComponent.cpp
  36. 1 1
      src/scene/LensFlareComponent.cpp
  37. 2 3
      src/scene/Light.cpp
  38. 1 1
      src/scene/LightComponent.cpp
  39. 1 1
      src/scene/ModelNode.cpp
  40. 1 1
      src/scene/MoveComponent.cpp
  41. 38 0
      src/scene/OccluderComponent.cpp
  42. 107 0
      src/scene/OccluderNode.cpp
  43. 1 1
      src/scene/ParticleEmitter.cpp
  44. 2 2
      src/scene/PlayerNode.cpp
  45. 1 1
      src/scene/ReflectionProbe.cpp
  46. 1 1
      src/scene/ReflectionProxy.cpp
  47. 1 1
      src/scene/RenderComponent.cpp
  48. 62 0
      src/scene/SceneComponent.cpp
  49. 2 0
      src/scene/SceneGraph.cpp
  50. 12 8
      src/scene/Sector.cpp
  51. 12 0
      src/scene/SoftwareRasterizer.cpp
  52. 1 1
      src/scene/SpatialComponent.cpp
  53. 112 2
      src/scene/Visibility.cpp
  54. 147 0
      src/script/Scene.cpp
  55. 16 0
      src/script/Scene.xml
  56. 3 2
      src/ui/UiInterfaceImpl.cpp
  57. 1 1
      thirdparty
  58. 27 0
      tools/scene/Exporter.cpp
  59. 8 0
      tools/scene/Exporter.h

+ 2 - 0
include/anki/Scene.h

@@ -23,3 +23,5 @@
 #include <anki/scene/ReflectionProxy.h>
 #include <anki/scene/ReflectionProxyComponent.h>
 #include <anki/scene/PlayerNode.h>
+#include <anki/scene/OccluderNode.h>
+#include <anki/scene/OccluderComponent.h>

+ 3 - 0
include/anki/core/Trace.h

@@ -31,6 +31,9 @@ enum class TraceEventType
 	SCENE_VISIBILITY_TEST,
 	SCENE_VISIBILITY_COMBINE_RESULTS,
 	SCENE_VISIBILITY_ITERATE_SECTORS,
+	SCENE_VISIBILITY_GATHER_TRIANGLES,
+	SCENE_VISIBILITY_RASTERIZE,
+	SCENE_RASTERIZER_TEST,
 	RENDER,
 	RENDER_MS,
 	RENDER_IS,

+ 1 - 1
include/anki/math/Vec.h

@@ -1865,7 +1865,7 @@ public:
 		return m_arr[i];
 	}
 
-	T operator[](const U i) const
+	const T& operator[](const U i) const
 	{
 		return m_arr[i];
 	}

+ 3 - 3
include/anki/resource/AsyncLoader.h

@@ -84,10 +84,10 @@ void AsyncLoader::newTask(TArgs&&... args)
 			ANKI_ASSERT(m_head == nullptr);
 			m_head = m_tail = newTask;
 		}
-	}
 
-	// Wake up the thread
-	m_condVar.notifyOne();
+		// Wake up the thread
+		m_condVar.notifyOne();
+	}
 }
 
 /// @}

+ 1 - 1
include/anki/resource/Model.h

@@ -91,7 +91,7 @@ private:
 		2,
 		MAX_INSTANCE_GROUPS>
 		m_pplines;
-	mutable SpinLock m_lock; ///< Protect m_pplines
+	mutable Mutex m_lock; ///< Protect m_pplines
 
 	Array<ResourceGroupPtr, MAX_LODS> m_grResources;
 

+ 3 - 6
include/anki/scene/BodyComponent.h

@@ -19,8 +19,10 @@ namespace anki
 class BodyComponent : public SceneComponent
 {
 public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::BODY;
+
 	BodyComponent(SceneNode* node, PhysicsBodyPtr body)
-		: SceneComponent(Type::BODY, node)
+		: SceneComponent(CLASS_TYPE, node)
 		, m_body(body)
 	{
 	}
@@ -48,11 +50,6 @@ public:
 		return ErrorCode::NONE;
 	}
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::BODY;
-	}
-
 private:
 	PhysicsBodyPtr m_body;
 	Transform m_trf;

+ 2 - 5
include/anki/scene/FrustumComponent.h

@@ -44,6 +44,8 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(FrustumComponentVisibilityTestFlag, inline)
 class FrustumComponent : public SceneComponent
 {
 public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::FRUSTUM;
+
 	struct VisibilityStats
 	{
 		U32 m_renderablesCount = 0;
@@ -180,11 +182,6 @@ public:
 		return m_flags.getAny(FrustumComponentVisibilityTestFlag::ALL_TESTS);
 	}
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::FRUSTUM;
-	}
-
 private:
 	enum Flags
 	{

+ 2 - 5
include/anki/scene/LensFlareComponent.h

@@ -19,6 +19,8 @@ namespace anki
 class LensFlareComponent final : public SceneComponent
 {
 public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::LENS_FLARE;
+
 	LensFlareComponent(SceneNode* node);
 
 	~LensFlareComponent();
@@ -93,11 +95,6 @@ public:
 	}
 	/// @}
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::LENS_FLARE;
-	}
-
 private:
 	TextureResourcePtr m_tex; ///< Array of textures.
 

+ 1 - 4
include/anki/scene/LightComponent.h

@@ -18,10 +18,7 @@ namespace anki
 class LightComponent : public SceneComponent
 {
 public:
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::LIGHT;
-	}
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::LIGHT;
 
 	enum class LightType : U8
 	{

+ 1 - 2
include/anki/scene/ModelNode.h

@@ -54,8 +54,7 @@ public:
 
 	~ModelNode();
 
-	ANKI_USE_RESULT Error init(
-		const CString& name, const CString& modelFname);
+	ANKI_USE_RESULT Error init(const CString& name, const CString& modelFname);
 
 	const Model& getModel() const
 	{

+ 1 - 4
include/anki/scene/MoveComponent.h

@@ -36,10 +36,7 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(MoveComponentFlag, inline)
 class MoveComponent : public SceneComponent
 {
 public:
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::MOVE;
-	}
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::MOVE;
 
 	/// The one and only constructor
 	/// @param node The scene node to steal it's allocators

+ 56 - 0
include/anki/scene/OccluderComponent.h

@@ -0,0 +1,56 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/SceneComponent.h>
+#include <anki/Math.h>
+#include <anki/collision/Aabb.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Occluder component.
+class OccluderComponent : public SceneComponent
+{
+public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::OCCLUDER;
+
+	/// @note The component won't own the triangles.
+	OccluderComponent(SceneNode* node)
+		: SceneComponent(CLASS_TYPE, node)
+	{
+	}
+
+	/// Get the vertex positions and other info.
+	void getVertices(const Vec3*& begin, U32& count, U32& stride) const
+	{
+		ANKI_ASSERT(m_begin && m_count && m_stride);
+		begin = m_begin;
+		count = m_count;
+		stride = m_stride;
+	}
+
+	/// Point the component to the vertex positions in world space. You are
+	/// not supposed to call this often.
+	void setVertices(const Vec3* begin, U count, U stride);
+
+	const Aabb& getBoundingVolume() const
+	{
+		return m_aabb;
+	}
+
+private:
+	const Vec3* m_begin = nullptr;
+	U32 m_count = 0;
+	U32 m_stride = 0;
+	Aabb m_aabb;
+};
+/// @}
+
+} // end namespace anki

+ 40 - 0
include/anki/scene/OccluderNode.h

@@ -0,0 +1,40 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/SceneNode.h>
+#include <anki/Math.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Occluder scene node.
+class OccluderNode : public SceneNode
+{
+	friend class OccluderMoveFeedbackComponent;
+
+public:
+	OccluderNode(SceneGraph* scene)
+		: SceneNode(scene)
+	{
+	}
+
+	~OccluderNode();
+
+	ANKI_USE_RESULT Error init(const CString& name, const CString& meshFname);
+
+private:
+	DynamicArray<Vec3> m_vertsL; ///< Verts in local space.
+	DynamicArray<Vec3> m_vertsW; ///< Verts in world space.
+
+	void onMoveComponentUpdate(MoveComponent& movec);
+};
+/// @}
+
+} // end namespace anki

+ 4 - 6
include/anki/scene/PlayerControllerComponent.h

@@ -18,9 +18,12 @@ namespace anki
 class PlayerControllerComponent : public SceneComponent
 {
 public:
+	static const SceneComponentType CLASS_TYPE =
+		SceneComponentType::PLAYER_CONTROLLER;
+
 	PlayerControllerComponent(
 		SceneNode* node, PhysicsPlayerControllerPtr player)
-		: SceneComponent(Type::PLAYER_CONTROLLER, node)
+		: SceneComponent(CLASS_TYPE, node)
 		, m_player(player)
 	{
 	}
@@ -51,11 +54,6 @@ public:
 		return ErrorCode::NONE;
 	}
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::PLAYER_CONTROLLER;
-	}
-
 private:
 	PhysicsPlayerControllerPtr m_player;
 	Transform m_trf;

+ 4 - 6
include/anki/scene/ReflectionProbeComponent.h

@@ -17,14 +17,12 @@ namespace anki
 class ReflectionProbeComponent : public SceneComponent
 {
 public:
-	ReflectionProbeComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::REFLECTION_PROBE, node)
-	{
-	}
+	static const SceneComponentType CLASS_TYPE =
+		SceneComponentType::REFLECTION_PROBE;
 
-	static Bool classof(const SceneComponent& c)
+	ReflectionProbeComponent(SceneNode* node)
+		: SceneComponent(CLASS_TYPE, node)
 	{
-		return c.getType() == Type::REFLECTION_PROBE;
 	}
 
 	const Vec4& getPosition() const

+ 4 - 6
include/anki/scene/ReflectionProxyComponent.h

@@ -18,6 +18,9 @@ namespace anki
 class ReflectionProxyComponent : public SceneComponent
 {
 public:
+	static const SceneComponentType CLASS_TYPE =
+		SceneComponentType::REFLECTION_PROXY;
+
 	/// Reflection proxy face. One out of many
 	class Face
 	{
@@ -27,7 +30,7 @@ public:
 	};
 
 	ReflectionProxyComponent(SceneNode* node, U faceCount)
-		: SceneComponent(SceneComponent::Type::REFLECTION_PROXY, node)
+		: SceneComponent(CLASS_TYPE, node)
 	{
 		ANKI_ASSERT(faceCount > 0);
 		m_faces.create(getAllocator(), faceCount);
@@ -38,11 +41,6 @@ public:
 		m_faces.destroy(getAllocator());
 	}
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::REFLECTION_PROXY;
-	}
-
 	void setQuad(
 		U index, const Vec4& a, const Vec4& b, const Vec4& c, const Vec4& d);
 

+ 2 - 5
include/anki/scene/RenderComponent.h

@@ -121,12 +121,9 @@ public:
 class RenderComponent : public SceneComponent
 {
 public:
-	using Variables = DynamicArray<RenderComponentVariable*>;
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::RENDER;
 
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::RENDER;
-	}
+	using Variables = DynamicArray<RenderComponentVariable*>;
 
 	RenderComponent(SceneNode* node, const Material* mtl, U64 hash = 0);
 

+ 67 - 29
include/anki/scene/SceneComponent.h

@@ -9,6 +9,7 @@
 #include <anki/core/Timestamp.h>
 #include <anki/util/Functions.h>
 #include <anki/util/BitMask.h>
+#include <anki/util/List.h>
 
 namespace anki
 {
@@ -16,41 +17,37 @@ namespace anki
 /// @addtogroup scene
 /// @{
 
+// The type of the components
+enum class SceneComponentType : U16
+{
+	NONE,
+	FRUSTUM,
+	MOVE,
+	RENDER,
+	SPATIAL,
+	LIGHT,
+	LENS_FLARE,
+	BODY,
+	SECTOR_PORTAL,
+	REFLECTION_PROBE,
+	REFLECTION_PROXY,
+	OCCLUDER,
+	PLAYER_CONTROLLER,
+
+	COUNT,
+	LAST_COMPONENT_ID = PLAYER_CONTROLLER
+};
+
 /// Scene node component
 class SceneComponent
 {
 public:
-	// The type of the components
-	enum class Type : U16
-	{
-		NONE,
-		FRUSTUM,
-		MOVE,
-		RENDER,
-		SPATIAL,
-		LIGHT,
-		LENS_FLARE,
-		BODY,
-		SECTOR_PORTAL,
-		REFLECTION_PROBE,
-		REFLECTION_PROXY,
-		OCCLUDER,
-		PLAYER_CONTROLLER,
-		LAST_COMPONENT_ID = PLAYER_CONTROLLER
-	};
-
 	/// Construct the scene component.
-	SceneComponent(Type type, SceneNode* node)
-		: m_node(node)
-		, m_type(type)
-	{
-	}
+	SceneComponent(SceneComponentType type, SceneNode* node);
 
-	virtual ~SceneComponent()
-	{
-	}
+	virtual ~SceneComponent();
 
-	Type getType() const
+	SceneComponentType getType() const
 	{
 		return m_type;
 	}
@@ -120,9 +117,50 @@ private:
 		AUTOMATIC_CLEANUP = 1 << 0
 	};
 
-	Type m_type;
+	SceneComponentType m_type;
 	BitMask<U8> m_flags;
 };
+
+/// Multiple lists of all types of components.
+class SceneComponentLists : public NonCopyable
+{
+anki_internal:
+	SceneComponentLists()
+	{
+	}
+
+	~SceneComponentLists()
+	{
+	}
+
+	void init(SceneAllocator<U8> alloc)
+	{
+		m_alloc = alloc;
+	}
+
+	void insertNew(SceneComponent* comp);
+
+	void remove(SceneComponent* comp);
+
+	template<typename TSceneComponentType, typename Func>
+	void iterateComponents(Func func)
+	{
+		auto it = m_lists[TSceneComponentType::CLASS_TYPE].getBegin();
+		auto end = m_lists[TSceneComponentType::CLASS_TYPE].getEnd();
+
+		while(it != end)
+		{
+			func(*static_cast<TSceneComponentType*>(*it));
+			++it;
+		}
+	}
+
+private:
+	SceneAllocator<U8> m_alloc;
+	Array<List<SceneComponent*>, U(SceneComponentType::COUNT)> m_lists;
+
+	List<SceneComponent*>::Iterator find(SceneComponent* comp);
+};
 /// @}
 
 } // end namespace anki

+ 7 - 0
include/anki/scene/SceneGraph.h

@@ -192,6 +192,11 @@ anki_internal:
 		return m_nodesUuid++;
 	}
 
+	SceneComponentLists& getSceneComponentLists()
+	{
+		return m_componentLists;
+	}
+
 private:
 	const Timestamp* m_globalTimestamp = nullptr;
 	Timestamp m_timestamp = 0; ///< Cached timestamp
@@ -224,6 +229,8 @@ private:
 
 	U64 m_nodesUuid = 0;
 
+	SceneComponentLists m_componentLists;
+
 	/// Put a node in the appropriate containers
 	ANKI_USE_RESULT Error registerNode(SceneNode* node);
 	void unregisterNode(SceneNode* node);

+ 8 - 6
include/anki/scene/SceneNode.h

@@ -134,9 +134,9 @@ public:
 		for(; !err && it != end; ++it)
 		{
 			SceneComponent* comp = *it;
-			if(isa<Component>(comp))
+			if(comp->getType() == Component::CLASS_TYPE)
 			{
-				err = func(*dcast<Component*>(comp));
+				err = func(*static_cast<Component*>(comp));
 			}
 		}
 
@@ -150,9 +150,10 @@ public:
 		U count = m_componentsCount;
 		while(count-- != 0)
 		{
-			if(isa<Component>(m_components[count]))
+			SceneComponent* comp = m_components[count];
+			if(comp->getType() == Component::CLASS_TYPE)
 			{
-				return dcast<Component*>(m_components[count]);
+				return static_cast<Component*>(comp);
 			}
 		}
 		return nullptr;
@@ -165,9 +166,10 @@ public:
 		U count = m_componentsCount;
 		while(count-- != 0)
 		{
-			if(isa<Component>(m_components[count]))
+			const SceneComponent* comp = m_components[count];
+			if(comp->getType() == Component::CLASS_TYPE)
 			{
-				return dcast<const Component*>(m_components[count]);
+				return static_cast<const Component*>(comp);
 			}
 		}
 		return nullptr;

+ 11 - 12
include/anki/scene/Sector.h

@@ -18,6 +18,7 @@ class SectorGroup;
 class FrustumComponent;
 class SpatialComponent;
 class Renderer;
+class SoftwareRasterizer;
 
 /// @addtogroup scene
 /// @{
@@ -26,14 +27,12 @@ class Renderer;
 class PortalSectorComponent : public SceneComponent
 {
 public:
-	PortalSectorComponent(SceneNode* node)
-		: SceneComponent(Type::SECTOR_PORTAL, node)
-	{
-	}
+	static const SceneComponentType CLASS_TYPE =
+		SceneComponentType::SECTOR_PORTAL;
 
-	static Bool classof(const SceneComponent& c)
+	PortalSectorComponent(SceneNode* node)
+		: SceneComponent(CLASS_TYPE, node)
 	{
-		return c.getType() == Type::SECTOR_PORTAL;
 	}
 };
 
@@ -51,8 +50,7 @@ public:
 
 	~PortalSectorBase();
 
-	ANKI_USE_RESULT Error init(
-		const CString& name, const CString& modelFname);
+	ANKI_USE_RESULT Error init(const CString& name, const CString& modelFname);
 
 	const CollisionShape& getBoundingShape() const
 	{
@@ -97,8 +95,7 @@ public:
 
 	~Portal();
 
-	ANKI_USE_RESULT Error init(
-		const CString& name, const CString& modelFname);
+	ANKI_USE_RESULT Error init(const CString& name, const CString& modelFname);
 
 	ANKI_USE_RESULT Error frameUpdate(
 		F32 prevUpdateTime, F32 crntTime) override;
@@ -130,8 +127,7 @@ public:
 
 	~Sector();
 
-	ANKI_USE_RESULT Error init(
-		const CString& name, const CString& modelFname);
+	ANKI_USE_RESULT Error init(const CString& name, const CString& modelFname);
 
 	void tryAddPortal(Portal* portal);
 	void tryRemovePortal(Portal* portal);
@@ -200,6 +196,7 @@ public:
 
 	void findVisibleNodes(const FrustumComponent& frc,
 		U threadId,
+		const SoftwareRasterizer* r,
 		SectorGroupVisibilityTestsContext& ctx) const;
 
 private:
@@ -211,12 +208,14 @@ private:
 	SpinLock m_mtx;
 
 	void findVisibleSectors(const FrustumComponent& frc,
+		const SoftwareRasterizer* r,
 		List<const Sector*>& visibleSectors,
 		U& spatialsCount) const;
 
 	/// Recursive method
 	void findVisibleSectorsInternal(const FrustumComponent& frc,
 		const Sector& s,
+		const SoftwareRasterizer* r,
 		List<const Sector*>& visibleSectors,
 		U& spatialsCount) const;
 

+ 3 - 0
include/anki/scene/SoftwareRasterizer.h

@@ -73,6 +73,9 @@ public: // XXX
 	/// @note Triangles in view space.
 	void clipTriangle(
 		const Vec4* inTriangle, Vec4* outTriangles, U& outTriangleCount) const;
+
+	Bool visibilityTestInternal(
+		const CollisionShape& cs, const Aabb& aabb) const;
 };
 /// @}
 

+ 1 - 4
include/anki/scene/SpatialComponent.h

@@ -26,10 +26,7 @@ class Sector;
 class SpatialComponent : public SceneComponent
 {
 public:
-	static Bool classof(const SceneComponent& c)
-	{
-		return c.getType() == Type::SPATIAL;
-	}
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::SPATIAL;
 
 	SpatialComponent(SceneNode* node, const CollisionShape* shape);
 

+ 19 - 2
include/anki/scene/Visibility.h

@@ -133,8 +133,25 @@ public:
 		m_shapeUpdateTimestamp = t;
 	}
 
-	void combineWith(
-		SceneFrameAllocator<U8> alloc, WeakArray<VisibilityTestResults*>& results);
+	void combineWith(SceneFrameAllocator<U8> alloc,
+		WeakArray<VisibilityTestResults*>& results);
+
+	template<typename TFunc>
+	void iterateAll(TFunc f)
+	{
+		for(VisibilityGroupType i = VisibilityGroupType::FIRST;
+			i < VisibilityGroupType::TYPE_COUNT;
+			++i)
+		{
+			VisibleNode* it = getBegin(i);
+			VisibleNode* end = getEnd(i);
+			while(it != end)
+			{
+				f(*it->m_node);
+				++it;
+			}
+		}
+	}
 
 private:
 	using Container = DynamicArray<VisibleNode>;

+ 53 - 1
include/anki/scene/VisibilityInternal.h

@@ -8,6 +8,7 @@
 #include <anki/scene/Visibility.h>
 #include <anki/scene/Sector.h>
 #include <anki/scene/SceneGraph.h>
+#include <anki/scene/SoftwareRasterizer.h>
 #include <anki/util/Thread.h>
 #include <anki/core/Trace.h>
 
@@ -58,6 +59,56 @@ public:
 	void submitNewWork(FrustumComponent& frc, ThreadHive& hive);
 };
 
+/// ThreadHive task to gather all visible triangles from the OccluderComponent.
+class GatherVisibleTrianglesTask
+{
+public:
+	class TriangleBatch : public IntrusiveListEnabled<TriangleBatch>
+	{
+	public:
+		const Vec3* m_begin = nullptr;
+		U32 m_count = 0;
+		U32 m_stride = 0;
+	};
+
+	WeakPtr<VisibilityContext> m_visCtx;
+	WeakPtr<FrustumComponent> m_frc;
+	IntrusiveList<TriangleBatch> m_batches;
+	U32 m_batchCount;
+	SoftwareRasterizer m_r;
+
+	/// Thread hive task.
+	static void callback(void* ud, U32 threadId, ThreadHive& hive)
+	{
+		GatherVisibleTrianglesTask& self =
+			*static_cast<GatherVisibleTrianglesTask*>(ud);
+		self.gather();
+	}
+
+private:
+	void gather();
+};
+
+/// ThreadHive task to rasterize triangles.
+class RasterizeTrianglesTask
+{
+public:
+	WeakPtr<GatherVisibleTrianglesTask> m_gatherTask;
+	U32 m_taskIdx;
+	U32 m_taskCount;
+
+	/// Thread hive task.
+	static void callback(void* ud, U32 threadId, ThreadHive& hive)
+	{
+		RasterizeTrianglesTask& self =
+			*static_cast<RasterizeTrianglesTask*>(ud);
+		self.rasterize();
+	}
+
+private:
+	void rasterize();
+};
+
 /// ThreadHive task to get visible nodes from sectors.
 class GatherVisiblesFromSectorsTask
 {
@@ -65,6 +116,7 @@ public:
 	WeakPtr<VisibilityContext> m_visCtx;
 	SectorGroupVisibilityTestsContext m_sectorsCtx;
 	WeakPtr<FrustumComponent> m_frc; ///< What to test against.
+	SoftwareRasterizer* m_r;
 
 	/// Thread hive task.
 	static void callback(void* ud, U32 threadId, ThreadHive& hive)
@@ -81,7 +133,7 @@ private:
 		U testIdx = m_visCtx->m_testsCount.fetchAdd(1);
 
 		m_visCtx->m_scene->getSectorGroup().findVisibleNodes(
-			*m_frc, testIdx, m_sectorsCtx);
+			*m_frc, testIdx, m_r, m_sectorsCtx);
 		ANKI_TRACE_STOP_EVENT(SCENE_VISIBILITY_ITERATE_SECTORS);
 	}
 };

+ 2 - 0
include/anki/util/ThreadHive.h

@@ -51,6 +51,8 @@ public:
 class ThreadHive : public NonCopyable
 {
 public:
+	static const U MAX_THREADS = 32;
+
 	/// Create the hive.
 	ThreadHive(U threadCount, GenericMemoryPoolAllocator<U8> alloc);
 

+ 6 - 3
src/core/Trace.cpp

@@ -21,9 +21,12 @@ static Array<const char*, U(TraceEventType::COUNT)> eventNames = {
 		"SCENE_PHYSICS_UPDATE",
 		"SCENE_NODES_UPDATE",
 		"SCENE_VISIBILITY_TESTS",
-		"SCENE_VISIBILITY_TEST",
-		"SCENE_VISIBILITY_COMBINE_RESULTS",
-		"SCENE_VISIBILITY_ITERATE_SECTORS",
+		"VIS_TEST",
+		"VIS_COMBINE_RESULTS",
+		"VIS_ITERATE_SECTORS",
+		"VIS_GATHER_TRIANGLES",
+		"VIS_RASTERIZE",
+		"VIS_RASTERIZER_TEST",
 		"RENDER",
 		"RENDER_MS",
 		"RENDER_IS",

+ 4 - 2
src/gr/gl/RenderingThread.cpp

@@ -111,9 +111,10 @@ void RenderingThread::flushCommandBuffer(CommandBufferPtr commands)
 		{
 			ANKI_LOGW("Rendering queue too small");
 		}
+
+		m_condVar.notifyOne(); // Wake the thread
 	}
 
-	m_condVar.notifyOne(); // Wake the thread
 #else
 	Error err = commands->getImplementation().executeAllCommands();
 	if(err)
@@ -308,9 +309,10 @@ void RenderingThread::swapBuffersInternal(GlState& state)
 	{
 		LockGuard<Mutex> lock(m_frameMtx);
 		m_frameWait = false;
+
+		m_frameCondVar.notifyOne();
 	}
 
-	m_frameCondVar.notifyOne();
 	state.checkDynamicMemoryConsumption();
 
 	ANKI_TRACE_STOP_EVENT(SWAP_BUFFERS);

+ 3 - 8
src/renderer/Dbg.cpp

@@ -76,8 +76,6 @@ Error Dbg::init(const ConfigSet& initializer)
 //==============================================================================
 Error Dbg::run(RenderingContext& ctx)
 {
-	Error err = ErrorCode::NONE;
-
 	ANKI_ASSERT(m_enabled);
 
 	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
@@ -93,10 +91,10 @@ Error Dbg::run(RenderingContext& ctx)
 	SceneGraph& scene = cam.getSceneGraph();
 
 	SceneDebugDrawer sceneDrawer(m_drawer);
-	err = scene.iterateSceneNodes([&](SceneNode& node) -> Error {
+	camFrc.getVisibilityTestResults().iterateAll([&](SceneNode& node) {
 		if(&node == &cam)
 		{
-			return ErrorCode::NONE;
+			return;
 		}
 
 		// Set position
@@ -145,10 +143,7 @@ Error Dbg::run(RenderingContext& ctx)
 				});
 			(void)err;
 		}
-
-		return ErrorCode::NONE;
 	});
-	(void)err;
 
 	if(m_flags.get(DbgFlag::PHYSICS))
 	{
@@ -192,7 +187,7 @@ Error Dbg::run(RenderingContext& ctx)
 	}
 #endif
 
-#if 1
+#if 0
 	{
 		m_drawer->setViewProjectionMatrix(Mat4::getIdentity());
 		m_drawer->setModelMatrix(Mat4::getIdentity());

+ 6 - 6
src/renderer/Is.cpp

@@ -514,9 +514,9 @@ Error Is::populateBuffers(RenderingContext& ctx)
 		taskData.m_pointLights =
 			WeakArray<ShaderPointLight>(data, visiblePointLightsCount);
 
-		taskData.m_vPointLights =
-			WeakArray<VisibleNode>(vi.getBegin(VisibilityGroupType::LIGHTS_POINT),
-				visiblePointLightsCount);
+		taskData.m_vPointLights = WeakArray<VisibleNode>(
+			vi.getBegin(VisibilityGroupType::LIGHTS_POINT),
+			visiblePointLightsCount);
 	}
 	else
 	{
@@ -534,9 +534,9 @@ Error Is::populateBuffers(RenderingContext& ctx)
 		taskData.m_spotLights =
 			WeakArray<ShaderSpotLight>(data, visibleSpotLightsCount);
 
-		taskData.m_vSpotLights =
-			WeakArray<VisibleNode>(vi.getBegin(VisibilityGroupType::LIGHTS_SPOT),
-				visibleSpotLightsCount);
+		taskData.m_vSpotLights = WeakArray<VisibleNode>(
+			vi.getBegin(VisibilityGroupType::LIGHTS_SPOT),
+			visibleSpotLightsCount);
 	}
 	else
 	{

+ 2 - 1
src/resource/AsyncLoader.cpp

@@ -50,9 +50,10 @@ void AsyncLoader::stop()
 	{
 		LockGuard<Mutex> lock(m_mtx);
 		m_quit = true;
+
+		m_condVar.notifyOne();
 	}
 
-	m_condVar.notifyOne();
 	Error err = m_thread.join();
 	(void)err;
 }

+ 1 - 1
src/resource/Model.cpp

@@ -133,7 +133,7 @@ PipelinePtr ModelPatch::getPipeline(const RenderingKey& key) const
 		ANKI_ASSERT(0);
 	}
 
-	LockGuard<SpinLock> lock(m_lock);
+	LockGuard<Mutex> lock(m_lock);
 
 	PipelinePtr& ppline =
 		m_pplines[U(key.m_pass)][key.m_lod][key.m_tessellation]

+ 1 - 1
src/scene/BodyNode.cpp

@@ -22,7 +22,7 @@ class BodyFeedbackComponent : public SceneComponent
 {
 public:
 	BodyFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 4 - 3
src/scene/Camera.cpp

@@ -17,7 +17,7 @@ class CameraMoveFeedbackComponent : public SceneComponent
 {
 public:
 	CameraMoveFeedbackComponent(Camera* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 
@@ -45,7 +45,7 @@ class CameraFrustumFeedbackComponent : public SceneComponent
 {
 public:
 	CameraFrustumFeedbackComponent(Camera* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 
@@ -98,7 +98,8 @@ Error Camera::init(const CString& name, Frustum* frustum)
 		| FrustumComponentVisibilityTestFlag::LIGHT_COMPONENTS
 		| FrustumComponentVisibilityTestFlag::LENS_FLARE_COMPONENTS
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROBES
-		| FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES);
+		| FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES
+		| FrustumComponentVisibilityTestFlag::OCCLUDERS);
 	addComponent(frc, true);
 
 	// Feedback component #2

+ 1 - 1
src/scene/FrustumComponent.cpp

@@ -12,7 +12,7 @@ namespace anki
 
 //==============================================================================
 FrustumComponent::FrustumComponent(SceneNode* node, Frustum* frustum)
-	: SceneComponent(Type::FRUSTUM, node)
+	: SceneComponent(CLASS_TYPE, node)
 	, m_frustum(frustum)
 	, m_flags(0)
 {

+ 1 - 1
src/scene/LensFlareComponent.cpp

@@ -13,7 +13,7 @@ namespace anki
 
 //==============================================================================
 LensFlareComponent::LensFlareComponent(SceneNode* node)
-	: SceneComponent(Type::LENS_FLARE, node)
+	: SceneComponent(CLASS_TYPE, node)
 {
 }
 

+ 2 - 3
src/scene/Light.cpp

@@ -21,7 +21,7 @@ class LightFeedbackComponent : public SceneComponent
 {
 public:
 	LightFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 
@@ -291,8 +291,7 @@ SpotLight::SpotLight(SceneGraph* scene)
 //==============================================================================
 Error SpotLight::init(const CString& name)
 {
-	ANKI_CHECK(
-		Light::init(name, LightComponent::LightType::SPOT, &m_frustum));
+	ANKI_CHECK(Light::init(name, LightComponent::LightType::SPOT, &m_frustum));
 
 	FrustumComponent* fr =
 		getSceneAllocator().newInstance<FrustumComponent>(this, &m_frustum);

+ 1 - 1
src/scene/LightComponent.cpp

@@ -10,7 +10,7 @@ namespace anki
 
 //==============================================================================
 LightComponent::LightComponent(SceneNode* node, LightType type)
-	: SceneComponent(Type::LIGHT, node)
+	: SceneComponent(CLASS_TYPE, node)
 	, m_type(type)
 {
 	setInnerAngle(toRad(45.0));

+ 1 - 1
src/scene/ModelNode.cpp

@@ -137,7 +137,7 @@ class ModelMoveFeedbackComponent : public SceneComponent
 {
 public:
 	ModelMoveFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 1 - 1
src/scene/MoveComponent.cpp

@@ -11,7 +11,7 @@ namespace anki
 
 //==============================================================================
 MoveComponent::MoveComponent(SceneNode* node, MoveComponentFlag flags)
-	: SceneComponent(Type::MOVE, node)
+	: SceneComponent(CLASS_TYPE, node)
 	, m_flags(flags)
 {
 	markForUpdate();

+ 38 - 0
src/scene/OccluderComponent.cpp

@@ -0,0 +1,38 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/OccluderComponent.h>
+
+namespace anki
+{
+
+//==============================================================================
+void OccluderComponent::setVertices(const Vec3* begin, U count, U stride)
+{
+	ANKI_ASSERT(begin);
+	ANKI_ASSERT(count > 0 && (count % 3) == 0);
+	ANKI_ASSERT(stride >= sizeof(Vec3));
+
+	m_begin = begin;
+	m_count = count;
+	m_stride = stride;
+
+	Vec3 minv(MAX_F32), maxv(MIN_F32);
+	while(count--)
+	{
+		const Vec3& v = *reinterpret_cast<const Vec3*>(
+			reinterpret_cast<const U8*>(begin) + stride * count);
+		for(U i = 0; i < 3; ++i)
+		{
+			minv[i] = min(minv[i], v[i]);
+			maxv[i] = max(maxv[i], v[i]);
+		}
+	}
+
+	m_aabb.setMin(minv.xyz0());
+	m_aabb.setMax(maxv.xyz0());
+}
+
+} // end namespace anki

+ 107 - 0
src/scene/OccluderNode.cpp

@@ -0,0 +1,107 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/OccluderNode.h>
+#include <anki/scene/SceneGraph.h>
+#include <anki/scene/MoveComponent.h>
+#include <anki/scene/OccluderComponent.h>
+#include <anki/resource/MeshLoader.h>
+
+namespace anki
+{
+
+//==============================================================================
+// OccluderMoveFeedbackComponent                                               =
+//==============================================================================
+
+/// Feedback component.
+class OccluderMoveFeedbackComponent : public SceneComponent
+{
+public:
+	OccluderMoveFeedbackComponent(SceneNode* node)
+		: SceneComponent(SceneComponentType::NONE, node)
+	{
+	}
+
+	ANKI_USE_RESULT Error update(
+		SceneNode& node, F32, F32, Bool& updated) override
+	{
+		updated = false;
+
+		MoveComponent& move = node.getComponent<MoveComponent>();
+		if(move.getTimestamp() == node.getGlobalTimestamp())
+		{
+			OccluderNode& mnode = static_cast<OccluderNode&>(node);
+			mnode.onMoveComponentUpdate(move);
+		}
+
+		return ErrorCode::NONE;
+	}
+};
+
+//==============================================================================
+// OccluderNode                                                                =
+//==============================================================================
+
+//==============================================================================
+OccluderNode::~OccluderNode()
+{
+	m_vertsL.destroy(getSceneAllocator());
+	m_vertsW.destroy(getSceneAllocator());
+}
+
+//==============================================================================
+Error OccluderNode::init(const CString& name, const CString& meshFname)
+{
+	ANKI_CHECK(SceneNode::init(name));
+
+	// Load mesh
+	MeshLoader loader(&getSceneGraph()._getResourceManager());
+	ANKI_CHECK(loader.load(meshFname));
+
+	const U16* indices = reinterpret_cast<const U16*>(loader.getIndexData());
+	U indexCount = loader.getIndexDataSize() / sizeof(U16);
+	U vertSize = loader.getVertexSize();
+
+	m_vertsL.create(getSceneAllocator(), indexCount);
+	m_vertsW.create(getSceneAllocator(), indexCount);
+
+	for(U i = 0; i < indexCount; ++i)
+	{
+		U idx = indices[i];
+		const Vec3* vert = reinterpret_cast<const Vec3*>(
+			loader.getVertexData() + idx * vertSize);
+		m_vertsL[i] = *vert;
+	}
+
+	// Create the components
+	SceneComponent* comp = getSceneAllocator().newInstance<MoveComponent>(this);
+	addComponent(comp, true);
+
+	comp = getSceneAllocator().newInstance<OccluderMoveFeedbackComponent>(this);
+	addComponent(comp, true);
+
+	comp = getSceneAllocator().newInstance<OccluderComponent>(this);
+	addComponent(comp, true);
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+void OccluderNode::onMoveComponentUpdate(MoveComponent& movec)
+{
+	const Transform& trf(movec.getWorldTransform());
+	U count = m_vertsL.getSize();
+	while(count--)
+	{
+
+		m_vertsW[count] = trf.transform(m_vertsL[count]);
+	}
+
+	getComponent<OccluderComponent>().setVertices(
+		&m_vertsW[0], m_vertsW.getSize(), sizeof(m_vertsW[0]));
+}
+
+} // end namespace anki

+ 1 - 1
src/scene/ParticleEmitter.cpp

@@ -236,7 +236,7 @@ class MoveFeedbackComponent : public SceneComponent
 {
 public:
 	MoveFeedbackComponent(ParticleEmitter* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 2 - 2
src/scene/PlayerNode.cpp

@@ -23,7 +23,7 @@ class PlayerNodeFeedbackComponent final : public SceneComponent
 {
 public:
 	PlayerNodeFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 
@@ -75,7 +75,7 @@ class PlayerNodeFeedbackComponent2 final : public SceneComponent
 {
 public:
 	PlayerNodeFeedbackComponent2(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 1 - 1
src/scene/ReflectionProbe.cpp

@@ -27,7 +27,7 @@ class ReflectionProbeMoveFeedbackComponent : public SceneComponent
 {
 public:
 	ReflectionProbeMoveFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 1 - 1
src/scene/ReflectionProxy.cpp

@@ -21,7 +21,7 @@ class ReflectionProxyMoveFeedbackComponent : public SceneComponent
 {
 public:
 	ReflectionProxyMoveFeedbackComponent(SceneNode* node)
-		: SceneComponent(SceneComponent::Type::NONE, node)
+		: SceneComponent(SceneComponentType::NONE, node)
 	{
 	}
 

+ 1 - 1
src/scene/RenderComponent.cpp

@@ -64,7 +64,7 @@ RenderComponentVariable::~RenderComponentVariable()
 
 //==============================================================================
 RenderComponent::RenderComponent(SceneNode* node, const Material* mtl, U64 hash)
-	: SceneComponent(Type::RENDER, node)
+	: SceneComponent(SceneComponentType::RENDER, node)
 	, m_mtl(mtl)
 	, m_hash(hash)
 {

+ 62 - 0
src/scene/SceneComponent.cpp

@@ -5,10 +5,29 @@
 
 #include <anki/scene/SceneComponent.h>
 #include <anki/scene/SceneNode.h>
+#include <anki/scene/SceneGraph.h>
 
 namespace anki
 {
 
+//==============================================================================
+// SceneComponent                                                              =
+//==============================================================================
+
+//==============================================================================
+SceneComponent::SceneComponent(SceneComponentType type, SceneNode* node)
+	: m_node(node)
+	, m_type(type)
+{
+	m_node->getSceneGraph().getSceneComponentLists().insertNew(this);
+}
+
+//==============================================================================
+SceneComponent::~SceneComponent()
+{
+	m_node->getSceneGraph().getSceneComponentLists().remove(this);
+}
+
 //==============================================================================
 Timestamp SceneComponent::getGlobalTimestamp() const
 {
@@ -51,4 +70,47 @@ SceneAllocator<U8> SceneComponent::getAllocator() const
 	return m_node->getSceneAllocator();
 }
 
+//==============================================================================
+// SceneComponentLists                                                         =
+//==============================================================================
+
+//==============================================================================
+void SceneComponentLists::insertNew(SceneComponent* comp)
+{
+	ANKI_ASSERT(comp);
+	ANKI_ASSERT(find(comp) == m_lists[comp->getType()].getEnd());
+
+	m_lists[comp->getType()].pushBack(m_alloc, comp);
+}
+
+//==============================================================================
+void SceneComponentLists::remove(SceneComponent* comp)
+{
+	ANKI_ASSERT(comp);
+
+	auto it = find(comp);
+	ANKI_ASSERT(it != m_lists[comp->getType()].getEnd());
+	m_lists[comp->getType()].erase(m_alloc, it);
+}
+
+//==============================================================================
+List<SceneComponent*>::Iterator SceneComponentLists::find(SceneComponent* comp)
+{
+	ANKI_ASSERT(comp);
+
+	List<SceneComponent*>& list = m_lists[comp->getType()];
+	auto it = list.getBegin();
+	auto end = list.getEnd();
+	while(it != end)
+	{
+		if(*it == comp)
+		{
+			break;
+		}
+		++it;
+	}
+
+	return it;
+}
+
 } // end namespace anki

+ 2 - 0
src/scene/SceneGraph.cpp

@@ -177,6 +177,8 @@ Error SceneGraph::init(AllocAlignedCallback allocCb,
 	m_maxReflectionProxyDistance =
 		config.getNumber("imageReflectionMaxDistance");
 
+	m_componentLists.init(m_alloc);
+
 	// Init the default main camera
 	ANKI_CHECK(newSceneNode<PerspectiveCamera>("mainCamera", m_defaultMainCam));
 	m_defaultMainCam->setAll(toRad(60.0), toRad(60.0), 0.1, 1000.0);

+ 12 - 8
src/scene/Sector.cpp

@@ -8,6 +8,7 @@
 #include <anki/scene/FrustumComponent.h>
 #include <anki/scene/MoveComponent.h>
 #include <anki/scene/SceneGraph.h>
+#include <anki/scene/SoftwareRasterizer.h>
 #include <anki/util/Logger.h>
 #include <anki/resource/ResourceManager.h>
 #include <anki/resource/MeshLoader.h>
@@ -531,6 +532,7 @@ void SectorGroup::spatialDeleted(SpatialComponent* sp)
 
 //==============================================================================
 void SectorGroup::findVisibleSectors(const FrustumComponent& frc,
+	const SoftwareRasterizer* r,
 	List<const Sector*>& visibleSectors,
 	U& spatialsCount) const
 {
@@ -565,20 +567,22 @@ void SectorGroup::findVisibleSectors(const FrustumComponent& frc,
 			if(frc.insideFrustum(s.getBoundingShape()))
 			{
 				findVisibleSectorsInternal(
-					frc, s, visibleSectors, spatialsCount);
+					frc, s, r, visibleSectors, spatialsCount);
 			}
 		}
 	}
 	else
 	{
 		// eye inside a sector
-		findVisibleSectorsInternal(frc, *(*it), visibleSectors, spatialsCount);
+		findVisibleSectorsInternal(
+			frc, *(*it), r, visibleSectors, spatialsCount);
 	}
 }
 
 //==============================================================================
 void SectorGroup::findVisibleSectorsInternal(const FrustumComponent& frc,
 	const Sector& s,
+	const SoftwareRasterizer* r,
 	List<const Sector*>& visibleSectors,
 	U& spatialsCount) const
 {
@@ -607,10 +611,9 @@ void SectorGroup::findVisibleSectorsInternal(const FrustumComponent& frc,
 	{
 		const Portal& p = *(*itp);
 
-		Aabb box;
-		p.getBoundingShape().computeAabb(box);
-
-		if(frc.insideFrustum(p.getBoundingShape()))
+		if(frc.insideFrustum(p.getBoundingShape())
+			&& (r == nullptr
+				   || r->visibilityTest(p.getBoundingShape(), p.m_aabb)))
 		{
 			auto it = p.m_sectors.getBegin();
 			auto end = p.m_sectors.getEnd();
@@ -619,7 +622,7 @@ void SectorGroup::findVisibleSectorsInternal(const FrustumComponent& frc,
 				if(*it != &s)
 				{
 					findVisibleSectorsInternal(
-						frc, *(*it), visibleSectors, spatialsCount);
+						frc, *(*it), r, visibleSectors, spatialsCount);
 				}
 			}
 		}
@@ -642,6 +645,7 @@ void SectorGroup::prepareForVisibilityTests()
 //==============================================================================
 void SectorGroup::findVisibleNodes(const FrustumComponent& frc,
 	U testId,
+	const SoftwareRasterizer* r,
 	SectorGroupVisibilityTestsContext& ctx) const
 {
 	auto alloc = m_scene->getFrameAllocator();
@@ -649,7 +653,7 @@ void SectorGroup::findVisibleNodes(const FrustumComponent& frc,
 	// Find visible sectors
 	ListAuto<const Sector*> visSectors(alloc);
 	U spatialsCount = 0;
-	findVisibleSectors(frc, visSectors, spatialsCount);
+	findVisibleSectors(frc, r, visSectors, spatialsCount);
 
 	if(ANKI_UNLIKELY(spatialsCount == 0))
 	{

+ 12 - 0
src/scene/SoftwareRasterizer.cpp

@@ -5,6 +5,7 @@
 
 #include <anki/scene/SoftwareRasterizer.h>
 #include <anki/collision/Functions.h>
+#include <anki/core/Trace.h>
 
 namespace anki
 {
@@ -312,6 +313,17 @@ void SoftwareRasterizer::rasterizeTriangle(const Vec4* tri)
 //==============================================================================
 Bool SoftwareRasterizer::visibilityTest(
 	const CollisionShape& cs, const Aabb& aabb) const
+{
+	ANKI_TRACE_START_EVENT(SCENE_RASTERIZER_TEST);
+	Bool inside = visibilityTestInternal(cs, aabb);
+	ANKI_TRACE_STOP_EVENT(SCENE_RASTERIZER_TEST);
+
+	return inside;
+}
+
+//==============================================================================
+Bool SoftwareRasterizer::visibilityTestInternal(
+	const CollisionShape& cs, const Aabb& aabb) const
 {
 	// Set the AABB points
 	const Vec4& minv = aabb.getMin();

+ 1 - 1
src/scene/SpatialComponent.cpp

@@ -13,7 +13,7 @@ namespace anki
 
 //==============================================================================
 SpatialComponent::SpatialComponent(SceneNode* node, const CollisionShape* shape)
-	: SceneComponent(Type::SPATIAL, node)
+	: SceneComponent(CLASS_TYPE, node)
 	, m_shape(shape)
 {
 	ANKI_ASSERT(shape);

+ 112 - 2
src/scene/Visibility.cpp

@@ -11,6 +11,7 @@
 #include <anki/scene/LensFlareComponent.h>
 #include <anki/scene/ReflectionProbeComponent.h>
 #include <anki/scene/ReflectionProxyComponent.h>
+#include <anki/scene/OccluderComponent.h>
 #include <anki/scene/Light.h>
 #include <anki/scene/MoveComponent.h>
 #include <anki/renderer/MainRenderer.h>
@@ -57,15 +58,70 @@ void VisibilityContext::submitNewWork(FrustumComponent& frc, ThreadHive& hive)
 	// Submit new work
 	//
 
+	// Software rasterizer tasks
+	SoftwareRasterizer* r = nullptr;
+	Array<ThreadHiveDependencyHandle, ThreadHive::MAX_THREADS> rasterizeDeps;
+	if(frc.visibilityTestsEnabled(
+		   FrustumComponentVisibilityTestFlag::OCCLUDERS))
+	{
+		// Gather triangles task
+		GatherVisibleTrianglesTask* gather =
+			alloc.newInstance<GatherVisibleTrianglesTask>();
+		gather->m_visCtx = this;
+		gather->m_frc = &frc;
+		gather->m_batchCount = 0;
+
+		r = &gather->m_r;
+
+		ThreadHiveTask gatherTask;
+		gatherTask.m_callback = GatherVisibleTrianglesTask::callback;
+		gatherTask.m_argument = gather;
+
+		hive.submitTasks(&gatherTask, 1);
+
+		// Rasterize triangles task
+		U count = hive.getThreadCount();
+		RasterizeTrianglesTask* rasterize =
+			alloc.newArray<RasterizeTrianglesTask>(count);
+
+		Array<ThreadHiveTask, ThreadHive::MAX_THREADS> rastTasks;
+		while(count--)
+		{
+			RasterizeTrianglesTask& rast = rasterize[count];
+			rast.m_gatherTask = gather;
+			rast.m_taskIdx = count;
+			rast.m_taskCount = hive.getThreadCount();
+
+			rastTasks[count].m_callback = RasterizeTrianglesTask::callback;
+			rastTasks[count].m_argument = &rast;
+			rastTasks[count].m_inDependencies =
+				WeakArray<ThreadHiveDependencyHandle>(
+					&gatherTask.m_outDependency, 1);
+		}
+
+		count = hive.getThreadCount();
+		hive.submitTasks(&rastTasks[0], count);
+		while(count--)
+		{
+			rasterizeDeps[count] = rastTasks[count].m_outDependency;
+		}
+	}
+
 	// Gather task
 	GatherVisiblesFromSectorsTask* gather =
 		alloc.newInstance<GatherVisiblesFromSectorsTask>();
 	gather->m_visCtx = this;
 	gather->m_frc = &frc;
+	gather->m_r = r;
 
 	ThreadHiveTask gatherTask;
 	gatherTask.m_callback = GatherVisiblesFromSectorsTask::callback;
 	gatherTask.m_argument = gather;
+	if(r)
+	{
+		gatherTask.m_inDependencies = WeakArray<ThreadHiveDependencyHandle>(
+			&rasterizeDeps[0], hive.getThreadCount());
+	}
 
 	hive.submitTasks(&gatherTask, 1);
 
@@ -88,8 +144,8 @@ void VisibilityContext::submitNewWork(FrustumComponent& frc, ThreadHive& hive)
 		auto& task = testTasks[i];
 		task.m_callback = VisibilityTestTask::callback;
 		task.m_argument = &test;
-		task.m_inDependencies =
-			WeakArray<ThreadHiveDependencyHandle>(&gatherTask.m_outDependency, 1);
+		task.m_inDependencies = WeakArray<ThreadHiveDependencyHandle>(
+			&gatherTask.m_outDependency, 1);
 	}
 
 	hive.submitTasks(&testTasks[0], testCount);
@@ -113,6 +169,60 @@ void VisibilityContext::submitNewWork(FrustumComponent& frc, ThreadHive& hive)
 	hive.submitTasks(&combineTask, 1);
 }
 
+//==============================================================================
+// GatherVisibleTrianglesTask                                                  =
+//==============================================================================
+
+//==============================================================================
+void GatherVisibleTrianglesTask::gather()
+{
+	ANKI_TRACE_START_EVENT(SCENE_VISIBILITY_GATHER_TRIANGLES);
+
+	auto alloc = m_visCtx->m_scene->getFrameAllocator();
+	SceneComponentLists& lists = m_visCtx->m_scene->getSceneComponentLists();
+
+	ANKI_ASSERT(m_batchCount == 0);
+	lists.iterateComponents<OccluderComponent>([&](OccluderComponent& comp) {
+		if(m_frc->insideFrustum(comp.getBoundingVolume()))
+		{
+			TriangleBatch* batch = alloc.newInstance<TriangleBatch>();
+			comp.getVertices(batch->m_begin, batch->m_count, batch->m_stride);
+			m_batches.pushBack(batch);
+			++m_batchCount;
+		}
+	});
+
+	m_r.init(alloc);
+	m_r.prepare(m_frc->getViewMatrix(), m_frc->getProjectionMatrix(), 80, 50);
+
+	ANKI_TRACE_STOP_EVENT(SCENE_VISIBILITY_GATHER_TRIANGLES);
+}
+
+//==============================================================================
+// RasterizeTrianglesTask                                                      =
+//==============================================================================
+
+//==============================================================================
+void RasterizeTrianglesTask::rasterize()
+{
+	ANKI_TRACE_START_EVENT(SCENE_VISIBILITY_RASTERIZE);
+
+	PtrSize start, endi;
+	ThreadPoolTask::choseStartEnd(
+		m_taskIdx, m_taskCount, m_gatherTask->m_batchCount, start, endi);
+
+	auto it = m_gatherTask->m_batches.getBegin() + start;
+	auto end = m_gatherTask->m_batches.getBegin() + endi;
+	while(it != end)
+	{
+		const F32* first = &it->m_begin[0][0];
+		m_gatherTask->m_r.draw(first, it->m_count, it->m_stride);
+		++it;
+	}
+
+	ANKI_TRACE_STOP_EVENT(SCENE_VISIBILITY_RASTERIZE);
+}
+
 //==============================================================================
 // VisibilityTestTask                                                          =
 //==============================================================================

+ 147 - 0
src/script/Scene.cpp

@@ -2518,6 +2518,83 @@ static inline void wrapReflectionProxy(lua_State* l)
 	lua_settop(l, 0);
 }
 
+//==============================================================================
+// OccluderNode                                                                =
+//==============================================================================
+
+//==============================================================================
+static const char* classnameOccluderNode = "OccluderNode";
+
+template<>
+I64 LuaBinder::getWrappedTypeSignature<OccluderNode>()
+{
+	return -6885028590097645115;
+}
+
+template<>
+const char* LuaBinder::getWrappedTypeName<OccluderNode>()
+{
+	return classnameOccluderNode;
+}
+
+//==============================================================================
+/// Pre-wrap method OccluderNode::getSceneNodeBase.
+static inline int pwrapOccluderNodegetSceneNodeBase(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 1);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(
+		   l, 1, classnameOccluderNode, -6885028590097645115, ud))
+	{
+		return -1;
+	}
+
+	OccluderNode* self = ud->getData<OccluderNode>();
+
+	// Call the method
+	SceneNode& ret = *self;
+
+	// Push return value
+	voidp = lua_newuserdata(l, sizeof(UserData));
+	ud = static_cast<UserData*>(voidp);
+	luaL_setmetatable(l, "SceneNode");
+	ud->initPointed(-2220074417980276571, const_cast<SceneNode*>(&ret));
+
+	return 1;
+}
+
+//==============================================================================
+/// Wrap method OccluderNode::getSceneNodeBase.
+static int wrapOccluderNodegetSceneNodeBase(lua_State* l)
+{
+	int res = pwrapOccluderNodegetSceneNodeBase(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+//==============================================================================
+/// Wrap class OccluderNode.
+static inline void wrapOccluderNode(lua_State* l)
+{
+	LuaBinder::createClass(l, classnameOccluderNode);
+	LuaBinder::pushLuaCFuncMethod(
+		l, "getSceneNodeBase", wrapOccluderNodegetSceneNodeBase);
+	lua_settop(l, 0);
+}
+
 //==============================================================================
 // SceneGraph                                                                  =
 //==============================================================================
@@ -3199,6 +3276,73 @@ static int wrapSceneGraphnewReflectionProxy(lua_State* l)
 	return 0;
 }
 
+//==============================================================================
+/// Pre-wrap method SceneGraph::newOccluderNode.
+static inline int pwrapSceneGraphnewOccluderNode(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 3);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(
+		   l, 1, classnameSceneGraph, -7754439619132389154, ud))
+	{
+		return -1;
+	}
+
+	SceneGraph* self = ud->getData<SceneGraph>();
+
+	// Pop arguments
+	const char* arg0;
+	if(LuaBinder::checkString(l, 2, arg0))
+	{
+		return -1;
+	}
+
+	const char* arg1;
+	if(LuaBinder::checkString(l, 3, arg1))
+	{
+		return -1;
+	}
+
+	// Call the method
+	OccluderNode* ret = newSceneNode<OccluderNode>(self, arg0, arg1);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(UserData));
+	ud = static_cast<UserData*>(voidp);
+	luaL_setmetatable(l, "OccluderNode");
+	ud->initPointed(-6885028590097645115, const_cast<OccluderNode*>(ret));
+
+	return 1;
+}
+
+//==============================================================================
+/// Wrap method SceneGraph::newOccluderNode.
+static int wrapSceneGraphnewOccluderNode(lua_State* l)
+{
+	int res = pwrapSceneGraphnewOccluderNode(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 //==============================================================================
 /// Pre-wrap method SceneGraph::setActiveCamera.
 static inline int pwrapSceneGraphsetActiveCamera(lua_State* l)
@@ -3273,6 +3417,8 @@ static inline void wrapSceneGraph(lua_State* l)
 		l, "newReflectionProbe", wrapSceneGraphnewReflectionProbe);
 	LuaBinder::pushLuaCFuncMethod(
 		l, "newReflectionProxy", wrapSceneGraphnewReflectionProxy);
+	LuaBinder::pushLuaCFuncMethod(
+		l, "newOccluderNode", wrapSceneGraphnewOccluderNode);
 	LuaBinder::pushLuaCFuncMethod(
 		l, "setActiveCamera", wrapSceneGraphsetActiveCamera);
 	lua_settop(l, 0);
@@ -3341,6 +3487,7 @@ void wrapModuleScene(lua_State* l)
 	wrapParticleEmitter(l);
 	wrapReflectionProbe(l);
 	wrapReflectionProxy(l);
+	wrapOccluderNode(l);
 	wrapSceneGraph(l);
 	LuaBinder::pushLuaCFunc(l, "getSceneGraph", wrapgetSceneGraph);
 }

+ 16 - 0
src/script/Scene.xml

@@ -268,6 +268,14 @@ static SceneGraph* getSceneGraph(lua_State* l)
 				</method>
 			</methods>
 		</class>
+		<class name="OccluderNode">
+			<methods>
+				<method name="getSceneNodeBase">
+					<overrideCall>SceneNode&amp; ret = *self;</overrideCall>
+					<return>SceneNode&amp;</return>
+				</method>
+			</methods>
+		</class>
 		<class name="SceneGraph">
 			<methods>
 				<method name="newPerspectiveCamera">
@@ -348,6 +356,14 @@ static SceneGraph* getSceneGraph(lua_State* l)
 					</args>
 					<return>ReflectionProxy*</return>
 				</method>
+				<method name="newOccluderNode">
+					<overrideCall><![CDATA[OccluderNode* ret = newSceneNode<OccluderNode>(self, arg0, arg1);]]></overrideCall>
+					<args>
+						<arg>const CString&amp;</arg>
+						<arg>const CString&amp;</arg>
+					</args>
+					<return>OccluderNode*</return>
+				</method>
 				<method name="setActiveCamera">
 					<args>
 						<arg>SceneNode*</arg>

+ 3 - 2
src/ui/UiInterfaceImpl.cpp

@@ -136,8 +136,9 @@ void UiInterfaceImpl::endRendering()
 }
 
 //==============================================================================
-void UiInterfaceImpl::drawLines(
-	const WeakArray<UVec2>& positions, const Color& color, const UVec2& canvasSize)
+void UiInterfaceImpl::drawLines(const WeakArray<UVec2>& positions,
+	const Color& color,
+	const UVec2& canvasSize)
 {
 	StageId stageId = StageId::LINES;
 

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit a0bb4fed92773fc84ce10bd2c00fd44a8137811e
+Subproject commit e7b5666520d2202d02a846156a6afdb011a08bd7

+ 27 - 0
tools/scene/Exporter.cpp

@@ -853,6 +853,16 @@ void Exporter::visitNode(const aiNode* ainode)
 				proxy.m_transform = toAnkiMatrix(ainode->mTransformation);
 				m_reflectionProxies.push_back(proxy);
 
+				special = true;
+			}
+			else if(prop.first == "occluder" && prop.second == "true")
+			{
+				OccluderNode occluder;
+
+				occluder.m_meshIndex = meshIndex;
+				occluder.m_transform = toAnkiMatrix(ainode->mTransformation);
+				m_occluders.push_back(occluder);
+
 				special = true;
 			}
 		}
@@ -1023,6 +1033,23 @@ void Exporter::exportAll()
 		++i;
 	}
 
+	//
+	// Export occluders
+	//
+	i = 0;
+	for(const OccluderNode& occluder : m_occluders)
+	{
+		const aiMesh& mesh = *m_scene->mMeshes[occluder.m_meshIndex];
+		exportMesh(mesh, nullptr, 3);
+
+		std::string name = "occluder" + std::to_string(i);
+		file << "\nnode = scene:newOccluderNode(\"" << name << "\", \""
+			 << m_rpath << mesh.mName.C_Str() << ".ankimesh\")\n";
+
+		writeNodeTransform("node", occluder.m_transform);
+		++i;
+	}
+
 	//
 	// Export nodes and models.
 	//

+ 8 - 0
tools/scene/Exporter.h

@@ -83,6 +83,13 @@ public:
 	uint32_t m_meshIndex; ///< Points to the scene that is not triangulated.
 };
 
+class OccluderNode
+{
+public:
+	aiMatrix4x4 m_transform;
+	uint32_t m_meshIndex; ///< Points to the scene that is not triangulated.
+};
+
 /// AnKi exporter.
 class Exporter
 {
@@ -110,6 +117,7 @@ public:
 	std::vector<ParticleEmitter> m_particleEmitters;
 	std::vector<ReflectionProbe> m_reflectionProbes;
 	std::vector<ReflectionProxy> m_reflectionProxies;
+	std::vector<OccluderNode> m_occluders;
 
 	/// Load the scene.
 	void load();