Browse Source

Bindless is almost complete. Needs more testing

Panagiotis Christopoulos Charitos 6 years ago
parent
commit
f0464f3200

+ 26 - 7
src/anki/gr/CommandBuffer.h

@@ -225,20 +225,24 @@ public:
 	/// @param texView The texture view to bind.
 	/// @param sampler The sampler to override the default sampler of the tex.
 	/// @param usage The state the tex is in.
-	void bindTextureAndSampler(U32 set, U32 binding, TextureViewPtr texView, SamplerPtr sampler, TextureUsageBit usage);
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindTextureAndSampler(
+		U32 set, U32 binding, TextureViewPtr texView, SamplerPtr sampler, TextureUsageBit usage, U32 arrayIdx = 0);
 
 	/// Bind sampler.
 	/// @param set The set to bind to.
 	/// @param binding The binding to bind to.
 	/// @param sampler The sampler to override the default sampler of the tex.
-	void bindSampler(U32 set, U32 binding, SamplerPtr sampler);
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindSampler(U32 set, U32 binding, SamplerPtr sampler, U32 arrayIdx = 0);
 
 	/// Bind a texture.
 	/// @param set The set to bind to.
 	/// @param binding The binding to bind to.
 	/// @param texView The texture view to bind.
 	/// @param usage The state the tex is in.
-	void bindTexture(U32 set, U32 binding, TextureViewPtr texView, TextureUsageBit usage);
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindTexture(U32 set, U32 binding, TextureViewPtr texView, TextureUsageBit usage, U32 arrayIdx = 0);
 
 	/// Bind uniform buffer.
 	/// @param set The set to bind to.
@@ -247,7 +251,8 @@ public:
 	/// @param offset The base of the binding.
 	/// @param range The bytes to bind starting from the offset. If it's MAX_PTR_SIZE then map from offset to the end
 	///              of the buffer.
-	void bindUniformBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range);
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindUniformBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, U32 arrayIdx = 0);
 
 	/// Bind storage buffer.
 	/// @param set The set to bind to.
@@ -256,13 +261,27 @@ public:
 	/// @param offset The base of the binding.
 	/// @param range The bytes to bind starting from the offset. If it's MAX_PTR_SIZE then map from offset to the end
 	///              of the buffer.
-	void bindStorageBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range);
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindStorageBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, U32 arrayIdx = 0);
 
 	/// Bind load/store image.
-	void bindImage(U32 set, U32 binding, TextureViewPtr img);
+	/// @param set The set to bind to.
+	/// @param binding The binding to bind to.
+	/// @param img The view to bind.
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindImage(U32 set, U32 binding, TextureViewPtr img, U32 arrayIdx = 0);
 
 	/// Bind texture buffer.
-	void bindTextureBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, Format fmt);
+	/// @param set The set to bind to.
+	/// @param binding The binding to bind to.
+	/// @param[in,out] buff The buffer to bind.
+	/// @param offset The base of the binding.
+	/// @param range The bytes to bind starting from the offset. If it's MAX_PTR_SIZE then map from offset to the end
+	///              of the buffer.
+	/// @param fmt The format of the buffer.
+	/// @param arrayIdx The array index if the binding is an array.
+	void bindTextureBuffer(
+		U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, Format fmt, U32 arrayIdx = 0);
 
 	/// Bind the bindless descriptor set into a slot.
 	void bindAllBindless(U32 set);

+ 1 - 1
src/anki/gr/Common.h

@@ -359,7 +359,7 @@ private:
 /// Compute max number of mipmaps for a 2D texture.
 inline U computeMaxMipmapCount2d(U w, U h, U minSizeOfLastMip = 1)
 {
-	ANKI_ASSERT(w > minSizeOfLastMip && h > minSizeOfLastMip);
+	ANKI_ASSERT(w >= minSizeOfLastMip && h >= minSizeOfLastMip);
 	U s = (w < h) ? w : h;
 	U count = 0;
 	while(s >= minSizeOfLastMip)

+ 7 - 1
src/anki/gr/ShaderCompiler.cpp

@@ -66,8 +66,12 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #		extension GL_KHR_shader_subgroup_ballot : require
 #		extension GL_KHR_shader_subgroup_shuffle : require
 #		extension GL_KHR_shader_subgroup_arithmetic : require
-#		extension GL_EXT_samplerless_texture_functions : require
 #	endif
+#	extension GL_EXT_samplerless_texture_functions : require
+#	extension GL_EXT_shader_image_load_formatted : require
+
+#	define ANKI_MAX_BINDLESS_TEXTURES %u
+#	define ANKI_MAX_BINDLESS_IMAGES %u
 #endif
 
 #define F32 float
@@ -236,6 +240,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,
 		&source[0]);
 }
 

+ 14 - 13
src/anki/gr/vulkan/CommandBuffer.cpp

@@ -162,43 +162,44 @@ void CommandBuffer::setBlendOperation(U32 attachment, BlendOperation funcRgb, Bl
 }
 
 void CommandBuffer::bindTextureAndSampler(
-	U32 set, U32 binding, TextureViewPtr texView, SamplerPtr sampler, TextureUsageBit usage)
+	U32 set, U32 binding, TextureViewPtr texView, SamplerPtr sampler, TextureUsageBit usage, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindTextureAndSamplerInternal(set, binding, texView, sampler, usage);
+	self.bindTextureAndSamplerInternal(set, binding, texView, sampler, usage, arrayIdx);
 }
 
-void CommandBuffer::bindTexture(U32 set, U32 binding, TextureViewPtr texView, TextureUsageBit usage)
+void CommandBuffer::bindTexture(U32 set, U32 binding, TextureViewPtr texView, TextureUsageBit usage, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindTextureInternal(set, binding, texView, usage);
+	self.bindTextureInternal(set, binding, texView, usage, arrayIdx);
 }
 
-void CommandBuffer::bindSampler(U32 set, U32 binding, SamplerPtr sampler)
+void CommandBuffer::bindSampler(U32 set, U32 binding, SamplerPtr sampler, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindSamplerInternal(set, binding, sampler);
+	self.bindSamplerInternal(set, binding, sampler, arrayIdx);
 }
 
-void CommandBuffer::bindUniformBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range)
+void CommandBuffer::bindUniformBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindUniformBufferInternal(set, binding, buff, offset, range);
+	self.bindUniformBufferInternal(set, binding, buff, offset, range, arrayIdx);
 }
 
-void CommandBuffer::bindStorageBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range)
+void CommandBuffer::bindStorageBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindStorageBufferInternal(set, binding, buff, offset, range);
+	self.bindStorageBufferInternal(set, binding, buff, offset, range, arrayIdx);
 }
 
