Sparse/Incomplete array of textures or rather incomplete descriptor set?

I’m in the process of switching from using OpenGL for all my rendering to Vulkan. As Vulkan doesn’t support bindless I’ve been using shaderSampledImageArrayDynamicIndexing and using a push constant to index into the array of textures as suggested elsewhere on this forum.

I have this working fine with a simple 2 object / 2 texture scene. But if I increase the capacity of my array so that I could support more textures than I might currently require, I’m getting a validation error:

ERROR: validation layer: Object: 0x0 | Shader expects at least 3 descriptors for binding 0.2 (used as type ptr to const uniform arr[3] of sampler+image(dim=1, sampled=1)) but only 2 provided

To be fair non of the hardware I’ve tested this on (Nvidia, AMD, Intel) seems to have a problem with this but it’s a bit annoying and I suppose could potentially break in future. If this was shipping code, I’d be concerned.
I’ve tried everything I can think of to make this validation error go away but I’m all out of ideas?

Has anyone else encountered this, and is there a way to prevent the validation error?

What operation provoked this validation layer error?

Looking back at the callstack it seems vkCreateGraphicsPipelines caused the error.

I will add that in the current state the code is in, the only modification made to my working code is in the fragment shader i.e.

layout(binding = 2) uniform sampler2D textures[3];

Previously the texture array size was 2 and I’d get no complaints as in this situation I’m only using 2 textures. I have tried different combinations of setting up the descriptor sets and / or pipeline layout for 3 textures including trying to write a dummy VkDescriptorImageInfo for the unavailable 3rd texture i.e. VkDescriptorImageInfo::imageView = VK_NULL_HANDLE; which obviously didn’t work and triggered a different validation error, as has everything else I have tried.

Also show the shader and your pipeline and descriptor set layout.

Preliminarilly, I would say one of the two is wrong.

Sure, I was trying to pass as little code as possible in case it was obvious. Mainly so I didn’t have to clean up the code as it’s currently got loads of little things I tried commented out.


//Fragment Shader:
#version 450
#extension GL_ARB_separate_shader_objects : enable

// Output
layout (location = 0) out vec4 color;

// Input from vertex shader
layout(location = 0) in VS_OUT
{
    vec3 N;
    vec3 L;
    vec3 V;
	vec2 tex_coord;
	flat int material_id;
} fs_in;

layout(binding = 2) uniform sampler2D textures[3];
 
layout (push_constant) uniform pushConstants_t
{
    layout (offset = 0) uint drawId;
};

void main(void)
{
	vec3 Kd = texture(textures[drawId], fs_in.tex_coord).xyz;

	vec3 Ks = vec3(1, 1, 1);	
	float m = 64.0;				

    vec3 n = normalize(fs_in.N);
    vec3 l = normalize(fs_in.L);
    vec3 v = normalize(fs_in.V);
    vec3 h = normalize(l + v);
	vec3 Lo = vec3(0.0);
 
	float nDotL = clamp(dot(n, l), 0.0, 1.0);
	float nDotH = clamp(dot(n, h), 0.0, 1.0);
	Lo = (Kd + Ks * pow(nDotH, m)) * max(nDotL, 0.1);
	color = vec4(Lo, 1.0);
}


