Skip to content

Commit

Permalink
UPBGE: Improve frustum culling performance. (#701)
Browse files Browse the repository at this point in the history
* UPBGE: Improve frustum culling performance.

Frustum culling was improved in two ways. First, simplificate the bounding
box update. Second the test are parallelized using TBB range loop.

The bounding box update is simplified by adding the call to RAS_Deformer::UpdateBuckets
in KX_GameObject::UpdateBounds after we checked that the objects as a
bounding box. By doing this we avoid a call to GetDeformer for all objects
without a bounding box, with an unmodified bounding box or without auto
update.

All the computation of culling objects is moved into KX_CullingHandler,
this class construct it's own objects list and returns it in Process function.
Process function build a CullTask and launch it usign tbb::parallal_reduce.

Each CullTask have an operator() to test a range of object, any objects passing
the culling test is added in a task local objects list. Once the tests
finished the CullTask merge these objects list in function join to end
up with the list of all non-culled objects.
This technique of reduce of list is way better than using a shared object
list for all tasks and lock a mutex before adding an object. The method
with mutex was always slower than without parallelization.

This patch was tested with cube meshes :
number of object | previous time | new time
1000 | 0.06 | 0.07
8000 | 1.04 | 0.55
27000 | 3.81 | 1.90
125000 | 16.16 | 8.31
  • Loading branch information
panzergame authored Jun 15, 2018
1 parent 3257be1 commit 20e9ff0
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 100 deletions.
2 changes: 2 additions & 0 deletions build_files/cmake/macros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@ function(setup_liblinks

#target_link_libraries(${target} ${PLATFORM_LINKLIBS} ${CMAKE_DL_LIBS})
target_link_libraries(${target} ${PLATFORM_LINKLIBS})

target_link_libraries(${target} ${TBB_LIBRARIES})
endfunction()


Expand Down
1 change: 1 addition & 0 deletions build_files/cmake/platform/platform_unix.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ find_package_wrapper(JPEG REQUIRED)
find_package_wrapper(PNG REQUIRED)
find_package_wrapper(ZLIB REQUIRED)
find_package_wrapper(Freetype REQUIRED)
find_package_wrapper(TBB REQUIRED)

if(WITH_LZO AND WITH_SYSTEM_LZO)
find_package_wrapper(LZO)
Expand Down
5 changes: 5 additions & 0 deletions build_files/cmake/platform/platform_win32.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,11 @@ if(WITH_SDL)
set(SDL_LIBRARY ${SDL_LIBPATH}/SDL2.lib)
endif()

if(WITH_GAMEENGINE)
set(TBB_LIBRARIES optimized ${LIBDIR}/tbb/lib/tbb.lib debug ${LIBDIR}/tbb/lib/tbb_debug.lib)
set(TBB_INCLUDE_DIR ${LIBDIR}/tbb/include)
endif()

# Audio IO
if(WITH_SYSTEM_AUDASPACE)
set(AUDASPACE_INCLUDE_DIRS ${LIBDIR}/audaspace/include/audaspace)
Expand Down
2 changes: 2 additions & 0 deletions source/gameengine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

remove_extra_strict_flags()

blender_include_dirs_sys("${TBB_INCLUDE_DIR}")

# there are too many inter-includes so best define here
if(WITH_PYTHON)
blender_include_dirs_sys("${PYTHON_INCLUDE_DIRS}")
Expand Down
8 changes: 4 additions & 4 deletions source/gameengine/Ketsji/KX_BoundingBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,31 +65,31 @@ const mt::vec3& KX_BoundingBox::GetMax() const
{
// Update AABB to make sure we have the last one.
m_owner->UpdateBounds(false);
const SG_BBox& box = m_owner->GetCullingNode()->GetAabb();
const SG_BBox& box = m_owner->GetCullingNode().GetAabb();
return box.GetMax();
}

const mt::vec3& KX_BoundingBox::GetMin() const
{
// Update AABB to make sure we have the last one.
m_owner->UpdateBounds(false);
const SG_BBox& box = m_owner->GetCullingNode()->GetAabb();
const SG_BBox& box = m_owner->GetCullingNode().GetAabb();
return box.GetMin();
}

const mt::vec3 KX_BoundingBox::GetCenter() const
{
// Update AABB to make sure we have the last one.
m_owner->UpdateBounds(false);
const SG_BBox& box = m_owner->GetCullingNode()->GetAabb();
const SG_BBox& box = m_owner->GetCullingNode().GetAabb();
return box.GetCenter();
}

float KX_BoundingBox::GetRadius() const
{
// Update AABB to make sure we have the last one.
m_owner->UpdateBounds(false);
const SG_BBox& box = m_owner->GetCullingNode()->GetAabb();
const SG_BBox& box = m_owner->GetCullingNode().GetAabb();
return box.GetRadius();
}

Expand Down
77 changes: 62 additions & 15 deletions source/gameengine/Ketsji/KX_CullingHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,64 @@

#include "SG_Node.h"

KX_CullingHandler::KX_CullingHandler(std::vector<KX_GameObject *>& objects, const SG_Frustum& frustum)
:m_activeObjects(objects),
m_frustum(frustum)
{
}
#include "tbb/tbb.h"

void KX_CullingHandler::Process(KX_GameObject *object)
class CullTask
{
SG_Node *sgnode = object->GetNode();
SG_CullingNode *node = object->GetCullingNode();
public:
std::vector<KX_GameObject *> m_activeObjects;
EXP_ListValue<KX_GameObject> *m_objects;
KX_CullingHandler& m_handler;
int m_layer;

CullTask(EXP_ListValue<KX_GameObject> *objects, KX_CullingHandler& handler, int layer)
:m_objects(objects),
m_handler(handler),
m_layer(layer)
{
}

CullTask(const CullTask& other, tbb::split)
:m_objects(other.m_objects),
m_handler(other.m_handler),
m_layer(other.m_layer)
{
}

void operator()(const tbb::blocked_range<size_t>& r)
{
for (unsigned int i = r.begin(), end = r.end(); i < end; ++i) {
KX_GameObject *obj = m_objects->GetValue(i);
if (obj->Renderable(m_layer)) {
// Update the object bounding volume box.
obj->UpdateBounds(false);

const mt::mat3x4 trans = sgnode->GetWorldTransform();
const mt::vec3 &scale = sgnode->GetWorldScaling();
const SG_BBox& aabb = node->GetAabb();
SG_CullingNode& node = obj->GetCullingNode();
const bool culled = m_handler.Test(obj->NodeGetWorldTransform(), obj->NodeGetWorldScaling(), node.GetAabb());

node.SetCulled(culled);
if (!culled) {
m_activeObjects.push_back(obj);
}
}
}
}

void join(const CullTask& other)
{
m_activeObjects.insert(m_activeObjects.end(), other.m_activeObjects.begin(), other.m_activeObjects.end());
}
};

KX_CullingHandler::KX_CullingHandler(EXP_ListValue<KX_GameObject> *objects, const SG_Frustum& frustum, int layer)
:m_objects(objects),
m_frustum(frustum),
m_layer(layer)
{
}

bool KX_CullingHandler::Test(const mt::mat3x4& trans, const mt::vec3& scale, const SG_BBox& aabb) const
{
bool culled = true;
const float maxscale = std::max(std::max(fabs(scale.x), fabs(scale.y)), fabs(scale.z));
const SG_Frustum::TestType sphereTest = m_frustum.SphereInsideFrustum(trans * aabb.GetCenter(), maxscale * aabb.GetRadius());
Expand All @@ -32,8 +75,12 @@ void KX_CullingHandler::Process(KX_GameObject *object)
culled = (m_frustum.AabbInsideFrustum(aabb.GetMin(), aabb.GetMax(), mat) == SG_Frustum::OUTSIDE);
}

node->SetCulled(culled);
if (!culled) {
m_activeObjects.push_back(object);
}
return culled;
}

std::vector<KX_GameObject *> KX_CullingHandler::Process()
{
CullTask task(m_objects, *this, m_layer);
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, m_objects->GetCount()), task);
return task.m_activeObjects;
}
27 changes: 19 additions & 8 deletions source/gameengine/Ketsji/KX_CullingHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@
#define __KX_CULLING_HANDLER_H__