-void CommandBuffer::bindImage(U32 set, U32 binding, TextureViewPtr img)
+void CommandBuffer::bindImage(U32 set, U32 binding, TextureViewPtr img, U32 arrayIdx)
 {
 	ANKI_VK_SELF(CommandBufferImpl);
-	self.bindImageInternal(set, binding, img);
+	self.bindImageInternal(set, binding, img, arrayIdx);
 }
 
-void CommandBuffer::bindTextureBuffer(U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, Format fmt)
+void CommandBuffer::bindTextureBuffer(
+	U32 set, U32 binding, BufferPtr buff, PtrSize offset, PtrSize range, Format fmt, U32 arrayIdx)
 {
 	ANKI_ASSERT(!"TODO");
 }

+ 5 - 0
src/anki/gr/vulkan/CommandBufferImpl.cpp

@@ -53,6 +53,11 @@ Error CommandBufferImpl::init(const CommandBufferInitInfo& init)
 		m_state.beginRenderPass(m_activeFb);
 	}
 
+	for(DescriptorSetState& state : m_dsetState)
+	{
+		state.init(m_alloc);
+	}
+
 	return Error::NONE;
 }
 

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

@@ -221,7 +221,7 @@ public:
 	}
 
 	void bindTextureAndSamplerInternal(
-		U32 set, U32 binding, TextureViewPtr& texView, SamplerPtr sampler, TextureUsageBit usage)
+		U32 set, U32 binding, TextureViewPtr& texView, SamplerPtr sampler, TextureUsageBit usage, U32 arrayIdx)
 	{
 		commandCommon();
 		const TextureViewImpl& view = static_cast<const TextureViewImpl&>(*texView);
@@ -229,13 +229,13 @@ public:
 		ANKI_ASSERT(tex.isSubresourceGoodForSampling(view.getSubresource()));
 		const VkImageLayout lay = tex.computeLayout(usage, 0);
 
-		m_dsetState[set].bindTextureAndSampler(binding, &view, sampler.get(), lay);
+		m_dsetState[set].bindTextureAndSampler(binding, arrayIdx, &view, sampler.get(), lay);
 
 		m_microCmdb->pushObjectRef(texView);
 		m_microCmdb->pushObjectRef(sampler);
 	}
 
-	void bindTextureInternal(U32 set, U32 binding, TextureViewPtr& texView, TextureUsageBit usage)
+	void bindTextureInternal(U32 set, U32 binding, TextureViewPtr& texView, TextureUsageBit usage, U32 arrayIdx)
 	{
 		commandCommon();
 		const TextureViewImpl& view = static_cast<const TextureViewImpl&>(*texView);
@@ -243,22 +243,22 @@ public:
 		ANKI_ASSERT(tex.isSubresourceGoodForSampling(view.getSubresource()));
 		const VkImageLayout lay = tex.computeLayout(usage, 0);
 
-		m_dsetState[set].bindTexture(binding, &view, lay);
+		m_dsetState[set].bindTexture(binding, arrayIdx, &view, lay);
 
 		m_microCmdb->pushObjectRef(texView);
 	}
 
-	void bindSamplerInternal(U32 set, U32 binding, SamplerPtr& sampler)
+	void bindSamplerInternal(U32 set, U32 binding, SamplerPtr& sampler, U32 arrayIdx)
 	{
 		commandCommon();
-		m_dsetState[set].bindSampler(binding, sampler.get());
+		m_dsetState[set].bindSampler(binding, arrayIdx, sampler.get());
 		m_microCmdb->pushObjectRef(sampler);
 	}
 
-	void bindImageInternal(U32 set, U32 binding, TextureViewPtr& img)
+	void bindImageInternal(U32 set, U32 binding, TextureViewPtr& img, U32 arrayIdx)
 	{
 		commandCommon();
-		m_dsetState[set].bindImage(binding, img.get());
+		m_dsetState[set].bindImage(binding, arrayIdx, img.get());
 		m_microCmdb->pushObjectRef(img);
 	}
 
@@ -350,17 +350,17 @@ public:
 
 	void bindShaderProgram(ShaderProgramPtr& prog);
 
-	void bindUniformBufferInternal(U32 set, U32 binding, BufferPtr& buff, PtrSize offset, PtrSize range)
+	void bindUniformBufferInternal(U32 set, U32 binding, BufferPtr& buff, PtrSize offset, PtrSize range, U32 arrayIdx)
 	{
 		commandCommon();
-		m_dsetState[set].bindUniformBuffer(binding, buff.get(), offset, range);
+		m_dsetState[set].bindUniformBuffer(binding, arrayIdx, buff.get(), offset, range);
 		m_microCmdb->pushObjectRef(buff);
 	}
 
-	void bindStorageBufferInternal(U32 set, U32 binding, BufferPtr& buff, PtrSize offset, PtrSize range)
+	void bindStorageBufferInternal(U32 set, U32 binding, BufferPtr& buff, PtrSize offset, PtrSize range, U32 arrayIdx)
 	{
 		commandCommon();
-		m_dsetState[set].bindStorageBuffer(binding, buff.get(), offset, range);
+		m_dsetState[set].bindStorageBuffer(binding, arrayIdx, buff.get(), offset, range);
 		m_microCmdb->pushObjectRef(buff);
 	}
 

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

@@ -320,7 +320,7 @@ inline void CommandBufferImpl::dispatchCompute(U32 groupCountX, U32 groupCountY,
 			Array<U32, MAX_BINDINGS_PER_DESCRIPTOR_SET> dynamicOffsets;
 			U dynamicOffsetCount;
 			if(getGrManagerImpl().getDescriptorSetFactory().newDescriptorSet(
-				   m_tid, m_dsetState[i], dset, dirty, dynamicOffsets, dynamicOffsetCount))
+				   m_tid, m_alloc, m_dsetState[i], dset, dirty, dynamicOffsets, dynamicOffsetCount))
 			{
 				ANKI_VK_LOGF("Cannot recover");
 			}
@@ -498,7 +498,7 @@ inline void CommandBufferImpl::drawcallCommon()
 			Array<U32, MAX_BINDINGS_PER_DESCRIPTOR_SET> dynamicOffsets;
 			U dynamicOffsetCount;
 			if(getGrManagerImpl().getDescriptorSetFactory().newDescriptorSet(
-				   m_tid, m_dsetState[i], dset, dirty, dynamicOffsets, dynamicOffsetCount))
+				   m_tid, m_alloc, m_dsetState[i], dset, dirty, dynamicOffsets, dynamicOffsetCount))
 			{
 				ANKI_VK_LOGF("Cannot recover");
 			}

