Browse Source

Refactor and fix the implementation of bindless resources

Panagiotis Christopoulos Charitos 5 years ago
parent
commit
514d4a0d3f

+ 8 - 15
src/anki/gr/Common.h

@@ -41,8 +41,6 @@ const U32 MAX_VERTEX_ATTRIBUTES = 8;
 const U32 MAX_COLOR_ATTACHMENTS = 4;
 const U32 MAX_DESCRIPTOR_SETS = 2; ///< Groups that can be bound at the same time.
 const U32 MAX_BINDINGS_PER_DESCRIPTOR_SET = 32;
-const U32 MAX_BINDLESS_TEXTURES = 256;
-const U32 MAX_BINDLESS_IMAGES = 32;
 
 const U32 MAX_FRAMES_IN_FLIGHT = 3; ///< Triple buffering.
 
@@ -130,6 +128,14 @@ public:
 	U8 m_majorApiVersion = 0;
 };
 
+/// Bindless related info.
+class BindlessLimits
+{
+public:
+	U32 m_bindlessTextureCount = 0;
+	U32 m_bindlessImageCount = 0;
+};
+
 /// The type of the allocator for heap allocations
 template<typename T>
 using GrAllocator = HeapAllocator<T>;
@@ -294,19 +300,6 @@ public:
 	}
 };
 
-enum class DescriptorType : U8
-{
-	COMBINED_TEXTURE_SAMPLER,
-	TEXTURE,
-	SAMPLER,
-	UNIFORM_BUFFER,
-	STORAGE_BUFFER,
-	IMAGE,
-	TEXTURE_BUFFER,
-
-	COUNT
-};
-
 /// The base of all init infos for GR.
 class GrBaseInitInfo
 {

+ 2 - 0
src/anki/gr/ConfigDefs.h

@@ -6,6 +6,8 @@
 ANKI_CONFIG_OPTION(gr_debugContext, 0, 0, 1)
 ANKI_CONFIG_OPTION(gr_vsync, 0, 0, 1)
 ANKI_CONFIG_OPTION(gr_debugMarkers, 0, 0, 1)
+ANKI_CONFIG_OPTION(gr_maxBindlessTextures, 256, 8, 1024)
+ANKI_CONFIG_OPTION(gr_maxBindlessImages, 32, 8, 1024)
 
 // Vulkan
 ANKI_CONFIG_OPTION(gr_diskShaderCacheMaxSize, 128_MB, 1_MB, 1_GB)

+ 6 - 0
src/anki/gr/GrManager.h

@@ -56,6 +56,11 @@ public:
 		return m_capabilities;
 	}
 
+	const BindlessLimits& getBindlessLimits() const
+	{
+		return m_bindlessLimits;
+	}
+
 	/// Get next presentable image. The returned Texture is valid until the following swapBuffers. After that it might
 	/// dissapear even if you hold the reference.
 	TexturePtr acquireNextPresentableTexture();
@@ -109,6 +114,7 @@ protected:
 	String m_cacheDir;
 	Atomic<U64> m_uuidIndex = {1};
 	GpuDeviceCapabilities m_capabilities;
+	BindlessLimits m_bindlessLimits;
 
 	GrManager();
 

+ 6 - 2
src/anki/gr/ShaderCompiler.cpp

@@ -34,6 +34,7 @@ void ShaderCompilerOptions::setFromGrManager(const GrManager& gr)
 	m_outLanguage = ShaderLanguage::GLSL;
 #endif
 	m_gpuCapabilities = gr.getDeviceCapabilities();
+	m_bindlessLimits = gr.getBindlessLimits();
 }
 
 static const Array<const char*, U(ShaderType::COUNT)> SHADER_NAME = {
@@ -70,6 +71,9 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #
 #	define ANKI_MAX_BINDLESS_TEXTURES %u
 #	define ANKI_MAX_BINDLESS_IMAGES %u
+#	define ANKI_BINDLESS_SET(set_) \
+		layout(set = set_, binding = 0) uniform utexture2D u_bindlessTextures[ANKI_MAX_BINDLESS_TEXTURES]; \
+		layout(set = set_, binding = 1) uniform readonly uimage2D u_bindlessImages[ANKI_MAX_BINDLESS_IMAGES];
 #endif
 
 #define F32 float
@@ -238,8 +242,8 @@ static void preappendAnkiSpecific(CString source, const ShaderCompilerOptions& o
 		options.m_gpuCapabilities.m_majorApiVersion,
 		&GPU_VENDOR_STR[options.m_gpuCapabilities.m_gpuVendor][0],
 		SHADER_NAME[options.m_shaderType],
-		MAX_BINDLESS_TEXTURES,
-		MAX_BINDLESS_IMAGES,
+		options.m_bindlessLimits.m_bindlessTextureCount,
+		options.m_bindlessLimits.m_bindlessImageCount,
 		&source[0]);
 }
 

+ 1 - 0
src/anki/gr/ShaderCompiler.h

@@ -31,6 +31,7 @@ public:
 	ShaderLanguage m_outLanguage;
 	ShaderType m_shaderType;
 	GpuDeviceCapabilities m_gpuCapabilities;
+	BindlessLimits m_bindlessLimits;
 
 	ShaderCompilerOptions()
 	{

+ 1 - 1
src/anki/gr/vulkan/CommandBufferImpl.h

@@ -265,7 +265,7 @@ public:
 	void bindAllBindlessInternal(U32 set)
 	{
 		commandCommon();
-		m_dsetState[set].bindCustumDescriptorSet(getGrManagerImpl().getBindlessDescriptorSet().getDescriptorSet());
+		m_dsetState[set].bindBindlessDescriptorSet();
 	}
 
 	U32 bindBindlessTextureInternal(TextureViewPtr tex, TextureUsageBit usage)

+ 1 - 1
src/anki/gr/vulkan/CommandBufferImpl.inl.h

@@ -741,7 +741,7 @@ inline void CommandBufferImpl::bindShaderProgram(ShaderProgramPtr& prog)
 		}
 		else
 		{
-			// According to the spec the bound DS may be disturbed if the ppline layout is not combatible. Play it safe
+			// According to the spec the bound DS may be disturbed if the ppline layout is not compatible. Play it safe
 			// and dirty the slot. That will force rebind of the DS at drawcall time.
 			m_dsetState[i].setLayout(DescriptorSetLayout());
 		}

+ 13 - 0
src/anki/gr/vulkan/Common.h

@@ -41,6 +41,19 @@ class GrManagerImpl;
 #define ANKI_VK_SELF(class_) class_& self = *static_cast<class_*>(this)
 #define ANKI_VK_SELF_CONST(class_) const class_& self = *static_cast<const class_*>(this)
 
