diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index c2e4ce034..78ba2d3ca 100644
--- a/util/test/demos/CMakeLists.txt
+++ b/util/test/demos/CMakeLists.txt
@@ -10,6 +10,7 @@ set(VULKAN_SRC
vk/vk_awkward_triangle.cpp
vk/vk_buffer_address.cpp
vk/vk_cbuffer_zoo.cpp
+ vk/vk_descriptor_index.cpp
vk/vk_discard_rects.cpp
vk/vk_draw_zoo.cpp
vk/vk_indirect.cpp
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index d36dd4758..f3ffefebe 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -195,6 +195,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index a86fcb2e9..5fe6c3756 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -258,6 +258,9 @@
Vulkan\demos
+
+ Vulkan\demos
+
D3D11\demos
diff --git a/util/test/demos/vk/vk_descriptor_index.cpp b/util/test/demos/vk/vk_descriptor_index.cpp
new file mode 100644
index 000000000..480b5ba06
--- /dev/null
+++ b/util/test/demos/vk/vk_descriptor_index.cpp
@@ -0,0 +1,573 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "vk_test.h"
+
+#define STRESS_TEST 0
+
+#if STRESS_TEST
+
+#define DESC_ARRAY1_SIZE 4096
+#define DESC_ARRAY2_SIZE (1024 * 1024 - DESC_ARRAY1_SIZE)
+
+#else
+
+#define DESC_ARRAY1_SIZE 128
+#define DESC_ARRAY2_SIZE (512)
+
+#endif
+
+#define BUFIDX 15
+#define INDEX1 49
+#define INDEX2 381
+#define NONUNIFORMIDX 20
+
+#define STRINGISE2(a) #a
+#define STRINGISE(a) STRINGISE2(a)
+
+struct VK_Descriptor_Indexing : VulkanGraphicsTest
+{
+ static constexpr const char *Description =
+ "Draws a triangle using descriptor indexing with large descriptor sets.";
+
+ std::string common = R"EOSHADER(
+
+#version 450 core
+
+#extension GL_EXT_nonuniform_qualifier : require
+
+struct v2f
+{
+ vec4 pos;
+ vec4 col;
+ vec4 uv;
+};
+
+)EOSHADER";
+
+ const std::string vertex = R"EOSHADER(
+
+layout(location = 0) in vec3 Position;
+layout(location = 1) in vec4 Color;
+layout(location = 2) in vec2 UV;
+
+layout(location = 0) out v2f vertOut;
+
+void main()
+{
+ vertOut.pos = vec4(Position.xyz*vec3(1,-1,1), 1);
+ gl_Position = vertOut.pos;
+ vertOut.col = Color;
+ vertOut.uv = vec4(UV.xy, 0, 1);
+}
+
+)EOSHADER";
+
+ const std::string comp = R"EOSHADER(
+
+#version 450 core
+
+#extension GL_EXT_nonuniform_qualifier : require
+
+layout(push_constant) uniform PushData
+{
+ uint bufidx;
+ uint idx1;
+ uint idx2;
+} push;
+
+struct tex_ref
+{
+ uint binding;
+ uint idx;
+};
+
+layout(binding = 0, std430) buffer outbuftype {
+ tex_ref outrefs[];
+} outbuf[];
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+void main()
+{
+ outbuf[push.bufidx].outrefs[0].binding = 0;
+ outbuf[push.bufidx].outrefs[0].idx = push.idx1;
+ outbuf[push.bufidx].outrefs[1].binding = 2;
+ outbuf[push.bufidx].outrefs[1].idx = push.idx2;
+
+ outbuf[push.bufidx].outrefs[2].binding = 1;
+ outbuf[push.bufidx].outrefs[2].idx = push.idx1;
+ outbuf[push.bufidx].outrefs[3].binding = 2;
+ outbuf[push.bufidx].outrefs[3].idx = push.idx2+5;
+
+ // terminator
+ outbuf[push.bufidx].outrefs[4].binding = 100;
+}
+
+)EOSHADER";
+
+ const std::string pixel = R"EOSHADER(
+
+layout(location = 0) in v2f vertIn;
+
+layout(location = 0, index = 0) out vec4 Color;
+
+layout(push_constant) uniform PushData
+{
+ uint bufidx;
+} push;
+
+struct tex_ref
+{
+ uint binding;
+ uint idx;
+};
+
+layout(binding = 0, std430) buffer inbuftype {
+ tex_ref inrefs[];
+} inbuf[];
+
+layout(binding = 1) uniform sampler2D tex1[)EOSHADER" STRINGISE(DESC_ARRAY1_SIZE) R"EOSHADER(];
+layout(binding = 2) uniform sampler2D tex2[];
+
+void add_color(sampler2D tex)
+{
+ Color *= (vec4(0.25f) + texture(tex, vertIn.uv.xy));
+}
+
+void add_indirect_color2(sampler2D texs[)EOSHADER" STRINGISE(DESC_ARRAY1_SIZE) R"EOSHADER(], uint idx)
+{
+ add_color(texs[idx]);
+}
+
+void add_indirect_color(int dummy,
+ sampler2D texs[)EOSHADER" STRINGISE(DESC_ARRAY1_SIZE) R"EOSHADER(], tex_ref t)
+{
+ // second array-param function call
+ add_indirect_color2(texs, t.idx);
+}
+
+void dispatch_indirect_color(int dummy1,
+ sampler2D texA[)EOSHADER" STRINGISE(DESC_ARRAY1_SIZE) R"EOSHADER(],
+ sampler2D texB[)EOSHADER" STRINGISE(DESC_ARRAY1_SIZE) R"EOSHADER(],
+ float dummy2, tex_ref t)
+{
+ if(t.binding == 0)
+ {
+ add_indirect_color(5, texA, t);
+ }
+ else
+ {
+ tex_ref t2 = t;
+ t2.idx += 10;
+ add_indirect_color(10, texB, t2);
+ }
+}
+
+void main()
+{
+ if(vertIn.uv.y < 0.2f)
+ {
+ // nonuniform dynamic index
+ Color = texture(tex1[nonuniformEXT(int(vertIn.col.w+0.5f))], vertIn.uv.xy);
+ }
+ else
+ {
+ Color = vec4(vertIn.col.xyz, 1.0f);
+
+ for(int i=0; i < 100; i++)
+ {
+ tex_ref t = inbuf[push.bufidx].inrefs[i];
+
+ if(t.binding == 100)
+ break;
+
+ // function call with array parameters
+ if(t.binding < 2)
+ dispatch_indirect_color(0, tex1, tex1, 5.0f, t);
+ else
+ add_color(tex2[t.idx]);
+ }
+ }
+}
+
+)EOSHADER";
+
+ int main(int argc, char **argv)
+ {
+ devExts.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
+
+ // dependencies of VK_EXT_descriptor_indexing
+ devExts.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME);
+ instExts.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+
+ features.fragmentStoresAndAtomics = VK_TRUE;
+
+ VkPhysicalDeviceDescriptorIndexingFeaturesEXT descIndexing = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
+ };
+
+ descIndexing.descriptorBindingPartiallyBound = VK_TRUE;
+ descIndexing.runtimeDescriptorArray = VK_TRUE;
+ descIndexing.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
+
+ devInfoNext = &descIndexing;
+
+ // initialise, create window, create context, etc
+ if(!Init(argc, argv))
+ return 3;
+
+ VkDescriptorSetLayoutBindingFlagsCreateInfoEXT descFlags = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT,
+ };
+
+ VkDescriptorBindingFlagsEXT bindFlags[3] = {
+ VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT, 0,
+ VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT,
+ };
+
+ descFlags.bindingCount = 3;
+ descFlags.pBindingFlags = bindFlags;
+
+ VkDescriptorSetLayout setlayout = createDescriptorSetLayout(
+ vkh::DescriptorSetLayoutCreateInfo(
+ {
+ {
+ 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DESC_ARRAY1_SIZE,
+ VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT,
+ },
+ {
+ 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESC_ARRAY1_SIZE,
+ VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT,
+ },
+ {
+ 2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESC_ARRAY2_SIZE,
+ VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT,
+ },
+ })
+ .next(&descFlags));
+
+ VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo(
+ {
+ setlayout,
+ },
+ {
+ vkh::PushConstantRange(VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT, 0,
+ sizeof(Vec4i)),
+ }));
+
+ vkh::GraphicsPipelineCreateInfo pipeCreateInfo;
+
+ pipeCreateInfo.layout = layout;
+ pipeCreateInfo.renderPass = swapRenderPass;
+
+ pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)};
+ pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = {
+ vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col),
+ vkh::vertexAttr(2, 0, DefaultA2V, uv),
+ };
+
+ pipeCreateInfo.stages = {
+ CompileShaderModule(common + vertex, ShaderLang::glsl, ShaderStage::vert, "main"),
+ CompileShaderModule(common + pixel, ShaderLang::glsl, ShaderStage::frag, "main"),
+ };
+
+ VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo);
+
+ VkPipeline comppipe = createComputePipeline(vkh::ComputePipelineCreateInfo(
+ layout, CompileShaderModule(comp, ShaderLang::glsl, ShaderStage::comp, "main")));
+
+ float left = float(NONUNIFORMIDX - 1.0f);
+ float middle = float(NONUNIFORMIDX);
+ float right = float(NONUNIFORMIDX + 1.0f);
+
+ DefaultA2V tri[3] = {
+ {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, left), Vec2f(0.0f, 0.0f)},
+ {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, middle), Vec2f(0.0f, 1.0f)},
+ {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, right), Vec2f(1.0f, 0.0f)},
+ };
+
+ AllocatedBuffer vb(allocator,
+ vkh::BufferCreateInfo(sizeof(tri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));
+
+ vb.upload(tri);
+
+ AllocatedImage img(allocator, vkh::ImageCreateInfo(
+ 4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+
+ setName(img.image, "Colour Tex");
+
+ VkImageView imgview = createImageView(
+ vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT));
+
+ float pixels[4 * 4 * 4];
+ for(int i = 0; i < 4 * 4 * 4; i++)
+ pixels[i] = RANDF(0.2f, 1.0f);
+
+ AllocatedBuffer uploadBuf(
+ allocator, vkh::BufferCreateInfo(sizeof(pixels), VK_BUFFER_USAGE_TRANSFER_SRC_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));
+
+ uploadBuf.upload(pixels);
+
+ // create an image with black contents for all the indices we aren't using
+
+ AllocatedImage badimg(allocator, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+
+ setName(badimg.image, "Black Tex");
+
+ VkImageView badimgview = createImageView(vkh::ImageViewCreateInfo(
+ badimg.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT));
+
+ {
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ vkh::cmdPipelineBarrier(
+ cmd,
+ {
+ vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, img.image),
+ vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, badimg.image),
+ });
+
+ VkBufferImageCopy copy = {};
+ copy.imageExtent = {4, 4, 1};
+ copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copy.imageSubresource.layerCount = 1;
+
+ vkCmdCopyBufferToImage(cmd, uploadBuf.buffer, img.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ©);
+
+ VkClearColorValue red = {{1.0f, 0.0f, 0.0f, 1.0f}};
+ VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+ vkCmdClearColorImage(cmd, badimg.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &red, 1, &range);
+
+ vkh::cmdPipelineBarrier(
+ cmd, {
+ vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, img.image),
+ vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, badimg.image),
+ });
+
+ vkEndCommandBuffer(cmd);
+
+ Submit(99, 99, {cmd});
+ }
+
+ VkDescriptorSet descset[5] = {};
+ VkDescriptorPool descpool = VK_NULL_HANDLE;
+
+ {
+ CHECK_VKR(vkCreateDescriptorPool(
+ device, vkh::DescriptorPoolCreateInfo(
+ 8,
+ {
+ {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESC_ARRAY2_SIZE * 10},
+ {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DESC_ARRAY1_SIZE * 10},
+ }),
+ NULL, &descpool));
+
+ CHECK_VKR(vkAllocateDescriptorSets(
+ device, vkh::DescriptorSetAllocateInfo(
+ descpool, {setlayout, setlayout, setlayout, setlayout, setlayout}),
+ descset));
+ }
+
+ VkSampler sampler = VK_NULL_HANDLE;
+
+ VkSamplerCreateInfo sampInfo = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
+ sampInfo.magFilter = VK_FILTER_LINEAR;
+ sampInfo.minFilter = VK_FILTER_LINEAR;
+
+ vkCreateSampler(device, &sampInfo, NULL, &sampler);
+
+ vkh::DescriptorImageInfo iminfo(badimgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler);
+ vkh::WriteDescriptorSet up(VK_NULL_HANDLE, 0, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {iminfo});
+
+ std::vector ims;
+ ims.resize(DESC_ARRAY2_SIZE, iminfo);
+
+ up.pImageInfo = ims.data();
+
+ std::vector ups;
+
+ // fill the descriptor sets with values so they aren't all empty
+ for(int i = 0; i < 5; i++)
+ {
+ up.dstSet = descset[i];
+
+ up.dstBinding = 1;
+ up.dstArrayElement = 0;
+ up.descriptorCount = DESC_ARRAY1_SIZE;
+
+ ups.push_back(up);
+
+ // leave the first 20 elements empty
+ up.dstBinding = 2;
+ up.dstArrayElement = 20;
+ up.descriptorCount = DESC_ARRAY2_SIZE - 20;
+
+ ups.push_back(up);
+ }
+
+ vkh::updateDescriptorSets(device, ups);
+
+ AllocatedBuffer ssbo(allocator,
+ vkh::BufferCreateInfo(1024 * 1024, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+
+ // update the buffer only
+ vkh::updateDescriptorSets(
+ device, {
+ vkh::WriteDescriptorSet(descset[0], 0, BUFIDX, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ {vkh::DescriptorBufferInfo(ssbo.buffer)}),
+ });
+
+ // overwrite the indices we want with the right image
+ vkh::updateDescriptorSets(
+ device,
+ {
+ vkh::WriteDescriptorSet(
+ descset[0], 1, INDEX1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {
+ vkh::DescriptorImageInfo(imgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler),
+ }),
+ vkh::WriteDescriptorSet(
+ descset[0], 1, INDEX1 + 10, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {
+ vkh::DescriptorImageInfo(imgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler),
+ }),
+ vkh::WriteDescriptorSet(
+ descset[0], 1, NONUNIFORMIDX, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {
+ vkh::DescriptorImageInfo(imgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler),
+ }),
+ vkh::WriteDescriptorSet(
+ descset[0], 2, INDEX2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {
+ vkh::DescriptorImageInfo(imgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler),
+ }),
+ vkh::WriteDescriptorSet(
+ descset[0], 2, INDEX2 + 5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ {
+ vkh::DescriptorImageInfo(imgview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler),
+ }),
+ });
+
+ while(Running())
+ {
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ VkImage swapimg =
+ StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+
+ vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL,
+ vkh::ClearColorValue(0.4f, 0.5f, 0.6f, 1.0f), 1,
+ vkh::ImageSubresourceRange());
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, comppipe);
+
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, 1, &descset[0], 0,
+ NULL);
+
+ Vec4i idx = {BUFIDX, INDEX1, INDEX2, 0};
+ vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT, 0,
+ sizeof(Vec4i), &idx);
+
+ static_assert(BUFIDX < DESC_ARRAY1_SIZE, "Buffer index is out of bounds");
+ static_assert(INDEX1 < DESC_ARRAY1_SIZE, "Index 1 is out of bounds");
+ static_assert(INDEX2 < DESC_ARRAY2_SIZE, "Index 2 is out of bounds");
+
+ vkCmdFillBuffer(cmd, ssbo.buffer, 0, 1024 * 1024, 0);
+
+ vkh::cmdPipelineBarrier(
+ cmd, {}, {vkh::BufferMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+ ssbo.buffer)});
+
+ // read the push constants, transform, pass them through the specified buffer to draw below
+ vkCmdDispatch(cmd, 1, 1, 1);
+
+ vkh::cmdPipelineBarrier(
+ cmd, {}, {vkh::BufferMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
+ ssbo.buffer)});
+
+ vkCmdBeginRenderPass(
+ cmd, vkh::RenderPassBeginInfo(swapRenderPass, swapFramebuffers[swapIndex], scissor),
+ VK_SUBPASS_CONTENTS_INLINE);
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+
+ // force all descriptor sets to be referenced
+ for(int i = 0; i < ARRAY_COUNT(descset); i++)
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &descset[i], 0,
+ NULL);
+
+ // bind the actual one
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &descset[0], 0,
+ NULL);
+ vkCmdSetViewport(cmd, 0, 1, &viewport);
+ vkCmdSetScissor(cmd, 0, 1, &scissor);
+ vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
+ idx = {BUFIDX, 0, 0, 0};
+ vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT, 0,
+ sizeof(Vec4i), &idx);
+ vkCmdDraw(cmd, 3, 1, 0, 0);
+
+ vkCmdEndRenderPass(cmd);
+
+ FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+
+ vkEndCommandBuffer(cmd);
+
+ Submit(0, 1, {cmd});
+
+ Present();
+ }
+
+ vkDestroyDescriptorPool(device, descpool, NULL);
+ vkDestroySampler(device, sampler, NULL);
+
+ return 0;
+ }
+};
+
+REGISTER_TEST(VK_Descriptor_Indexing);
\ No newline at end of file
diff --git a/util/test/tests/Vulkan/VK_Descriptor_Indexing.py b/util/test/tests/Vulkan/VK_Descriptor_Indexing.py
new file mode 100644
index 000000000..cf5d14542
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Descriptor_Indexing.py
@@ -0,0 +1,52 @@
+import rdtest
+import renderdoc as rd
+
+
+class VK_Descriptor_Indexing(rdtest.TestCase):
+ def get_capture(self):
+ return rdtest.run_and_capture("demos_x64", "VK_Descriptor_Indexing", 5)
+
+ def check_capture(self):
+
+ draw = self.find_draw("Draw")
+
+ self.check(draw is not None)
+
+ self.controller.SetFrameEvent(draw.eventId, False)
+
+ pipe: rd.VKState = self.controller.GetVulkanPipelineState()
+
+ # Check bindings:
+ # - buffer 15 in bind 0 should be used
+ # - images 19, 20, 21 in bind 1 should be used for the non-uniform index
+ # images 49 & 59 in bind 1 should be used for the first fixed index
+ # - images 381 & 386 in bind 2 should be used for the second fixed index
+ bind_info = {
+ 0: { 'dynamicallyUsedCount': 1, 'used': [15] },
+ 1: { 'dynamicallyUsedCount': 5, 'used': [19, 20, 21, 49, 59] },
+ 2: { 'dynamicallyUsedCount': 2, 'used': [381, 386] },
+ }
+
+ if len(pipe.graphics.descriptorSets) != 1:
+ raise rdtest.TestFailureException("Wrong number of sets is bound: {}, not 1".format(len(pipe.graphics.descriptorSets)))
+
+ desc_set: rd.VKDescriptorSet = pipe.graphics.descriptorSets[0]
+
+ binding: rd.VKDescriptorBinding
+ for bind, binding in enumerate(desc_set.bindings):
+ if binding.dynamicallyUsedCount != bind_info[bind]['dynamicallyUsedCount']:
+ raise rdtest.TestFailureException("Bind {} doesn't have the right used count. {} is not the expected count of {}"
+ .format(bind, binding.dynamicallyUsedCount, bind_info[bind]['dynamicallyUsedCount']))
+
+ el: rd.VKBindingElement
+ for idx, el in enumerate(binding.binds):
+ expected_used = idx in bind_info[bind]['used']
+ actually_used = el.dynamicallyUsed
+
+ if expected_used and not actually_used:
+ raise rdtest.TestFailureException("Bind {} element {} expected to be used, but isn't.".format(bind, idx))
+
+ if not expected_used and actually_used:
+ raise rdtest.TestFailureException("Bind {} element {} expected to be unused, but is.".format(bind, idx))
+
+ rdtest.log.success("Dynamic usage is as expected")