+ 168 - 108
src/anki/gr/vulkan/DescriptorSet.cpp

@@ -49,13 +49,15 @@ public:
 	ANKI_USE_RESULT Error init();
 	ANKI_USE_RESULT Error createNewPool();
 
-	ANKI_USE_RESULT Error getOrCreateSet(
-		U64 hash, const Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings, const DS*& out)
+	ANKI_USE_RESULT Error getOrCreateSet(U64 hash,
+		const Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings,
+		StackAllocator<U8>& tmpAlloc,
+		const DS*& out)
 	{
 		out = tryFindSet(hash);
 		if(out == nullptr)
 		{
-			ANKI_CHECK(newSet(hash, bindings, out));
+			ANKI_CHECK(newSet(hash, bindings, tmpAlloc, out));
 		}
 
 		return Error::NONE;
@@ -63,9 +65,13 @@ public:
 
 private:
 	ANKI_USE_RESULT const DS* tryFindSet(U64 hash);
-	ANKI_USE_RESULT Error newSet(
-		U64 hash, const Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings, const DS*& out);
-	void writeSet(const Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings, const DS& set);
+	ANKI_USE_RESULT Error newSet(U64 hash,
+		const Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings,
+		StackAllocator<U8>& tmpAlloc,
+		const DS*& out);
+	void writeSet(const Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings,
+		const DS& set,
+		StackAllocator<U8>& tmpAlloc);
 };
 
 /// Cache entry. It's built around a specific descriptor set layout.
@@ -77,6 +83,7 @@ public:
 	U64 m_hash = 0; ///< Layout hash.
 	VkDescriptorSetLayout m_layoutHandle = {};
 	BitSet<MAX_BINDINGS_PER_DESCRIPTOR_SET, U32> m_activeBindings = {false};
+	Array<U32, MAX_BINDINGS_PER_DESCRIPTOR_SET> m_bindingArraySize = {};
 	Array<DescriptorType, MAX_BINDINGS_PER_DESCRIPTOR_SET> m_bindingType = {};
 	U32 m_minBinding = MAX_U32;
 	U32 m_maxBinding = 0;
@@ -183,8 +190,10 @@ const DS* DSThreadAllocator::tryFindSet(U64 hash)
 	}
 }
 
