Просмотр исходного кода

Render queue sorting by material

BearishSun 10 лет назад
Родитель
Сommit
8831b3cc2f

+ 40 - 27
BansheeEngine/Include/BsRenderQueue.h

@@ -6,20 +6,29 @@
 
 namespace BansheeEngine 
 {
+	/**
+	 * @brief	Controls if and how a render queue groups renderable objects
+	 * 			by material.
+	 */
+	enum class MaterialGrouping
+	{
+		None, /**< No grouping based on material will be done. */
+		PreferMaterial, /**< Elements will be grouped by material first, by per-element sort type second. */
+		PreferSortType /**< Elements will be grouped by per-element sort type first, material second. */
+	};
+
 	/**
 	 * @brief	Contains data needed for performing a single rendering pass.
 	 */
 	struct BS_EXPORT RenderQueueElement
 	{
 		RenderQueueElement()
-			:passIdx(0)
+			:passIdx(0), renderElem(nullptr), applyPass(true)
 		{ }
 
 		RenderableElement* renderElem;
-		SPtr<MaterialCore> material;
-		SPtr<MeshCoreBase> mesh;
-		SubMesh subMesh;
 		UINT32 passIdx;
+		bool applyPass;
 	};
 
 	/**
@@ -30,19 +39,19 @@ namespace BansheeEngine
 	class BS_EXPORT RenderQueue
 	{
 		/**
-		 * @brief	Data used for renderable elemnt sorting.
+		 * @brief	Data used for renderable element sorting. Represents a single pass for a single mesh.
 		 */
-		struct SortData
+		struct SortableElement
 		{
-			RenderQueueElement element;
-			QueueSortType sortType;
 			UINT32 seqIdx;
 			INT32 priority;
 			float distFromCamera;
+			UINT32 shaderId;
+			UINT32 passIdx;
 		};
 
 	public:
-		RenderQueue();
+		RenderQueue(MaterialGrouping grouping = MaterialGrouping::PreferSortType);
 		virtual ~RenderQueue() { }
 
 		/**
@@ -53,21 +62,6 @@ namespace BansheeEngine
 		 */
 		void add(RenderableElement* element, float distFromCamera);
 
-		/**
-		 * @brief	Adds a new entry to the render queue.
-		 *
-		 * @param	material		Material that will be used for rendering the object.
-		 * @param	mesh			Mesh representing the geometry of the object.
-		 * @param	subMesh			Portion of the mesh to draw.
-		 * @param	distFromCamera	Distance of this object from the camera. Used for distance sorting.
-		 */
-		void add(const SPtr<MaterialCore>& material, const SPtr<MeshCoreBase>& mesh, const SubMesh& subMesh, float distFromCamera);
-
-		/**
-		 * @brief	Adds new entries from the provided render queue to this queue.
-		 */
-		void add(const RenderQueue& renderQueue);
-
 		/**
 		 * @brief	Clears all render operations from the queue.
 		 */
@@ -84,13 +78,32 @@ namespace BansheeEngine
 		 */
 		const Vector<RenderQueueElement>& getSortedElements() const;
 
+		/**
+		 * @brief	Controls if and how a render queue groups renderable objects by material.
+		 */
+		void setMaterialGrouping(MaterialGrouping grouping) { mGrouping = grouping; }
+
 	protected:
 		/**
-		 * @brief	Callback used for sorting elements.
+		 * @brief	Callback used for sorting elements with no material grouping.
+		 */
+		static bool elementSorterNoGroup(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup);
+
+		/**
+		 * @brief	Callback used for sorting elements with preferred material grouping.
+		 */
+		static bool elementSorterPreferGroup(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup);
+
+		/**
+		 * @brief	Callback used for sorting elements with material grouping after sorting.
 		 */
-		static bool elementSorter(const SortData&, const SortData&);
+		static bool elementSorterPreferSort(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup);
+
+		Vector<SortableElement> mSortableElements;
+		Vector<UINT32> mSortableElementIdx;
+		Vector<RenderableElement*> mElements;
 
-		Set<SortData, std::function<bool(const SortData&, const SortData&)>> mRenderElements;
 		Vector<RenderQueueElement> mSortedRenderElements;
+		MaterialGrouping mGrouping;
 	};
 }

+ 125 - 63
BansheeEngine/Source/BsRenderQueue.cpp

@@ -5,107 +5,169 @@
 #include "BsMaterial.h"
 #include "BsRenderableElement.h"
 
