Browse Source

Optimizations

Panagiotis Christopoulos Charitos 8 năm trước cách đây
mục cha
commit
dd58569270

+ 12 - 0
src/anki/renderer/RenderQueue.h

@@ -57,6 +57,11 @@ public:
 	Vec3 m_specularColor;
 	Array<RenderQueue*, 6> m_shadowRenderQueues;
 	U32 m_textureArrayIndex; ///< Renderer internal.
+
+	Bool hasShadow() const
+	{
+		return m_shadowRenderQueues[0] != nullptr;
+	}
 };
 
 static_assert(
@@ -75,6 +80,11 @@ public:
 	Vec3 m_specularColor;
 	RenderQueue* m_shadowRenderQueue;
 	U32 m_textureArrayIndex; ///< Renderer internal.
+
+	Bool hasShadow() const
+	{
+		return m_shadowRenderQueue != nullptr;
+	}
 };
 
 static_assert(std::is_trivially_destructible<SpotLightQueueElement>::value == true, "Should be trivially destructible");
@@ -134,7 +144,9 @@ public:
 	WeakArray<RenderableQueueElement> m_renderables; ///< Deferred shading or shadow renderables.
 	WeakArray<RenderableQueueElement> m_forwardShadingRenderables;
 	WeakArray<PointLightQueueElement> m_pointLights;
+	WeakArray<PointLightQueueElement*> m_shadowPointLights; ///< Points to elements in m_pointLights.
 	WeakArray<SpotLightQueueElement> m_spotLights;
+	WeakArray<SpotLightQueueElement*> m_shadowSpotLights; ///< Points to elements in m_spotLights.
 	WeakArray<ReflectionProbeQueueElement> m_reflectionProbes;
 	WeakArray<LensFlareQueueElement> m_lensFlares;
 	WeakArray<DecalQueueElement> m_decals;

+ 0 - 5
src/anki/renderer/Renderer.h

@@ -93,9 +93,6 @@ public:
 		/// [casterIdx][threadIdx][faceIdx]
 		DynamicArrayAuto<CommandBufferPtr> m_omniCommandBuffers;
 
-		DynamicArrayAuto<SpotLightQueueElement*> m_spots;
-		DynamicArrayAuto<PointLightQueueElement*> m_omnis;
-
 		ShadowMapping(const StackAllocator<U8>& alloc)
 			: m_spotFramebuffers(alloc)
 			, m_omniFramebuffers(alloc)
@@ -103,8 +100,6 @@ public:
 			, m_omniCacheIndices(alloc)
 			, m_spotCommandBuffers(alloc)
 			, m_omniCommandBuffers(alloc)
-			, m_spots(alloc)
-			, m_omnis(alloc)
 		{
 		}
 	} m_shadowMapping;

+ 67 - 62
src/anki/renderer/ShadowMapping.cpp

@@ -115,7 +115,7 @@ void ShadowMapping::run(RenderingContext& ctx)
 	const U threadCount = m_r->getThreadPool().getThreadsCount();
 
 	// Spot lights
-	for(U i = 0; i < ctx.m_shadowMapping.m_spots.getSize(); ++i)
+	for(U i = 0; i < ctx.m_renderQueue->m_shadowSpotLights.getSize(); ++i)
 	{
 		cmdb->beginRenderPass(ctx.m_shadowMapping.m_spotFramebuffers[i]);
 		for(U j = 0; j < threadCount; ++j)
@@ -133,7 +133,7 @@ void ShadowMapping::run(RenderingContext& ctx)
 	}
 
 	// Omni lights
