0
$\begingroup$

I originally asked this question on the gamdev stack exchange but was told by a friend I would be better served here.

Original Question

So I will start off by laying out the relevant structure of my program.

I am using vulkan to render the same mesh over a few instances. To do this I have:

  • a vertex buffer, used per vertex, which is an array of the following structure
pub struct ShaderVertexData {
    pub position: na::Vector3<f32>,
    pub uv: na::Vector2<f32>,
    pub normal: na::Vector3<f32>,
}
  • an "instance" buffer, used per instance, which is an array of this structure.
pub struct InstanceData {
    pub model: [[f32; 4]; 4],
    pub texture_index: u32,
}
  • An array of textures provided via a descriptor set
let descriptor_binding_flags = [vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT];
let mut descriptorset_layout_binding_flags =
    vk::DescriptorSetLayoutBindingFlagsCreateInfo::builder()
        .binding_flags(&descriptor_binding_flags);

let layout_bindings = [DescriptorSetLayoutBinding::builder()
    .stage_flags(vk::ShaderStageFlags::FRAGMENT)
    .binding(0)
    .descriptor_count(MAX_IMAGES)
    .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
    .build()];

let descriptor_set_layout_info = vk::DescriptorSetLayoutCreateInfo::builder()
    .bindings(&layout_bindings)
    .push_next(&mut descriptorset_layout_binding_flags);

let descriptor_set_layout_texture = unsafe {
    logical_device.create_descriptor_set_layout(&descriptor_set_layout_info, None)
}?;

let descriptor_pool_sizes = [vk::DescriptorPoolSize::builder()
    .ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
    .descriptor_count(MAX_IMAGES * 3)
    .build()];

let descriptor_pool_info = vk::DescriptorPoolCreateInfo::builder()
    .pool_sizes(&descriptor_pool_sizes)
    .max_sets(3);

let descriptor_pool =
    unsafe { logical_device.create_descriptor_pool(&descriptor_pool_info, None) }?;

let desc_layouts_texture = vec![descriptor_set_layout_texture; 3];
// TODO: Move this into the texture code to allocate as needed. For now I just hard-code 2 textures.
let mut variable = DescriptorSetVariableDescriptorCountAllocateInfo::builder()
    .descriptor_counts(&[2, 2, 2]);
let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder()
    .descriptor_pool(descriptor_pool)
    .set_layouts(&desc_layouts_texture)
    .push_next(&mut variable);

let descriptor_sets =
    unsafe { logical_device.allocate_descriptor_sets(&descriptor_set_allocate_info) }?;

let descriptor_set_layouts = [descriptor_set_layout_texture];

let pipelinelayout_info = vk::PipelineLayoutCreateInfo::builder()
    .push_constant_ranges(&push_constant_ranges)
    .set_layouts(&descriptor_set_layouts);

//------------------------------------------------
// Later I copy in the texture info
//------------------------------------------------
let descriptor_image_infos = self.texture_store.get_descriptor_image_info();
let descriptor_writes = [vk::WriteDescriptorSet {
    descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
    dst_set: self.graphics_pipeline.descriptor_sets
        [frame_buffer_info.image_index as usize],
    dst_binding: 0,
    dst_array_element: 0,
    p_image_info: descriptor_image_infos.as_ptr(),
    descriptor_count: 2,
    ..Default::default()
}];

I then have the following shaders

// vertex
#version 450

layout(push_constant)uniform constants{
    mat4 proj;
}PushConstants;

layout(location=0)in mat4 model;
layout(location=4)in uint tex_id;
layout(location=5)in vec3 position;
layout(location=6)in vec2 uv;
layout(location=7)in vec3 normal;

layout(location=0)out vec2 uv_for_fragment_shader;
layout(location=1)out uint tex_id_for_fragment_shader;

void main(){
    gl_Position=PushConstants.proj*model*vec4(position,1);
    tex_id_for_fragment_shader = tex_id;
    uv_for_fragment_shader=uv;
}

// fragment
#version 450
#extension GL_EXT_nonuniform_qualifier : require

layout(set=0,binding=0)uniform sampler2D tex_samplers[];

layout(location=0)in vec2 uv_from_vertex_shader;
layout(location=1)in flat uint tex_id_from_vertex_shader;

layout(location=0)out vec4 output_colour;