-Error DSThreadAllocator::newSet(
-	U64 hash, const Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings, const DS*& out_)
+Error DSThreadAllocator::newSet(U64 hash,
+	const Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings,
+	StackAllocator<U8>& tmpAlloc,
+	const DS*& out_)
 {
 	DS* out = nullptr;
 
@@ -249,21 +258,83 @@ Error DSThreadAllocator::newSet(
 	out->m_hash = hash;
 
 	// Finally, write it
-	writeSet(bindings, *out);
+	writeSet(bindings, *out, tmpAlloc);
 
 	out_ = out;
 	return Error::NONE;
 }
 
-void DSThreadAllocator::writeSet(const Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings, const DS& set)
+void DSThreadAllocator::writeSet(const Array<AnyBindingExtended, MAX_BINDINGS_PER_DESCRIPTOR_SET>& bindings,
+	const DS& set,
+	StackAllocator<U8>& tmpAlloc)
 {
-	Array<VkWriteDescriptorSet, MAX_BINDINGS_PER_DESCRIPTOR_SET> writes;
-	U writeCount = 0;
+	DynamicArrayAuto<VkWriteDescriptorSet> writeInfos(tmpAlloc);
+	DynamicArrayAuto<VkDescriptorImageInfo> texInfos(tmpAlloc);
+	DynamicArrayAuto<VkDescriptorBufferInfo> buffInfos(tmpAlloc);
 
-	Array<VkDescriptorImageInfo, MAX_BINDINGS_PER_DESCRIPTOR_SET> tex;
-	U texCount = 0;
-	Array<VkDescriptorBufferInfo, MAX_BINDINGS_PER_DESCRIPTOR_SET> buff;
-	U buffCount = 0;
+	// First pass: Populate the VkDescriptorImageInfo and VkDescriptorBufferInfo
+	for(U bindingIdx = m_layoutEntry->m_minBinding; bindingIdx <= m_layoutEntry->m_maxBinding; ++bindingIdx)
+	{
+		if(m_layoutEntry->m_activeBindings.get(bindingIdx))
+		{
+			for(U arrIdx = 0; arrIdx < bindings[bindingIdx].m_arraySize; ++arrIdx)
+			{
+				const AnyBinding& b = (bindings[bindingIdx].m_arraySize == 1) ? bindings[bindingIdx].m_single
+																			  : bindings[bindingIdx].m_array[arrIdx];
+
+				switch(b.m_type)
+				{
+				case DescriptorType::COMBINED_TEXTURE_SAMPLER:
+				{
+					VkDescriptorImageInfo& info = *texInfos.emplaceBack();
+					info.sampler = b.m_texAndSampler.m_samplerHandle;
+					info.imageView = b.m_texAndSampler.m_imgViewHandle;
+					info.imageLayout = b.m_texAndSampler.m_layout;
+					break;
+				}
+				case DescriptorType::TEXTURE:
+				{
+					VkDescriptorImageInfo& info = *texInfos.emplaceBack();
+					info.sampler = VK_NULL_HANDLE;
+					info.imageView = b.m_tex.m_imgViewHandle;
+					info.imageLayout = b.m_tex.m_layout;
+					break;
+				}
+				case DescriptorType::SAMPLER:
+				{
+					VkDescriptorImageInfo& info = *texInfos.emplaceBack();
+					info.sampler = b.m_sampler.m_samplerHandle;
+					info.imageView = VK_NULL_HANDLE;
+					info.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+					break;
+				}
+				case DescriptorType::UNIFORM_BUFFER:
+				case DescriptorType::STORAGE_BUFFER:
+				{
+					VkDescriptorBufferInfo& info = *buffInfos.emplaceBack();
+					info.buffer = b.m_buff.m_buffHandle;
+					info.offset = 0;
+					info.range = (b.m_buff.m_range == MAX_PTR_SIZE) ? VK_WHOLE_SIZE : b.m_buff.m_range;
+					break;
+				}
+				case DescriptorType::IMAGE:
+				{
+					VkDescriptorImageInfo& info = *texInfos.emplaceBack();
+					info.sampler = VK_NULL_HANDLE;
+					info.imageView = b.m_image.m_imgViewHandle;
+					info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+					break;
+				}
+				default:
+					ANKI_ASSERT(0);
+				}
+			}
+		}
+	}
+
+	// Second pass: Populate the VkWriteDescriptorSet with VkDescriptorImageInfo and VkDescriptorBufferInfo
+	U texCounter = 0;
+	U buffCounter = 0;
 
 	VkWriteDescriptorSet writeTemplate = {};
 	writeTemplate.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
@@ -271,72 +342,42 @@ void DSThreadAllocator::writeSet(const Array<AnyBinding, MAX_BINDINGS_PER_DESCRI
 	writeTemplate.dstSet = set.m_handle;
 	writeTemplate.descriptorCount = 1;
 
-	for(U i = m_layoutEntry->m_minBinding; i <= m_layoutEntry->m_maxBinding; ++i)
+	for(U bindingIdx = m_layoutEntry->m_minBinding; bindingIdx <= m_layoutEntry->m_maxBinding; ++bindingIdx)
 	{
-		if(m_layoutEntry->m_activeBindings.get(i))
+		if(m_layoutEntry->m_activeBindings.get(bindingIdx))
 		{
-			const AnyBinding& b = bindings[i];
-
-			VkWriteDescriptorSet& w = writes[writeCount++];
-			w = writeTemplate;
-			w.dstBinding = i;
-			w.descriptorType = convertDescriptorType(b.m_type);
-
-			switch(b.m_type)
+			for(U arrIdx = 0; arrIdx < bindings[bindingIdx].m_arraySize; ++arrIdx)
 			{
-			case DescriptorType::COMBINED_TEXTURE_SAMPLER:
-				tex[texCount].sampler = b.m_texAndSampler.m_sampler->getHandle();
-				tex[texCount].imageView = b.m_texAndSampler.m_texView->getHandle();
-				tex[texCount].imageLayout = b.m_texAndSampler.m_layout;
-
-				w.pImageInfo = &tex[texCount];
-
-				++texCount;
-				break;
-			case DescriptorType::TEXTURE:
-				tex[texCount].sampler = VK_NULL_HANDLE;
-				tex[texCount].imageView = b.m_tex.m_texView->getHandle();
-				tex[texCount].imageLayout = b.m_tex.m_layout;
+				const AnyBinding& b = (bindings[bindingIdx].m_arraySize == 1) ? bindings[bindingIdx].m_single
+																			  : bindings[bindingIdx].m_array[arrIdx];
 
-				w.pImageInfo = &tex[texCount];
+				VkWriteDescriptorSet& writeInfo = *writeInfos.emplaceBack(writeTemplate);
+				writeInfo.descriptorType = convertDescriptorType(b.m_type);
+				writeInfo.dstArrayElement = arrIdx;
+				writeInfo.dstBinding = bindingIdx;
 
-				++texCount;
-				break;
-			case DescriptorType::SAMPLER:
-				tex[texCount].sampler = b.m_sampler.m_sampler->getHandle();
-				tex[texCount].imageView = VK_NULL_HANDLE;
-				tex[texCount].imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-
-				w.pImageInfo = &tex[texCount];
-
-				++texCount;
-				break;
-			case DescriptorType::UNIFORM_BUFFER:
-			case DescriptorType::STORAGE_BUFFER:
-				buff[buffCount].buffer = b.m_buff.m_buff->getHandle();
-				buff[buffCount].offset = 0;
-				buff[buffCount].range = (b.m_buff.m_range == MAX_PTR_SIZE) ? VK_WHOLE_SIZE : b.m_buff.m_range;
-
-				w.pBufferInfo = &buff[buffCount];
-
-				++buffCount;
-				break;
-			case DescriptorType::IMAGE:
-				tex[texCount].sampler = VK_NULL_HANDLE;
-				tex[texCount].imageView = b.m_image.m_texView->getHandle();
-				tex[texCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
-
-				w.pImageInfo = &tex[texCount];
-
-				++texCount;
-				break;
-			default:
-				ANKI_ASSERT(0);
+				switch(b.m_type)
+				{
+				case DescriptorType::COMBINED_TEXTURE_SAMPLER:
+				case DescriptorType::TEXTURE:
+				case DescriptorType::SAMPLER:
+				case DescriptorType::IMAGE:
+					writeInfo.pImageInfo = &texInfos[texCounter++];
+					break;
+				case DescriptorType::UNIFORM_BUFFER:
+				case DescriptorType::STORAGE_BUFFER:
+					writeInfo.pBufferInfo = &buffInfos[buffCounter++];
+					break;
+				default:
+					ANKI_ASSERT(0);
+				}
 			}
 		}
 	}
 
-	vkUpdateDescriptorSets(m_layoutEntry->m_factory->m_dev, writeCount, &writes[0], 0, nullptr);
+	// Write
+	ANKI_ASSERT(writeInfos.getSize() > 0);
+	vkUpdateDescriptorSets(m_layoutEntry->m_factory->m_dev, writeInfos.getSize(), &writeInfos[0], 0, nullptr);
 }
 
 DSLayoutCacheEntry::~DSLayoutCacheEntry()
@@ -374,7 +415,7 @@ Error DSLayoutCacheEntry::init(const DescriptorBinding* bindings, U bindingCount
 		const DescriptorBinding& ak = bindings[i];
 
 		vk.binding = ak.m_binding;
-		vk.descriptorCount = 1;
+		vk.descriptorCount = ak.m_arraySizeMinusOne + 1;
 		vk.descriptorType = convertDescriptorType(ak.m_type);
 		vk.pImmutableSamplers = nullptr;
 		vk.stageFlags = convertShaderTypeBit(ak.m_stageMask);
@@ -382,6 +423,7 @@ Error DSLayoutCacheEntry::init(const DescriptorBinding* bindings, U bindingCount
 		ANKI_ASSERT(m_activeBindings.get(ak.m_binding) == false);
 		m_activeBindings.set(ak.m_binding);
 		m_bindingType[ak.m_binding] = ak.m_type;
+		m_bindingArraySize[ak.m_binding] = ak.m_arraySizeMinusOne + 1;
 		m_minBinding = min<U32>(m_minBinding, ak.m_binding);
 		m_maxBinding = max<U32>(m_maxBinding, ak.m_binding);
 	}
@@ -513,43 +555,60 @@ void DescriptorSetState::flush(U64& hash,
 		{
 			if(entry.m_activeBindings.get(i))
 			{
+				ANKI_ASSERT(m_bindingSet.get(i) && "Forgot to bind");
+				ANKI_ASSERT(m_bindings[i].m_arraySize >= entry.m_bindingArraySize[i] && "Bound less");
+
 				const Bool crntBindingDirty = m_dirtyBindings.get(i);
 				m_dirtyBindings.unset(i);
 
-				toHash[toHashCount++] = m_bindings[i].m_uuids[0];
-
-				switch(entry.m_bindingType[i])
+				for(U arrIdx = 0; arrIdx < entry.m_bindingArraySize[i]; ++arrIdx)
 				{
-				case DescriptorType::COMBINED_TEXTURE_SAMPLER:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::COMBINED_TEXTURE_SAMPLER
-								&& "Have bound the wrong type");
-					toHash[toHashCount++] = m_bindings[i].m_uuids[1];
-					toHash[toHashCount++] = U64(m_bindings[i].m_texAndSampler.m_layout);
-					break;
-				case DescriptorType::TEXTURE:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::TEXTURE && "Have bound the wrong type");
-					toHash[toHashCount] = U64(m_bindings[i].m_tex.m_layout);
-					break;
-				case DescriptorType::SAMPLER:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::SAMPLER && "Have bound the wrong type");
-					break;
-				case DescriptorType::UNIFORM_BUFFER:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::UNIFORM_BUFFER && "Have bound the wrong type");
-					toHash[toHashCount++] = m_bindings[i].m_buff.m_range;
-					dynamicOffsets[dynamicOffsetCount++] = m_bindings[i].m_buff.m_offset;
-					dynamicOffsetsDirty = dynamicOffsetsDirty || crntBindingDirty;
-					break;
-				case DescriptorType::STORAGE_BUFFER:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::STORAGE_BUFFER && "Have bound the wrong type");
-					toHash[toHashCount++] = m_bindings[i].m_buff.m_range;
-					dynamicOffsets[dynamicOffsetCount++] = m_bindings[i].m_buff.m_offset;
-					dynamicOffsetsDirty = dynamicOffsetsDirty || crntBindingDirty;
-					break;
-				case DescriptorType::IMAGE:
-					ANKI_ASSERT(m_bindings[i].m_type == DescriptorType::IMAGE && "Have bound the wrong type");
-					break;
-				default:
-					ANKI_ASSERT(0);
+					ANKI_ASSERT(arrIdx < m_bindings[i].m_arraySize);
+					if(arrIdx > 1)
+					{
+						ANKI_ASSERT(m_bindings[i].m_array[arrIdx].m_type == m_bindings[i].m_array[arrIdx - 1].m_type);
+					}
+
+					const AnyBinding& anyBinding =
+						(m_bindings[i].m_arraySize == 1) ? m_bindings[i].m_single : m_bindings[i].m_array[arrIdx];
+
+					ANKI_ASSERT(anyBinding.m_uuids[0] != 0 && "Forgot to bind");
+
+					toHash[toHashCount++] = anyBinding.m_uuids[0];
+
+					switch(entry.m_bindingType[i])
+					{
+					case DescriptorType::COMBINED_TEXTURE_SAMPLER:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::COMBINED_TEXTURE_SAMPLER
+									&& "Have bound the wrong type");
+						toHash[toHashCount++] = anyBinding.m_uuids[1];
+						toHash[toHashCount++] = U64(anyBinding.m_texAndSampler.m_layout);
+						break;
+					case DescriptorType::TEXTURE:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::TEXTURE && "Have bound the wrong type");
+						toHash[toHashCount] = U64(anyBinding.m_tex.m_layout);
+						break;
+					case DescriptorType::SAMPLER:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::SAMPLER && "Have bound the wrong type");
+						break;
+					case DescriptorType::UNIFORM_BUFFER:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::UNIFORM_BUFFER && "Have bound the wrong type");
+						toHash[toHashCount++] = anyBinding.m_buff.m_range;
+						dynamicOffsets[dynamicOffsetCount++] = anyBinding.m_buff.m_offset;
+						dynamicOffsetsDirty = dynamicOffsetsDirty || crntBindingDirty;
+						break;
+					case DescriptorType::STORAGE_BUFFER:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::STORAGE_BUFFER && "Have bound the wrong type");
+						toHash[toHashCount++] = anyBinding.m_buff.m_range;
+						dynamicOffsets[dynamicOffsetCount++] = anyBinding.m_buff.m_offset;
+						dynamicOffsetsDirty = dynamicOffsetsDirty || crntBindingDirty;
+						break;
+					case DescriptorType::IMAGE:
+						ANKI_ASSERT(anyBinding.m_type == DescriptorType::IMAGE && "Have bound the wrong type");
+						break;
+					default:
+						ANKI_ASSERT(0);
+					}
 				}
 			}
 		}
