Browse Source

Added Vulkan vertex input manager

BearishSun 9 years ago
parent
commit
d404af9297

+ 1 - 1
Source/BansheeCore/Include/BsVertexDeclaration.h

@@ -194,7 +194,7 @@ namespace BansheeEngine
 	public:
 		friend class VertexDeclarationRTTI;
 		static RTTITypeBase* getRTTIStatic();
-		virtual RTTITypeBase* getRTTI() const override;
+		RTTITypeBase* getRTTI() const override;
     };
 
 	/**	Converts a vertex semantic enum to a readable name. */

+ 0 - 1
Source/BansheeD3D11RenderAPI/Include/BsD3D11Prerequisites.h

@@ -42,7 +42,6 @@ namespace BansheeEngine
 	class D3D11GpuProgramManager;
 	class D3D11IndexBuffer;
 	class D3D11HLSLProgramFactory;
-	class D3D11VertexDeclaration;
 	class D3D11Device;
 	class D3D11HardwareBuffer;
 	class D3D11GpuVertexProgram;

+ 10 - 7
Source/BansheeUtility/Include/BsStaticAlloc.h

@@ -13,16 +13,19 @@ namespace BansheeEngine
 	 */
 
 	/**
-	 * Static allocator that attempts to perform zero heap allocations by always keeping an active stack-allocated buffer. 
-	 * If the size of allocated data goes over the set limit dynamic allocations will occur however.
-	 *
+	 * Static allocator that attempts to perform zero heap (dynamic) allocations by always keeping an active preallocated 
+	 * buffer. The allocator provides a fixed amount of preallocated memory, and if the size of the allocated data goes over
+	 * that limit the allocator will fall back to dynamic heap allocations.
+	 * 
 	 * @note	This kind of allocator is only able to free all of its memory at once. Freeing individual elements
 	 *			will not free the memory until a call to clear().
 	 * 			
-	 * @tparam	BlockSize			Size of the initially allocated static block, and minimum size of any dynamically allocated memory.
-	 * @tparam	MaxDynamicMemory	Maximum amount of unused memory allowed in the buffer after a call to clear(). Keeping active dynamic 
-	 *								buffers can help prevent further memory allocations at the cost of memory. This is not relevant
-	 *								if you stay within the bounds of the statically allocated memory.
+	 * @tparam	BlockSize			Size of the initially allocated static block, and minimum size of any dynamically 
+	 *								allocated memory.
+	 * @tparam	MaxDynamicMemory	Maximum amount of unused memory allowed in the buffer after a call to clear(). Keeping 
+	 *								active dynamic buffers can help prevent further memory allocations at the cost of 
+	 *								memory. This is not relevant if you stay within the bounds of the statically allocated 
+	 *								memory.
 	 */
 	template<int BlockSize = 512, int MaxDynamicMemory = 512>
 	class StaticAlloc

+ 4 - 2
Source/BansheeVulkanRenderAPI/CMakeSources.cmake