void main(){
    output_colour = texture(tex_samplers[tex_id_from_vertex_shader], uv_from_vertex_shader);
}

Finally I draw the instances like so

self.logical_device.cmd_push_constants(
    commandbuffer,
    self.graphics_pipeline.layout,
    vk::ShaderStageFlags::VERTEX,
    0,
    &std::mem::transmute::<[[f32; 4]; 4], [u8; 64]>(projection),
);

self.logical_device.cmd_bind_descriptor_sets(
    commandbuffer,
    vk::PipelineBindPoint::GRAPHICS,
    self.graphics_pipeline.layout,
    0,
    &[self.graphics_pipeline.descriptor_sets
        [frame_buffer_info.image_index as usize]],
    &[],
);
self.cube.bind(&self.logical_device, commandbuffer);

self.logical_device.cmd_bind_vertex_buffers(
    commandbuffer,
    VertexBufferBindings::InstanceBuffer as u32,
    &[self.instance_buffer.buffer],
    &[0],
);

self.logical_device.cmd_draw_indexed(
    commandbuffer,
    self.cube.index_count() as u32,
    4,
    0,
    0,
    0,
);

With that out of the way we can get to the problem at hand, The texture samplers seem to bleed over into the separate instances

2 Cubes with different textures but patches of each-others textures

I have checked that the texture index itself is consistent across the entire instance by rendering

output_colour = vec4(tex_id_from_vertex_shader, 0,0,1);

And I get no artefacts

No artefacts

So it seems like the sampler itself is hanging around where it shouldn't. Any idea what might be causing this to happen?

If it matters I am on an AMD Radeon 7900 XTX and using the latest drivers

$\endgroup$
2
  • $\begingroup$ I don't know enough about Vulkan to identify the actual problem, but the fact that the errors are in small patches, with rectangular edges, that don't match your geometry is a sign that something is going wrong with the execution of the fragment shader (which is done in small patches like that). $\endgroup$
    – Kevin Reid
    Apr 15 at 16:48
  • $\begingroup$ It seems as though you are correct, its as though the sampler is just locked for the entire fragment. Interestingly enough. Adding a branch in the code like this: ``` if (tex_id_from_vertex_shader == 0) { albedo = texture(tex_samplers[0], uv_from_vertex_shader); } else { albedo = texture(tex_samplers[1], uv_from_vertex_shader); } ``` Works just fine. So it is some kind of optimisation? $\endgroup$ Apr 16 at 9:50

1 Answer 1

1
$\begingroup$

I finally got an answer on the AMD Forums.

The issue was that you need to tell the GPU when you plan on indexing resource arrays with non uniform values.

My shader now looks like this:

#version 450
#extension GL_EXT_nonuniform_qualifier : require

layout(set=0,binding=0)uniform sampler2D tex_samplers[];

layout(location=0)in vec2 uv_from_vertex_shader;
layout(location=1)in vec3 normal_from_vertex_shader;
layout(location=2)in flat uint tex_id_from_vertex_shader;


layout(location=0)out vec4 output_colour;

void main(){
    vec4 albedo = texture(tex_samplers[nonuniformEXT(tex_id_from_vertex_shader)], uv_from_vertex_shader);
    float light = clamp(dot(normal_from_vertex_shader, normalize(vec3(1,1,1))), 0.2, 1);
    output_colour =  vec4(albedo.rgb * light, albedo.a);
}

The important part is the nonuniformEXT that wraps the index

It is also worth mentioning that technically this also requires that you enable shaderSampledImageArrayNonUniformIndexing on your VkPhysicalDeviceDescriptorIndexingFeatures

$\endgroup$
3
  • $\begingroup$ Do you have the validation layers turned on? This error should have been reported by the layers. $\endgroup$
    – pmw1234
    Apr 20 at 21:36
  • $\begingroup$ Yeah I do have them enabled. Perhaps there is some specific config needed to catch this flavour of error but this didn't show up for me $\endgroup$ Apr 21 at 22:39
  • $\begingroup$ In the Vulkan Configurator under "Validation" in VK_LAYER_KHRONOS_validation->Validation Areas->Shader-Based->GPU-Assisted->Check descriptor indexing accesses Should trigger the error. $\endgroup$
    – pmw1234
    Apr 22 at 11:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.