@@ -659,6 +718,7 @@ Error DescriptorSetFactory::newDescriptorSetLayout(const DescriptorSetLayoutInit
 }
 
 Error DescriptorSetFactory::newDescriptorSet(ThreadId tid,
+	StackAllocator<U8>& tmpAlloc,
 	DescriptorSetState& state,
 	DescriptorSet& set,
 	Bool& dirty,
@@ -691,7 +751,7 @@ Error DescriptorSetFactory::newDescriptorSet(ThreadId tid,
 
 			// Finally, allocate
 			const DS* s;
-			ANKI_CHECK(alloc->getOrCreateSet(hash, state.m_bindings, s));
+			ANKI_CHECK(alloc->getOrCreateSet(hash, state.m_bindings, tmpAlloc, s));
 			set.m_handle = s->m_handle;
 			ANKI_ASSERT(set.m_handle != VK_NULL_HANDLE);
 		}

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

@@ -29,7 +29,7 @@ public:
 	DescriptorType m_type = DescriptorType::COUNT;
 	ShaderTypeBit m_stageMask = ShaderTypeBit::NONE;
 	U8 m_binding = MAX_U8;
-	U8 m_arraySize = 0;
+	U8 m_arraySizeMinusOne = 0;
 };
 
 static_assert(sizeof(DescriptorBinding) == 4, "See file");
@@ -75,28 +75,28 @@ private:
 class TextureSamplerBinding
 {
 public:
-	const TextureViewImpl* m_texView;
-	const MicroSampler* m_sampler;
+	VkImageView m_imgViewHandle;
+	VkSampler m_samplerHandle;
 	VkImageLayout m_layout;
 };
 
 class TextureBinding
 {
 public:
-	const TextureViewImpl* m_texView;
+	VkImageView m_imgViewHandle;
 	VkImageLayout m_layout;
 };
 
 class SamplerBinding
 {
 public:
-	const MicroSampler* m_sampler;
+	VkSampler m_samplerHandle;
 };
 
 class BufferBinding
 {
 public:
-	const BufferImpl* m_buff;
+	VkBuffer m_buffHandle;
 	PtrSize m_offset;
 	PtrSize m_range;
 };