@@ -17,7 +17,6 @@ set(BS_BANSHEEVULKANRENDERAPI_INC_NOFILTER
 	"Include/BsVulkanRenderAPI.h"
 	"Include/BsVulkanCommandBuffer.h"
 	"Include/BsVulkanDevice.h"
-	"Include/BsVulkanRenderStateManager.h"
 	"Include/BsVulkanGpuPipelineState.h"
 	"Include/BsVulkanSwapChain.h"
 	"Include/BsVulkanFramebuffer.h"
@@ -32,6 +31,8 @@ set(BS_BANSHEEVULKANRENDERAPI_INC_MANAGERS
 	"Include/BsVulkanHardwareBufferManager.h"
 	"Include/BsVulkanRenderAPIFactory.h"
 	"Include/BsVulkanCommandBufferManager.h"
+	"Include/BsVulkanRenderStateManager.h"
+	"Include/BsVulkanVertexInputManager.h"
 )
 
 set(BS_BANSHEEVULKANRENDERAPI_SRC_NOFILTER
@@ -53,7 +54,6 @@ set(BS_BANSHEEVULKANRENDERAPI_SRC_NOFILTER
 	"Source/BsVulkanRenderAPI.cpp"
 	"Source/BsVulkanCommandBuffer.cpp"
 	"Source/BsVulkanDevice.cpp"
-	"Source/BsVulkanRenderStateManager.cpp"
 	"Source/BsVulkanGpuPipelineState.cpp"
 	"Source/BsVulkanSwapChain.cpp"
 	"Source/BsVulkanFramebuffer.cpp"
@@ -68,6 +68,8 @@ set(BS_BANSHEEVULKANRENDERAPI_SRC_MANAGERS
 	"Source/BsVulkanHardwareBufferManager.cpp"
 	"Source/BsVulkanRenderAPIFactory.cpp"
 	"Source/BsVulkanCommandBufferManager.cpp"
+	"Source/BsVulkanRenderStateManager.cpp"
+	"Source/BsVulkanVertexInputManager.cpp"
 )
 
 source_group("Header Files" FILES ${BS_BANSHEEVULKANRENDERAPI_INC_NOFILTER})

+ 84 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanVertexInputManager.h

@@ -0,0 +1,84 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsVulkanPrerequisites.h"
+#include "BsModule.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup Vulkan
+	 *  @{
+	 */
+
+	/** 
+	 * Maps vertex buffer structure and vertex shader inputs in order to create vertex input description usable by Vulkan.  
+	 */
+	class VulkanVertexInputManager : public Module<VulkanVertexInputManager>
+    {
+	private:
+		/**	Key uniquely identifying buffer and shader vertex declarations. */
+		struct VertexDeclarationKey
+		{
+			UINT32 bufferDeclId;
+			UINT32 shaderDeclId;
+		};
+
+		/**	Creates a hash from vertex declaration key. */
+		class HashFunc
+		{
+		public:
+			::std::size_t operator()(const VertexDeclarationKey& key) const;
+		};
+
+		/**	Compares two vertex declaration keys. */
+		class EqualFunc
+		{
+		public:
+			bool operator()(const VertexDeclarationKey& a, const VertexDeclarationKey& b) const;
+		};
+
+		/**	Contains data about a single instance of vertex input object. */
+		struct VertexInputEntry
+		{
+			VertexInputEntry() {}
+
+			VkVertexInputAttributeDescription* attributes;
+			VkVertexInputBindingDescription* bindings;
+			VkPipelineVertexInputStateCreateInfo vertexInputCI;
+			UINT32 lastUsedIdx;
+		};
+
+	public:
+		VulkanVertexInputManager();
+		~VulkanVertexInputManager();
+
+		/** 
+		 * Returns an object that describes how vertex buffer elements map to vertex shader inputs. 
+		 * 
+		 * @param[in]	vbDecl		Describes the structure of a single vertex in a vertex buffer.
+		 * @param[in]	shaderDecl	Describes the vertex element inputs expected by a vertex shader.
+		 * @return					Vertex input state description, usable by Vulkan.
+		 */
+		const VkPipelineVertexInputStateCreateInfo& getVertexInfo(const SPtr<VertexDeclarationCore>& vbDecl, 
+			const SPtr<VertexDeclarationCore>& shaderDecl);
+
+	private:
+		/**	Creates a vertex input using the specified parameters and stores it in the input layout map. */
+		void addNew(const SPtr<VertexDeclarationCore>& vbDecl, const SPtr<VertexDeclarationCore>& shaderDecl);
+
+		/**	Removes the least used vertex input. */
+		void removeLeastUsed();
+
+	private:
+		static const int DECLARATION_BUFFER_SIZE = 1024;
+		static const int NUM_ELEMENTS_TO_PRUNE = 64;
+
+		UnorderedMap<VertexDeclarationKey, VertexInputEntry*, HashFunc, EqualFunc> mVertexInputMap;
+
+		bool mWarningShown;
+		UINT32 mLastUsedCounter;
+    };
+
+	/** @} */
+}

+ 5 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanRenderAPI.cpp

@@ -13,6 +13,7 @@
 #include "BsVulkanQueryManager.h"
 #include "BsVulkanGLSLProgramFactory.h"
 #include "BsVulkanCommandBufferManager.h"
+#include "BsVulkanVertexInputManager.h"
 #include "Win32/BsWin32VideoModeInfo.h"
 
 namespace BansheeEngine
@@ -230,6 +231,9 @@ namespace BansheeEngine
 		// Create query manager 
 		QueryManager::startUp<VulkanQueryManager>();
 
+		// Create vertex input manager
+		VulkanVertexInputManager::startUp();
+
 		// Create & register HLSL factory		
 		mGLSLFactory = bs_new<VulkanGLSLProgramFactory>();
 
@@ -253,6 +257,7 @@ namespace BansheeEngine
 			mGLSLFactory = nullptr;
 		}
 
+		VulkanVertexInputManager::shutDown();
 		QueryManager::shutDown();
 		RenderStateCoreManager::shutDown();
 		RenderWindowCoreManager::shutDown();

+ 210 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanVertexInputManager.cpp

@@ -0,0 +1,210 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsVulkanVertexInputManager.h"
+#include "BsVulkanUtility.h"
+#include "BsVertexDeclaration.h"
+#include "BsRenderStats.h"
+
+namespace BansheeEngine
+{
+	size_t VulkanVertexInputManager::HashFunc::operator()(const VertexDeclarationKey& key) const
+	{
+		size_t hash = 0;
+		hash_combine(hash, key.bufferDeclId);
+		hash_combine(hash, key.shaderDeclId);
+
+		return hash;
+	}
+
+	bool VulkanVertexInputManager::EqualFunc::operator()(const VertexDeclarationKey& a, const VertexDeclarationKey& b) const
+	{
+		if (a.bufferDeclId != b.bufferDeclId)
+			return false;
+
+		if (a.shaderDeclId != b.shaderDeclId)
+			return false;
+
+		return true;
+	}
+
+	VulkanVertexInputManager::VulkanVertexInputManager()
+		:mLastUsedCounter(0), mWarningShown(false)
+	{ }
+
+	VulkanVertexInputManager::~VulkanVertexInputManager()
+	{
+		while (mVertexInputMap.begin() != mVertexInputMap.end())
+		{
+			auto firstElem = mVertexInputMap.begin();
+
+			bs_free(firstElem->second);
+			mVertexInputMap.erase(firstElem);
+		}
+	}
+
+	const VkPipelineVertexInputStateCreateInfo& VulkanVertexInputManager::getVertexInfo(
+		const SPtr<VertexDeclarationCore>& vbDecl, const SPtr<VertexDeclarationCore>& shaderDecl)
+	{
+		VertexDeclarationKey pair;
+		pair.bufferDeclId = vbDecl->getId();
+		pair.shaderDeclId = shaderDecl->getId();
+
+		auto iterFind = mVertexInputMap.find(pair);
+		if (iterFind == mVertexInputMap.end())
+		{
+			if (mVertexInputMap.size() >= DECLARATION_BUFFER_SIZE)
+				removeLeastUsed(); // Prune so the buffer doesn't just infinitely grow
+
+			addNew(vbDecl, shaderDecl);
+
+			iterFind = mVertexInputMap.find(pair);
+		}
+
+		iterFind->second->lastUsedIdx = ++mLastUsedCounter;
+		return iterFind->second->vertexInputCI;
+	}
+
+	void VulkanVertexInputManager::addNew(const SPtr<VertexDeclarationCore>& vbDecl, 
+		const SPtr<VertexDeclarationCore>& shaderInputDecl)
+	{
+		const List<VertexElement>& vbElements = vbDecl->getProperties().getElements();
+		const List<VertexElement>& inputElements = shaderInputDecl->getProperties().getElements();
+
+		UINT32 numAttributes = 0;
+		UINT32 numBindings = 0;
+
+		for (auto& vbElem : vbElements)
+		{
+			bool foundSemantic = false;
+			for (auto& inputElem : inputElements)
+			{
+				if (inputElem.getSemantic() == vbElem.getSemantic() && inputElem.getSemanticIdx() == vbElem.getSemanticIdx())
+				{
+					foundSemantic = true;
+					break;
+				}
+			}
+
+			if (!foundSemantic) // Shader needs to have a matching input attribute, otherwise we skip it
+				continue;
+
+			numAttributes++;
+			numBindings = std::max(numBindings, (UINT32)vbElem.getStreamIdx() + 1);
+		}
+
+		UINT32 attributesBytes = sizeof(VkVertexInputAttributeDescription) * numAttributes;
+		UINT32 bindingBytes = sizeof(VkVertexInputBindingDescription) * numBindings;
+		UINT32 totalBytes = sizeof(VertexInputEntry) + attributesBytes + bindingBytes;
+
+		UINT8* data = (UINT8*)bs_alloc(totalBytes);
+		VertexInputEntry* newEntry = (VertexInputEntry*)data;
+		data += sizeof(VertexInputEntry);
+
+		newEntry->attributes = (VkVertexInputAttributeDescription*)data;
+		data += attributesBytes;
+
+		newEntry->bindings = (VkVertexInputBindingDescription*)data;
+		data += bindingBytes;
+
+		newEntry->lastUsedIdx = ++mLastUsedCounter;
+
+		for (UINT32 i = 0; i < numBindings; i++)
+		{
+			VkVertexInputBindingDescription& binding = newEntry->bindings[i];
+			binding.binding = i;
+			binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+			binding.stride = 0;
+		}
+
+		UINT32 attribIdx = 0;
+		for (auto& vbElem : vbElements)
+		{
+			VkVertexInputAttributeDescription& attribute = newEntry->attributes[attribIdx];
+
+			bool foundSemantic = false;
+			for (auto& inputElem : inputElements)
+			{
+				if (inputElem.getSemantic() == vbElem.getSemantic() && inputElem.getSemanticIdx() == vbElem.getSemanticIdx())
+				{
+					foundSemantic = true;
+					attribute.location = inputElem.getOffset();
+					break;
+				}
+			}
+
+			if (!foundSemantic) // Shader needs to have a matching input attribute, otherwise we skip it
+				continue;
+
+			attribute.binding = vbElem.getStreamIdx();
+			attribute.format = VulkanUtility::getVertexType(vbElem.getType());
+			attribute.offset = vbElem.getOffset();
+
+			VkVertexInputBindingDescription& binding = newEntry->bindings[attribute.binding];
+
+			bool isPerVertex = vbElem.getInstanceStepRate() == 0;
+			bool isFirstInBinding = binding.stride == 0;
+			if (isFirstInBinding)
+				binding.inputRate = isPerVertex ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
+			else
+			{
+				if ((binding.inputRate == VK_VERTEX_INPUT_RATE_VERTEX && !isPerVertex) ||
+					(binding.inputRate == VK_VERTEX_INPUT_RATE_INSTANCE && isPerVertex))
+				{
+					LOGERR("Found multiple vertex attributes belonging to the same binding but with different input rates. "
+						"All attributes in a binding must have the same input rate. Ignoring invalid input rates.")
+				}
+			}
+
+			binding.stride += vbElem.getSize();
+
+			attribIdx++;
+		}
+
+		numAttributes = attribIdx; // It's possible some attributes were invalid, in which case we keep the memory allocated but ignore them otherwise
+
+		newEntry->vertexInputCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+		newEntry->vertexInputCI.pNext = nullptr;
+		newEntry->vertexInputCI.flags = 0;
+		newEntry->vertexInputCI.pVertexBindingDescriptions = newEntry->bindings;
+		newEntry->vertexInputCI.vertexBindingDescriptionCount = numBindings;
+		newEntry->vertexInputCI.pVertexAttributeDescriptions = newEntry->attributes;
+		newEntry->vertexInputCI.vertexAttributeDescriptionCount = numAttributes;
+
+		// Create key and add to the layout map
+		VertexDeclarationKey pair;
+		pair.bufferDeclId = vbDecl->getId();
+		pair.shaderDeclId = shaderInputDecl->getId();
+
+		mVertexInputMap[pair] = newEntry;
+	}
+
+	void VulkanVertexInputManager::removeLeastUsed()
+	{
+		if (!mWarningShown)
+		{
+			LOGWRN("Vertex input buffer is full, pruning last " + toString(NUM_ELEMENTS_TO_PRUNE) + " elements. This is "
+				"probably okay unless you are creating a massive amount of input layouts as they will get re-created every "
+				"frame. In that case you should increase the layout buffer size. This warning won't be shown again.");
+
+			mWarningShown = true;
+		}
+
+		Map<UINT32, VertexDeclarationKey> leastFrequentlyUsedMap;
+
+		for (auto iter = mVertexInputMap.begin(); iter != mVertexInputMap.end(); ++iter)
+			leastFrequentlyUsedMap[iter->second->lastUsedIdx] = iter->first;
+
+		UINT32 elemsRemoved = 0;
+		for (auto iter = leastFrequentlyUsedMap.begin(); iter != leastFrequentlyUsedMap.end(); ++iter)
+		{
+			auto inputLayoutIter = mVertexInputMap.find(iter->second);
+
+			bs_free(inputLayoutIter->second);
+			mVertexInputMap.erase(inputLayoutIter);
+
+			elemsRemoved++;
+			if (elemsRemoved >= NUM_ELEMENTS_TO_PRUNE)
+				break;
+		}
+	}
+}