+enum class DescriptorType : U8
+{
+	COMBINED_TEXTURE_SAMPLER,
+	TEXTURE,
+	SAMPLER,
+	UNIFORM_BUFFER,
+	STORAGE_BUFFER,
+	IMAGE,
+	TEXTURE_BUFFER,
+
+	COUNT
+};
+
 enum class VulkanExtensions : U16
 {
 	NONE = 0,

+ 347 - 254
src/anki/gr/vulkan/DescriptorSet.cpp

@@ -14,6 +14,265 @@
 namespace anki
 {
 
+/// Wraps a global descriptor set that is used to store bindless textures.
+class DescriptorSetFactory::BindlessDescriptorSet
+{
+public:
+	~BindlessDescriptorSet();
+
+	Error init(const GrAllocator<U8>& alloc, VkDevice dev, const BindlessLimits& bindlessLimits);
+
+	/// Bind a sampled image.
+	/// @note It's thread-safe.
+	U32 bindTexture(const VkImageView view, const VkImageLayout layout);
+
+	/// Bind a storage image.
+	/// @note It's thread-safe.
+	U32 bindImage(const VkImageView view);
+
+	/// @note It's thread-safe.
+	void unbindTexture(U32 idx)
+	{
+		unbindCommon(idx, m_freeTexIndices, m_freeTexIndexCount);
+	}
+
+	/// @note It's thread-safe.
+	void unbindImage(U32 idx)
+	{
+		unbindCommon(idx, m_freeImgIndices, m_freeImgIndexCount);
+	}
+
+	DescriptorSet getDescriptorSet() const
+	{
+		ANKI_ASSERT(m_dset);
+		DescriptorSet out;
+		out.m_handle = m_dset;
+		return out;
+	}
+
+	VkDescriptorSetLayout getDescriptorSetLayout() const
+	{
+		ANKI_ASSERT(m_layout);
+		return m_layout;
+	}
+
+private:
+	GrAllocator<U8> m_alloc;
+	VkDevice m_dev = VK_NULL_HANDLE;
+	VkDescriptorSetLayout m_layout = VK_NULL_HANDLE;
+	VkDescriptorPool m_pool = VK_NULL_HANDLE;
+	VkDescriptorSet m_dset = VK_NULL_HANDLE;
+	Mutex m_mtx;
+
+	DynamicArray<U16> m_freeTexIndices;
+	DynamicArray<U16> m_freeImgIndices;
+
+	U16 m_freeTexIndexCount ANKI_DEBUG_CODE(= MAX_U16);
+	U16 m_freeImgIndexCount ANKI_DEBUG_CODE(= MAX_U16);
+
+	void unbindCommon(U32 idx, DynamicArray<U16>& freeIndices, U16& freeIndexCount);
+};
+
+DescriptorSetFactory::BindlessDescriptorSet::~BindlessDescriptorSet()
+{
+	ANKI_ASSERT(m_freeTexIndexCount == m_freeTexIndices.getSize() && "Forgot to unbind some textures");
+	ANKI_ASSERT(m_freeImgIndexCount == m_freeImgIndices.getSize() && "Forgot to unbind some images");
+
+	if(m_pool)
+	{
+		vkDestroyDescriptorPool(m_dev, m_pool, nullptr);
+		m_pool = VK_NULL_HANDLE;
+		m_dset = VK_NULL_HANDLE;
+	}
+
+	if(m_layout)
+	{
+		vkDestroyDescriptorSetLayout(m_dev, m_layout, nullptr);
+		m_layout = VK_NULL_HANDLE;
+	}
+
+	m_freeImgIndices.destroy(m_alloc);
+	m_freeTexIndices.destroy(m_alloc);
+}
+
+Error DescriptorSetFactory::BindlessDescriptorSet::init(
+	const GrAllocator<U8>& alloc, VkDevice dev, const BindlessLimits& bindlessLimits)
+{
+	ANKI_ASSERT(dev);
+	ANKI_ASSERT(bindlessLimits.m_bindlessTextureCount <= MAX_U16);
+	ANKI_ASSERT(bindlessLimits.m_bindlessImageCount <= MAX_U16);
+	m_alloc = alloc;
+	m_dev = dev;
+
+	// Create the layout
+	{
+		Array<VkDescriptorSetLayoutBinding, 2> bindings = {};
+		bindings[0].binding = 0;
+		bindings[0].stageFlags = VK_SHADER_STAGE_ALL;
+		bindings[0].descriptorCount = bindlessLimits.m_bindlessTextureCount;
+		bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+		bindings[1].binding = 1;
+		bindings[1].stageFlags = VK_SHADER_STAGE_ALL;
+		bindings[1].descriptorCount = bindlessLimits.m_bindlessImageCount;
+		bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+
+		Array<VkDescriptorBindingFlagsEXT, 2> bindingFlags = {};
+		bindingFlags[0] = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT
+						  | VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT
+						  | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT;
+		bindingFlags[1] = bindingFlags[0];
+
+		VkDescriptorSetLayoutBindingFlagsCreateInfoEXT extraInfos = {};
+		extraInfos.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
+		extraInfos.bindingCount = bindingFlags.getSize();
+		extraInfos.pBindingFlags = &bindingFlags[0];
+
+		VkDescriptorSetLayoutCreateInfo ci = {};
+		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		ci.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT;
+		ci.bindingCount = bindings.getSize();
+		ci.pBindings = &bindings[0];
+		ci.pNext = &extraInfos;
+
+		ANKI_VK_CHECK(vkCreateDescriptorSetLayout(m_dev, &ci, nullptr, &m_layout));
+	}
+
+	// Create the pool
+	{
+		Array<VkDescriptorPoolSize, 2> sizes = {};
+		sizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+		sizes[0].descriptorCount = bindlessLimits.m_bindlessTextureCount;
+		sizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+		sizes[1].descriptorCount = bindlessLimits.m_bindlessImageCount;
+
+		VkDescriptorPoolCreateInfo ci = {};
+		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+		ci.maxSets = 1;
+		ci.poolSizeCount = sizes.getSize();
+		ci.pPoolSizes = &sizes[0];
+		ci.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT;
+
+		ANKI_VK_CHECK(vkCreateDescriptorPool(m_dev, &ci, nullptr, &m_pool));
+	}
+
+	// Create the descriptor set
+	{
+		VkDescriptorSetAllocateInfo ci = {};
+		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+		ci.descriptorPool = m_pool;
+		ci.descriptorSetCount = 1;
+		ci.pSetLayouts = &m_layout;
+
+		ANKI_VK_CHECK(vkAllocateDescriptorSets(m_dev, &ci, &m_dset));
+	}
+
+	// Init the free arrays
+	{
+		m_freeTexIndices.create(m_alloc, bindlessLimits.m_bindlessTextureCount);
+		m_freeTexIndexCount = U16(m_freeTexIndices.getSize());
+
+		for(U32 i = 0; i < m_freeTexIndices.getSize(); ++i)
+		{
+			m_freeTexIndices[i] = U16(m_freeTexIndices.getSize() - i - 1);
+		}
+
+		m_freeImgIndices.create(m_alloc, bindlessLimits.m_bindlessImageCount);
+		m_freeImgIndexCount = U16(m_freeImgIndices.getSize());
+
+		for(U32 i = 0; i < m_freeImgIndices.getSize(); ++i)
+		{
+			m_freeImgIndices[i] = U16(m_freeImgIndices.getSize() - i - 1);
+		}
+	}
+
+	return Error::NONE;
+}
+
+U32 DescriptorSetFactory::BindlessDescriptorSet::bindTexture(const VkImageView view, const VkImageLayout layout)
+{
+	ANKI_ASSERT(layout == VK_IMAGE_LAYOUT_GENERAL || layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+	ANKI_ASSERT(view);
+	LockGuard<Mutex> lock(m_mtx);
+	ANKI_ASSERT(m_freeTexIndexCount > 0 && "Out of indices");
+
+	// Get the index
+	--m_freeTexIndexCount;
+	const U16 idx = m_freeTexIndices[m_freeTexIndexCount];
+	ANKI_ASSERT(idx < m_freeTexIndices.getSize());
+
+	// Update the set
+	VkDescriptorImageInfo imageInf = {};
+	imageInf.imageView = view;
+	imageInf.imageLayout = layout;
+
+	VkWriteDescriptorSet write = {};
+	write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	write.pNext = nullptr;
+	write.dstSet = m_dset;
+	write.dstBinding = 0;
+	write.descriptorCount = 1;
+	write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+	write.dstArrayElement = idx;
+	write.pImageInfo = &imageInf;
+
+	vkUpdateDescriptorSets(m_dev, 1, &write, 0, nullptr);
+
+	return idx;
+}
+
+U32 DescriptorSetFactory::BindlessDescriptorSet::bindImage(const VkImageView view)
+{
+	ANKI_ASSERT(view);
+	LockGuard<Mutex> lock(m_mtx);
+	ANKI_ASSERT(m_freeImgIndexCount > 0 && "Out of indices");
+
+	// Get the index
+	--m_freeImgIndexCount;
+	const U32 idx = m_freeImgIndices[m_freeImgIndexCount];
+	ANKI_ASSERT(idx < m_freeImgIndices.getSize());
+
+	// Update the set
+	VkDescriptorImageInfo imageInf = {};
+	imageInf.imageView = view;
+	imageInf.imageLayout = VK_IMAGE_LAYOUT_GENERAL; // Storage images are always in general.
+
+	VkWriteDescriptorSet write = {};
+	write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	write.pNext = nullptr;
+	write.dstSet = m_dset;
+	write.dstBinding = 1;
+	write.descriptorCount = 1;
+	write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+	write.dstArrayElement = idx;
+	write.pImageInfo = &imageInf;
+
+	vkUpdateDescriptorSets(m_dev, 1, &write, 0, nullptr);
+
+	return idx;
+}
+
+void DescriptorSetFactory::BindlessDescriptorSet::unbindCommon(
+	U32 idx, DynamicArray<U16>& freeIndices, U16& freeIndexCount)
+{
+	ANKI_ASSERT(idx < freeIndices.getSize());
+
+	LockGuard<Mutex> lock(m_mtx);
+	ANKI_ASSERT(freeIndexCount < freeIndices.getSize());
+
+	freeIndices[freeIndexCount] = U16(idx);
+	++freeIndexCount;
+
+	// Sort the free indices to minimize fragmentation
+	std::sort(&freeIndices[0], &freeIndices[0] + freeIndexCount, std::greater<U16>());
+
+	// Make sure there are no duplicates
+	for(U32 i = 1; i < freeIndexCount; ++i)
+	{
+		ANKI_ASSERT(freeIndices[i] != freeIndices[i - 1]);
+	}
+}
+
 /// Descriptor set internal class.
 class DS : public IntrusiveListEnabled<DS>
 {
@@ -104,6 +363,7 @@ public:
 
 	ANKI_USE_RESULT Error init(const DescriptorBinding* bindings, U32 bindingCount, U64 hash);
 
+	/// @note Thread-safe.
 	ANKI_USE_RESULT Error getOrCreateThreadAllocator(ThreadId tid, DSThreadAllocator*& alloc);
 };
 
@@ -527,14 +787,14 @@ Error DSLayoutCacheEntry::getOrCreateThreadAllocator(ThreadId tid, DSThreadAlloc
 void DescriptorSetState::flush(U64& hash,
 	Array<PtrSize, MAX_BINDINGS_PER_DESCRIPTOR_SET>& dynamicOffsets,
 	U32& dynamicOffsetCount,
-	DescriptorSet& customDSet)
+	Bool& bindlessDSet)
 {
 	// Set some values
 	hash = 0;
 	dynamicOffsetCount = 0;
-	customDSet = {};
+	bindlessDSet = false;
 
-	if(m_customDSet.m_handle == VK_NULL_HANDLE)
+	if(!m_bindlessDSetBound)
 	{
 		// Get cache entry
 		ANKI_ASSERT(m_layout.m_entry);
@@ -636,14 +896,14 @@ void DescriptorSetState::flush(U64& hash,
 	{
 		// Custom set
 
-		if(!m_customDSetDirty && !m_layoutDirty)
+		if(!m_bindlessDSetDirty && !m_layoutDirty)
 		{
 			return;
 		}
 
-		customDSet = m_customDSet;
+		bindlessDSet = true;
 		hash = 1;
-		m_customDSetDirty = false;
+		m_bindlessDSetDirty = false;
 		m_layoutDirty = false;
 	}
 }
@@ -652,10 +912,16 @@ DescriptorSetFactory::~DescriptorSetFactory()
 {
 }
 
-void DescriptorSetFactory::init(const GrAllocator<U8>& alloc, VkDevice dev)
+Error DescriptorSetFactory::init(const GrAllocator<U8>& alloc, VkDevice dev, const BindlessLimits& bindlessLimits)
 {
 	m_alloc = alloc;
 	m_dev = dev;
+
+	m_bindless = m_alloc.newInstance<BindlessDescriptorSet>();
+	ANKI_CHECK(m_bindless->init(alloc, dev, bindlessLimits));
+	m_bindlessLimits = bindlessLimits;
+
+	return Error::NONE;
 }
 
 void DescriptorSetFactory::destroy()
@@ -666,13 +932,18 @@ void DescriptorSetFactory::destroy()
 	}
 
 	m_caches.destroy(m_alloc);
+
+	if(m_bindless)
+	{
+		m_alloc.deleteInstance(m_bindless);
+	}
 }
 
 Error DescriptorSetFactory::newDescriptorSetLayout(const DescriptorSetLayoutInitInfo& init, DescriptorSetLayout& layout)
 {
 	// Compute the hash for the layout
 	Array<DescriptorBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET> bindings;
-	U32 bindingCount = init.m_bindings.getSize();
+	const U32 bindingCount = init.m_bindings.getSize();
 	U64 hash;
 
 	if(init.m_bindings.getSize() > 0)
@@ -690,33 +961,66 @@ Error DescriptorSetFactory::newDescriptorSetLayout(const DescriptorSetLayoutInit
 		hash = 1;
 	}
 
-	// Find or create the cache entry
-	LockGuard<SpinLock> lock(m_cachesMtx);
-
-	DSLayoutCacheEntry* cache = nullptr;
-	U count = 0;
-	for(DSLayoutCacheEntry* it : m_caches)
+	// Identify if the DS is the bindless one. It is if there is at least one binding that matches the criteria
+	Bool isBindless = false;
+	if(bindingCount > 0)
 	{
-		if(it->m_hash == hash)
+		isBindless = true;
+		for(U32 i = 0; i < bindingCount; ++i)
 		{
-			cache = it;
-			break;
+			const DescriptorBinding& binding = bindings[i];
+			if(binding.m_binding == 0 && binding.m_type == DescriptorType::TEXTURE
+				&& binding.m_arraySizeMinusOne == m_bindlessLimits.m_bindlessTextureCount - 1)
+			{
+				// All good
+			}
+			else if(binding.m_binding == 1 && binding.m_type == DescriptorType::IMAGE
+					&& binding.m_arraySizeMinusOne == m_bindlessLimits.m_bindlessImageCount - 1)
+			{
+				// All good
+			}
+			else
+			{
+				isBindless = false;
+			}
 		}
-		++count;
 	}
 
-	if(cache == nullptr)
+	// Find or create the cache entry
+	if(isBindless)
 	{
-		cache = m_alloc.newInstance<DSLayoutCacheEntry>(this);
-		ANKI_CHECK(cache->init(&bindings[0], bindingCount, hash));
-
-		m_caches.resize(m_alloc, m_caches.getSize() + 1);
-		m_caches[m_caches.getSize() - 1] = cache;
+		layout.m_handle = m_bindless->getDescriptorSetLayout();
+		layout.m_entry = nullptr;
 	}
+	else
+	{
+		LockGuard<SpinLock> lock(m_cachesMtx);
 
-	// Set the layout
-	layout.m_handle = cache->m_layoutHandle;
-	layout.m_entry = cache;
+		DSLayoutCacheEntry* cache = nullptr;
+		U count = 0;
+		for(DSLayoutCacheEntry* it : m_caches)
+		{
+			if(it->m_hash == hash)
+			{
+				cache = it;
+				break;
+			}
+			++count;
+		}
+
+		if(cache == nullptr)
+		{
+			cache = m_alloc.newInstance<DSLayoutCacheEntry>(this);
+			ANKI_CHECK(cache->init(&bindings[0], bindingCount, hash));
+
+			m_caches.resize(m_alloc, m_caches.getSize() + 1);
+			m_caches[m_caches.getSize() - 1] = cache;
+		}
+
+		// Set the layout
+		layout.m_handle = cache->m_layoutHandle;
+		layout.m_entry = cache;
+	}
 
 	return Error::NONE;
 }
@@ -732,8 +1036,8 @@ Error DescriptorSetFactory::newDescriptorSet(ThreadId tid,
 	ANKI_TRACE_SCOPED_EVENT(VK_DESCRIPTOR_SET_GET_OR_CREATE);
 
 	U64 hash;
-	DescriptorSet customDSet;
-	state.flush(hash, dynamicOffsets, dynamicOffsetCount, customDSet);
+	Bool bindlessDSet;
+	state.flush(hash, dynamicOffsets, dynamicOffsetCount, bindlessDSet);
 
 	if(hash == 0)
 	{
@@ -744,7 +1048,7 @@ Error DescriptorSetFactory::newDescriptorSet(ThreadId tid,
 	{
 		dirty = true;
 
-		if(customDSet.m_handle == VK_NULL_HANDLE)
+		if(!bindlessDSet)
 		{
 			DescriptorSetLayout layout = state.m_layout;
 			DSLayoutCacheEntry& entry = *layout.m_entry;
@@ -761,246 +1065,35 @@ Error DescriptorSetFactory::newDescriptorSet(ThreadId tid,
 		}
 		else
 		{
-			set = customDSet;
+			set = m_bindless->getDescriptorSet();
 		}
 	}
 
 	return Error::NONE;
 }
 
-BindlessDescriptorSet::~BindlessDescriptorSet()
+U32 DescriptorSetFactory::bindBindlessTexture(const VkImageView view, const VkImageLayout layout)
 {
-	ANKI_ASSERT(m_dset == VK_NULL_HANDLE);
-	ANKI_ASSERT(m_layout == VK_NULL_HANDLE);
-	ANKI_ASSERT(m_pool == VK_NULL_HANDLE);
+	ANKI_ASSERT(m_bindless);
+	return m_bindless->bindTexture(view, layout);
 }
 
-Error BindlessDescriptorSet::init(const GrAllocator<U8>& alloc, VkDevice dev)
+U32 DescriptorSetFactory::bindBindlessImage(const VkImageView view)
 {
-	ANKI_ASSERT(dev);
-	m_alloc = alloc;
-	m_dev = dev;
-
-	// Create the layout
-	{
-		Array<VkDescriptorSetLayoutBinding, 2> bindings = {};
-		bindings[0].binding = 0;
-		bindings[0].stageFlags = VK_SHADER_STAGE_ALL;
-		bindings[0].descriptorCount = MAX_BINDLESS_TEXTURES;
-		bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-		bindings[1].binding = 1;
-		bindings[1].stageFlags = VK_SHADER_STAGE_ALL;
-		bindings[1].descriptorCount = MAX_BINDLESS_IMAGES;
-		bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
-
-		Array<VkDescriptorBindingFlagsEXT, 2> bindingFlags = {};
-		bindingFlags[0] = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT
-						  | VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT
-						  | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT;
-		bindingFlags[1] = bindingFlags[0];
-
-		VkDescriptorSetLayoutBindingFlagsCreateInfoEXT extraInfos = {};
-		extraInfos.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
-		extraInfos.bindingCount = bindingFlags.getSize();
-		extraInfos.pBindingFlags = &bindingFlags[0];
-
-		VkDescriptorSetLayoutCreateInfo ci = {};
-		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
-		ci.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT;
-		ci.bindingCount = bindings.getSize();
-		ci.pBindings = &bindings[0];
-		ci.pNext = &extraInfos;
-
-		ANKI_VK_CHECK(vkCreateDescriptorSetLayout(m_dev, &ci, nullptr, &m_layout));
-	}
-
-	// Create the pool
-	{
-		Array<VkDescriptorPoolSize, 2> sizes = {};
-		sizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-		sizes[0].descriptorCount = MAX_BINDLESS_TEXTURES;
-		sizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
-		sizes[1].descriptorCount = MAX_BINDLESS_IMAGES;
-
-		VkDescriptorPoolCreateInfo ci = {};
-		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
-		ci.maxSets = 1;
-		ci.poolSizeCount = sizes.getSize();
-		ci.pPoolSizes = &sizes[0];
-		ci.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT;
-
-		ANKI_VK_CHECK(vkCreateDescriptorPool(m_dev, &ci, nullptr, &m_pool));
-	}
-
-	// Create the descriptor set
-	{
-		VkDescriptorSetAllocateInfo ci = {};
-		ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
-		ci.descriptorPool = m_pool;
-		ci.descriptorSetCount = 1;
-		ci.pSetLayouts = &m_layout;
-
-		ANKI_VK_CHECK(vkAllocateDescriptorSets(m_dev, &ci, &m_dset));
-	}
-
-	// Init the free arrays
-	{
-		m_freeTexIndices.create(m_alloc, MAX_BINDLESS_TEXTURES);
-		m_freeTexIndexCount = U16(m_freeTexIndices.getSize());
-
-		for(U32 i = 0; i < m_freeTexIndices.getSize(); ++i)
-		{
-			m_freeTexIndices[i] = U16(m_freeTexIndices.getSize() - i - 1);
-		}
-
-		m_freeImgIndices.create(m_alloc, MAX_BINDLESS_IMAGES);
-		m_freeImgIndexCount = U16(m_freeImgIndices.getSize());
-
-		for(U32 i = 0; i < m_freeImgIndices.getSize(); ++i)
-		{
-			m_freeImgIndices[i] = U16(m_freeImgIndices.getSize() - i - 1);
-		}
-	}
-
-	return Error::NONE;
+	ANKI_ASSERT(m_bindless);
+	return m_bindless->bindImage(view);
 }
 
-void BindlessDescriptorSet::destroy()
+void DescriptorSetFactory::unbindBindlessTexture(U32 idx)
 {
-	if(m_pool)
-	{
-		vkDestroyDescriptorPool(m_dev, m_pool, nullptr);
-		m_pool = VK_NULL_HANDLE;
-		m_dset = VK_NULL_HANDLE;
-	}
-
-	if(m_layout)
-	{
-		vkDestroyDescriptorSetLayout(m_dev, m_layout, nullptr);
-		m_layout = VK_NULL_HANDLE;
-	}
-
-	m_freeImgIndices.destroy(m_alloc);
-	m_freeTexIndices.destroy(m_alloc);
+	ANKI_ASSERT(m_bindless);
+	m_bindless->unbindTexture(idx);
 }
 
-U32 BindlessDescriptorSet::bindTexture(const VkImageView view, const VkImageLayout layout)
+void DescriptorSetFactory::unbindBindlessImage(U32 idx)
 {
-	ANKI_ASSERT(layout == VK_IMAGE_LAYOUT_GENERAL || layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-	ANKI_ASSERT(view);
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_freeTexIndexCount > 0 && "Out of indices");
-
-	// Get the index
-	--m_freeTexIndexCount;
-	const U16 idx = m_freeTexIndices[m_freeTexIndexCount];
-	ANKI_ASSERT(idx < MAX_BINDLESS_TEXTURES);
-
-	// Update the set
-	VkDescriptorImageInfo imageInf = {};
-	imageInf.imageView = view;
-	imageInf.imageLayout = layout;
-
-	VkWriteDescriptorSet write = {};
-	write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-	write.pNext = nullptr;
-	write.dstSet = m_dset;
-	write.dstBinding = 0;
-	write.descriptorCount = 1;
-	write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-	write.dstArrayElement = idx;
-	write.pImageInfo = &imageInf;
-
-	vkUpdateDescriptorSets(m_dev, 1, &write, 0, nullptr);
-
-	return idx;
-}
-
-U32 BindlessDescriptorSet::bindImage(const VkImageView view)
-{
-	ANKI_ASSERT(view);
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_freeImgIndexCount > 0 && "Out of indices");
-
-	// Get the index
-	--m_freeImgIndexCount;
-	const U32 idx = m_freeImgIndices[m_freeImgIndexCount];
-	ANKI_ASSERT(idx < MAX_BINDLESS_IMAGES);
-
-	// Update the set
-	VkDescriptorImageInfo imageInf = {};
-	imageInf.imageView = view;
-	imageInf.imageLayout = VK_IMAGE_LAYOUT_GENERAL; // Storage images are always in general.
-
-	VkWriteDescriptorSet write = {};
-	write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-	write.pNext = nullptr;
-	write.dstSet = m_dset;
-	write.dstBinding = 1;
-	write.descriptorCount = 1;
-	write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
-	write.dstArrayElement = idx;
-	write.pImageInfo = &imageInf;
-
-	vkUpdateDescriptorSets(m_dev, 1, &write, 0, nullptr);
-
-	return idx;
-}
-
-void BindlessDescriptorSet::unbindCommon(U32 idx, DynamicArray<U16>& freeIndices, U16& freeIndexCount)
-{
-	ANKI_ASSERT(idx < freeIndices.getSize());
-
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(freeIndexCount < freeIndices.getSize());
-
-	freeIndices[freeIndexCount] = U16(idx);
-	++freeIndexCount;
-
-	// Sort the free indices to minimize fragmentation
-	std::sort(&freeIndices[0], &freeIndices[0] + freeIndexCount, std::greater<U16>());
-
-	// Make sure there are no duplicates
-	for(U32 i = 1; i < freeIndexCount; ++i)
-	{
-		ANKI_ASSERT(freeIndices[i] != freeIndices[i - 1]);
-	}
-}
-
-Error BindlessDescriptorSet::initDeviceFeatures(
-	VkPhysicalDevice pdev, VkPhysicalDeviceDescriptorIndexingFeaturesEXT& indexingFeatures)
-{
-	indexingFeatures = {};
-	indexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
-
-	VkPhysicalDeviceFeatures2 features = {};
-	features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-	features.pNext = &indexingFeatures;
-
-	vkGetPhysicalDeviceFeatures2(pdev, &features);
-
-	if(!indexingFeatures.shaderSampledImageArrayNonUniformIndexing
-		|| !indexingFeatures.shaderStorageImageArrayNonUniformIndexing)
-	{
-		ANKI_VK_LOGE("Non uniform indexing is not supported by the device");
-		return Error::FUNCTION_FAILED;
-	}
-
-	if(!indexingFeatures.descriptorBindingSampledImageUpdateAfterBind
-		|| !indexingFeatures.descriptorBindingStorageImageUpdateAfterBind)
-	{
-		ANKI_VK_LOGE("Update descriptors after bind is not supported by the device");
-		return Error::FUNCTION_FAILED;
-	}
-
-	if(!indexingFeatures.descriptorBindingUpdateUnusedWhilePending)
-	{
-		ANKI_VK_LOGE("Update descriptors while cmd buffer is pending is not supported by the device");
-		return Error::FUNCTION_FAILED;
-	}
-
-	return Error::NONE;
+	ANKI_ASSERT(m_bindless);
+	m_bindless->unbindImage(idx);
 }
 
 } // end namespace anki

+ 29 - 68
src/anki/gr/vulkan/DescriptorSet.h

@@ -31,8 +31,7 @@ public:
 	U8 m_binding = MAX_U8;
 	U8 m_arraySizeMinusOne = 0;
 };
-
-static_assert(sizeof(DescriptorBinding) == 4, "See file");
+static_assert(sizeof(DescriptorBinding) == 4, "Should be packed because it will be hashed");
 
 class DescriptorSetLayoutInitInfo
 {
@@ -198,7 +197,7 @@ public:
 		b.m_texAndSampler.m_layout = layout;
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
 	void bindTexture(U32 binding, U32 arrayIdx, const TextureView* texView, VkImageLayout layout)
@@ -215,7 +214,7 @@ public:
 		b.m_tex.m_layout = layout;
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
 	void bindSampler(U32 binding, U32 arrayIdx, const Sampler* sampler)
@@ -227,7 +226,7 @@ public:
 		b.m_sampler.m_samplerHandle = static_cast<const SamplerImpl*>(sampler)->m_sampler->getHandle();
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
 	void bindUniformBuffer(U32 binding, U32 arrayIdx, const Buffer* buff, PtrSize offset, PtrSize range)
@@ -242,7 +241,7 @@ public:
 		b.m_buff.m_range = range;
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
 	void bindStorageBuffer(U32 binding, U32 arrayIdx, const Buffer* buff, PtrSize offset, PtrSize range)
@@ -257,7 +256,7 @@ public:
 		b.m_buff.m_range = range;
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
 	void bindImage(U32 binding, U32 arrayIdx, const TextureView* texView)
@@ -274,14 +273,14 @@ public:
 		b.m_image.m_imgViewHandle = impl->getHandle();
 
 		m_dirtyBindings.set(binding);
-		unbindCustomDSet();
+		unbindBindlessDSet();
 	}
 
-	void bindCustumDescriptorSet(const DescriptorSet& dset)
+	/// Forget all the rest of the bindings and bind the whole bindless descriptor set.
+	void bindBindlessDescriptorSet()
 	{
-		ANKI_ASSERT(dset.m_handle);
-		m_customDSet = dset;
-		m_customDSetDirty = true;
+		m_bindlessDSetBound = true;
+		m_bindlessDSetDirty = true;
 	}
 
 private:
@@ -289,25 +288,25 @@ private:
 	DescriptorSetLayout m_layout;
 
 	Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET> m_bindings;
-	DescriptorSet m_customDSet;
 
 	U64 m_lastHash = 0;
 
 	BitSet<MAX_BINDINGS_PER_DESCRIPTOR_SET, U32> m_dirtyBindings = {true};
 	BitSet<MAX_BINDINGS_PER_DESCRIPTOR_SET, U32> m_bindingSet = {false};
 	Bool m_layoutDirty = true;
-	Bool m_customDSetDirty = true;
+	Bool m_bindlessDSetDirty = true;
+	Bool m_bindlessDSetBound = false;
 
 	/// Only DescriptorSetFactory should call this.
 	/// @param hash If hash is zero then the DS doesn't need rebind.
 	void flush(U64& hash,
 		Array<PtrSize, MAX_BINDINGS_PER_DESCRIPTOR_SET>& dynamicOffsets,
 		U32& dynamicOffsetCount,
-		DescriptorSet& customDSet);
+		Bool& bindlessDSet);
 
-	void unbindCustomDSet()
+	void unbindBindlessDSet()
 	{
-		m_customDSet = {};
+		m_bindlessDSetBound = false;
 	}
 
 	AnyBinding& getBindingToPopulate(U32 bindingIdx, U32 arrayIdx)
@@ -371,14 +370,14 @@ public:
 	DescriptorSetFactory() = default;
 	~DescriptorSetFactory();
 
-	void init(const GrAllocator<U8>& alloc, VkDevice dev);
+	ANKI_USE_RESULT Error init(const GrAllocator<U8>& alloc, VkDevice dev, const BindlessLimits& bindlessLimits);
 
 	void destroy();
 
 	/// @note It's thread-safe.
 	ANKI_USE_RESULT Error newDescriptorSetLayout(const DescriptorSetLayoutInitInfo& init, DescriptorSetLayout& layout);
 
-	/// @note Obviously not thread-safe.
+	/// @note It's thread-safe.
 	ANKI_USE_RESULT Error newDescriptorSet(ThreadId tid,
 		StackAllocator<U8>& tmpAlloc,
 		DescriptorSetState& state,
@@ -392,70 +391,32 @@ public:
 		++m_frameCount;
 	}
 
-private:
-	GrAllocator<U8> m_alloc;
-	VkDevice m_dev = VK_NULL_HANDLE;
-	U64 m_frameCount = 0;
-
-	DynamicArray<DSLayoutCacheEntry*> m_caches;
-	SpinLock m_cachesMtx; ///< Not a mutex because after a while there will be no reason to lock
-};
-
-/// Wraps a global descriptor set that is used to store bindless textures.
-class BindlessDescriptorSet
-{
-public:
-	~BindlessDescriptorSet();
-
-	Error init(const GrAllocator<U8>& alloc, VkDevice dev);
-
-	void destroy();
-
-	static Error initDeviceFeatures(VkPhysicalDevice pdev, VkPhysicalDeviceDescriptorIndexingFeaturesEXT& features);
-
 	/// Bind a sampled image.
 	/// @note It's thread-safe.
-	U32 bindTexture(const VkImageView view, const VkImageLayout layout);
+	U32 bindBindlessTexture(const VkImageView view, const VkImageLayout layout);
 
 	/// Bind a storage image.
 	/// @note It's thread-safe.
-	U32 bindImage(const VkImageView view);
+	U32 bindBindlessImage(const VkImageView view);
 
 	/// @note It's thread-safe.
-	void unbindTexture(U32 idx)
-	{
-		unbindCommon(idx, m_freeTexIndices, m_freeTexIndexCount);
-	}
+	void unbindBindlessTexture(U32 idx);
 
 	/// @note It's thread-safe.
-	void unbindImage(U32 idx)
-	{
-		unbindCommon(idx, m_freeImgIndices, m_freeImgIndexCount);
-	}
-
-	DescriptorSet getDescriptorSet() const
-	{
-		ANKI_ASSERT(m_dset);
-		DescriptorSet out;
-		out.m_handle = m_dset;
-		return out;
-	}
+	void unbindBindlessImage(U32 idx);
 
 private:
+	class BindlessDescriptorSet;
+
 	GrAllocator<U8> m_alloc;
 	VkDevice m_dev = VK_NULL_HANDLE;
-	VkDescriptorSetLayout m_layout = VK_NULL_HANDLE;
-	VkDescriptorPool m_pool = VK_NULL_HANDLE;
-	VkDescriptorSet m_dset = VK_NULL_HANDLE;
-	Mutex m_mtx;
-
-	DynamicArray<U16> m_freeTexIndices;
-	DynamicArray<U16> m_freeImgIndices;
+	U64 m_frameCount = 0;
 
-	U16 m_freeTexIndexCount ANKI_DEBUG_CODE(= MAX_U16);
-	U16 m_freeImgIndexCount ANKI_DEBUG_CODE(= MAX_U16);
+	DynamicArray<DSLayoutCacheEntry*> m_caches;
+	SpinLock m_cachesMtx; ///< Not a mutex because after a while there will be no reason to lock
 
-	void unbindCommon(U32 idx, DynamicArray<U16>& freeIndices, U16& freeIndexCount);
+	BindlessDescriptorSet* m_bindless = nullptr;
+	BindlessLimits m_bindlessLimits;
 };
 /// @}
 

+ 40 - 7
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -48,8 +48,6 @@ GrManagerImpl::~GrManagerImpl()
 	m_pplineLayoutFactory.destroy();
 	m_descrFactory.destroy();
 
-	m_bindlessDset.destroy();
-
 	m_pplineCache.destroy(m_device, m_physicalDevice, getAllocator());
 
 	m_fences.destroy();
@@ -204,12 +202,11 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 		}
 	}
 