@@ -104,14 +104,13 @@ public:
 class ImageBinding
 {
 public:
-	const TextureViewImpl* m_texView;
+	VkImageView m_imgViewHandle;
 };
 
 class AnyBinding
 {
 public:
-	DescriptorType m_type = DescriptorType::COUNT;
-	Array<U64, 2> m_uuids = {};
+	Array<U64, 2> m_uuids;
 
 	union
 	{
@@ -121,7 +120,23 @@ public:
 		BufferBinding m_buff;
 		ImageBinding m_image;
 	};
+
+	DescriptorType m_type;
 };
+static_assert(std::is_trivial<AnyBinding>::value, "Shouldn't have constructor for perf reasons");
+
+class AnyBindingExtended
+{
+public:
+	union
+	{
+		AnyBinding m_single;
+		AnyBinding* m_array;
+	};
+
+	U32 m_arraySize;
+};
+static_assert(std::is_trivial<AnyBindingExtended>::value, "Shouldn't have constructor for perf reasons");
 
 /// Descriptor set thin wraper.
 class DescriptorSet
@@ -147,6 +162,11 @@ class DescriptorSetState
 	friend class DescriptorSetFactory;
 
 public:
+	void init(StackAllocator<U8>& alloc)
+	{
+		m_alloc = alloc;
+	}
+
 	void setLayout(const DescriptorSetLayout& layout)
 	{
 		if(layout.isCreated())
@@ -161,62 +181,63 @@ public:
 		m_layout = layout;
 	}
 
-	void bindTextureAndSampler(U binding, const TextureView* texView, const Sampler* sampler, VkImageLayout layout)
+	void bindTextureAndSampler(
+		U binding, U arrayIdx, const TextureView* texView, const Sampler* sampler, VkImageLayout layout)
 	{
 		const TextureViewImpl& viewImpl = static_cast<const TextureViewImpl&>(*texView);
 		ANKI_ASSERT(viewImpl.getTextureImpl().isSubresourceGoodForSampling(viewImpl.getSubresource()));
 
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::COMBINED_TEXTURE_SAMPLER;
 		b.m_uuids[0] = viewImpl.getHash();
 		b.m_uuids[1] = ptrToNumber(static_cast<const SamplerImpl*>(sampler)->m_sampler->getHandle());
 
-		b.m_texAndSampler.m_texView = &viewImpl;
-		b.m_texAndSampler.m_sampler = static_cast<const SamplerImpl*>(sampler)->m_sampler.get();
+		b.m_texAndSampler.m_imgViewHandle = viewImpl.getHandle();
+		b.m_texAndSampler.m_samplerHandle = static_cast<const SamplerImpl*>(sampler)->m_sampler->getHandle();
 		b.m_texAndSampler.m_layout = layout;
 
 		m_dirtyBindings.set(binding);
 		unbindCustomDSet();
 	}
 
-	void bindTexture(U binding, const TextureView* texView, VkImageLayout layout)
+	void bindTexture(U binding, U arrayIdx, const TextureView* texView, VkImageLayout layout)
 	{
 		const TextureViewImpl& viewImpl = static_cast<const TextureViewImpl&>(*texView);
 		ANKI_ASSERT(viewImpl.getTextureImpl().isSubresourceGoodForSampling(viewImpl.getSubresource()));
 
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::TEXTURE;
 		b.m_uuids[0] = b.m_uuids[1] = viewImpl.getHash();
 
-		b.m_tex.m_texView = &viewImpl;
+		b.m_tex.m_imgViewHandle = viewImpl.getHandle();
 		b.m_tex.m_layout = layout;
 
 		m_dirtyBindings.set(binding);
 		unbindCustomDSet();
 	}
 
-	void bindSampler(U binding, const Sampler* sampler)
+	void bindSampler(U binding, U arrayIdx, const Sampler* sampler)
 	{
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::SAMPLER;
 		b.m_uuids[0] = b.m_uuids[1] = ptrToNumber(static_cast<const SamplerImpl*>(sampler)->m_sampler->getHandle());
-		b.m_sampler.m_sampler = static_cast<const SamplerImpl*>(sampler)->m_sampler.get();
+		b.m_sampler.m_samplerHandle = static_cast<const SamplerImpl*>(sampler)->m_sampler->getHandle();
 
 		m_dirtyBindings.set(binding);
 		unbindCustomDSet();
 	}
 
-	void bindUniformBuffer(U binding, const Buffer* buff, PtrSize offset, PtrSize range)
+	void bindUniformBuffer(U binding, U arrayIdx, const Buffer* buff, PtrSize offset, PtrSize range)
 	{
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::UNIFORM_BUFFER;
 		b.m_uuids[0] = b.m_uuids[1] = buff->getUuid();
 
-		b.m_buff.m_buff = static_cast<const BufferImpl*>(buff);
+		b.m_buff.m_buffHandle = static_cast<const BufferImpl*>(buff)->getHandle();
 		b.m_buff.m_offset = offset;
 		b.m_buff.m_range = range;
 
@@ -224,14 +245,14 @@ public:
 		unbindCustomDSet();
 	}
 
-	void bindStorageBuffer(U binding, const Buffer* buff, PtrSize offset, PtrSize range)
+	void bindStorageBuffer(U binding, U arrayIdx, const Buffer* buff, PtrSize offset, PtrSize range)
 	{
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::STORAGE_BUFFER;
 		b.m_uuids[0] = b.m_uuids[1] = buff->getUuid();
 
-		b.m_buff.m_buff = static_cast<const BufferImpl*>(buff);
+		b.m_buff.m_buffHandle = static_cast<const BufferImpl*>(buff)->getHandle();
 		b.m_buff.m_offset = offset;
 		b.m_buff.m_range = range;
 
@@ -239,18 +260,18 @@ public:
 		unbindCustomDSet();
 	}
 
-	void bindImage(U binding, const TextureView* texView)
+	void bindImage(U binding, U arrayIdx, const TextureView* texView)
 	{
 		ANKI_ASSERT(texView);
 		const TextureViewImpl* impl = static_cast<const TextureViewImpl*>(texView);
 		ANKI_ASSERT(impl->getTextureImpl().isSubresourceGoodForImageLoadStore(impl->getSubresource()));
 
-		AnyBinding& b = m_bindings[binding];
+		AnyBinding& b = getBindingToPopulate(binding, arrayIdx);
 		b = {};
 		b.m_type = DescriptorType::IMAGE;
 		ANKI_ASSERT(impl->getHash());
 		b.m_uuids[0] = b.m_uuids[1] = impl->getHash();
-		b.m_image.m_texView = impl;
+		b.m_image.m_imgViewHandle = impl->getHandle();
 
 		m_dirtyBindings.set(binding);
 		unbindCustomDSet();
@@ -264,14 +285,16 @@ public:
 	}
 
 private:
