reworking the alignment readme

0.12
Ben Hansen 2 years ago
parent 4ae6c3f89f
commit 7a42a0d4db

@ -52,10 +52,17 @@ module.exports = {
'/showcase/pong/',
'/showcase/compute/',
'/showcase/alignment/',
'/showcase/imgui-demo/',
// '/showcase/imgui-demo/',
]
},
'/news/'
{
title: 'News',
collapsable: true,
children: [
'/news/0.12/',
'/news/pre-0.12/',
]
}
]
}
}

@ -27,11 +27,27 @@ struct LightUniform {
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding: u32,
color: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding2: u32,
}
```
Our `LightUniform` represents a colored point in space. We're just going to use pure white light, but it's good to allow different colors of light.
<div class="note">
The rule of thumb for alignment with WGSL structs is field alignments are
always powers of 2. For example a `vec3` may only have 3 float fields giving
it a size of 12, the alignment will be bumped up to the next power of 2 being
16. This means that you have to be more careful with how you layout your struct
in Rust.
Some developers choose the use `vec4`s instead of `vec3`s to avoid alignment
issues. You can learn more about the alignment rules in the [wgsl spec](https://www.w3.org/TR/WGSL/#alignment-and-size)
</div>
We're going to create another buffer to store our light in.
```rust
@ -39,6 +55,7 @@ let light_uniform = LightUniform {
position: [2.0, 2.0, 2.0],
_padding: 0,
color: [1.0, 1.0, 1.0],
_padding2: 0,
};
// We'll want to update our lights position, so we use COPY_DST
@ -51,6 +68,7 @@ let light_buffer = device.create_buffer_init(
);
```
Don't forget to add the `light_uniform` and `light_buffer` to `State`. After that we need to create a bind group layout and bind group for our light.
```rust

@ -1,33 +0,0 @@
# Update to 0.12!
There's not a ton of changes in this release, so the migration
wasn't too painful.
## Multi view added
## No more block attribute
## More validation
```
thread 'main' panicked at 'wgpu error: Validation Error
Caused by:
In a RenderPass
note: encoder = `Render Encoder`
In a draw command, indexed:true indirect:false
note: render pipeline = `Render Pipeline`
Buffer is bound with size 28 where the shader expects 32 in group[1] compact index 0
```
```rust
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LightUniform {
position: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding: u32,
color: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding2: u32,
}
```

