Playing around with bindless rendering, I have one big static SSBO that holds my vertex data. The vertices are packed in memory as a contiguous array where each vertex has the following layout:
| Position (floats) | Normal (snorm shorts) | Pad |
--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
| P.x | P.y | P.z | N.x | N.y | N.z | |
--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
| float | float | float | uint | uint |
Note how each vertex is 20 bytes / 5 "words" / 1.25 vec4
s. Not exactly a round number for a GPU. So instead of doing a bunch of padding and using uneccessary memory, I have opted to unpack the data "manually".
Vertex shader:
...
layout(std430, set = 0, binding = 1)
readonly buffer FloatStaticBuffer
{
float staticBufferFloats[];
};
layout(std430, set = 0, binding = 1) // Using the same binding?!
readonly buffer UintStaticBuffer
{
uint staticBufferUInts[];
};
...
void main()
{
const uint vertexBaseDataI = gl_VertexIndex * 5u;
// Unpack position
const vec3 position = vec3(
staticBufferFloats[vertexBaseDataI 0u],
staticBufferFloats[vertexBaseDataI 1u],
staticBufferFloats[vertexBaseDataI 2u]);
// Unpack normal
const vec3 normal = vec3(
unpackSnorm2x16(staticBufferUInts[vertexBaseDataI 3u]),
unpackSnorm2x16(staticBufferUInts[vertexBaseDataI 4u]).x);
...
}
It is awfully convenient to be able to "alias" the buffer as both float
and uint
data.
The question: is "aliasing" a SSBO this way a terrible idea, and I'm just getting lucky, or is this actually a valid option that would work across platforms?
Alternatives:
- Use just one buffer, say
staticBufferUInts
, and then useuintBitsToFloat
to extract the positions. Not a big deal, but might have a small performance cost? - Bind the same buffer twice on the CPU to two different bindings. Again, not a big deal, just slightly annoying.
CodePudding user response:
Vulkan allows incompatible resources to alias in memory as long as no malformed values are read from it. (Actually, I think it's allowed even when you read from the invalid sections - you should just get garbage. But I can't find the section of the standard right now that spells this out. The Vulkan standard is way too complicated.)
From the standard, section "Memory Aliasing":
Otherwise, the aliases interpret the contents of the memory differently, and writes via one alias make the contents of memory partially or completely undefined to the other alias. If the first alias is a host-accessible subresource, then the bytes affected are those written by the memory operations according to its addressing scheme. If the first alias is not host-accessible, then the bytes affected are those overlapped by the image subresources that were written. If the second alias is a host-accessible subresource, the affected bytes become undefined. If the second alias is not host-accessible, all sparse image blocks (for sparse partially-resident images) or all image subresources (for non-sparse image and fully resident sparse images) that overlap the affected bytes become undefined.
Note that the standard talks about bytes being written and becoming undefined in aliasing resources. It's not the entire resource that becomes invalid.
Let's see it this way: You have two aliasing SSBOs (in reality just one that's bound twice) with different types (float, short int). Any bytes that you wrote floats into became valid in the "float view" and invalid in the "int view" the moment you wrote into the buffer. The same goes for the ints: The bytes occupied by them have become valid in the int view but invalid in the float view. According to the standard, this means that both views have invalid sections in them; however, neither of them is fully invalid. In particular, the sections you care about are still valid and may be read from.
In short: It's allowed.