Textures and Draw Calls

I have a conceptual question after hours perusing code and information on the topic of textures, arrays of textures and such.

There is this natural desire to make things perform well and it works beautifully in Vulkan by setting up a big buffer with all the vertices for my all my models and using their corresponding indices I’m able to reduce the vkDrawIndexed calls to the number of models even accomplishing model matrices via push constants.

Great. Now come textures and mess it all up…

Regardless of various techniques I read about I cannot for the world see a way of accomplishing what I want without going down to a per mesh draw loop.

Do I have tunnel vision or is there really no other way and if so how expensive is the potential multiplier effect of draw calls in the command buffer.

In opengl the draw call was THE number 1 cost in such a context - or at least that’s what was said and I could read every where.

Sadly, as nice as all the vast majority of code examples are for starting vulkan, the nitty gritty of establishing a framework to draw models with textures and some movement is something that a whole lot of people will want to accomplish in a good way (decent performance without having to break up render work into n number of chunks depending on textures and instance drawing…

But, yeah, maybe it’s simple and I fail to see it…

oh, forgot one thing: so even transferring texture image data to device local memory initially I’d have to adjust any index in the shader on a per mesh basis which, again, seems to say there is no way around a per mesh draw call.

ok, one last thought added: I see there is a DrawIndirect but I struggle to fully understand that before I have read more about it. If anybody has used it any ideas/comments would be appreciated.

You were either reading bad information or misunderstanding it (it’s a common misunderstanding because “draws == bad” is easier to parrot than useful facts). It’s the state changes between draw calls that are a problem. And not every state change is equally costly.

It’s also important to remember that Vulkan is not OpenGL. The bulk of OpenGL’s performance issues are structural API issues, not GPU issues.

Generally, the nature of Vulkan’s APIs give you a first-order approximation of the relative cost of some operation. For example, consider the drawing commands you get: multi-draw-indirect (indexed and non-indexed), and single-draw-direct. There is no direct equivalent to glMultiDrawArrays/Elements; you would have to do an upload into GPU-accessible memory to get an exact equivalent, which isn’t something you can do in a render pass.

This suggests that the CPU cost of a draw call is pretty minimal; if it wasn’t, there would be a way to more easily bundle a group of draws into a single invocation. Indirect multi-draw is important because the GPU is typically generating that data.

(Of course with the DrawIndex value, being able to do a multi-draw using CPU storage without having to upload would be a nice thing… unless GPUs don’t like reading that stuff from command buffer data).

Changing memory vs. push constants for providing new information to a shader. You need to explicitly double-buffer memory changes, which affects which descriptors you bind and so forth. By contrast, push constants work in-situ within a render pass, requiring no action other than just providing the data. That suggests that push constants are faster but more restrictive in size compared to bulk memory operations.

The complexity of render passes and the nature of having to explicitly start and end them suggests that changing render passes is not cheap. The fact that pipeline state is big suggests that changing it is not cheap (but probably cheaper than render pass state).

Basically, obvious and sane use of the API will get you good results. That’s how the API is designed.

1 Like

Thank you @Alfonse_Reinheart for pointing out that discrepancy on the cost of draw calls.

Despite using graphics APIs just as a hobby I do realize that Vulkan is quite different from OpenGl.

I try to use its performance features by moving most things to device local memory, I also use push constants.

Yet the state change with drawing becomes relevant with textures I feel where I can use push constants only in so far as communicating indexes into arrays to know what texture to use. But, still, I’m going to have to communicate that per mesh (and potentially I could have more textures per mesh even - normals say) and draw per mesh.

Maybe that is a really good way to go (as compared to descriptor bindings) I guess.

I wish there was better information on Indirect Drawing out there, I think it might suit me better, working in memory I prefer to all these bindings and structs that come with it.

btw. thank you for the video link - watching it right now :slight_smile:

I found an older thread relating to this: Vulkan (vs.)? MultiDrawIndirect

together with this resource on indirect drawing (https://vkguide.dev/docs/gpudriven/draw_indirect/) and another example on arrays of textures (http://kylehalladay.com/blog/tutorial/vulkan/2018/01/28/Textue-Arrays-Vulkan.html) it gives me some good pointers where to head.