//DescriptorSet setup
	VkDescriptorBufferInfo descriptorBufferInfo = {};
	descriptorBufferInfo.buffer = m_sceneUniformBuffer->m_buffer;
	descriptorBufferInfo.offset = 0;
	descriptorBufferInfo.range = sizeof(SceneUniformData);

	VkWriteDescriptorSet writeDescriptorSet[3] = {};
	writeDescriptorSet[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	writeDescriptorSet[0].dstSet = m_vkDescriptorSet;
	writeDescriptorSet[0].dstBinding = 0;
	writeDescriptorSet[0].dstArrayElement = 0;
	writeDescriptorSet[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	writeDescriptorSet[0].descriptorCount = 1;
	writeDescriptorSet[0].pBufferInfo = &descriptorBufferInfo;

	VkDescriptorBufferInfo descriptorBufferInfo2 = {};
	descriptorBufferInfo2.buffer = scene.m_modelMatrixUniformBuffer->m_buffer;
	descriptorBufferInfo2.offset = 0;
	descriptorBufferInfo2.range = sizeof(ModelUniformData);

	writeDescriptorSet[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	writeDescriptorSet[1].dstSet = m_vkDescriptorSet;
	writeDescriptorSet[1].dstBinding = 1;
	writeDescriptorSet[1].dstArrayElement = 0;
	writeDescriptorSet[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	writeDescriptorSet[1].descriptorCount = 1;
	writeDescriptorSet[1].pBufferInfo = &descriptorBufferInfo2;

	VkDescriptorImageInfo *texImageInfo = static_cast<VkDescriptorImageInfo *>(alloca(sizeof(VkDescriptorImageInfo) * m_numTextures));
	memset(texImageInfo, 0, sizeof(VkDescriptorImageInfo) * m_numTextures);

	for (uint32_t i = 0; i < m_numTextures; i++)
	{
		texImageInfo[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
		texImageInfo[i].imageView = m_textures[i]->m_vkImageView;
		texImageInfo[i].sampler = m_textures[i]->m_vkSampler;
	}

	uint32_t texIndex = 2;
	writeDescriptorSet[texIndex].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	writeDescriptorSet[texIndex].dstSet = m_vkDescriptorSet;
	writeDescriptorSet[texIndex].dstBinding = 2;
	writeDescriptorSet[texIndex].dstArrayElement = 0;
	writeDescriptorSet[texIndex].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	writeDescriptorSet[texIndex].descriptorCount = 2;
	writeDescriptorSet[texIndex].pImageInfo = texImageInfo;

	vkUpdateDescriptorSets(m_vkDevice, 3, writeDescriptorSet, 0, nullptr);


//Pipeline layout.
	VkDescriptorSetLayoutBinding uboLayoutBinding = {};
	uboLayoutBinding.binding = 0;
	uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	uboLayoutBinding.descriptorCount = 1;
	uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

	VkDescriptorSetLayoutBinding ubo2LayoutBinding = {};
	ubo2LayoutBinding.binding = 1;
	ubo2LayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	ubo2LayoutBinding.descriptorCount = 1;
	ubo2LayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

	VkDescriptorSetLayoutBinding textureLayoutBinding = {};
	textureLayoutBinding.binding = 2;
	textureLayoutBinding.descriptorCount = 2;
	textureLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	textureLayoutBinding.pImmutableSamplers = nullptr;
	textureLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

	VkDescriptorSetLayoutBinding bindings[] = { uboLayoutBinding, ubo2LayoutBinding, textureLayoutBinding };

	VkDescriptorSetLayoutCreateInfo descriptorLayoutCreateInfo = {};
	descriptorLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	descriptorLayoutCreateInfo.bindingCount = sizeof(bindings) / sizeof(bindings[0]);
	descriptorLayoutCreateInfo.pBindings = bindings;

	if (vkCreateDescriptorSetLayout(m_vkDevice, &descriptorLayoutCreateInfo, nullptr, &m_vkDescriptorSetLayout) != VK_SUCCESS)
	{
		print("failed to create descriptor layout
");
		exit(1);
	}

I’m happy to post more if needed, but just trying to keep things to a minimum.

If I set textureLayoutBinding.descriptorCount = 3; I get the following validation error instead when building my command buffers: ‘ERROR: validation layer: Object: 0x3b | Descriptor set 0x3b encountered the following validation error at vkCmdDrawIndexed() time: Descriptor in binding #2 at global descriptor index 4 is being used in draw but has not been updated.’

Similarly if I set writeDescriptorSet[texIndex].descriptorCount = 3; I get: ‘ERROR: validation layer: Object: 0x0 | vkUpdateDescriptorSets: required parameter pDescriptorWrites[2].pImageInfo[2].imageView specified as VK_NULL_HANDLE’

If I set textureLayoutBinding.descriptorCount = 3

You say “if” as though that were not in fact a requirement. Which it is; you’re not allowed to lie about your descriptor layout:

Though I’m surprised that there’s nothing which forbids the array declaration size from being smaller than the descriptorCount. I suppose that, since the final compilation is done using the descriptor set layout, the layout can impose its potentially larger size on the shader. But it would be wrong for the shader to have a larger size, since the shader is promising something that the descriptor layout can’t provide.

As for what the error resulting from fixing this means, I can’t say.

My “if” was purely a statement, if I do this then this. However I take your point, my original question was based on a code state that contravened the spec.

However as I said, with textureLayoutBinding.descriptorCount = 3 I get another validation warning, seemingly because it’s expecting an additional VkDescriptorImageInfo for the missing 3rd texture. This seems to hold true because if I pass in another pointing to the first texture again the validation layer seems happy.

So my question now becomes. Is it possible in some way to pass few VkDescriptotImageInfo’s than the descriptor set expects without upsetting the validation layer? One suggestion I had from a colleague today was I create an empty / dummy 1x1 VkImage / VkImageView pair and use them for every entry in the descriptor set that I don’t have an actual texture for. This seems like it might be workable but it’s a slightly ugly solution, especially if you have a large array size to cope for any eventuality.