#include "SG_Frustum.h"
#include <vector>
#include "SG_BBox.h"

#include "EXP_ListValue.h"

#ifdef WIN32
# ifndef NOMINMAX
# define NOMINMAX
# endif
#endif

class KX_GameObject;

class KX_CullingHandler
{
private:
/// List of all objects to render after the culling pass.
std::vector<KX_GameObject *>& m_activeObjects;
/// List of all objects to test.
EXP_ListValue<KX_GameObject> *m_objects;
/// The camera frustum data.
const SG_Frustum& m_frustum;
/// Layer to ignore some objects.
int m_layer;


public:
KX_CullingHandler(std::vector<KX_GameObject *>& objects, const SG_Frustum& frustum);
KX_CullingHandler(EXP_ListValue<KX_GameObject> *objects, const SG_Frustum& frustum, int layer);
~KX_CullingHandler() = default;

/** Process the culling of a new object, if the culling succeeded the
* object is added in m_activeObjects.
*/
void Process(KX_GameObject *object);
bool Test(const mt::mat3x4& trans, const mt::vec3& scale, const SG_BBox& aabb) const;

/// Process the culling of all object and return a list of non-culled objects.
std::vector<KX_GameObject *> Process();
};

#endif // __KX_CULLING_HANDLER_H__
20 changes: 15 additions & 5 deletions source/gameengine/Ketsji/KX_GameObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "RAS_Mesh.h"
#include "RAS_MeshUser.h"
#include "RAS_BoundingBoxManager.h"
#include "RAS_Deformer.h"
#include "KX_NavMeshObject.h"
#include "KX_Mesh.h"
#include "KX_PolyProxy.h"
Expand Down Expand Up @@ -753,9 +754,9 @@ RAS_MeshUser *KX_GameObject::GetMeshUser() const
return m_meshUser;
}