@ -0,0 +1,74 @@
# Update to 0.12!
There's not a ton of changes in this release, so the migration
wasn't too painful.
## Multi view added
## No more block attribute
The WGSL spec has changed and the `block` attribute is no longer a thing.
This means that structs in WGSL no longer need to be anotated to be used
as uniform input. For example:
```wgsl
[[block]]
struct Camera {
view_pos: vec4<f32>;
view_proj: mat4x4<f32>;
};
```
Can be simplified to just
```wgsl
struct Camera {
view_pos: vec4<f32>;
view_proj: mat4x4<f32>;
};
```
## More validation
Wgpu now has a validation error where if your uniform doesn't match the
alignment specified in the shader, the program will crash when you go to
draw:
```
thread 'main' panicked at 'wgpu error: Validation Error
Caused by:
In a RenderPass
note: encoder = `Render Encoder`
In a draw command, indexed:true indirect:false
note: render pipeline = `Render Pipeline`
Buffer is bound with size 28 where the shader expects 32 in group[1] compact index 0
```
The only struct that I needed to change was the `LightUniform` struct. All
I needed to do was add a padding field:
```rust
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LightUniform {
position: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding: u32,
color: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding2: u32,
}
```
I updated the [lighting tutorial](../../intermediate/tutorial10-lighting) to reflect
this change.
## Misc
Due to the recent deploy to [anyhow](https://docs.rs/anyhow/latest/) that
breaks glob imports (aka. `use anyhow::*`), I had to switch qualified
imports and uses (ie. `anyhow::Result`). This was mostly an issue on my
build scripts for some of showcase examples.
The main tutorial examples weren't affected, and the changes are minor, so
if your curious feel free to look at the repo.

Before

Width:  |  Height:  |  Size: 722 KiB

After

Width:  |  Height:  |  Size: 722 KiB

@ -1,4 +1,4 @@
# News
# News (Pre 0.12)
## Pong working on the web

@ -1,29 +1,91 @@
# Memory Layout in GLSL
## Alignments
The address of the position of an instance in memory has to a multiple of its alignment.
Normally alignment is the same as size. Exceptions are vec3, structs and arrays.
A vec3 is padded to be a vec4 which means it behaves as if it was a vec4 just that the last entry is not used.
`{i,u,b}vec` is shorthand for `ivec`, `uvec`, `bvec` so a vector of `int` or `uint` or `bool`. A `vec` is a vec of `float`.
| type | Alignment in bytes | size in bytes |
| --------------------------- | ------------------ | ------------- |
| int, uint, float, bool | 4 | 4 |
| double | 8 | 8 |
| {i,u,b}vec2 | 8 | 8 |
| dvec2 | 16 | 16 |
| {i,u,b}vec3 | **16** | 12 |
| {i,u,b}vec4 | 16 | 16 |
| dvec3 | **32** | 24 |
| dvec4 | 32 | 32 |
| mat3 (like array of 3 vec3) | **16** | 3*16 |
| mat4 (like array of 4 vec4) | 16 | 4*16 |
[Reference](https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159) section 7.6.2.2
# Memory Layout in WGSL
## Alignment of vertex and index buffers
Vertex buffers require defining a `VertexBufferLayout`, so the memory alignment is whatever
you tell WebGPU it should be. This can be really convenient for keeping down memory usage
on the GPU.
The Index Buffer use the alignment of whatever primitive type you specify via the `IndexFormat`
you pass into `RenderEncoder::set_index_buffer()`.
## Alignment of Uniform and Storage buffers
GPUs are designed to process thousands of pixels in parallel. In order to achieve this,
some sacrifices had to be made. Graphics hardware likes to have all the bytes you intend
on processing aligned by powers of 2. The exact specifics of why this is are beyond
my level of knowledge, but it's important to know so that you can trouble shoot why your
shaders aren't working.
<!-- The The address of the position of an instance in memory has to a multiple of its alignment.
Normally alignment is the same as size. Exceptions are vec3, structs and arrays. A vec3
is padded to be a vec4 which means it behaves as if it was a vec4 just that the last entry
is not used. -->
Let's take a look at the following table:
---------------------------------------------------------------
| Type | Alignment in Bytes | Size in Bytes |
|------------------------|--------------------|---------------|
| scalar (i32, u32, f32) | 4 | 4 |
| vec2&lt;T&gt; | 8 | 8 |
| vec3&lt;T&gt; | **16** | 12 |
| vec4&lt;T&gt; | 16 | 16 |
You can see for `vec3` the alignment is the next power of 2 from the size, 16. This can
catche beginners (and even veterans) as it's not the most intuitive. This becomes especially
important when we start laying out structs. Take the light struct from the [lighting tutorial](../../intermediate/tutorial10-lighting/#seeing-the-light):
You can see the full table of the alignments in section [4.3.7.1 of the WGSL spec](https://www.w3.org/TR/WGSL/#alignment-and-size)
```wgsl
struct Light {
position: vec3<f32>;
color: vec3<f32>;
};
```
So what's the alignment of this scruct? Your first guess would be that it's the sum of
the alignments of the individual fields. That might make sense if we were in Rust-land,
but in shader-land, it's a little more involved. The alignment for a given struct is given
by the following equation:
```
// S is the struct in question
// M is a member of the struct
AlignOf(S) = max(AlignOfMember(S, M1), ... , AlignOfMember(S, Mn))
```
Basically the alignment of the struct is the maximum of the alignments of the members of
the struct. This means that:
```
AlignOf(Light)
= max(AlignOfMember(Light, position), AlignOfMember(Light, color))
= max(16, 16)
= 16
```
This is why the `LightUniform` has those padding fields. WGPU won't accept it if the data
is not aligned correctly.
## How to deal with alignment issues
In general 16, is the max alignment you'll see. In that case you might think that we should
be able to do something like the following:
```rust
#[repr(C, align(16))]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LightUniform {
position: [f32; 3],
color: [f32; 3],
}
```
But this won't compile. The [bytemuck crate](https://docs.rs/bytemuck/) doesn't work with
structs with implicit padding bytes. Rust can't guarantee that the memory between the fields
has been initialized properly. The are potential security
## In WGPU

Loading…
Cancel
Save