Spirv-val prohibits passing StorageClass Uniform pointer to function

When passing a StorageBuffer pointer to a function, spirv-val says:
“StorageBuffer pointer operand 5[%foo] requires a variable pointers capability”

When passing a Uniform pointer to a function, spirv-val says:
“Invalid storage class for pointer operand 5[%foo]”

Where does it say a Uniform pointer is a variable pointer? I’m passing it from the OpVariable declaration.

The spec says:
A variable pointer must point to one of the following storage classes:

  • StorageBuffer
  • Workgroup (if the VariablePointers capability is declared)

I don’t see anything about Uniform pointers not being Logical. I don’t really see why I shouldn’t be able to pass a StorageBuffer pointer either. How am I supposed to make member functions that take arguments (including “this”) from different storage classes without having these pointers? I’m mean, I’m not involving any of the Variable pointer instructions listed in 2.2.2.

To fend off replies like “you should not want to do this,” I have a quality use case:

This is an entire shadertoy program defined as a C++ function object. The function object is bound as a UBO. The render member function is called by the generic fragment shader function template. The implicit object argument therefore has a Uniform storage class. By prohibiting this, all functions have to be inlined into the entry point, and modularity and readability of source goes out the window.

For graphics shaders? You aren’t.

The logical addressing model is described as this:

The Logical addressing model means pointers are abstract, having no physical size or numeric value. In this mode, pointers can only be created from existing objects, and they cannot be stored into an object, unless additional capabilities, e.g., VariablePointers , are declared to add such functionality.

You aren’t meant to be able to pass pointers to such things to different functions in logical addressing. Variable pointers allow specific exceptions, but only for StorageBuffers.

In logical addressing, pointers are a fiction that mainly exists to allow SPIR-V code for shaders and for OpenCL compute operations to share a lot of functionality. This also allows graphics shaders to gain more functionality over time without having to use a completely different language. This has advanced somewhat with variable pointers and the less restrictive PhysicalStorageBuffer64 addressing mode.

It’s also important to realize what UBOs conceptually represent. They’re not (supposed to be) pointers to memory. They’re static storage within the shader itself. Conceptually, the system uploads to the memory, it gets used during an invocation, and that’s it. You don’t get pointers to it.

That’s why something like PhysicalStorageBuffer64 doesn’t exist for UBOs; that’s not what they’re for.

So try to use variable pointers if you can.

1 Like

Alphonse said a lot of good stuff.

See 2.16.1 Universal Validation Rules, for logical pointers, where you don’t have VariablePointers or VariablePointersStorageBuffer. Then the following rule applies:

Any pointer operand to an OpFunctionCall must point into one of the following storage classes:

  • UniformConstant
  • Function
  • Private
  • Workgroup
  • AtomicCounter

So pointer-to-Uniform is not permitted. That’s the contract with the driver. In particular, conformance tests don’t check this and the driver won’t work.

Now, you say

To fend off replies like “you should not want to do this,” I have a quality use case:

By prohibiting this, all functions have to be inlined into the entry point, and modularity and readability of source goes out the window.

Your Circle compiler can still present a programming model to the user where pointer-to-Uniform is allowed. But then it has the responsibility of inlining that whole stack of functions before presenting the SPIR-V code to the driver. The user doesn’t have to know of the limitation.

In many cases this works just fine. One can construct cases where you get exponential blowup of the code, but those are rare-ish. Secondly, I know there can be loss of utility when debugging the code, but shader debugging is a more advanced case, and “proper” debug info tracks inlining anyway.