-	for(U i = 0; i < ctx.m_shadowMapping.m_omnis.getSize(); ++i)
+	for(U i = 0; i < ctx.m_renderQueue->m_shadowPointLights.getSize(); ++i)
 	{
 		for(U j = 0; j < 6; ++j)
 		{
@@ -235,22 +235,22 @@ void ShadowMapping::buildCommandBuffers(RenderingContext& ctx, U threadId, U thr
 {
 	ANKI_TRACE_SCOPED_EVENT(RENDER_SM);
 
-	for(U i = 0; i < ctx.m_shadowMapping.m_spots.getSize(); ++i)
+	for(U i = 0; i < ctx.m_renderQueue->m_shadowSpotLights.getSize(); ++i)
 	{
 		U idx = i * threadCount + threadId;
 
-		doSpotLight(*ctx.m_shadowMapping.m_spots[i],
+		doSpotLight(*ctx.m_renderQueue->m_shadowSpotLights[i],
 			ctx.m_shadowMapping.m_spotCommandBuffers[idx],
 			ctx.m_shadowMapping.m_spotFramebuffers[i],
 			threadId,
 			threadCount);
 	}
 
-	for(U i = 0; i < ctx.m_shadowMapping.m_omnis.getSize(); ++i)
+	for(U i = 0; i < ctx.m_renderQueue->m_shadowPointLights.getSize(); ++i)
 	{
 		U idx = i * threadCount * 6 + threadId * 6 + 0;
 
-		doOmniLight(*ctx.m_shadowMapping.m_omnis[i],
+		doOmniLight(*ctx.m_renderQueue->m_shadowPointLights[i],
 			&ctx.m_shadowMapping.m_omniCommandBuffers[idx],
 			ctx.m_shadowMapping.m_omniFramebuffers[i],
 			threadId,
@@ -348,104 +348,109 @@ void ShadowMapping::prepareBuildCommandBuffers(RenderingContext& ctx)
 {
 	ANKI_TRACE_SCOPED_EVENT(RENDER_SM);
 
-	// Gather the lights
+	// Do some checks
+	//
 	RenderQueue& rqueue = *ctx.m_renderQueue;
 
-	const U MAX = 64;
-	Array<SpotLightQueueElement*, MAX> spotCasters;
-	Array<PointLightQueueElement*, MAX> omniCasters;
-	U spotCastersCount = 0;
-	U omniCastersCount = 0;
+	if(rqueue.m_shadowPointLights.getSize() > m_omnis.getSize())
+	{
+		ANKI_R_LOGW("Too many point shadow casters");
+
+		rqueue.m_shadowPointLights =
+			WeakArray<PointLightQueueElement*>(rqueue.m_shadowPointLights.getBegin(), m_omnis.getSize());
+	}
 
+	if(rqueue.m_shadowSpotLights.getSize() > m_spots.getSize())
 	{
-		auto it = rqueue.m_pointLights.getBegin();
-		auto lend = rqueue.m_pointLights.getEnd();
-		for(; it != lend; ++it)
+		ANKI_R_LOGW("Too many spot shadow casters");
+
+		rqueue.m_shadowSpotLights =
+			WeakArray<SpotLightQueueElement*>(rqueue.m_shadowSpotLights.getBegin(), m_spots.getSize());
+	}
+
+	// Set the texture arr indices and skip some stale lights
+	//
+	U lightCount = rqueue.m_shadowSpotLights.getSize();
+	if(lightCount)
+	{
+		WeakArray<SpotLightQueueElement*> newArr(
+			getFrameAllocator().newArray<SpotLightQueueElement*>(lightCount), lightCount);
+		U newLightCount = 0;
+		for(U i = 0; i < lightCount; ++i)
 		{
-			const Bool castsShadow = it->m_shadowRenderQueues[0] != nullptr;
+			ShadowmapSpot* sm;
+			bestCandidate(*rqueue.m_shadowSpotLights[i], m_spots, sm);
 
-			if(castsShadow)
+			if(!skip(*rqueue.m_shadowSpotLights[i], *sm))
 			{
-				ShadowmapOmni* sm;
-				bestCandidate(*it, m_omnis, sm);
-
-				if(!skip(*it, *sm))
-				{
-					omniCasters[omniCastersCount++] = &(*it);
-				}
+				newArr[newLightCount++] = rqueue.m_shadowSpotLights[i];
 			}
 		}
+
+		rqueue.m_shadowSpotLights = WeakArray<SpotLightQueueElement*>(newArr.getBegin(), newLightCount);
 	}
 
+	lightCount = rqueue.m_shadowPointLights.getSize();
+	if(lightCount)
 	{
-		auto it = rqueue.m_spotLights.getBegin();
-		auto lend = rqueue.m_spotLights.getEnd();
-		for(; it != lend; ++it)
+		WeakArray<PointLightQueueElement*> newArr(
+			getFrameAllocator().newArray<PointLightQueueElement*>(lightCount), lightCount);
+		U newLightCount = 0;
+		for(U i = 0; i < lightCount; ++i)
 		{
-			const Bool castsShadow = it->m_shadowRenderQueue != nullptr;
+			ShadowmapOmni* sm;
+			bestCandidate(*rqueue.m_shadowPointLights[i], m_omnis, sm);
 
-			if(castsShadow)
+			if(!skip(*rqueue.m_shadowPointLights[i], *sm))
 			{
-				ShadowmapSpot* sm;
-				bestCandidate(*it, m_spots, sm);
-
-				if(!skip(*it, *sm))
-				{
-					spotCasters[spotCastersCount++] = &(*it);
-				}
+				newArr[newLightCount++] = rqueue.m_shadowPointLights[i];
 			}
 		}
-	}
 
-	if(omniCastersCount > m_omnis.getSize() || spotCastersCount > m_spots.getSize())
-	{
-		ANKI_R_LOGW("Too many shadow casters");
-		omniCastersCount = min<U>(omniCastersCount, m_omnis.getSize());
-		spotCastersCount = min<U>(spotCastersCount, m_spots.getSize());
+		rqueue.m_shadowPointLights = WeakArray<PointLightQueueElement*>(newArr.getBegin(), newLightCount);
 	}
 
-	if(spotCastersCount > 0)
+	// Build the command buffers
+	//
+	lightCount = rqueue.m_shadowSpotLights.getSize();
+	if(lightCount > 0)
 	{
-		ctx.m_shadowMapping.m_spots.create(spotCastersCount);
-		memcpy(&ctx.m_shadowMapping.m_spots[0], &spotCasters[0], sizeof(SpotLightQueueElement*) * spotCastersCount);
+		ctx.m_shadowMapping.m_spotCommandBuffers.create(lightCount * m_r->getThreadPool().getThreadsCount());
 
-		ctx.m_shadowMapping.m_spotCommandBuffers.create(spotCastersCount * m_r->getThreadPool().getThreadsCount());
-
-		ctx.m_shadowMapping.m_spotCacheIndices.create(spotCastersCount);
+		ctx.m_shadowMapping.m_spotCacheIndices.create(lightCount);
 #if ANKI_EXTRA_CHECKS
 		memset(&ctx.m_shadowMapping.m_spotCacheIndices[0],
 			0xFF,
-			sizeof(ctx.m_shadowMapping.m_spotCacheIndices[0]) * spotCastersCount);
+			sizeof(ctx.m_shadowMapping.m_spotCacheIndices[0]) * lightCount);
 #endif
 
-		ctx.m_shadowMapping.m_spotFramebuffers.create(spotCastersCount);
-		for(U i = 0; i < spotCastersCount; ++i)
+		ctx.m_shadowMapping.m_spotFramebuffers.create(lightCount);
+		for(U i = 0; i < lightCount; ++i)
 		{
-			const U idx = ctx.m_shadowMapping.m_spots[i]->m_textureArrayIndex;
+			const U idx = rqueue.m_shadowSpotLights[i]->m_textureArrayIndex;
+			ANKI_ASSERT(idx != MAX_U32);
 
 			ctx.m_shadowMapping.m_spotFramebuffers[i] = m_spots[idx].m_fb;
 			ctx.m_shadowMapping.m_spotCacheIndices[i] = idx;
 		}
 	}
 
-	if(omniCastersCount > 0)
+	lightCount = rqueue.m_shadowPointLights.getSize();
+	if(lightCount > 0)
 	{
-		ctx.m_shadowMapping.m_omnis.create(omniCastersCount);
-		memcpy(&ctx.m_shadowMapping.m_omnis[0], &omniCasters[0], sizeof(PointLightQueueElement*) * omniCastersCount);
-
-		ctx.m_shadowMapping.m_omniCommandBuffers.create(omniCastersCount * 6 * m_r->getThreadPool().getThreadsCount());
+		ctx.m_shadowMapping.m_omniCommandBuffers.create(lightCount * 6 * m_r->getThreadPool().getThreadsCount());
 
-		ctx.m_shadowMapping.m_omniCacheIndices.create(omniCastersCount);
+		ctx.m_shadowMapping.m_omniCacheIndices.create(lightCount);
 #if ANKI_EXTRA_CHECKS
 		memset(&ctx.m_shadowMapping.m_omniCacheIndices[0],
 			0xFF,
-			sizeof(ctx.m_shadowMapping.m_omniCacheIndices[0]) * omniCastersCount);
+			sizeof(ctx.m_shadowMapping.m_omniCacheIndices[0]) * lightCount);
 #endif
 
-		ctx.m_shadowMapping.m_omniFramebuffers.create(omniCastersCount);
-		for(U i = 0; i < omniCastersCount; ++i)
+		ctx.m_shadowMapping.m_omniFramebuffers.create(lightCount);
+		for(U i = 0; i < lightCount; ++i)
 		{
-			const U idx = ctx.m_shadowMapping.m_omnis[i]->m_textureArrayIndex;
+			const U idx = rqueue.m_shadowPointLights[i]->m_textureArrayIndex;
 
 			for(U j = 0; j < 6; ++j)
 			{

+ 121 - 33
src/anki/scene/Visibility.cpp

@@ -379,6 +379,7 @@ void VisibilityTestTask::test(ThreadHive& hive)
 			{
 				PointLightQueueElement* el = m_result.m_pointLights.newElement(alloc);
 				lc->setupPointLightQueueElement(*el);
+				el->m_textureArrayIndex = MAX_U32;
 
 				if(lc->getShadowEnabled())
 				{
@@ -391,6 +392,9 @@ void VisibilityTestTask::test(ThreadHive& hive)
 					el->m_shadowRenderQueues[3] = &nextQueues[3];
 					el->m_shadowRenderQueues[4] = &nextQueues[4];
 					el->m_shadowRenderQueues[5] = &nextQueues[5];
+
+					U32* p = m_result.m_shadowPointLights.newElement(alloc);
+					*p = m_result.m_pointLights.m_elementCount - 1;
 				}
 				else
 				{
@@ -403,12 +407,16 @@ void VisibilityTestTask::test(ThreadHive& hive)
 			{
 				SpotLightQueueElement* el = m_result.m_spotLights.newElement(alloc);
 				lc->setupSpotLightQueueElement(*el);
+				el->m_textureArrayIndex = MAX_U32;
 
 				if(lc->getShadowEnabled())
 				{
 					RenderQueue* a = alloc.newInstance<RenderQueue>();
 					nextQueues = WeakArray<RenderQueue>(a, 1);
 					el->m_shadowRenderQueue = a;
+
+					U32* p = m_result.m_shadowSpotLights.newElement(alloc);
+					*p = m_result.m_spotLights.m_elementCount - 1;
 				}
 				else
 				{
@@ -491,27 +499,59 @@ void CombineResultsTask::combine()
 			max(m_results->m_shadowRenderablesLastUpdateTimestamp, m_tests[i].m_timestamp);
 	}
 
-#define ANKI_VIS_COMBINE(t_, member_)                                                       \
-	{                                                                                       \
-		Array<TRenderQueueElementStorage<t_>*, 64> subStorages;                             \
-		for(U i = 0; i < m_tests.getSize(); ++i)                                            \
-		{                                                                                   \
-			subStorages[i] = &m_tests[i].m_result.member_;                                  \
-		}                                                                                   \
-		combineQueueElements(alloc,                                                         \
-			WeakArray<TRenderQueueElementStorage<t_>*>(&subStorages[0], m_tests.getSize()), \
-			m_results->member_);                                                            \
+#define ANKI_VIS_COMBINE(t_, member_)                                                      \
+	{                                                                                      \
+		Array<TRenderQueueElementStorage<t_>, 64> subStorages;                             \
+		for(U i = 0; i < m_tests.getSize(); ++i)                                           \
+		{                                                                                  \
+			subStorages[i] = m_tests[i].m_result.member_;                                  \
+		}                                                                                  \
+		combineQueueElements<t_>(alloc,                                                    \
+			WeakArray<TRenderQueueElementStorage<t_>>(&subStorages[0], m_tests.getSize()), \
+			nullptr,                                                                       \
+			m_results->member_,                                                            \
+			nullptr);                                                                      \
+	}
+
+#define ANKI_VIS_COMBINE_AND_PTR(t_, member_, ptrMember_)                                      \
+	{                                                                                          \
+		Array<TRenderQueueElementStorage<t_>, 64> subStorages;                                 \
+		Array<TRenderQueueElementStorage<U32>, 64> ptrSubStorages;                             \
+		for(U i = 0; i < m_tests.getSize(); ++i)                                               \
+		{                                                                                      \
+			subStorages[i] = m_tests[i].m_result.member_;                                      \
+			ptrSubStorages[i] = m_tests[i].m_result.ptrMember_;                                \
+		}                                                                                      \
+		WeakArray<TRenderQueueElementStorage<U32>> arr(&ptrSubStorages[0], m_tests.getSize()); \
+		combineQueueElements<t_>(alloc,                                                        \
+			WeakArray<TRenderQueueElementStorage<t_>>(&subStorages[0], m_tests.getSize()),     \
+			&arr,                                                                              \
+			m_results->member_,                                                                \
+			&m_results->ptrMember_);                                                           \
 	}
 
 	ANKI_VIS_COMBINE(RenderableQueueElement, m_renderables);
 	ANKI_VIS_COMBINE(RenderableQueueElement, m_forwardShadingRenderables);
-	ANKI_VIS_COMBINE(PointLightQueueElement, m_pointLights);
-	ANKI_VIS_COMBINE(SpotLightQueueElement, m_spotLights);
+	ANKI_VIS_COMBINE_AND_PTR(PointLightQueueElement, m_pointLights, m_shadowPointLights);
+	ANKI_VIS_COMBINE_AND_PTR(SpotLightQueueElement, m_spotLights, m_shadowSpotLights);
 	ANKI_VIS_COMBINE(ReflectionProbeQueueElement, m_reflectionProbes);
 	ANKI_VIS_COMBINE(LensFlareQueueElement, m_lensFlares);
 	ANKI_VIS_COMBINE(DecalQueueElement, m_decals);
 
 #undef ANKI_VIS_COMBINE
+#undef ANKI_VIS_COMBINE_AND_PTR
+
+#if ANKI_EXTRA_CHECKS
+	for(PointLightQueueElement* light : m_results->m_shadowPointLights)
+	{
+		ANKI_ASSERT(light->hasShadow());
+	}
+
+	for(SpotLightQueueElement* light : m_results->m_shadowSpotLights)
+	{
+		ANKI_ASSERT(light->hasShadow());
+	}
+#endif
 
 	// Sort some of the arrays
 	std::sort(m_results->m_renderables.getBegin(),
@@ -529,49 +569,97 @@ void CombineResultsTask::combine()
 }
 
 template<typename T>
-void CombineResultsTask::combineQueueElements(
-	SceneFrameAllocator<U8>& alloc, WeakArray<TRenderQueueElementStorage<T>*> subStorages, WeakArray<T>& combined)
+void CombineResultsTask::combineQueueElements(SceneFrameAllocator<U8>& alloc,
+	WeakArray<TRenderQueueElementStorage<T>> subStorages,
+	WeakArray<TRenderQueueElementStorage<U32>>* ptrSubStorages,
+	WeakArray<T>& combined,
+	WeakArray<T*>* ptrCombined)
 {
-	U totalElCount = subStorages[0]->m_elementCount;
+	U totalElCount = subStorages[0].m_elementCount;
 	U biggestSubStorageIdx = 0;
 	for(U i = 1; i < subStorages.getSize(); ++i)
 	{
-		totalElCount += subStorages[i]->m_elementCount;
+		totalElCount += subStorages[i].m_elementCount;
 
-		if(subStorages[i]->m_elementStorage > subStorages[biggestSubStorageIdx]->m_elementStorage)
+		if(subStorages[i].m_elementStorage > subStorages[biggestSubStorageIdx].m_elementStorage)
 		{
 			biggestSubStorageIdx = i;
 		}
 	}
 
-	if(totalElCount > 0)
+	if(totalElCount == 0)
 	{
-		T* it;
-		if(totalElCount > subStorages[biggestSubStorageIdx]->m_elementStorage)
-		{
-			// Can't reuse any storage, will allocate
+		return;
+	}
 
-			it = alloc.newArray<T>(totalElCount);
-			biggestSubStorageIdx = MAX_U;
+	// Count ptrSubStorage elements
+	T** ptrIt = nullptr;
+	if(ptrSubStorages != nullptr)
+	{
+		ANKI_ASSERT(ptrCombined);
+		U ptrTotalElCount = (*ptrSubStorages)[0].m_elementCount;
 
-			combined = WeakArray<T>(it, totalElCount);
+		for(U i = 1; i < ptrSubStorages->getSize(); ++i)
+		{
+			ptrTotalElCount += (*ptrSubStorages)[i].m_elementCount;
 		}
-		else
+
+		// Create the new storage
+		if(ptrTotalElCount > 0)
 		{
-			// Will reuse existing storage
+			ptrIt = alloc.newArray<T*>(ptrTotalElCount);
+			*ptrCombined = WeakArray<T*>(ptrIt, ptrTotalElCount);
+		}
+	}
 
-			it = subStorages[biggestSubStorageIdx]->m_elements + subStorages[biggestSubStorageIdx]->m_elementCount;
+	T* it;
+	if(totalElCount > subStorages[biggestSubStorageIdx].m_elementStorage)
+	{
+		// Can't reuse any of the existing storage, will allocate a brand new one
+
+		it = alloc.newArray<T>(totalElCount);
+		biggestSubStorageIdx = MAX_U;
+
+		combined = WeakArray<T>(it, totalElCount);
+	}
+	else
+	{
+		// Will reuse existing storage
 
-			combined = WeakArray<T>(subStorages[biggestSubStorageIdx]->m_elements, totalElCount);
+		it = subStorages[biggestSubStorageIdx].m_elements + subStorages[biggestSubStorageIdx].m_elementCount;
+
+		combined = WeakArray<T>(subStorages[biggestSubStorageIdx].m_elements, totalElCount);
+	}
+
+	for(U i = 0; i < subStorages.getSize(); ++i)
+	{
+		if(subStorages[i].m_elementCount == 0)
+		{
+			continue;
 		}
 
-		for(U i = 0; i < subStorages.getSize(); ++i)
+		// Copy the pointers
+		if(ptrIt)
 		{
-			if(i != biggestSubStorageIdx && subStorages[i]->m_elementCount > 0)
+			T* base = (i != biggestSubStorageIdx) ? it : subStorages[biggestSubStorageIdx].m_elements;
+
+			for(U x = 0; x < (*ptrSubStorages)[i].m_elementCount; ++x)
 			{
-				memcpy(it, subStorages[i]->m_elements, sizeof(T) * subStorages[i]->m_elementCount);
-				it += subStorages[i]->m_elementCount;
+				ANKI_ASSERT((*ptrSubStorages)[i].m_elements[x] < subStorages[i].m_elementCount);
+
+				*ptrIt = base + (*ptrSubStorages)[i].m_elements[x];
+
+				++ptrIt;
 			}
+
+			ANKI_ASSERT(ptrIt <= ptrCombined->getEnd());
+		}
+
+		// Copy the elements
+		if(i != biggestSubStorageIdx)
+		{
+			memcpy(it, subStorages[i].m_elements, sizeof(T) * subStorages[i].m_elementCount);
+			it += subStorages[i].m_elementCount;
 		}
 	}
 }

+ 7 - 4
src/anki/scene/VisibilityInternal.h

@@ -52,8 +52,6 @@ public:
 	U32 m_elementCount = 0;
 	U32 m_elementStorage = 0;
 
-	Timestamp m_lastUpdateTimestamp;
-
 	T* newElement(SceneFrameAllocator<T> alloc)
 	{
 		if(ANKI_UNLIKELY(m_elementCount + 1 > m_elementStorage))
@@ -79,7 +77,9 @@ public:
 	TRenderQueueElementStorage<RenderableQueueElement> m_renderables; ///< Deferred shading or shadow renderables.
 	TRenderQueueElementStorage<RenderableQueueElement> m_forwardShadingRenderables;
 	TRenderQueueElementStorage<PointLightQueueElement> m_pointLights;
+	TRenderQueueElementStorage<U32> m_shadowPointLights;
 	TRenderQueueElementStorage<SpotLightQueueElement> m_spotLights;
+	TRenderQueueElementStorage<U32> m_shadowSpotLights;
 	TRenderQueueElementStorage<ReflectionProbeQueueElement> m_reflectionProbes;
 	TRenderQueueElementStorage<LensFlareQueueElement> m_lensFlares;
 	TRenderQueueElementStorage<DecalQueueElement> m_decals;
@@ -225,8 +225,11 @@ private:
 	void combine();
 
 	template<typename T>
-	void combineQueueElements(
-		SceneFrameAllocator<U8>& alloc, WeakArray<TRenderQueueElementStorage<T>*> subStorages, WeakArray<T>& combined);
+	static void combineQueueElements(SceneFrameAllocator<U8>& alloc,
+		WeakArray<TRenderQueueElementStorage<T>> subStorages,
+		WeakArray<TRenderQueueElementStorage<U32>>* ptrSubStorage,
+		WeakArray<T>& combined,
+		WeakArray<T*>* ptrCombined);
 };
 /// @}