gkVertexAttribPointer - will a small type really save me size?


say I supply a buffer of type GL_UNSIGNED_BYTE with one byte per component to a vertex attribute of type uint using glVertexAttribPointer. I assume that the driver will convert the byte values to uint at some point, but if this is true where is this usually done? Before the data is sent to GPU memory or afterwards?


Buffers don’t have “types”. None of the buffer object APIs define the “type” of a buffer object. Buffer objects are just blocks of GPU accessible memory.

You can use buffers as the source for vertex arrays in rendering operations. And this process defines the type that will be read for a particular attribute from the buffer. But this doesn’t change the buffer object itself; it’s just one way of using GPU memory.

Type conversions are part of the process by which the rendering system reads the data into your vertex shaders. They’re either patched into the VS directly or there is dedicated hardware for doing such conversions.

That was the question where this conversion takes place, on cpu or gpu side, by the gpu hardware or explicitly by the shader coder. Or is this implementation dependent?

The last paragraph of my post specifies where it takes place.

Is there a way to find out if the gpu has dedicated hardware?

Btw, since you also answer to my vulkan threads:
Submitting byte buffers with a one-byte-per-component type to uint shader attributes will the make validation layer complain for type mismatch. Is there a convenient way to replicate the conversion methods of OpenGL in Vulkan?

Does it matter? The hardware is the hardware; it’s not going to magically get faster if you manually decompress the data yourself in the shader rather than relying on their VS-based implementation.

Premature optimization is the root of all evil.

In OpenGL, implementations are forced to support all of the various vertex formats that OpenGL says they have to. In Vulkan, you must ask the implementation which formats are available for use in vertex buffers. There are certain ones that are required to be supported, but 3-channel formats are not among them, for example.

If that’s not the problem, then you need to explain what exactly the error message is and what format you’re using in your code.

This is an OpenGL forum. So oh yes, it definitely matters. Shader patching in OpenGL is “one of the roots of all evil” when optimizing an OpenGL app for:

  1. consistent rendering performance and
  2. total time to compile/link/optimize/patch all shader permutations.

Vulkan as I understand it hasn’t solved this problem. It’s just forced it all to startup where you build your pipelines. So #2 is still at-large, in a big way.

So I would second that question.

(…and am tempted to expand it to all causes of shader patching, but I won’t.)

No, the more you more you know about how your GPU (and GPU driver) works, the more efficient software you will write the first time, and the less time you will spend/waste on needless optimizing.

It’s only a problem if you’re constantly applying different vertex formats to the same program. And AZDO techniques already tell us to avoid changing VAO state.

That is, you should be minimizing your vertex format usage regardless of whether the performance cost is in “shader patching” or just changing the vertex input hardware state. It doesn’t really matter where the cost is or which hardware has which cost. There is a cost, the cost is significant, so minimize how much of it you do.

I will try to sum up my problem again:

  • I have an OpenGL based engine with GLSL shaders that is to be ported to Vulkan. I want to keep the shaders as much as possible and use spirv cross and the GL_KHR_vulkan_glsl standard for that.
  • I have a scenario where a byte buffer is submitted as a vertex attribute which is then read out in the shader as uint.
  • When porting the code to Vulkan, the validator complained about the type mismatch between the single-component byte type of the buffer and the uint type of the shader.
  • My short-term solution is to convert the whole buffer to form byte to uint but I wonder if there is a more convenient solution that will let me keep the byte format of the buffer and the uint format of the shader without manual conversions and with good caching behaviour.

I asked this in the OpenGL forum because

a) The shader/buffer should still be compatible with the OpenGL engine.
b) I wanted to know if the byte format causes cpu side conversions in OpenGL that actually create more cpu footprint than if I provided it as uint32 to begin with. If yes, then I would just uint32 as the buffer format for OpenGL and Vulkan.

Ok, in the hopes that, maybe, someone has compassion on me:

I have a cpu side vector of triplets of uint8_t. Each uint8_t is a component in a three-component vector, each component has values from 0-255. I want to make these values available to the vertex shader in a vertex attribute of type vec3, but each component has to be divided by 256.0 first. That means a cpu side entry of ( 255, 255, 255 ) will map to shader input of (1.0, 1.0, 1.0).

Is there a VkVertexInputAttributeDescription / shader variable type combination that will achieve this out of the box? Currently I read them in as ivec3, call vec3 constructor and divide each component by 256.0 in the shader, but I wonder if there is a more elegant method.

Yeah, that’s no good. GPUs do not like misaligned reads like this. Always pad your attributes out so that each one starts on a 4-byte boundary.

That’s called a “normalized integer”.

Again, as previously stated, in Vulkan you have to ask the implementation, as they are allowed to say no to some of the more esoteric formats.

The format you would be asking about is VK_FORMAT_R8G8B8_UNORM. The “UNORM” of course meaning “unsigned normalized”. Again, this is not a required format, so you must ask your implementation if it supports it. And since this is a 3-component format that can be misaligned, I would not expect an implementation to support it. So you should be prepared to use a 4-component format (note: VK_FORMAT_R8G8B8A8_UNORM is required to be supported for vertex attributes).

1 Like

Ok, let’s sum it up

  • VK_FORMAT_R8G8B8A8_UNORM as VkFormat
  • uvec4 for attribute type?
  • Convert the uvec4 to vec4 in the shader
  • Divide each component by 256

Or is division by 256 automatically at some point?

When I linked to the “normalized integer” page, I expected you to read it. It wasn’t optional.

If you use VK_FORMAT_R8G8B8A8_UNORM as the format of a texture, would you use a usampler2D when accessing it? Would you divide it by “256” to turn it into a float?

If not, why would you expect an attribute using the exact same format to behave differently?

Replaced all VkFormats by UNORM formats + vec2/3/4 and it now works like charm!

Btw I have another case where 2.14 floating point values are stored as 16 bit signed ints (for conversion, values are multiplied/divided by 16384). Does Vulkan/GLSL have a normalized format that supports this? Or should I use, say, a scaled format and do the conversion manually?

Vulkan’s specification is not an unreadable, difficult-to-find document. It has a giant listing of every format that exists, as well as detailed explanations of what that format means. If you want to know whether there is a particular format, you can look at that.

According to this document Vulkan® 1.1.178 - A Specification

the conversion from unsigned normalized fixed-point to to floating point is defined as

f = c / (2^b - 1)

with b as the bit width. In order to obtain the conversion I want I would need a NORM format with 14-bit width per component, right? But I cannot find such a thing. I see types with all kinds of component width from 4-12 but no 14.

It isn’t there.

In particular, all normalised formats map to [0,1] for unsigned or [-1,1] for signed. AFAICT, you want something which maps to [-2,2] (more precisely, to [-2,1.99994]). You’ll need to implement that yourself, passing the data as either SINT, SNORM or SSCALED, then applying the appropriate scale factor yourself.

Using SNORM wouldn’t quite work as per your description because -2b-1 and -(2b-1-1) both map to -1.0; the value is divided by 2b-1-1 (not 2b-1) then clamped to [-1,1] to prevent -2b-1 from mapping to a value less than -1.

1 Like

It should be noted that this is probably the behavior you want, as mapping purely from a signed integer range to [-1, 1] means that you can never provide a value of 0 exactly.