+	StackAllocator<U8> m_alloc;
 	DescriptorSetLayout m_layout;
 
-	Array<AnyBinding, MAX_BINDINGS_PER_DESCRIPTOR_SET> m_bindings;
+	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;
 
@@ -286,6 +309,56 @@ private:
 	{
 		m_customDSet = {};
 	}
+
+	AnyBinding& getBindingToPopulate(U32 bindingIdx, U32 arrayIdx)
+	{
+		ANKI_ASSERT(bindingIdx < MAX_BINDINGS_PER_DESCRIPTOR_SET);
+
+		AnyBindingExtended& extended = m_bindings[bindingIdx];
+		AnyBinding* out;
+		const Bool bindingIsSet = m_bindingSet.get(bindingIdx);
+		m_bindingSet.set(bindingIdx);
+		extended.m_arraySize = (!bindingIsSet) ? 0 : extended.m_arraySize;
+
+		if(ANKI_LIKELY(arrayIdx == 0 && extended.m_arraySize <= 1))
+		{
+			// Array idx is zero, most common case
+			out = &extended.m_single;
+			extended.m_arraySize = 1;
+		}
+		else if(arrayIdx < extended.m_arraySize)
+		{
+			// It's (or was) an array and there enough space in thar array
+			out = &extended.m_array[arrayIdx];
+		}
+		else
+		{
+			// Need to grow
+			const U32 newSize = max(extended.m_arraySize * 2, arrayIdx + 1);
+			AnyBinding* newArr = m_alloc.newArray<AnyBinding>(newSize);
+
+			if(extended.m_arraySize == 1)
+			{
+				newArr[0] = extended.m_single;
+			}
+			else if(extended.m_arraySize > 1)
+			{
+				// Copy old to new.
+				memcpy(newArr, extended.m_array, sizeof(AnyBinding) * extended.m_arraySize);
+			}
+
+			// Zero the rest
+			memset(newArr + extended.m_arraySize, 0, sizeof(AnyBinding) * (newSize - extended.m_arraySize));
+			extended.m_arraySize = newSize;
+			extended.m_array = newArr;
+
+			// Return
+			out = &extended.m_array[arrayIdx];
+		}
+
+		ANKI_ASSERT(out);
+		return *out;
+	}
 };
 
 /// Creates new descriptor set layouts and descriptor sets.
@@ -307,6 +380,7 @@ public:
 
 	/// @note Obviously not thread-safe.
 	ANKI_USE_RESULT Error newDescriptorSet(ThreadId tid,
+		StackAllocator<U8>& tmpAlloc,
 		DescriptorSetState& state,
 		DescriptorSet& set,
 		Bool& dirty,

+ 23 - 7
src/anki/gr/vulkan/ShaderImpl.cpp

@@ -135,23 +135,39 @@ void ShaderImpl::doReflection(ConstWeakArray<U8> spirv, SpecConstsVector& specCo
 			{
 				ANKI_ASSERT(typeInfo.array.size() == 1 && "Only 1D arrays are supported");
 				arraySize = typeInfo.array[0];
-				ANKI_ASSERT(arraySize > 0 && arraySize < MAX_U8);
+				ANKI_ASSERT(arraySize > 0 && (arraySize - 1) <= MAX_U8);
 			}
 
 			m_descriptorSetMask.set(set);
 			m_activeBindingMask[set].set(set);
 
 			// Check that there are no other descriptors with the same binding
+			U foundIdx = MAX_U;
 			for(U i = 0; i < counts[set]; ++i)
 			{
-				ANKI_ASSERT(descriptors[set][i].m_binding != binding && "Found 2 descriptors with the same binding");
+				if(descriptors[set][i].m_binding == binding)
+				{
+					foundIdx = i;
+					break;
+				}
 			}
 
-			DescriptorBinding& descriptor = descriptors[set][counts[set]++];
-			descriptor.m_binding = binding;
-			descriptor.m_type = type;
-			descriptor.m_stageMask = static_cast<ShaderTypeBit>(1 << m_shaderType);
-			descriptor.m_arraySize = arraySize;
+			if(foundIdx == MAX_U)
+			{
+				// New binding, init it
+				DescriptorBinding& descriptor = descriptors[set][counts[set]++];
+				descriptor.m_binding = binding;
+				descriptor.m_type = type;
+				descriptor.m_stageMask = static_cast<ShaderTypeBit>(1 << m_shaderType);
+				descriptor.m_arraySizeMinusOne = arraySize - 1;
+			}
+			else
+			{
+				// Same binding, make sure the type is compatible
+				ANKI_ASSERT(type == descriptors[set][foundIdx].m_type && "Same binding different type");
+				ANKI_ASSERT(arraySize - 1 == descriptors[set][foundIdx].m_arraySizeMinusOne
+							&& "Same binding different array size");
+			}
 		}
 	};
 

+ 190 - 0
tests/gr/Gr.cpp

@@ -2171,4 +2171,194 @@ void main()
 	COMMON_END()
 }
 
