Graphics/CullingList: Improve culling list

- Now supports box culling
- Removed branch
- Removed complex hash combination and replaced it with a much faster algorithm
- It now supports partial visibility
This commit is contained in:
Jérôme Leclercq 2018-08-31 17:26:50 +02:00
parent 56873b92b0
commit 7bb6c84752
4 changed files with 205 additions and 168 deletions

View File

@ -217,7 +217,10 @@ namespace Ndk
if (camComponent.UpdateVisibility(visibilityHash) || m_forceRenderQueueInvalidation || forceInvalidation)
{
renderQueue->Clear();
for (const GraphicsComponent* gfxComponent : m_drawableCulling)
for (const GraphicsComponent* gfxComponent : m_drawableCulling.GetFullyVisibleResults())
gfxComponent->AddToRenderQueue(renderQueue);
for (const GraphicsComponent* gfxComponent : m_drawableCulling.GetPartiallyVisibleResults())
gfxComponent->AddToRenderQueue(renderQueue);
for (const Ndk::EntityHandle& light : m_lights)

View File

@ -23,11 +23,13 @@ namespace Nz
{
public:
template<CullTest> class Entry;
class BoxEntry;
class NoTestEntry;
class SphereEntry;
class VolumeEntry;
template<CullTest> friend class Entry;
friend BoxEntry;
friend NoTestEntry;
friend SphereEntry;
friend VolumeEntry;
@ -43,6 +45,10 @@ namespace Nz
std::size_t FillWithAllEntries(bool* forceInvalidation = nullptr);
const ResultContainer& GetFullyVisibleResults() const;
const ResultContainer& GetPartiallyVisibleResults() const;
BoxEntry RegisterBoxTest(const T* renderable);
NoTestEntry RegisterNoTest(const T* renderable);
SphereEntry RegisterSphereTest(const T* renderable);
VolumeEntry RegisterVolumeTest(const T* renderable);
@ -50,37 +56,24 @@ namespace Nz
CullingList& operator=(const CullingList& renderable) = delete;
CullingList& operator=(CullingList&& renderable) = delete;
// STL API
typename ResultContainer::iterator begin();
typename ResultContainer::const_iterator begin() const;
typename ResultContainer::const_iterator cbegin() const;
typename ResultContainer::const_iterator cend() const;
typename ResultContainer::const_reverse_iterator crbegin() const;
typename ResultContainer::const_reverse_iterator crend() const;
bool empty() const;
typename ResultContainer::iterator end();
typename ResultContainer::const_iterator end() const;
typename ResultContainer::reverse_iterator rbegin();
typename ResultContainer::const_reverse_iterator rbegin() const;
typename ResultContainer::reverse_iterator rend();
typename ResultContainer::const_reverse_iterator rend() const;
typename ResultContainer::size_type size() const;
NazaraSignal(OnCullingListRelease, CullingList* /*cullingList*/);
private:
inline void NotifyBoxUpdate(std::size_t index, const Boxf& boundingVolume);
inline void NotifyForceInvalidation(CullTest type, std::size_t index);
inline void NotifyMovement(CullTest type, std::size_t index, void* oldPtr, void* newPtr);
inline void NotifyRelease(CullTest type, std::size_t index);
inline void NotifySphereUpdate(std::size_t index, const Spheref& sphere);
inline void NotifyVolumeUpdate(std::size_t index, const BoundingVolumef& boundingVolume);
struct BoxVisibilityEntry
{
Boxf box;
BoxEntry* entry;
const T* renderable;
bool forceInvalidation;
};
struct NoTestVisibilityEntry
{
NoTestEntry* entry;
@ -104,10 +97,12 @@ namespace Nz
bool forceInvalidation;
};
std::vector<BoxVisibilityEntry> m_boxTestList;
std::vector<NoTestVisibilityEntry> m_noTestList;
std::vector<SphereVisibilityEntry> m_sphereTestList;
std::vector<VolumeVisibilityEntry> m_volumeTestList;
ResultContainer m_results;
ResultContainer m_fullyVisibleResults;
ResultContainer m_partiallyVisibleResults;
};
template<typename T>
@ -135,6 +130,24 @@ namespace Nz
std::size_t m_index;
CullingList* m_parent;
};
template<typename T>
class CullingList<T>::BoxEntry : public CullingList<T>::template Entry<CullTest::Box>
{
friend CullingList;
public:
BoxEntry();
BoxEntry(BoxEntry&&) = default;
~BoxEntry() = default;
void UpdateBox(const Boxf& box);
BoxEntry& operator=(BoxEntry&&) = default;
private:
BoxEntry(CullingList* parent, std::size_t index);
};
template<typename T>
class CullingList<T>::NoTestEntry : public CullingList<T>::template Entry<CullTest::NoTest>

View File