-	m_descrFactory.init(getAllocator(), m_device);
+	m_bindlessLimits.m_bindlessTextureCount = init.m_config->getNumberU32("gr_maxBindlessTextures");
+	m_bindlessLimits.m_bindlessImageCount = init.m_config->getNumberU32("gr_maxBindlessImages");
+	ANKI_CHECK(m_descrFactory.init(getAllocator(), m_device, m_bindlessLimits));
 	m_pplineLayoutFactory.init(getAllocator(), m_device);
 
-	// Bindless descriptors
-	ANKI_CHECK(m_bindlessDset.init(m_alloc, m_device));
-
 	return Error::NONE;
 }
 
@@ -568,7 +565,43 @@ Error GrManagerImpl::initDevice(const GrManagerInitInfo& init)
 		}
 		else
 		{
-			ANKI_CHECK(BindlessDescriptorSet::initDeviceFeatures(m_physicalDevice, m_descriptorIndexingFeatures));
+			// Enable the bindless features required
+
+			m_descriptorIndexingFeatures = {};
+			m_descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
+
+			VkPhysicalDeviceFeatures2 features = {};
+			features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+			features.pNext = &m_descriptorIndexingFeatures;
+
+			vkGetPhysicalDeviceFeatures2(m_physicalDevice, &features);
+
+			if(!m_descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing
+				|| !m_descriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing)
+			{
+				ANKI_VK_LOGE("Non uniform indexing is not supported by the device");
+				return Error::FUNCTION_FAILED;
+			}
+
+			if(!m_descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind
+				|| !m_descriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind)
+			{
+				ANKI_VK_LOGE("Update descriptors after bind is not supported by the device");
+				return Error::FUNCTION_FAILED;
+			}
+
+			if(!m_descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending)
+			{
+				ANKI_VK_LOGE("Update descriptors while cmd buffer is pending is not supported by the device");
+				return Error::FUNCTION_FAILED;
+			}
+
+			if(!m_descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount)
+			{
+				ANKI_VK_LOGE("Variable descriptor count for bindless is not supported");
+				return Error::FUNCTION_FAILED;
+			}
+
 			ci.pNext = &m_descriptorIndexingFeatures;
 		}
 