+ANKI_TEST(Gr, BindingWithArray)
+{
+	COMMON_BEGIN()
+
+	// Create result buffer
+	BufferPtr resBuff =
+		gr->newBuffer(BufferInitInfo(sizeof(Vec4), BufferUsageBit::ALL_COMPUTE, BufferMapAccessBit::READ));
+
+	Array<BufferPtr, 4> uniformBuffers;
+	F32 count = 1.0f;
+	for(BufferPtr& ptr : uniformBuffers)
+	{
+		ptr = gr->newBuffer(BufferInitInfo(sizeof(Vec4), BufferUsageBit::ALL_COMPUTE, BufferMapAccessBit::WRITE));
+
+		Vec4* mapped = static_cast<Vec4*>(ptr->map(0, sizeof(Vec4), BufferMapAccessBit::WRITE));
+		*mapped = Vec4(count, count + 1.0f, count + 2.0f, count + 3.0f);
+		count += 4.0f;
+		ptr->unmap();
+	}
+
+	// Create program
+	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 u_
+{
+	vec4 m_vec;
+} u_ubos[4];
+
+layout(set = 0, binding = 1) writeonly buffer ss_
+{
+	vec4 u_result;
+};
+
+void main()
+{
+	u_result = u_ubos[0].m_vec + u_ubos[1].m_vec + u_ubos[2].m_vec + u_ubos[3].m_vec;
+})";
+
+	ShaderPtr shader = createShader(PROG_SRC, ShaderType::COMPUTE, *gr);
+	ShaderProgramInitInfo sprogInit;
+	sprogInit.m_shaders[ShaderType::COMPUTE] = shader;
+	ShaderProgramPtr prog = gr->newShaderProgram(sprogInit);
+
+	// Run
+	CommandBufferInitInfo cinit;
+	cinit.m_flags = CommandBufferFlag::COMPUTE_WORK | CommandBufferFlag::SMALL_BATCH;
+	CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
+
+	for(U i = 0; i < uniformBuffers.getSize(); ++i)
+	{
+		cmdb->bindUniformBuffer(0, 0, uniformBuffers[i], 0, MAX_PTR_SIZE, i);
+	}
+
+	cmdb->bindStorageBuffer(0, 1, resBuff, 0, MAX_PTR_SIZE);
+
+	cmdb->bindShaderProgram(prog);
+	cmdb->dispatchCompute(1, 1, 1);
+
+	cmdb->flush();
+	gr->finish();
+
+	// Check result
+	Vec4* res = static_cast<Vec4*>(resBuff->map(0, sizeof(Vec4), BufferMapAccessBit::READ));
+
+	ANKI_TEST_EXPECT_EQ(res->x(), 28.0f);
+	ANKI_TEST_EXPECT_EQ(res->y(), 32.0f);
+	ANKI_TEST_EXPECT_EQ(res->z(), 36.0f);
+	ANKI_TEST_EXPECT_EQ(res->w(), 40.0f);
+
+	resBuff->unmap();
+
+	COMMON_END();
+}
+
+ANKI_TEST(Gr, Bindless)
+{
+	COMMON_BEGIN()
+
+	// Create texture A
+	TextureInitInfo texInit;
+	texInit.m_width = 1;
+	texInit.m_height = 1;
+	texInit.m_format = Format::R32G32B32A32_UINT;
+	texInit.m_usage = TextureUsageBit::ALL_COMPUTE | TextureUsageBit::TRANSFER_ALL;
+	texInit.m_mipmapCount = 1;
+
+	TexturePtr texA = gr->newTexture(texInit);
+
+	// Create texture B
+	TexturePtr texB = gr->newTexture(texInit);
+
+	// Create sampler
+	SamplerInitInfo samplerInit;
+	SamplerPtr sampler = gr->newSampler(samplerInit);
+
+	// Create views
+	TextureViewPtr viewA = gr->newTextureView(TextureViewInitInfo(texA, TextureSurfaceInfo()));
+	TextureViewPtr viewB = gr->newTextureView(TextureViewInitInfo(texB, TextureSurfaceInfo()));
+
+	// Create result buffer
+	BufferPtr resBuff =
+		gr->newBuffer(BufferInitInfo(sizeof(UVec4), BufferUsageBit::ALL_COMPUTE, BufferMapAccessBit::READ));
+
+	// Create program A
+	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];
+
+layout(set = 1, binding = 0) writeonly buffer ss_
+{
+	uvec4 u_result;
+};
+
+layout(set = 1, binding = 1) uniform sampler u_sampler;
+
+layout(push_constant) uniform pc_
+{
+	uvec4 u_texIndices;
+};
+
+void main()
+{
+	uvec4 val0 = imageLoad(u_bindlessImages[u_texIndices[0]], ivec2(0));
+	uvec4 val1 = texelFetch(usampler2D(u_bindlessTextures[u_texIndices[1]], u_sampler), ivec2(0), 0);
+
+	u_result = val0 + val1;
+})";
+
+	ShaderPtr shader = createShader(PROG_SRC, ShaderType::COMPUTE, *gr);
+	ShaderProgramInitInfo sprogInit;
+	sprogInit.m_shaders[ShaderType::COMPUTE] = shader;
+	ShaderProgramPtr prog = gr->newShaderProgram(sprogInit);
+
+	// Run
+	CommandBufferInitInfo cinit;
+	cinit.m_flags = CommandBufferFlag::COMPUTE_WORK | CommandBufferFlag::SMALL_BATCH;
+	CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
+
+	cmdb->setTextureSurfaceBarrier(
+		texA, TextureUsageBit::NONE, TextureUsageBit::TRANSFER_DESTINATION, TextureSurfaceInfo());
+	cmdb->setTextureSurfaceBarrier(
+		texB, TextureUsageBit::NONE, TextureUsageBit::TRANSFER_DESTINATION, TextureSurfaceInfo());
+
+	TransferGpuAllocatorHandle handle0, handle1;
+	const UVec4 mip0 = UVec4(1, 2, 3, 4);
+	UPLOAD_TEX_SURFACE(cmdb, texA, TextureSurfaceInfo(0, 0, 0, 0), &mip0[0], sizeof(mip0), handle0);
+	const UVec4 mip1 = UVec4(10, 20, 30, 40);
+	UPLOAD_TEX_SURFACE(cmdb, texB, TextureSurfaceInfo(0, 0, 0, 0), &mip1[0], sizeof(mip1), handle1);
+
+	cmdb->setTextureSurfaceBarrier(
+		texA, TextureUsageBit::TRANSFER_DESTINATION, TextureUsageBit::IMAGE_COMPUTE_READ, TextureSurfaceInfo());
+	cmdb->setTextureSurfaceBarrier(
+		texB, TextureUsageBit::TRANSFER_DESTINATION, TextureUsageBit::SAMPLED_COMPUTE, TextureSurfaceInfo());
+
+	cmdb->bindStorageBuffer(1, 0, resBuff, 0, MAX_PTR_SIZE);
+	cmdb->bindSampler(1, 1, sampler);
+	cmdb->bindShaderProgram(prog);
+
+	const U32 idx0 = cmdb->bindBindlessImage(viewA);
+	const U32 idx1 = cmdb->bindBindlessTexture(viewB, TextureUsageBit::SAMPLED_COMPUTE);
+	UVec4 pc(idx0, idx1, 0, 0);
+	cmdb->setPushConstants(&pc, sizeof(pc));
+
+	cmdb->bindAllBindless(0);
+
+	cmdb->dispatchCompute(1, 1, 1);
+
+	// Read result
+	FencePtr fence;
+	cmdb->flush(&fence);
+	transfAlloc->release(handle0, fence);
+	transfAlloc->release(handle1, fence);
+	gr->finish();
+
+	// Check result
+	UVec4* res = static_cast<UVec4*>(resBuff->map(0, sizeof(UVec4), BufferMapAccessBit::READ));
+
+	ANKI_TEST_EXPECT_EQ(res->x(), 11);
+	ANKI_TEST_EXPECT_EQ(res->y(), 22);
+	ANKI_TEST_EXPECT_EQ(res->z(), 33);
+	ANKI_TEST_EXPECT_EQ(res->w(), 44);
+
+	resBuff->unmap();
+
+	COMMON_END()
+}
+
 } // end namespace anki