You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 05_Uniform_buffers/01_Descriptor_pool_and_sets.md
+120Lines changed: 120 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -251,6 +251,126 @@ corrects for aspect ratio. The `updateUniformBuffer` takes care of screen
251
251
resizing, so we don't need to recreate the descriptor set in
252
252
`recreateSwapChain`.
253
253
254
+
## Alignment requirements
255
+
256
+
One thing we've glossed over so far is how exactly the data in the C++ structure should match with the uniform definition in the shader. It seems obvious enough to simply use the same types in both:
257
+
258
+
```c++
259
+
structUniformBufferObject {
260
+
glm::mat4 model;
261
+
glm::mat4 view;
262
+
glm::mat4 proj;
263
+
};
264
+
265
+
layout(binding = 0) uniform UniformBufferObject {
266
+
mat4 model;
267
+
mat4 view;
268
+
mat4 proj;
269
+
} ubo;
270
+
```
271
+
272
+
However, that's not all there is to it. For example, try modifying the struct and shader to look like this:
273
+
274
+
```c++
275
+
struct UniformBufferObject {
276
+
glm::vec2 foo;
277
+
glm::mat4 model;
278
+
glm::mat4 view;
279
+
glm::mat4 proj;
280
+
};
281
+
282
+
layout(binding = 0) uniform UniformBufferObject {
283
+
vec2 foo;
284
+
mat4 model;
285
+
mat4 view;
286
+
mat4 proj;
287
+
} ubo;
288
+
```
289
+
290
+
Recompile your shader and your program and run it and you'll find that the colorful square you worked so far has disappeared! That's because we haven't taken into account the *alignment requirements*.
291
+
292
+
Vulkan expects the data in your structure to be aligned in memory in a specific way, for example:
293
+
294
+
* Scalars have to be aligned by N (= 4 bytes given 32 bit floats).
295
+
* A `vec2` must be aligned by 2N (= 8 bytes)
296
+
* A `vec3` or `vec4` must be aligned by 4N (= 16 bytes)
297
+
* A nested structure must be aligned by the base alignment of its members rounded up to a multiple of 16.
298
+
* A `mat4` matrix must have the same alignment as a `vec4`.
299
+
300
+
You can find the full list of alignment requirements in [the specification](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/chap14.html#interfaces-resources-layout).
301
+
302
+
Our original shader with just three `mat4` fields already met the alignment requirements. As each `mat4` is 4 x 4 x 4 = 64 bytes in size, `model` has an offset of `0`, `view` has an offset of 64 and `proj` has an offset of 128. All of these are multiples of 16 and that's why it worked fine.
303
+
304
+
The new structure starts with a `vec2` which is only 8 bytes in size and therefore throws off all of the offsets. Now `model` has an offset of `8`, `view` an offset of `72` and `proj` an offset of `136`, none of which are multiples of 16. To fix this problem we can use the [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) specifier introduced in C++11:
305
+
306
+
```c++
307
+
structUniformBufferObject {
308
+
glm::vec2 foo;
309
+
alignas(16) glm::mat4 model;
310
+
glm::mat4 view;
311
+
glm::mat4 proj;
312
+
};
313
+
```
314
+
315
+
If you now compile and run your program again you should see that the shader correctly receives its matrix values once again.
316
+
317
+
Luckily there is a way to not have to think about these alignment requirements *most* of the time. We can define `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` right before including GLM:
318
+
319
+
```c++
320
+
#define GLM_FORCE_RADIANS
321
+
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES
322
+
#include <glm/glm.hpp>
323
+
```
324
+
325
+
This will force GLM to use a version of `vec2` and `mat4` that has the alignment requirements already specified for us. If you add this definition then you can remove the `alignas` specifier and your program should still work.
326
+
327
+
Unfortunately this method can break down if you start using nested structures. Consider the following definition in the C++ code:
328
+
329
+
```c++
330
+
structFoo {
331
+
glm::vec2 v;
332
+
};
333
+
334
+
struct UniformBufferObject {
335
+
Foo f1;
336
+
Foo f2;
337
+
};
338
+
```
339
+
340
+
And the following shader definition:
341
+
342
+
```c++
343
+
struct Foo {
344
+
vec2 v;
345
+
};
346
+
347
+
layout(binding = 0) uniform UniformBufferObject {
348
+
Foo f1;
349
+
Foo f2;
350
+
} ubo;
351
+
```
352
+
353
+
In this case `f2` will have an offset of `8` whereas it should have an offset of `16` since it is a nested structure. In this case you must specify the alignment yourself:
354
+
355
+
```c++
356
+
structUniformBufferObject {
357
+
Foo f1;
358
+
alignas(16) Foo f2;
359
+
};
360
+
```
361
+
362
+
These gotchas are a good reason to always be explicit about alignment. That way you won't be caught offguard by the strange symptoms of alignment errors.
363
+
364
+
```c++
365
+
struct UniformBufferObject {
366
+
alignas(16) glm::mat4 model;
367
+
alignas(16) glm::mat4 view;
368
+
alignas(16) glm::mat4 proj;
369
+
};
370
+
```
371
+
372
+
Don't forget to recompile your shader after removing the `foo` field.
373
+
254
374
## Multiple descriptor sets
255
375
256
376
As some of the structures and function calls hinted at, it is actually possible
0 commit comments