Explorar el Código

Vulkan descriptor layouts are now properly re-used if they share the same structure
GpuParams now creates a descriptor layout per-device

BearishSun hace 9 años
padre
commit
598bb85a3d

+ 6 - 0
Source/BansheeVulkanRenderAPI/CMakeSources.cmake

@@ -23,6 +23,8 @@ set(BS_BANSHEEVULKANRENDERAPI_INC_NOFILTER
 	"Include/BsVulkanUtility.h"
 	"Include/BsVulkanGpuParams.h"
 	"Include/BsVulkanDescriptorPool.h"
+	"Include/BsVulkanDescriptorLayout.h"
+	"Include/BsVulkanResource.h"
 )
 
 set(BS_BANSHEEVULKANRENDERAPI_INC_MANAGERS
@@ -35,6 +37,7 @@ set(BS_BANSHEEVULKANRENDERAPI_INC_MANAGERS
 	"Include/BsVulkanCommandBufferManager.h"
 	"Include/BsVulkanRenderStateManager.h"
 	"Include/BsVulkanVertexInputManager.h"
+	"Include/BsVulkanDescriptorManager.h"
 )
 
 set(BS_BANSHEEVULKANRENDERAPI_SRC_NOFILTER
@@ -62,6 +65,8 @@ set(BS_BANSHEEVULKANRENDERAPI_SRC_NOFILTER
 	"Source/BsVulkanUtility.cpp"
 	"Source/BsVulkanGpuParams.cpp"
 	"Source/BsVulkanDescriptorPool.cpp"
+	"Source/BsVulkanDescriptorLayout.cpp"
+	"Source/BsVulkanResource.cpp"
 )
 
 set(BS_BANSHEEVULKANRENDERAPI_SRC_MANAGERS
@@ -74,6 +79,7 @@ set(BS_BANSHEEVULKANRENDERAPI_SRC_MANAGERS
 	"Source/BsVulkanCommandBufferManager.cpp"
 	"Source/BsVulkanRenderStateManager.cpp"
 	"Source/BsVulkanVertexInputManager.cpp"
+	"Source/BsVulkanDescriptorManager.cpp"
 )
 
 source_group("Header Files" FILES ${BS_BANSHEEVULKANRENDERAPI_INC_NOFILTER})

+ 4 - 4
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -17,11 +17,11 @@ namespace BansheeEngine
 #define BS_MAX_VULKAN_COMMAND_BUFFERS_PER_QUEUE 32
 
 	/** Pool that allocates and distributes Vulkan command buffers. */
-	class CommandBufferPool
+	class VulkanCmdBufferPool
 	{
 	public:
-		CommandBufferPool(VulkanDevice& device);
-		~CommandBufferPool();
+		VulkanCmdBufferPool(VulkanDevice& device);
+		~VulkanCmdBufferPool();
 
 		/** Attempts to find a free command buffer, or creates a new one if not found. */
 		LVulkanCommandBuffer* getBuffer(CommandBufferType type, UINT32 queueIdx, bool secondary);
@@ -95,7 +95,7 @@ namespace BansheeEngine
 		void refreshFenceStatus();
 
 	private:
-		friend class CommandBufferPool;
+		friend class VulkanCmdBufferPool;
 
 		State mState;
 		VulkanDevice& mDevice;

+ 29 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanDescriptorLayout.h

@@ -0,0 +1,29 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsVulkanPrerequisites.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup Vulkan
+	 *  @{
+	 */
+
+	/** Wrapper for the Vulkan descriptor layout object. */
+	class VulkanDescriptorLayout
+	{
+	public:
+		VulkanDescriptorLayout(VulkanDevice& device, VkDescriptorSetLayoutBinding* bindings, UINT32 numBindings);
+		~VulkanDescriptorLayout();
+
+		/** Returns a handle to the Vulkan set layout object. */
+		VkDescriptorSetLayout getHandle() const { return mLayout; }
+
+	protected:
+		VulkanDevice& mDevice;
+		VkDescriptorSetLayout mLayout;
+	};
+
+	/** @} */
+}

+ 71 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanDescriptorManager.h

@@ -0,0 +1,71 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsVulkanPrerequisites.h"
+
+namespace BansheeEngine
+{
+	/** Used as a key in a hash map containing VulkanDescriptorLayout%s. */
+	struct VulkanLayoutKey
+	{
+		VulkanLayoutKey(VkDescriptorSetLayoutBinding* bindings, UINT32 numBindings);
+
+		/** Compares two descriptor layouts. */
+		bool operator==(const VulkanLayoutKey& rhs) const;
+
+		/** Calculates a hash value for a descriptor layout. */
+		size_t calculateHash() const;
+
+		UINT32 numBindings;
+		VkDescriptorSetLayoutBinding* bindings;
+
+		VulkanDescriptorLayout* layout;
+	};
+}
+
+/** @cond STDLIB */
+/** @addtogroup Vulkan
+ *  @{
+ */
+
+namespace std
+{
+	/**	Hash value generator for VulkanDescriptorLayout. */
+	template<>
+	struct hash<BansheeEngine::VulkanLayoutKey>
+	{
+		size_t operator()(const BansheeEngine::VulkanLayoutKey& value) const
+		{
+			return (size_t)value.calculateHash();
+		}
+	};
+}
+
+/** @} */
+/** @endcond */
+
+namespace BansheeEngine
+{
+	/** @addtogroup Vulkan
+	 *  @{
+	 */
+
+	/** Manages allocation of descriptor layouts and sets for a single Vulkan device. */
+	class VulkanDescriptorManager
+	{
+	public:
+		VulkanDescriptorManager(VulkanDevice& device);
+		~VulkanDescriptorManager();
+
+		/** Attempts to find an existing one, or allocates a new descriptor set layout from the provided set of bindings. */
+		VulkanDescriptorLayout* getLayout(VkDescriptorSetLayoutBinding* bindings, UINT32 numBindings);
+
+	protected:
+		VulkanDevice& mDevice;
+
+		UnorderedMap<VulkanLayoutKey, UINT32> mSets; // TODO - Just dummy value for now, keep a list of sets here normally
+	};
+
+	/** @} */
+}

+ 8 - 3
Source/BansheeVulkanRenderAPI/Include/BsVulkanDevice.h

@@ -4,6 +4,7 @@
 
 #include "BsVulkanPrerequisites.h"
 #include "BsRenderAPI.h"
+#include "BsVulkanDescriptorManager.h"
 
 namespace BansheeEngine
 {
@@ -46,7 +47,10 @@ namespace BansheeEngine
 		UINT32 getQueueFamily(VulkanQueueType type) const { return mQueueInfos[(int)type].familyIdx; }
 
 		/** Returns a pool that can be used for allocating command buffers for all queues on this device. */
-		CommandBufferPool& getCmdBufferPool() const { return *mCommandBufferPool; }
+		VulkanCmdBufferPool& getCmdBufferPool() const { return *mCommandBufferPool; }
+
+		/** Returns a manager that can be used for allocating descriptor layouts and sets. */
+		VulkanDescriptorManager& getDescriptorManager() const { return *mDescriptorManager; }
 
 		/** 
 		 * Allocates memory for the provided image, and binds it to the image. Returns null if it cannot find memory
@@ -76,7 +80,8 @@ namespace BansheeEngine
 		VkPhysicalDevice mPhysicalDevice;
 		VkDevice mLogicalDevice;
 
-		CommandBufferPool* mCommandBufferPool;
+		VulkanCmdBufferPool* mCommandBufferPool;
+		VulkanDescriptorManager* mDescriptorManager;
 
 		VkPhysicalDeviceProperties mDeviceProperties;
 		VkPhysicalDeviceFeatures mDeviceFeatures;
@@ -94,4 +99,4 @@ namespace BansheeEngine
 	};
 
 	/** @} */
-}
+}

+ 13 - 7
Source/BansheeVulkanRenderAPI/Include/BsVulkanGpuParams.h

@@ -37,19 +37,25 @@ namespace BansheeEngine
 		void setLoadStoreSurface(UINT32 set, UINT32 slot, const TextureSurface& surface) override;
 
 	protected:
-		friend class VulkanHardwareBufferCoreManager;
+		/** All GPU param data related to a single descriptor set. */
+		struct PerSetData
+		{
+			VulkanDescriptorLayout* layout;
+		};
 
-		/** Information about a single descriptor set. */
-		struct SetInfo
+		/** All GPU param data beloning to a single device. */
+		struct PerDeviceData
 		{
-			VkDescriptorSetLayout layout;
-			VkDescriptorSet set;
+			PerSetData* perSetData;
+			UINT32 numSets;
 		};
 
+		friend class VulkanHardwareBufferCoreManager;
+
 		VulkanGpuParams(const GPU_PARAMS_DESC& desc, GpuDeviceFlags deviceMask);
 
-		SetInfo* mSets;
-		UINT32 mNumSets;
+		PerDeviceData mPerDeviceData[BS_MAX_LINKED_DEVICES];
+		UINT32 mNumDevices;
 	};
 
 	/** @} */

+ 7 - 2
Source/BansheeVulkanRenderAPI/Include/BsVulkanPrerequisites.h

@@ -37,7 +37,9 @@ namespace BansheeEngine
 	class VulkanDevice;
 	class VulkanGLSLProgramFactory;
 	class VulkanSwapChain;
-	class CommandBufferPool;
+	class VulkanDescriptorLayout;
+	class VulkanDescriptorManager;
+	class VulkanCmdBufferPool;
 
 	VkAllocationCallbacks* gVulkanAllocator = nullptr;
 
@@ -72,4 +74,7 @@ namespace BansheeEngine
 	vk##name = reinterpret_cast<PFN_vk##name>(vkGetDeviceProcAddr(device, "vk"#name));
 
 /** Maximum number of hardware devices usable at once. */
-#define BS_MAX_VULKAN_DEVICES 5
+#define BS_MAX_VULKAN_DEVICES 5U
+
+/** Maximum number of devices one resource can exist at the same time. */
+#define BS_MAX_LINKED_DEVICES 4U

+ 1 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanResource.h

@@ -0,0 +1 @@
+#pragma once

+ 5 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanUtility.h

@@ -57,6 +57,11 @@ namespace BansheeEngine
 
 		/** Converts between a command buffer type and a Vulkan queue type. */
 		static VulkanQueueType getQueueType(CommandBufferType type);
+
+		/** 
+		 * Populates the provided array with Vulkan devices that correspond to provided flags. Sets null in unused slots. 
+		 */
+		static void getDevices(const VulkanRenderAPI& rapi, GpuDeviceFlags flags, VulkanDevice* (&devices)[BS_MAX_LINKED_DEVICES]);
 	};
 
 	/** @} */

+ 6 - 6
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -6,7 +6,7 @@
 
 namespace BansheeEngine
 {
-	CommandBufferPool::CommandBufferPool(VulkanDevice& device)
+	VulkanCmdBufferPool::VulkanCmdBufferPool(VulkanDevice& device)
 		:mDevice(device), mPools{}, mBuffers {}
 	{
 		for (UINT32 i = 0; i < VQT_COUNT; i++)
@@ -26,7 +26,7 @@ namespace BansheeEngine
 		}
 	}
 
-	CommandBufferPool::~CommandBufferPool()
+	VulkanCmdBufferPool::~VulkanCmdBufferPool()
 	{
 		for (UINT32 i = 0; i < VQT_COUNT; i++)
 		{
@@ -52,7 +52,7 @@ namespace BansheeEngine
 		}
 	}
 
-	LVulkanCommandBuffer* CommandBufferPool::getBuffer(CommandBufferType type, UINT32 queueIdx, bool secondary)
+	LVulkanCommandBuffer* VulkanCmdBufferPool::getBuffer(CommandBufferType type, UINT32 queueIdx, bool secondary)
 	{
 		assert(queueIdx < BS_MAX_QUEUES_PER_TYPE);
 
@@ -81,14 +81,14 @@ namespace BansheeEngine
 		return buffers[i];
 	}
 
-	LVulkanCommandBuffer* CommandBufferPool::createBuffer(VulkanQueueType type, bool secondary)
+	LVulkanCommandBuffer* VulkanCmdBufferPool::createBuffer(VulkanQueueType type, bool secondary)
 	{
 		VkCommandPool pool = getPool(type);
 
 		return bs_new<LVulkanCommandBuffer>(mDevice, pool, secondary);
 	}
 
-	VkCommandPool CommandBufferPool::getPool(VulkanQueueType type)
+	VkCommandPool VulkanCmdBufferPool::getPool(VulkanQueueType type)
 	{
 		VkCommandPool pool = mPools[type];
 		if (pool == VK_NULL_HANDLE)
@@ -226,7 +226,7 @@ namespace BansheeEngine
 
 	void VulkanCommandBuffer::acquireNewBuffer()
 	{
-		CommandBufferPool& pool = mDevice.getCmdBufferPool();
+		VulkanCmdBufferPool& pool = mDevice.getCmdBufferPool();
 
 		mBuffer = pool.getBuffer(mType, mQueueIdx, mIsSecondary);
 	}

+ 25 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanDescriptorLayout.cpp

@@ -0,0 +1,25 @@
+#include "BsVulkanDescriptorLayout.h"
+#include "BsVulkanDevice.h"
+
+namespace BansheeEngine
+{
+	VulkanDescriptorLayout::VulkanDescriptorLayout(VulkanDevice& device, VkDescriptorSetLayoutBinding* bindings, 
+		UINT32 numBindings)
+		:mDevice(device)
+	{
+		VkDescriptorSetLayoutCreateInfo layoutCI;
+		layoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		layoutCI.pNext = nullptr;
+		layoutCI.flags = 0;
+		layoutCI.bindingCount = numBindings;
+		layoutCI.pBindings = bindings;
+
+		VkResult result = vkCreateDescriptorSetLayout(device.getLogical(), &layoutCI, gVulkanAllocator, &mLayout);
+		assert(result == VK_SUCCESS);
+	}
+
+	VulkanDescriptorLayout::~VulkanDescriptorLayout()
+	{
+		vkDestroyDescriptorSetLayout(mDevice.getLogical(), mLayout, gVulkanAllocator);
+	}
+}

+ 87 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanDescriptorManager.cpp

@@ -0,0 +1,87 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsVulkanDescriptorManager.h"
+#include "BsVulkanDescriptorLayout.h"
+
+namespace BansheeEngine
+{
+	VulkanLayoutKey::VulkanLayoutKey(VkDescriptorSetLayoutBinding* bindings, UINT32 numBindings)
+		:bindings(bindings), numBindings(numBindings)
+	{ }
+
+	bool VulkanLayoutKey::operator==(const VulkanLayoutKey& rhs) const
+	{
+		if (numBindings != rhs.numBindings)
+			return false;
+
+		for (UINT32 i = 0; i < numBindings; i++)
+		{
+			if (bindings[i].binding != rhs.bindings[i].binding)
+				return false;
+
+			if (bindings[i].descriptorType != rhs.bindings[i].descriptorType)
+				return false;
+
+			if (bindings[i].descriptorCount != rhs.bindings[i].descriptorCount)
+				return false;
+
+			if (bindings[i].stageFlags != rhs.bindings[i].stageFlags)
+				return false;
+		}
+
+		return true;
+	}
+
+	size_t VulkanLayoutKey::calculateHash() const
+	{
+		size_t hash = 0;
+		for (UINT32 i = 0; i < numBindings; i++)
+		{
+			size_t bindingHash = 0;
+			hash_combine(bindingHash, bindings[i].binding);
+			hash_combine(bindingHash, bindings[i].descriptorCount);
+			hash_combine(bindingHash, bindings[i].descriptorType);
+			hash_combine(bindingHash, bindings[i].stageFlags);
+			assert(bindings[i].pImmutableSamplers == nullptr); // Not accounted for in hash, assumed always null
+
+			hash_combine(hash, bindingHash);
+		}
+
+		return hash;
+	}
+
+	VulkanDescriptorManager::VulkanDescriptorManager(VulkanDevice& device)
+		:mDevice(device)
+	{
+
+	}
+
+	VulkanDescriptorManager::~VulkanDescriptorManager()
+	{
+		for (auto& entry : mSets)
+		{
+			bs_delete(entry.first.layout);
+			bs_free(entry.first.bindings);
+		}
+
+		// TODO - Ensure all sets get released
+	}
+
+	VulkanDescriptorLayout* VulkanDescriptorManager::getLayout(VkDescriptorSetLayoutBinding* bindings, UINT32 numBindings)
+	{
+		VulkanLayoutKey key(bindings, numBindings);
+
+		auto iterFind = mSets.find(key);
+		if (iterFind != mSets.end())
+			return iterFind->first.layout;
+
+		// Create new
+		key.bindings = bs_allocN<VkDescriptorSetLayoutBinding>(numBindings);
+		memcpy(key.bindings, bindings, numBindings * sizeof(VkDescriptorSetLayoutBinding));
+
+		key.layout = bs_new<VulkanDescriptorLayout>(mDevice, key.bindings, numBindings);
+		mSets.insert(std::make_pair(key, 0));
+		
+		return key.layout;
+	}
+}

+ 5 - 2
Source/BansheeVulkanRenderAPI/Source/BsVulkanDevice.cpp

@@ -2,6 +2,7 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsVulkanDevice.h"
 #include "BsVulkanCommandBuffer.h"
+#include "BsVulkanDescriptorManager.h"
 
 namespace BansheeEngine
 {
@@ -101,14 +102,16 @@ namespace BansheeEngine
 				vkGetDeviceQueue(mLogicalDevice, mQueueInfos[i].familyIdx, j, &mQueueInfos[i].queues[j]);
 		}
 
-		// Create command buffer pools
-		mCommandBufferPool = bs_new<CommandBufferPool>(*this);
+		// Create pools/managers
+		mCommandBufferPool = bs_new<VulkanCmdBufferPool>(*this);
+		mDescriptorManager = bs_new<VulkanDescriptorManager>(*this);
 	}
 
 	VulkanDevice::~VulkanDevice()
 	{
 		vkDeviceWaitIdle(mLogicalDevice);
 
+		bs_delete(mDescriptorManager);
 		bs_delete(mCommandBufferPool);
 		vkDestroyDevice(mLogicalDevice, gVulkanAllocator);
 	}

+ 44 - 16
Source/BansheeVulkanRenderAPI/Source/BsVulkanGpuParams.cpp

@@ -1,16 +1,20 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsVulkanGpuParams.h"
+#include "BsVulkanUtility.h"
+#include "BsVulkanRenderAPI.h"
+#include "BsVulkanDevice.h"
 #include "BsGpuParamDesc.h"
 
 namespace BansheeEngine
 {
 	VulkanGpuParams::VulkanGpuParams(const GPU_PARAMS_DESC& desc, GpuDeviceFlags deviceMask)
-		:GpuParamsCore(desc, deviceMask)
+		:GpuParamsCore(desc, deviceMask), mPerDeviceData{}, mNumDevices(0)
 	{
+		// Generate all required bindings
 		UINT32 numBindings = 0;
 		UINT32 numSets = 0;
-		
+
 		UINT32 numElementTypes = (UINT32)ElementType::Count;
 		for (UINT32 i = 0; i < numElementTypes; i++)
 		{
@@ -26,10 +30,10 @@ namespace BansheeEngine
 		memset(bindings, 0, bindingsSize);
 
 		UINT32 globalBindingIdx = 0;
-		for(UINT32 i = 0; i < numSets; i++)
+		for (UINT32 i = 0; i < numSets; i++)
 		{
 			bindingsPerSet[i] = 0;
-			for(UINT32 j = 0; j < numElementTypes; j++)
+			for (UINT32 j = 0; j < numElementTypes; j++)
 			{
 				if (i >= mNumSets[j])
 					continue;
@@ -43,11 +47,11 @@ namespace BansheeEngine
 					end = mNumElements[j];
 
 				UINT32 elementsInSet = end - start;
-				for(UINT32 k = 0; k < elementsInSet; k++)
+				for (UINT32 k = 0; k < elementsInSet; k++)
 				{
 					VkDescriptorSetLayoutBinding& binding = bindings[globalBindingIdx + k];
 					binding.binding = bindingsPerSet[i] + k;
-				};
+				}
 
 				globalBindingIdx += elementsInSet;
 				bindingsPerSet[i] += elementsInSet;
@@ -99,31 +103,55 @@ namespace BansheeEngine
 			setUpBindings(paramDesc->samplers, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
 		}
 
+		VulkanRenderAPI& rapi = static_cast<VulkanRenderAPI&>(RenderAPICore::instance());
+		VulkanDevice* devices[BS_MAX_LINKED_DEVICES];
+		VulkanUtility::getDevices(rapi, deviceMask, devices);
 
-		for (UINT32 i = 0; i < numSets; i++)
+		// Allocate layouts per-device
+		for (UINT32 i = 0; i < BS_MAX_LINKED_DEVICES; i++)
 		{
+			if (devices[i] == nullptr)
+				break;
+
+			mNumDevices++;
+		}
+
+		UINT32 perSetBytes = sizeof(PerSetData) * numSets;
+		UINT8* data = (UINT8*)bs_alloc(perSetBytes * mNumDevices);
+
+		for(UINT32 i = 0; i < mNumDevices; i++)
+		{
+			mPerDeviceData[i].numSets = numSets;
+			mPerDeviceData[i].perSetData = (PerSetData*)data;
+			data += sizeof(perSetBytes);
+
+			VulkanDescriptorManager& descManager = devices[i]->getDescriptorManager();
+
+			UINT32 bindingOffset = 0;
+			for (UINT32 j = 0; j < numSets; j++)
+			{
+				mPerDeviceData[i].perSetData[j].layout = descManager.getLayout(&bindings[bindingOffset], bindingsPerSet[j]);
+
+				bindingOffset += bindingsPerSet[j];
+			}
 		}
 
-		// TODO - Actually allocate and create layouts and sets
-		// TODO - Create sets per-device
-		// TODO - Prepare write descs and see what's the best way to update them
 
 		bs_stack_free(bindingOffsets);
 		bs_stack_free(bindings);
 		bs_stack_free(bindingsPerSet);
 
-		
-
-
-		// Note: Set layout creation should be moved up to Shader, since one layout can be shared between multiple
-		// GPU params. Right now we take the easy route and just create a new layout every time (meaning there's a lot of
-		// duplicates).
+		// TODO - Create sets
+		// TODO - Prepare write descs
+		// TODO - Update write descs as params change
 	}
 
 	VulkanGpuParams::~VulkanGpuParams()
 	{
 		// TODO - Need to wait to ensure it isn't used on the GPU anymore
 
+		bs_free(mPerDeviceData); // Everything allocated under a single buffer to a single free is enough
+
 		// TODO - CLean up mSets
 		// - Queue for destroy, remember fence counters for all available queues, only destroy after all queues execute?
 		// - Or ensure the object knows which queue it was used on?

+ 0 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanResource.cpp


+ 33 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanUtility.cpp

@@ -1,6 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsVulkanUtility.h"
+#include "BsVulkanRenderAPI.h"
 #include "BsException.h"
 
 namespace BansheeEngine
@@ -417,4 +418,36 @@ namespace BansheeEngine
 		// Unsupported type
 		return VQT_GRAPHICS;
 	}
+
+	void VulkanUtility::getDevices(const VulkanRenderAPI& rapi, GpuDeviceFlags flags, VulkanDevice*(&devices)[BS_MAX_LINKED_DEVICES])
+	{
+		if(flags == GDF_DEFAULT)
+		{
+			const Vector<SPtr<VulkanDevice>>& primaryDevices = rapi._getPrimaryDevices();
+			UINT32 count = std::min(BS_MAX_LINKED_DEVICES, (UINT32)primaryDevices.size());
+
+			for (UINT32 i = 0; i < count; i++)
+				devices[i] = primaryDevices[i].get();
+
+			for (UINT32 i = count; i < BS_MAX_LINKED_DEVICES; i++)
+				devices[i] = nullptr;
+		}
+		else
+		{
+			UINT32 numDevices = rapi._getNumDevices();
+
+			UINT32 deviceIdx = 0;
+			for(UINT32 i = 0; i < numDevices; i++)
+			{
+				if (flags & (1 << i))
+					devices[deviceIdx++] = rapi._getDevice(i).get();
+
+				if (deviceIdx >= BS_MAX_LINKED_DEVICES)
+					break;
+			}
+
+			for (UINT32 i = deviceIdx; i < BS_MAX_LINKED_DEVICES; i++)
+				devices[i] = nullptr;
+		}
+	}
 }