Parallel reduce-add with push constants unexpectedly works

To give some context: I’m trying some approaches for performing parallel reductions on the GPU using compute shaders. The current code just adds together two halves of the array for each dispatch, so I need to perform log(N) compute shader dispatches. However, each shader invocation obviously needs to know the index of the current dispatch or the size of the current array half. To do that I just passed it using vkCmdPushConstants like this:

0 - Begin command buffer
1 - Bind descriptor set (set the target storage buffer)
2 - Bind the compute shader pipeline
3 - Set step_size to half the storage buffer length in elements
4 - while step_size != 0:
 a - Update push constants with the step_size value
 b - Dispatch the compute shader
 c - Insert memory barrier
 d - step_size /= 2
5 - End command buffer, submit and await.

While this worked, I later realized that this contradicts what I know about push constants. That is, I would have expected the value of the push constants to be immutable after the first invocation, being bound to the command buffer, which means that I should be seeing an error or that the reduction should not be working. Is my understanding of how push constants work incorrect?

Low level APIs do not owe you any errors. They preassume your code is correct.

Undefined behavior is undefined. If it was guaranteed it does not work or always yield an error message, it would be partially-defined behavior.

I think I presented my question poorly, I don’t have an issue with Vulkan’s error reporting facilities (in fact I quite like them).

My question is whether, for a compute pipeline that has been bound once, it is correct to repeatedly perform push constant updates and dispatches. This is because I thought that push constant data was fixed per-command buffer instead of per-dispatch/draw.

I have been re-reading the spec and I think that what I was doing was correct, but I would like to be 100% sure. This may be a dumb question, I’ve been working on code related to specialization constants for a while and I think I might be expecting push constants to be more limited than they are.

Right. I don’t immediately recall per-what they are, but pretty sure they are not per-cmdbuff. That would make no sense. They are per action command, but there are some headaches around pipeline binding, I think.

I mean they are command buffer state (and new command buffer starts with fresh state, which all needs to be built again), but they can be changed throughout or supplied per-partes.

1 Like

There are no commands in Vulkan which are “fixed per-command buffer”. Any commands which set state (like push constant data, descriptor bindings, etc) can be set to different state at any time after setting them within the same CB. Most state is local to a CB, but all that means is that state is not maintained between CBs.

State commands do not travel backwards in time, however; any action commands (like rendering) which consumes some state use whatever state was set prior to recording the action command. They cannot be affected by any CB state commands recorded after the action command.

Note that state commands are different from commands like descriptor set writes. These don’t set state; they modify objects. Note that they are also not recorded into CBs; they happen immediately.

1 Like