+ 0 - 12
src/anki/gr/vulkan/GrManagerImpl.h

@@ -222,16 +222,6 @@ public:
 
 	void printPipelineShaderInfo(VkPipeline ppline, CString name, ShaderTypeBit stages, U64 hash = 0) const;
 
-	const BindlessDescriptorSet& getBindlessDescriptorSet() const
-	{
-		return m_bindlessDset;
-	}
-
-	BindlessDescriptorSet& getBindlessDescriptorSet()
-	{
-		return m_bindlessDset;
-	}
-
 private:
 	U64 m_frame = 0;
 
@@ -313,8 +303,6 @@ private:
 
 	PipelineCache m_pplineCache;
 
-	BindlessDescriptorSet m_bindlessDset;
-
 	Bool m_r8g8b8ImagesSupported = false;
 	Bool m_s8ImagesSupported = false;
 	Bool m_d24S8ImagesSupported = false;

+ 18 - 0
src/anki/gr/vulkan/TextureImpl.cpp

@@ -29,6 +29,24 @@ TextureImpl::~TextureImpl()
 		{
 			vkDestroyImageView(getDevice(), it.m_handle, nullptr);
 		}
+
+		if(it.m_bindlessIndices[0] != MAX_U32)
+		{
+			getGrManagerImpl().getDescriptorSetFactory().unbindBindlessTexture(it.m_bindlessIndices[0]);
+			it.m_bindlessIndices[0] = MAX_U32;
+		}
+
+		if(it.m_bindlessIndices[1] != MAX_U32)
+		{
+			getGrManagerImpl().getDescriptorSetFactory().unbindBindlessTexture(it.m_bindlessIndices[1]);
+			it.m_bindlessIndices[1] = MAX_U32;
+		}
+
+		if(it.m_bindlessIndices[2] != MAX_U32)
+		{
+			getGrManagerImpl().getDescriptorSetFactory().unbindBindlessImage(it.m_bindlessIndices[2]);
+			it.m_bindlessIndices[2] = MAX_U32;
+		}
 	}
 
 	m_viewsMap.destroy(getAllocator());