@ -16,16 +16,48 @@ namespace Nz
template<typename T>
std::size_t CullingList<T>::Cull(const Frustumf& frustum, bool* forceInvalidation)
{
m_results.clear();
m_fullyVisibleResults.clear();
m_partiallyVisibleResults.clear();
bool forcedInvalidation = false;
std::size_t visibleHash = 0U;
std::size_t fullyVisibleHash = 5U;
std::size_t partiallyVisibleHash = 5U;
auto CombineHash = [](std::size_t currentHash, std::size_t newHash)
{
return currentHash * 23 + newHash;
};
for (BoxVisibilityEntry& entry : m_boxTestList)
{
switch (frustum.Intersect(entry.box))
{
case IntersectionSide_Inside:
m_fullyVisibleResults.push_back(entry.renderable);
CombineHash(fullyVisibleHash, std::hash<const T*>()(entry.renderable));
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
break;
case IntersectionSide_Intersecting:
m_partiallyVisibleResults.push_back(entry.renderable);
CombineHash(partiallyVisibleHash, std::hash<const T*>()(entry.renderable));
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
break;
case IntersectionSide_Outside:
break;
}
}
for (NoTestVisibilityEntry& entry : m_noTestList)
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
m_fullyVisibleResults.push_back(entry.renderable);
CombineHash(fullyVisibleHash, std::hash<const T*>()(entry.renderable));
if (entry.forceInvalidation)
{
@ -36,34 +68,87 @@ namespace Nz
for (SphereVisibilityEntry& entry : m_sphereTestList)
{
if (frustum.Contains(entry.sphere))
switch (frustum.Intersect(entry.sphere))
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
case IntersectionSide_Inside:
m_fullyVisibleResults.push_back(entry.renderable);
CombineHash(fullyVisibleHash, std::hash<const T*>()(entry.renderable));
if (entry.forceInvalidation)
{
forcedInvalidation = true;
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
}
break;
case IntersectionSide_Intersecting:
m_partiallyVisibleResults.push_back(entry.renderable);
CombineHash(partiallyVisibleHash, std::hash<const T*>()(entry.renderable));
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
break;
case IntersectionSide_Outside:
break;
}
}
for (VolumeVisibilityEntry& entry : m_volumeTestList)
{
if (frustum.Contains(entry.volume))
switch (frustum.Intersect(entry.volume))
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
case IntersectionSide_Inside:
m_fullyVisibleResults.push_back(entry.renderable);
CombineHash(fullyVisibleHash, std::hash<const T*>()(entry.renderable));
if (entry.forceInvalidation)
{
forcedInvalidation = true;
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
}
break;
case IntersectionSide_Intersecting:
m_partiallyVisibleResults.push_back(entry.renderable);
CombineHash(partiallyVisibleHash, std::hash<const T*>()(entry.renderable));
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
break;
case IntersectionSide_Outside:
break;
}
}
if (forceInvalidation)
*forceInvalidation = forcedInvalidation;
return 5 + partiallyVisibleHash * 17 + fullyVisibleHash;
}
template<typename T>
std::size_t CullingList<T>::FillWithAllEntries(bool* forceInvalidation)
{
m_fullyVisibleResults.clear();
m_partiallyVisibleResults.clear();
bool forcedInvalidation = false;
std::size_t visibleHash = 5U;
auto FillWithList = [&](auto& testList)
{
for (auto& entry : testList)
{
m_fullyVisibleResults.push_back(entry.renderable);
visibleHash = visibleHash * 23 + std::hash<const T*>()(entry.renderable);
forcedInvalidation = forcedInvalidation | entry.forceInvalidation;
entry.forceInvalidation = false;
}
};
FillWithList(m_boxTestList);
FillWithList(m_noTestList);
FillWithList(m_sphereTestList);
FillWithList(m_volumeTestList);
if (forceInvalidation)
*forceInvalidation = forcedInvalidation;
@ -71,53 +156,24 @@ namespace Nz
}
template<typename T>
std::size_t CullingList<T>::FillWithAllEntries(bool* forceInvalidation)
auto CullingList<T>::GetFullyVisibleResults() const -> const ResultContainer&
{
m_results.clear();
return m_fullyVisibleResults;
}
bool forcedInvalidation = false;
template<typename T>
auto CullingList<T>::GetPartiallyVisibleResults() const -> const ResultContainer&
{
return m_partiallyVisibleResults;
}
std::size_t visibleHash = 0U;
for (NoTestVisibilityEntry& entry : m_noTestList)
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
template<typename T>
auto CullingList<T>::RegisterBoxTest(const T* renderable) -> BoxEntry
{
BoxEntry newEntry(this, m_boxTestList.size());
m_boxTestList.emplace_back(BoxVisibilityEntry{ Nz::Boxf(), &newEntry, renderable, false }); //< Address of entry will be updated when moving
if (entry.forceInvalidation)
{
forcedInvalidation = true;
entry.forceInvalidation = false;
}
}
for (SphereVisibilityEntry& entry : m_sphereTestList)
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
if (entry.forceInvalidation)
{
forcedInvalidation = true;
entry.forceInvalidation = false;
}
}
for (VolumeVisibilityEntry& entry : m_volumeTestList)
{
m_results.push_back(entry.renderable);
Nz::HashCombine(visibleHash, entry.renderable);
if (entry.forceInvalidation)
{
forcedInvalidation = true;
entry.forceInvalidation = false;
}
}
if (forceInvalidation)
*forceInvalidation = forcedInvalidation;
return visibleHash;
return newEntry;
}
template<typename T>
@ -132,7 +188,7 @@ namespace Nz
template<typename T>
auto CullingList<T>::RegisterSphereTest(const T* renderable) -> SphereEntry
{
SphereEntry newEntry(this, m_sphereTestList.size() - 1);
SphereEntry newEntry(this, m_sphereTestList.size());
m_sphereTestList.emplace_back(SphereVisibilityEntry{Nz::Spheref(), &newEntry, renderable, false}); //< Address of entry will be updated when moving
return newEntry;
@ -147,89 +203,10 @@ namespace Nz
return newEntry;
}
// Interface STD
template<typename T>
typename CullingList<T>::ResultContainer::iterator CullingList<T>::begin()
inline void CullingList<T>::NotifyBoxUpdate(std::size_t index, const Boxf& box)
{
return m_results.begin();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_iterator CullingList<T>::begin() const
{
return m_results.begin();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_iterator CullingList<T>::cbegin() const
{
return m_results.cbegin();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_iterator CullingList<T>::cend() const
{
return m_results.cend();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_reverse_iterator CullingList<T>::crbegin() const
{
return m_results.crbegin();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_reverse_iterator CullingList<T>::crend() const
{
return m_results.crend();
}
template<typename T>
bool CullingList<T>::empty() const
{
return m_results.empty();
}
template<typename T>
typename CullingList<T>::ResultContainer::iterator CullingList<T>::end()
{
return m_results.end();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_iterator CullingList<T>::end() const
{
return m_results.end();
}
template<typename T>
typename CullingList<T>::ResultContainer::reverse_iterator CullingList<T>::rbegin()
{
return m_results.rbegin();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_reverse_iterator CullingList<T>::rbegin() const
{
return m_results.rbegin();
}
template<typename T>
typename CullingList<T>::ResultContainer::reverse_iterator CullingList<T>::rend()
{
return m_results.rend();
}
template<typename T>
typename CullingList<T>::ResultContainer::const_reverse_iterator CullingList<T>::rend() const
{
return m_results.rend();
}
template<typename T>
typename CullingList<T>::ResultContainer::size_type CullingList<T>::size() const
{
return m_results.size();
m_boxTestList[index].box = box;
}
template<typename T>
@ -237,6 +214,12 @@ namespace Nz
{
switch (type)
{
case CullTest::Box:
{
m_boxTestList[index].forceInvalidation = true;
break;
}
case CullTest::NoTest:
{
m_noTestList[index].forceInvalidation = true;
@ -268,6 +251,15 @@ namespace Nz
switch (type)
{
case CullTest::Box:
{
BoxVisibilityEntry& entry = m_boxTestList[index];
NazaraAssert(entry.entry == oldPtr, "Invalid box entry");
entry.entry = static_cast<BoxEntry*>(newPtr);
break;
}
case CullTest::NoTest:
{
NoTestVisibilityEntry& entry = m_noTestList[index];
@ -306,6 +298,14 @@ namespace Nz
{
switch (type)
{
case CullTest::Box:
{
m_boxTestList[index] = std::move(m_boxTestList.back());
m_boxTestList[index].entry->UpdateIndex(index);
m_boxTestList.pop_back();
break;
}
case CullTest::NoTest:
{
m_noTestList[index] = std::move(m_noTestList.back());
@ -424,6 +424,26 @@ namespace Nz
return *this;
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
CullingList<T>::BoxEntry::BoxEntry() :
Entry<CullTest::Box>()
{
}
template<typename T>
CullingList<T>::BoxEntry::BoxEntry(CullingList* parent, std::size_t index) :
Entry<CullTest::Box>(parent, index)
{
}
template<typename T>
void CullingList<T>::BoxEntry::UpdateBox(const Boxf& box)
{
this->m_parent->NotifyBoxUpdate(this->m_index, box);
}
//////////////////////////////////////////////////////////////////////////

View File

@ -21,6 +21,7 @@ namespace Nz
enum class CullTest
{
Box,
NoTest,
Sphere,
Volume