You would select the matrix from an array of all matrices you intend to use.
This is pretty standard stuff in high-performance rendering. You change a single index value per-object, which is used by the shader to fetch that object’s per-object data from buffers. It can also be used to fetch textures from array textures or arrays of samplers.
InstanceIndex is a popular choice; you can set the base instance for each draw command in a multidraw indirect command. Or rather, it would have been in OpenGL if
gl_InstanceID worked (in OpenGL, it doesn’t include the base instance). Fortunately, Vulkan’s
InstanceIndex does work for this.
I’m using VkUpdateDescriptorSets to update the texture descriptor set with the object’s texture, right before each object is drawn.
OK, Vulkan is very well designed for performance. That is, the API makes it abundantly clear when you are about to do something that is not good for performance. Just look at the renderpass stuff; does it seem to you like it’s reasonably fast to change renderpasses frequently? Of course not; it’s harder to change renderpasses (requiring lots of pipelines, since they’re all linked to the renderpass) than it is to change subpasses.
Subpasses are cheap; renderpasses are expensive.
Similarly, which will probably take more time:
VkUpdateDescriptorSets which changes the entire descriptor set, or using a push-constant, which the shader uses to select which texture to sample from, thus allowing you to have a single descriptor set which remains static across multiple rendering calls?
OpenGL makes the former (binding a texture) simpler and easier to use than the latter. But the latter is almost certainly faster. Vulkan makes it clear which is the better choice for performance.
You seem familiar with standard OpenGL-style development. You should take some time to look at the AZDO presentation, which effectively lays out how Vulkan-style development and structure ought to be done (by using OpenGL features to emulate it).
You should not attempt to code Vulkan applications the way you would OpenGL applications. Vulkan exists for performance; if you’re not willing to do things the fast way, you shouldn’t be using it at all.
I can’t use method 2b, since I can’t store images in a sequential buffer.
You don’t need to store them in a sequential buffer. You store them in an array of images. They all get one binding location, and your descriptor layout would contain an array of images for that location.