+ 7 - 1
src/anki/gr/vulkan/TextureImpl.h

@@ -47,7 +47,13 @@ public:
 	/// Because for example a single surface view of a cube texture will be a 2D view.
 	TextureType m_derivedTextureType = TextureType::COUNT;
 
-	MicroImageView() = default;
+	MicroImageView()
+	{
+		for(U32 idx : m_bindlessIndices)
+		{
+			ANKI_ASSERT(idx == MAX_U32 && "Forgot to unbind the bindless");
+		}
+	}
 
 	MicroImageView(const MicroImageView& b)
 	{

+ 2 - 2
src/anki/gr/vulkan/TextureViewImpl.cpp

@@ -63,11 +63,11 @@ U32 TextureViewImpl::getOrCreateBindlessIndex(VkImageLayout layout, DescriptorTy
 
 		if(resourceType == DescriptorType::TEXTURE)
 		{
-			outIdx = getGrManagerImpl().getBindlessDescriptorSet().bindTexture(m_handle, layout);
+			outIdx = getGrManagerImpl().getDescriptorSetFactory().bindBindlessTexture(m_handle, layout);
 		}
 		else
 		{
-			outIdx = getGrManagerImpl().getBindlessDescriptorSet().bindImage(m_handle);
+			outIdx = getGrManagerImpl().getDescriptorSetFactory().bindBindlessImage(m_handle);
 		}
 
 		m_microImageView->m_bindlessIndices[arrayIdx] = outIdx;

+ 2 - 1
src/anki/shader_compiler/ShaderProgramCompiler.cpp

@@ -220,6 +220,7 @@ Error compileShaderProgram(CString fname,
 	U32 backendMinor,
 	U32 backendMajor,
 	GpuVendor gpuVendor,
+	const BindlessLimits& bindlessLimits,
 	ShaderProgramBinaryWrapper& binaryW)
 {
 	// Initialize the binary
@@ -233,7 +234,7 @@ Error compileShaderProgram(CString fname,
 
 	// Parse source
 	ShaderProgramParser parser(
-		fname, &fsystem, tempAllocator, pushConstantsSize, backendMinor, backendMajor, gpuVendor);
+		fname, &fsystem, tempAllocator, pushConstantsSize, backendMinor, backendMajor, gpuVendor, bindlessLimits);
 	ANKI_CHECK(parser.parse());
 
 	// Mutators

+ 2 - 0
src/anki/shader_compiler/ShaderProgramCompiler.h

@@ -26,6 +26,7 @@ class ShaderProgramBinaryWrapper : public NonCopyable
 		U32 backendMinor,
 		U32 backendMajor,
 		GpuVendor gpuVendor,
+		const BindlessLimits& bindlessLimits,
 		ShaderProgramBinaryWrapper& binary);
 
 public:
@@ -65,6 +66,7 @@ ANKI_USE_RESULT Error compileShaderProgram(CString fname,
 	U32 backendMinor,
 	U32 backendMajor,
 	GpuVendor gpuVendor,
+	const BindlessLimits& bindlessLimits,
 	ShaderProgramBinaryWrapper& binary);
 
 /// Create a human readable representation of the shader binary.

+ 5 - 3
src/anki/shader_compiler/ShaderProgramParser.cpp

@@ -98,7 +98,8 @@ ShaderProgramParser::ShaderProgramParser(CString fname,
 	U32 pushConstantsSize,
 	U32 backendMinor,
 	U32 backendMajor,
-	GpuVendor gpuVendor)
+	GpuVendor gpuVendor,
+	const BindlessLimits& bindlessLimits)
 	: m_alloc(alloc)
 	, m_fname(alloc, fname)
 	, m_fsystem(fsystem)
@@ -106,6 +107,7 @@ ShaderProgramParser::ShaderProgramParser(CString fname,
 	, m_backendMinor(backendMinor)
 	, m_backendMajor(backendMajor)
 	, m_gpuVendor(gpuVendor)
+	, m_bindlessLimits(bindlessLimits)
 {
 }
 
@@ -705,8 +707,8 @@ Error ShaderProgramParser::generateVariant(
 		m_backendMinor,
 		m_backendMajor,
 		GPU_VENDOR_STR[m_gpuVendor].cstr(),
-		MAX_BINDLESS_TEXTURES,
-		MAX_BINDLESS_IMAGES);
+		m_bindlessLimits.m_bindlessTextureCount,
+		m_bindlessLimits.m_bindlessImageCount);
 
 	// Generate souce per stage
 	for(ShaderType shaderType = ShaderType::FIRST; shaderType < ShaderType::COUNT; ++shaderType)

+ 4 - 1
src/anki/shader_compiler/ShaderProgramParser.h

@@ -94,7 +94,8 @@ public:
 		U32 pushConstantsSize,
 		U32 backendMinor,
 		U32 backendMajor,
-		GpuVendor gpuVendor);
+		GpuVendor gpuVendor,
+		const BindlessLimits& bindlessLimits);
 
 	~ShaderProgramParser();
 