bool KX_GameObject::UseCulling() const
bool KX_GameObject::Renderable(int layer) const
{
return (m_meshUser != nullptr);
return (m_meshUser != nullptr) && m_bVisible && (layer == 0 || m_layer & layer);
}

void KX_GameObject::SetLodManager(KX_LodManager *lodManager)
Expand Down Expand Up @@ -1383,6 +1384,15 @@ void KX_GameObject::UpdateBounds(bool force)
return;
}

RAS_Deformer *deformer = GetDeformer();
if (deformer) {
/** Update all the deformer, not only per material.
* One of the side effect is to clear some flags about AABB calculation.
* like in KX_SoftBodyDeformer.
*/
deformer->UpdateBuckets();
}

// AABB Box : min/max.
mt::vec3 aabbMin;
mt::vec3 aabbMax;
Expand All @@ -1409,9 +1419,9 @@ void KX_GameObject::GetBoundsAabb(mt::vec3 &aabbMin, mt::vec3 &aabbMax) const
m_cullingNode.GetAabb().Get(aabbMin, aabbMax);
}

SG_CullingNode *KX_GameObject::GetCullingNode()
SG_CullingNode& KX_GameObject::GetCullingNode()
{
return &m_cullingNode;
return m_cullingNode;
}

KX_GameObject::ActivityCullingInfo& KX_GameObject::GetActivityCullingInfo()
Expand Down Expand Up @@ -2682,7 +2692,7 @@ int KX_GameObject::pyattr_set_visible(EXP_PyObjectPlus *self_v, const EXP_PYATTR
PyObject *KX_GameObject::pyattr_get_culled(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef)
{
KX_GameObject *self = static_cast<KX_GameObject *>(self_v);
return PyBool_FromLong(self->GetCulled());
return PyBool_FromLong(self->GetCullingNode().GetCulled());
}

PyObject *KX_GameObject::pyattr_get_cullingBox(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef)
Expand Down
22 changes: 3 additions & 19 deletions source/gameengine/Ketsji/KX_GameObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,8 @@ class KX_GameObject : public SCA_IObject, public mt::SimdClassAllocator
/// Return the mesh user of this game object.
RAS_MeshUser *GetMeshUser() const;

/// Return true when the object can be culled.
bool UseCulling() const;
/// Return true when the object can be rendered.
bool Renderable(int layer) const;

/**
* Was this object marked visible? (only for the explicit
Expand All @@ -737,22 +737,6 @@ class KX_GameObject : public SCA_IObject, public mt::SimdClassAllocator
bool recursive
);

/**
* Was this object culled?
*/
inline bool
GetCulled(
void
) { return m_cullingNode.GetCulled(); }

/**
* Set culled flag of this object
*/
inline void
SetCulled(
bool c
) { m_cullingNode.SetCulled(c); }

/**
* Is this object an occluder?
*/
Expand Down Expand Up @@ -809,7 +793,7 @@ class KX_GameObject : public SCA_IObject, public mt::SimdClassAllocator
void SetBoundsAabb(const mt::vec3 &aabbMin, const mt::vec3 &aabbMax);
void GetBoundsAabb(mt::vec3 &aabbMin, mt::vec3 &aabbMax) const;

SG_CullingNode *GetCullingNode();
SG_CullingNode& GetCullingNode();

ActivityCullingInfo& GetActivityCullingInfo();
void SetActivityCullingInfo(const ActivityCullingInfo& cullingInfo);
Expand Down
7 changes: 2 additions & 5 deletions source/gameengine/Ketsji/KX_KetsjiEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,7 @@ void KX_KetsjiEngine::RenderShadowBuffers(KX_Scene *scene)
/* binds framebuffer object, sets up camera .. */
raslight->BindShadowBuffer(m_canvas, cam, camtrans);

std::vector<KX_GameObject *> objects;
/* update scene */
scene->CalculateVisibleMeshes(objects, cam, raslight->GetShadowLayer());
const std::vector<KX_GameObject *> objects = scene->CalculateVisibleMeshes(cam, raslight->GetShadowLayer());

m_logger.StartLog(tc_animations, m_kxsystem->GetTimeInSeconds());
UpdateAnimations(scene);
Expand Down Expand Up @@ -992,8 +990,7 @@ void KX_KetsjiEngine::RenderCamera(KX_Scene *scene, const CameraRenderData& came

m_logger.StartLog(tc_scenegraph, m_kxsystem->GetTimeInSeconds());

std::vector<KX_GameObject *> objects;
scene->CalculateVisibleMeshes(objects, cullingcam, 0);
const std::vector<KX_GameObject *> objects = scene->CalculateVisibleMeshes(cullingcam, 0);

// update levels of detail
scene->UpdateObjectLods(cullingcam, objects);
Expand Down
Loading

0 comments on commit 20e9ff0

Please sign in to comment.