Skip to content

Commit

Permalink
RenderData caching fixes (ezEngine#319)
Browse files Browse the repository at this point in the history
* Make sure the right "SUPER" is used for component manager init/deinit.

* added missing super::init/deinit calls for some component managers which caused leaked cached render data.

* Debug check for leaked cached render data

* Made render data caching more robust and fixed ezEngine#177

* Removed a few validation asserts

* Added new event that is triggered before an object is deleted, this is used to clear render data caches for objects that don't have a render component attached.

* Make sure to only cache render data for a component if all parts should be cached

* Build fixes after rebase

* Formatting

* Re-activated finish action on plasma impact sound effect again, since it works now with render data caching
  • Loading branch information
C-Core authored Aug 28, 2020
1 parent 09acf40 commit f2bbb15
Show file tree
Hide file tree
Showing 20 changed files with 305 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void ezEngineProcessDocumentContext::HandleMessage(const ezEditorEngineDocumentM
ezGameObject* pObject = static_cast<ezGameObject*>(target.m_pObject);
if (pObject != nullptr && pObject->IsStatic())
{
ezRenderWorld::DeleteCachedRenderDataRecursive(pObject);
ezRenderWorld::DeleteCachedRenderDataForObjectRecursive(pObject);
}
}
else if (target.m_pType->IsDerivedFrom<ezComponent>())
Expand Down
1 change: 1 addition & 0 deletions Code/Engine/Core/World/ComponentManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ezComponentManager : public ezComponentManagerBase
{
public:
typedef T ComponentType;
typedef ezComponentManagerBase SUPER;

/// \brief Although the constructor is public always use ezWorld::CreateComponentManager to create an instance.
ezComponentManager(ezWorld* pWorld);
Expand Down
10 changes: 10 additions & 0 deletions Code/Engine/Core/World/GameObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ class EZ_CORE_DLL ezGameObject final
/// \brief Returns a list of all components attached to this object.
ezArrayPtr<const ezComponent* const> GetComponents() const;

/// \brief Returns the current version of components attached to this object.
/// This version is increased whenever components are added or removed and can be used for cache validation.
ezUInt16 GetComponentVersion() const;


/// \brief Sends a message to all components of this object.
bool SendMessage(ezMessage& msg);
Expand Down Expand Up @@ -553,6 +557,12 @@ class EZ_CORE_DLL ezGameObject final

ezSmallArrayBase<ezComponent*, NUM_INPLACE_COMPONENTS> m_Components;

struct ComponentUserData
{
ezUInt16 m_uiVersion;
ezUInt16 m_uiUnused;
};

ezTagSet m_Tags;
};

Expand Down
2 changes: 2 additions & 0 deletions Code/Engine/Core/World/Implementation/GameObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ void ezGameObject::AddComponent(ezComponent* pComponent)

pComponent->m_pOwner = this;
m_Components.PushBack(pComponent, GetWorld()->GetAllocator());
m_Components.GetUserData<ComponentUserData>().m_uiVersion++;

pComponent->UpdateActiveState(IsActive());

Expand All @@ -748,6 +749,7 @@ void ezGameObject::RemoveComponent(ezComponent* pComponent)

pComponent->m_pOwner = nullptr;
m_Components.RemoveAtAndSwap(uiIndex);
m_Components.GetUserData<ComponentUserData>().m_uiVersion++;

if (m_Flags.IsSet(ezObjectFlags::ComponentChangesNotifications))
{
Expand Down
5 changes: 5 additions & 0 deletions Code/Engine/Core/World/Implementation/GameObject_inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ EZ_ALWAYS_INLINE ezArrayPtr<const ezComponent* const> ezGameObject::GetComponent
return ezMakeArrayPtr(const_cast<const ezComponent* const*>(m_Components.GetData()), m_Components.GetCount());
}

EZ_ALWAYS_INLINE ezUInt16 ezGameObject::GetComponentVersion() const
{
return m_Components.GetUserData<ComponentUserData>().m_uiVersion;
}

EZ_ALWAYS_INLINE ezTagSet& ezGameObject::GetTags()
{
return m_Tags;
Expand Down
3 changes: 3 additions & 0 deletions Code/Engine/Core/World/Implementation/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ void ezWorld::DeleteObjectNow(const ezGameObjectHandle& hObject)
if (!m_Data.m_Objects.TryGetValue(hObject, pObject))
return;

// inform external systems that we are about to delete this object
m_Data.m_ObjectDeletionEvent.Broadcast(pObject);

// set object to inactive so components and children know that they shouldn't access the object anymore.
pObject->m_Flags.Remove(ezObjectFlags::ActiveFlag | ezObjectFlags::ActiveState);

Expand Down
1 change: 1 addition & 0 deletions Code/Engine/Core/World/Implementation/WorldData.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace ezInternal
ObjectStorage m_ObjectStorage;

ezSet<ezGameObject*, ezCompareHelper<ezGameObject*>, ezLocalAllocatorWrapper> m_DeadObjects;
ezEvent<const ezGameObject*> m_ObjectDeletionEvent;

public:
class EZ_CORE_DLL ConstObjectIterator
Expand Down
5 changes: 5 additions & 0 deletions Code/Engine/Core/World/Implementation/World_inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ EZ_FORCE_INLINE ezGameObjectHandle ezWorld::CreateObject(const ezGameObjectDesc&
return CreateObject(desc, pNewObject);
}

EZ_ALWAYS_INLINE const ezEvent<const ezGameObject*>& ezWorld::GetObjectDeletionEvent() const
{
return m_Data.m_ObjectDeletionEvent;
}

EZ_FORCE_INLINE bool ezWorld::IsValidObject(const ezGameObjectHandle& object) const
{
CheckForReadAccess();
Expand Down
7 changes: 5 additions & 2 deletions Code/Engine/Core/World/World.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class EZ_CORE_DLL ezWorld final
/// valid until then.
void DeleteObjectDelayed(const ezGameObjectHandle& object);

/// \brief Returns the event that is triggered before an object is deleted. This can be used for external systems to cleanup data
/// which is associated with the deleted object.
const ezEvent<const ezGameObject*>& GetObjectDeletionEvent() const;

/// \brief Returns whether the given handle corresponds to a valid object.
bool IsValidObject(const ezGameObjectHandle& object) const;

Expand Down Expand Up @@ -351,8 +355,7 @@ class EZ_CORE_DLL ezWorld final
void SetObjectGlobalKey(ezGameObject* pObject, const ezHashedString& sGlobalKey);
const char* GetObjectGlobalKey(const ezGameObject* pObject) const;

void PostMessage(
const ezGameObjectHandle& receiverObject, const ezMessage& msg, ezObjectMsgQueueType::Enum queueType, ezTime delay, bool bRecursive) const;
void PostMessage(const ezGameObjectHandle& receiverObject, const ezMessage& msg, ezObjectMsgQueueType::Enum queueType, ezTime delay, bool bRecursive) const;
void ProcessQueuedMessage(const ezInternal::WorldData::MessageQueue::Entry& entry);
void ProcessQueuedMessages(ezObjectMsgQueueType::Enum queueType);

Expand Down
24 changes: 19 additions & 5 deletions Code/Engine/RendererCore/Pipeline/Declarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,25 @@ namespace ezInternal
{
EZ_DECLARE_POD_TYPE();

const ezRenderData* m_pRenderData;
ezUInt32 m_uiSortingKey;
ezUInt16 m_uiCategory;
ezUInt16 m_uiComponentIndex : 15;
ezUInt16 m_uiCacheIfStatic : 1;
const ezRenderData* m_pRenderData = nullptr;
ezUInt16 m_uiCategory = 0;
ezUInt16 m_uiComponentIndex = 0;
ezUInt16 m_uiPartIndex = 0;

EZ_ALWAYS_INLINE bool operator==(const RenderDataCacheEntry& other) const
{
return m_pRenderData == other.m_pRenderData && m_uiCategory == other.m_uiCategory &&
m_uiComponentIndex == other.m_uiComponentIndex && m_uiPartIndex == other.m_uiPartIndex;
}

// Cache entries need to be sorted by component index and then by part index
EZ_ALWAYS_INLINE bool operator<(const RenderDataCacheEntry& other) const
{
if (m_uiComponentIndex == other.m_uiComponentIndex)
return m_uiPartIndex < other.m_uiPartIndex;

return m_uiComponentIndex < other.m_uiComponentIndex;
}
};
} // namespace ezInternal

Expand Down
109 changes: 66 additions & 43 deletions Code/Engine/RendererCore/Pipeline/Implementation/Extractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,51 @@ bool ezExtractor::FilterByViewTags(const ezView& view, const ezGameObject* pObje
return false;
}

void ezExtractor::ExtractRenderData(
const ezView& view, const ezGameObject* pObject, ezMsgExtractRenderData& msg, ezExtractedRenderData& extractedRenderData) const
void ezExtractor::ExtractRenderData(const ezView& view, const ezGameObject* pObject, ezMsgExtractRenderData& msg, ezExtractedRenderData& extractedRenderData) const
{
if (FilterByViewTags(view, pObject))
{
return;
}

msg.m_ExtractedRenderData.Clear();
ezUInt32 uiNumUncachedRenderData = 0;
auto AddRenderDataFromMessage = [&](const ezMsgExtractRenderData& msg) {
if (msg.m_OverrideCategory != ezInvalidRenderDataCategory)
{
for (auto& data : msg.m_ExtractedRenderData)
{
extractedRenderData.AddRenderData(data.m_pRenderData, msg.m_OverrideCategory);
}
}
else
{
for (auto& data : msg.m_ExtractedRenderData)
{
extractedRenderData.AddRenderData(data.m_pRenderData, ezRenderData::Category(data.m_uiCategory));
}
}

#if EZ_ENABLED(EZ_COMPILE_FOR_DEVELOPMENT)
m_uiNumUncachedRenderData += msg.m_ExtractedRenderData.GetCount();
#endif
};

if (pObject->IsStatic())
{
auto cachedRenderData = ezRenderWorld::GetCachedRenderData(view, pObject->GetHandle());
ezUInt16 uiComponentVersion = pObject->GetComponentVersion();

auto cachedRenderData = ezRenderWorld::GetCachedRenderData(view, pObject->GetHandle(), uiComponentVersion);

#if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG)
for (ezUInt32 i = 1; i < cachedRenderData.GetCount(); ++i)
{
EZ_ASSERT_DEBUG(cachedRenderData[i - 1].m_uiComponentIndex <= cachedRenderData[i].m_uiComponentIndex, "Cached render data needs to be sorted");
if (cachedRenderData[i - 1].m_uiComponentIndex == cachedRenderData[i].m_uiComponentIndex)
{
EZ_ASSERT_DEBUG(cachedRenderData[i - 1].m_uiPartIndex < cachedRenderData[i].m_uiPartIndex, "Cached render data needs to be sorted");
}
}
#endif

ezUInt32 uiCacheIndex = 0;

auto components = pObject->GetComponents();
Expand All @@ -160,9 +191,14 @@ void ezExtractor::ExtractRenderData(
bool bCacheFound = false;
while (uiCacheIndex < cachedRenderData.GetCount() && cachedRenderData[uiCacheIndex].m_uiComponentIndex == uiComponentIndex)
{
if (cachedRenderData[uiCacheIndex].m_pRenderData != nullptr)
const ezInternal::RenderDataCacheEntry& cacheEntry = cachedRenderData[uiCacheIndex];
if (cacheEntry.m_pRenderData != nullptr)
{
msg.m_ExtractedRenderData.PushBack(cachedRenderData[uiCacheIndex]);
extractedRenderData.AddRenderData(cacheEntry.m_pRenderData, msg.m_OverrideCategory != ezInvalidRenderDataCategory ? msg.m_OverrideCategory : ezRenderData::Category(cacheEntry.m_uiCategory));

#if EZ_ENABLED(EZ_COMPILE_FOR_DEVELOPMENT)
++m_uiNumCachedRenderData;
#endif
}
++uiCacheIndex;

Expand All @@ -174,66 +210,53 @@ void ezExtractor::ExtractRenderData(
continue;
}

ezUInt32 uiOldRenderDataCount = msg.m_ExtractedRenderData.GetCount();
const ezComponent* pComponent = components[uiComponentIndex];

msg.m_ExtractedRenderData.Clear();
msg.m_uiNumCacheIfStatic = 0;

if (pComponent->SendMessage(msg))
{
if (msg.m_ExtractedRenderData.GetCount() > uiOldRenderDataCount)
// Only cache render data if all parts should be cached otherwise the cache is incomplete and we won't call SendMessage again
if (msg.m_uiNumCacheIfStatic > 0 && msg.m_ExtractedRenderData.GetCount() == msg.m_uiNumCacheIfStatic)
{
auto newCacheEntries = msg.m_ExtractedRenderData.GetArrayPtr().GetSubArray(uiOldRenderDataCount);
if (newCacheEntries[0].m_uiCacheIfStatic)
{
for (auto& newCacheEntry : newCacheEntries)
{
newCacheEntry.m_uiComponentIndex = uiComponentIndex;
}
ezHybridArray<ezInternal::RenderDataCacheEntry, 16> newCacheEntries(ezFrameAllocator::GetCurrentAllocator());

ezRenderWorld::CacheRenderData(view, pObject->GetHandle(), pComponent->GetHandle(), newCacheEntries);
for (ezUInt32 uiPartIndex = 0; uiPartIndex < msg.m_ExtractedRenderData.GetCount(); ++uiPartIndex)
{
auto& newCacheEntry = newCacheEntries.ExpandAndGetRef();
newCacheEntry.m_pRenderData = msg.m_ExtractedRenderData[uiPartIndex].m_pRenderData;
newCacheEntry.m_uiCategory = msg.m_ExtractedRenderData[uiPartIndex].m_uiCategory;
newCacheEntry.m_uiComponentIndex = uiComponentIndex;
newCacheEntry.m_uiPartIndex = uiPartIndex;
}

uiNumUncachedRenderData += newCacheEntries.GetCount();
ezRenderWorld::CacheRenderData(view, pObject->GetHandle(), pComponent->GetHandle(), uiComponentVersion, newCacheEntries);
}

AddRenderDataFromMessage(msg);
}
else // component does not handle extract message at all
else if (pComponent->IsActiveAndInitialized()) // component does not handle extract message at all
{
EZ_ASSERT_DEV(pComponent->GetDynamicRTTI()->CanHandleMessage<ezMsgExtractRenderData>() == false, "");

// Create a dummy cache entry so we don't call send message next time
ezInternal::RenderDataCacheEntry dummyEntry;
dummyEntry.m_pRenderData = nullptr;
dummyEntry.m_uiSortingKey = 0;
dummyEntry.m_uiCategory = ezInvalidRenderDataCategory.m_uiValue;
dummyEntry.m_uiComponentIndex = uiComponentIndex;
dummyEntry.m_uiCacheIfStatic = true;

ezRenderWorld::CacheRenderData(view, pObject->GetHandle(), pComponent->GetHandle(), ezMakeArrayPtr(&dummyEntry, 1));
ezRenderWorld::CacheRenderData(view, pObject->GetHandle(), pComponent->GetHandle(), uiComponentVersion, ezMakeArrayPtr(&dummyEntry, 1));
}
}
}
else
{
msg.m_ExtractedRenderData.Clear();
pObject->SendMessage(msg);

uiNumUncachedRenderData += msg.m_ExtractedRenderData.GetCount();
}

if (msg.m_OverrideCategory != ezInvalidRenderDataCategory)
{
for (auto& cached : msg.m_ExtractedRenderData)
{
extractedRenderData.AddRenderData(cached.m_pRenderData, msg.m_OverrideCategory);
}
}
else
{
for (auto& cached : msg.m_ExtractedRenderData)
{
extractedRenderData.AddRenderData(cached.m_pRenderData, ezRenderData::Category(cached.m_uiCategory));
}
AddRenderDataFromMessage(msg);
}

#if EZ_ENABLED(EZ_COMPILE_FOR_DEVELOPMENT)
m_uiNumCachedRenderData += msg.m_ExtractedRenderData.GetCount() - uiNumUncachedRenderData;
m_uiNumUncachedRenderData += uiNumUncachedRenderData;
#endif
}

void ezExtractor::Extract(const ezView& view, const ezDynamicArray<const ezGameObject*>& visibleObjects, ezExtractedRenderData& extractedRenderData)
Expand Down
43 changes: 17 additions & 26 deletions Code/Engine/RendererCore/Pipeline/Implementation/RenderData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,30 +154,18 @@ void ezRenderData::ClearRendererInstances()

//////////////////////////////////////////////////////////////////////////

ezRenderData::Category ezDefaultRenderDataCategories::Light =
ezRenderData::RegisterCategory("Light", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Decal =
ezRenderData::RegisterCategory("Decal", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Sky =
ezRenderData::RegisterCategory("Sky", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitOpaque =
ezRenderData::RegisterCategory("LitOpaque", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitMasked =
ezRenderData::RegisterCategory("LitMasked", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitTransparent =
ezRenderData::RegisterCategory("LitTransparent", &ezRenderSortingFunctions::BackToFrontThenByRenderData);
ezRenderData::Category ezDefaultRenderDataCategories::LitForeground =
ezRenderData::RegisterCategory("LitForeground", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleOpaque =
ezRenderData::RegisterCategory("SimpleOpaque", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleTransparent =
ezRenderData::RegisterCategory("SimpleTransparent", &ezRenderSortingFunctions::BackToFrontThenByRenderData);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleForeground =
ezRenderData::RegisterCategory("SimpleForeground", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Selection =
ezRenderData::RegisterCategory("Selection", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::GUI =
ezRenderData::RegisterCategory("GUI", &ezRenderSortingFunctions::BackToFrontThenByRenderData);
ezRenderData::Category ezDefaultRenderDataCategories::Light = ezRenderData::RegisterCategory("Light", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Decal = ezRenderData::RegisterCategory("Decal", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Sky = ezRenderData::RegisterCategory("Sky", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitOpaque = ezRenderData::RegisterCategory("LitOpaque", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitMasked = ezRenderData::RegisterCategory("LitMasked", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::LitTransparent = ezRenderData::RegisterCategory("LitTransparent", &ezRenderSortingFunctions::BackToFrontThenByRenderData);
ezRenderData::Category ezDefaultRenderDataCategories::LitForeground = ezRenderData::RegisterCategory("LitForeground", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleOpaque = ezRenderData::RegisterCategory("SimpleOpaque", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleTransparent = ezRenderData::RegisterCategory("SimpleTransparent", &ezRenderSortingFunctions::BackToFrontThenByRenderData);
ezRenderData::Category ezDefaultRenderDataCategories::SimpleForeground = ezRenderData::RegisterCategory("SimpleForeground", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::Selection = ezRenderData::RegisterCategory("Selection", &ezRenderSortingFunctions::ByRenderDataThenFrontToBack);
ezRenderData::Category ezDefaultRenderDataCategories::GUI = ezRenderData::RegisterCategory("GUI", &ezRenderSortingFunctions::BackToFrontThenByRenderData);

//////////////////////////////////////////////////////////////////////////

Expand All @@ -187,8 +175,11 @@ void ezMsgExtractRenderData::AddRenderData(
auto& cached = m_ExtractedRenderData.ExpandAndGetRef();
cached.m_pRenderData = pRenderData;
cached.m_uiCategory = category.m_uiValue;
cached.m_uiComponentIndex = 0x7FFF;
cached.m_uiCacheIfStatic = (cachingBehavior == ezRenderData::Caching::IfStatic);

if (cachingBehavior == ezRenderData::Caching::IfStatic)
{
++m_uiNumCacheIfStatic;
}
}

EZ_STATICLINK_FILE(RendererCore, RendererCore_Pipeline_Implementation_RenderData);
6 changes: 3 additions & 3 deletions Code/Engine/RendererCore/Pipeline/Implementation/View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ void ezView::SetWorld(ezWorld* pWorld)
{
if (m_pWorld != pWorld)
{
ezRenderWorld::DeleteCachedRenderData(*this);
}
m_pWorld = pWorld;

m_pWorld = pWorld;
ezRenderWorld::ResetRenderDataCache(*this);
}
}

void ezView::SetRenderTargetSetup(ezGALRenderTargetSetup& renderTargetSetup)
Expand Down
Loading

0 comments on commit f2bbb15

Please sign in to comment.