@@ -166,6 +167,8 @@ private:
 	const U32 m_backendMajor = 1;
 	const GpuVendor m_gpuVendor = GpuVendor::AMD;
 
+	BindlessLimits m_bindlessLimits;
+
 	ANKI_USE_RESULT Error parseFile(CString fname, U32 depth);
 	ANKI_USE_RESULT Error parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth);
 	ANKI_USE_RESULT Error parseInclude(

+ 1 - 2
tests/gr/Gr.cpp

@@ -2286,8 +2286,7 @@ ANKI_TEST(Gr, Bindless)
 	static const char* PROG_SRC = R"(
 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
 
-layout(set = 0, binding = 0) uniform utexture2D u_bindlessTextures[ANKI_MAX_BINDLESS_TEXTURES];
-layout(set = 0, binding = 1) uniform readonly uimage2D u_bindlessImages[ANKI_MAX_BINDLESS_IMAGES];
+ANKI_BINDLESS_SET(0)
 
 layout(set = 1, binding = 0) writeonly buffer ss_
 {

+ 3 - 1
tests/shader_compiler/ShaderProgramCompiler.cpp

@@ -149,7 +149,9 @@ void main()
 
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
 	ShaderProgramBinaryWrapper binary(alloc);
-	ANKI_TEST_EXPECT_NO_ERR(compileShaderProgram("test.glslp", fsystem, alloc, 128, 1, 1, GpuVendor::AMD, binary));
+	BindlessLimits bindlessLimits;
+	ANKI_TEST_EXPECT_NO_ERR(
+		compileShaderProgram("test.glslp", fsystem, alloc, 128, 1, 1, GpuVendor::AMD, bindlessLimits, binary));
 
 #if 0
 	StringAuto dis(alloc);

+ 2 - 1
tests/shader_compiler/ShaderProgramParser.cpp

@@ -45,7 +45,8 @@ ANKI_TEST(ShaderCompiler, ShaderCompilerParser)
 		}
 	} interface;
 
-	ShaderProgramParser parser("filename0", &interface, alloc, 128, 1, 1, GpuVendor::AMD);
+	BindlessLimits bindlessLimits;
+	ShaderProgramParser parser("filename0", &interface, alloc, 128, 1, 1, GpuVendor::AMD, bindlessLimits);
 	ANKI_TEST_EXPECT_NO_ERR(parser.parse());
 
 	// Test a variant