+using namespace std::placeholders;
+
 namespace BansheeEngine
 {
-	RenderQueue::RenderQueue()
-		:mRenderElements(&elementSorter)
+	RenderQueue::RenderQueue(MaterialGrouping grouping)
+		:mGrouping(grouping)
 	{
 
 	}
 
 	void RenderQueue::clear()
 	{
-		mRenderElements.clear();
+		mSortableElements.clear();
+		mSortableElementIdx.clear();
+		mElements.clear();
+
 		mSortedRenderElements.clear();
 	}
 
 	void RenderQueue::add(RenderableElement* element, float distFromCamera)
 	{
-		SortData sortData;
-
-		RenderQueueElement& renderOp = sortData.element;
-		renderOp.renderElem = element;
-		renderOp.material = element->material;
-		renderOp.mesh = element->mesh;
-		renderOp.subMesh = element->subMesh;
+		SPtr<MaterialCore> material = element->material;
+		SPtr<ShaderCore> shader = material->getShader();
 
-		sortData.distFromCamera = distFromCamera;
-		sortData.priority = element->material->getShader()->getQueuePriority();
-		sortData.sortType = element->material->getShader()->getQueueSortType();
-		sortData.seqIdx = (UINT32)mRenderElements.size();
+		mElements.push_back(element);
+		
+		UINT32 queuePriority = shader->getQueuePriority();
+		QueueSortType sortType = shader->getQueueSortType();
+		UINT32 shaderId = shader->getId();
+		bool separablePasses = shader->getAllowSeparablePasses();
 
-		// TODO - Make sure elements are cached so we dont allocate memory for them every frame
-		mRenderElements.insert(sortData);
-	}
+		switch (sortType)
+		{
+		case QueueSortType::None:
+			distFromCamera = 0;
+			break;
+		case QueueSortType::BackToFront:
+			distFromCamera = -distFromCamera;
+			break;
+		}
 
-	void RenderQueue::add(const SPtr<MaterialCore>& material, const SPtr<MeshCoreBase>& mesh, const SubMesh& subMesh, float distFromCamera)
-	{
-		SortData sortData;
+		UINT32 numPasses = material->getNumPasses();
+		if (!separablePasses)
+			numPasses = std::min(1U, numPasses);
 
-		RenderQueueElement& renderOp = sortData.element;
-		renderOp.renderElem = nullptr;
-		renderOp.material = material;
-		renderOp.mesh = mesh;
-		renderOp.subMesh = subMesh;
+		for (UINT32 i = 0; i < numPasses; i++)
+		{
+			UINT32 idx = (UINT32)mSortableElementIdx.size();
+			mSortableElementIdx.push_back(idx);
 
-		sortData.distFromCamera = distFromCamera;
-		sortData.priority = material->getShader()->getQueuePriority();
-		sortData.sortType = material->getShader()->getQueueSortType();
-		sortData.seqIdx = (UINT32)mRenderElements.size();
+			mSortableElements.push_back(SortableElement());
+			SortableElement& sortableElem = mSortableElements.back();
 
-		// TODO - Make sure elements are cached so we dont allocate memory for them every frame
-		mRenderElements.insert(sortData);
+			sortableElem.seqIdx = idx;
+			sortableElem.priority = queuePriority;
+			sortableElem.shaderId = shaderId;
+			sortableElem.passIdx = i;
+			sortableElem.distFromCamera = distFromCamera;
+		}
 	}
 
-	void RenderQueue::add(const RenderQueue& renderQueue)
+	void RenderQueue::sort()
 	{
-		for (auto& elem : renderQueue.mRenderElements)
+		std::function<bool(UINT32, UINT32, const Vector<SortableElement>&)> sortMethod;
+
+		switch (mGrouping)
 		{
-			if (elem.element.renderElem != nullptr)
-				add(elem.element.renderElem, elem.distFromCamera);
-			else
-				add(elem.element.material, elem.element.mesh, elem.element.subMesh, elem.distFromCamera);
+		case MaterialGrouping::None:
+			sortMethod = &elementSorterNoGroup;
+			break;
+		case MaterialGrouping::PreferMaterial:
+			sortMethod = &elementSorterPreferGroup;
+			break;
+		case MaterialGrouping::PreferSortType:
+			sortMethod = &elementSorterPreferSort;
+			break;
 		}
-	}
 
-	void RenderQueue::sort()
-	{
-		// TODO - I'm ignoring "separate pass" material parameter.
-		for (auto& sortData : mRenderElements)
+		// Sort only indices since we generate an entirely new data set anyway, it doesn't make sense to move sortable elements
+		std::sort(mSortableElementIdx.begin(), mSortableElementIdx.end(), std::bind(sortMethod, _1, _2, mSortableElements));
+
+		UINT32 prevShaderId = (UINT32)-1;
+		UINT32 prevPassIdx = (UINT32)-1;
+		RenderableElement* renderElem = nullptr;
+		INT32 currentElementIdx = -1;
+		UINT32 numPassesInCurrentElement = 0;
+		bool separablePasses = true;
+		for (UINT32 i = 0; i < (UINT32)mSortableElementIdx.size(); i++)
 		{
-			const RenderQueueElement& renderElem = sortData.element;
-			UINT32 numPasses = (UINT32)renderElem.material->getNumPasses();
-			for (UINT32 i = 0; i < numPasses; i++)
+			UINT32 idx = mSortableElementIdx[i];
+
+			while (numPassesInCurrentElement == 0)
+			{
+				currentElementIdx++;
+				renderElem = mElements[currentElementIdx];
+				numPassesInCurrentElement = renderElem->material->getNumPasses();
+				separablePasses = renderElem->material->getShader()->getAllowSeparablePasses();
+			}
+
+			const SortableElement& elem = mSortableElements[idx];
+			if (separablePasses)
 			{
 				mSortedRenderElements.push_back(RenderQueueElement());
 
 				RenderQueueElement& sortedElem = mSortedRenderElements.back();
-				sortedElem.renderElem = renderElem.renderElem;
-				sortedElem.material = renderElem.material;
-				sortedElem.mesh = renderElem.mesh;
-				sortedElem.subMesh = renderElem.subMesh;
-				sortedElem.passIdx = i;
+				sortedElem.renderElem = renderElem;
+				sortedElem.passIdx = elem.passIdx;
+
+				if (prevShaderId != elem.shaderId || prevPassIdx != elem.passIdx)
+				{
+					sortedElem.applyPass = true;
+					prevShaderId = elem.shaderId;
+					prevPassIdx = elem.passIdx;
+				}
+				else
+					sortedElem.applyPass = false;
+
+				numPassesInCurrentElement--;
 			}
+			else
+			{
+				for (UINT32 j = 0; j < numPassesInCurrentElement; j++)
+				{
+					mSortedRenderElements.push_back(RenderQueueElement());
+
+					RenderQueueElement& sortedElem = mSortedRenderElements.back();
+					sortedElem.renderElem = renderElem;
+					sortedElem.passIdx = j;
+					sortedElem.applyPass = true;
+
+					prevShaderId = elem.shaderId;
+					prevPassIdx = j;
+				}
+
+				numPassesInCurrentElement = 0;
+			}			
 		}
 	}
 
-	bool RenderQueue::elementSorter(const SortData& a, const SortData& b)
+	bool RenderQueue::elementSorterNoGroup(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup)
 	{
-		if (a.priority == b.priority)
-		{
-			if (a.sortType == QueueSortType::None || a.sortType != b.sortType)
-				return a.seqIdx < b.seqIdx;
+		const SortableElement& a = lookup[aIdx];
+		const SortableElement& b = lookup[bIdx];
 
-			if (a.distFromCamera == b.distFromCamera)
-				return a.seqIdx < b.seqIdx;
+		return (a.priority > b.priority) | (a.distFromCamera < b.distFromCamera) | (a.seqIdx < b.seqIdx);
+	}
 
-			if (a.sortType == QueueSortType::FrontToBack)
-				return a.distFromCamera < b.distFromCamera;
-			else
-				return a.distFromCamera > b.distFromCamera;
-		}
+	bool RenderQueue::elementSorterPreferGroup(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup)
+	{
+		const SortableElement& a = lookup[aIdx];
+		const SortableElement& b = lookup[bIdx];
+		
+		return (a.priority > b.priority) | (a.shaderId < b.shaderId) | (a.passIdx < b.passIdx) |
+			(a.distFromCamera < b.distFromCamera) | (a.seqIdx < b.seqIdx);
+	}
+
+	bool RenderQueue::elementSorterPreferSort(UINT32 aIdx, UINT32 bIdx, const Vector<SortableElement>& lookup)
+	{
+		const SortableElement& a = lookup[aIdx];
+		const SortableElement& b = lookup[bIdx];
 
-		return a.priority > b.priority;
+		return (a.priority > b.priority) | (a.distFromCamera < b.distFromCamera) | (a.shaderId < b.shaderId) | 
+			(a.passIdx < b.passIdx) | (a.seqIdx < b.seqIdx);
 	}
 
 	const Vector<RenderQueueElement>& RenderQueue::getSortedElements() const

+ 4 - 4
RenderBeast/Source/BsRenderBeast.cpp

@@ -465,7 +465,7 @@ namespace BansheeEngine
 		const Vector<RenderQueueElement>& opaqueElements = cameraData.opaqueQueue->getSortedElements();
 		for(auto iter = opaqueElements.begin(); iter != opaqueElements.end(); ++iter)
 		{
-			SPtr<MaterialCore> material = iter->material;
+			SPtr<MaterialCore> material = iter->renderElem->material;
 
 			BeastRenderableElement* renderElem = static_cast<BeastRenderableElement*>(iter->renderElem);
 			if (renderElem != nullptr)
@@ -507,14 +507,14 @@ namespace BansheeEngine
 			else
 				setPass(material, iter->passIdx, nullptr);
 
-			draw(iter->mesh, iter->subMesh);
+			draw(iter->renderElem->mesh, iter->renderElem->subMesh);
 		}
 
 		// Render transparent
 		const Vector<RenderQueueElement>& transparentElements = cameraData.transparentQueue->getSortedElements();
 		for (auto iter = transparentElements.begin(); iter != transparentElements.end(); ++iter)
 		{
-			SPtr<MaterialCore> material = iter->material;
+			SPtr<MaterialCore> material = iter->renderElem->material;
 
 			BeastRenderableElement* renderElem = static_cast<BeastRenderableElement*>(iter->renderElem);
 			if (renderElem != nullptr)
@@ -556,7 +556,7 @@ namespace BansheeEngine
 			else
 				setPass(material, iter->passIdx, nullptr);
 
-			draw(iter->mesh, iter->subMesh);
+			draw(iter->renderElem->mesh, iter->renderElem->subMesh);
 		}
 
 		cameraData.opaqueQueue->clear();