summaryrefslogtreecommitdiff
path: root/doc/UNIFORM_BUFFER_GUIDELINES.md
blob: ac02223e6ef6d5ab6489cc8f75b97e8cdae7c339 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# WGSL Uniform Buffer Guidelines

This document outlines the rules and best practices for defining and using uniform buffers in WGSL shaders within this project, focusing on alignment, size, and consistency.

## WGSL Alignment Rules

Understanding WGSL's memory layout rules is crucial for correct uniform buffer implementation. The following are the general alignment requirements for common WGSL types:

- `f32`: 4-byte alignment.
- `vec2<f32>`: 8-byte alignment (4 bytes per component * 2 components = 8 bytes).
- `vec3<f32>`: 16-byte alignment (4 bytes per component * 3 components = 12 bytes, padded to 16).
- `vec4<f32>`: 16-byte alignment (4 bytes per component * 4 components = 16 bytes).
- `array<T, N>`: The alignment of an array is typically the alignment of its base type `T`.

Structs are padded to the alignment of their largest member. Any trailing space in a struct is also padded to match the maximum alignment of any member within the struct.

## Standard Uniform Buffer Pattern

To maintain consistency and facilitate efficient rendering, a standard pattern for uniform buffer usage is established:

- **Binding 0 & 1:** Reserved for Sampler and Texture access (handled by `pp_update_bind_group`).
- **Binding 2:** **Common Uniforms** (`CommonPostProcessUniforms` or similar). This buffer should contain frequently used data like resolution, aspect ratio, time, beat, and audio intensity.
- **Binding 3:** **Effect-Specific Parameters**. This buffer holds parameters unique to a particular effect (e.g., `strength`, `speed`, `fade_amount`).

This pattern ensures that common data is shared efficiently across effects, while effect-specific data remains isolated.

## Defining Uniform Structs

### WGSL Definitions

When defining uniform structs in WGSL, adhere to the following:

- **Explicit Padding:** Use padding fields (`_pad0`, `_pad1`, etc.) where necessary to ensure correct alignment, especially when mixing types of different alignment requirements (e.g., `vec2<f32>` followed by `f32`s).
- **Use `vec2<f32>` for 8-byte padding:** If you need 8 bytes of padding, use `_pad0: vec2<f32>` instead of `_pad0: f32, _pad1: f32` for potentially better clarity and to leverage WGSL's type system.
- **Minimize Padding:** Only add padding where required by alignment rules to reduce memory usage.

**Example (CommonPostProcessUniforms / HeptagonUniforms):**

```wgsl
struct CommonUniforms {
  resolution: vec2<f32>,
  _pad0: vec2<f32>, // 8 bytes padding to align subsequent members
  aspect_ratio: f32,
  time: f32,
  beat: f32,
  audio_intensity: f32,
};
// Expected size: 32 bytes
```

**Example (EffectParams with f32 members):**

```wgsl
struct EffectParams {
  parameter1: f32,
  parameter2: f32,
  // ... more parameters ...
};
// Expected size: 8 bytes (if only two f32s)
```

### C++ Definitions and Validation

For every WGSL uniform struct, a corresponding C++ struct must exist. This C++ struct must include a `static_assert` to verify its size and alignment matches the WGSL definition.

- **Mirror WGSL Structure:** The C++ struct should mirror the WGSL struct's member order and types as closely as possible to ensure accurate size calculation.
- **`static_assert`:** Always include `static_assert(sizeof(MyStruct) == EXPECTED_SIZE, "MyStruct must be EXPECTED_SIZE bytes for WGSL alignment");`.
- **Use `float` for `f32`:** Use `float` for `f32` in C++.
- **Use `vec2<f32>` mapping:** If WGSL uses `vec2<f32>`, map it to an equivalent C++ type that occupies 8 bytes, typically `float[2]` or a `struct Vec2 { float x, y; }` if more complex type handling is needed.
- **Padding:** C++ padding rules can differ from WGSL. Pay close attention to `static_assert` for validation.

**Example (C++ CommonPostProcessUniforms):**

```cpp
struct CommonPostProcessUniforms {
  vec2 resolution;    // 8 bytes
  float _pad[2];      // 8 bytes padding (matches vec2<f32> in WGSL)
  float aspect_ratio; // 4 bytes
  float time;         // 4 bytes
  float beat;         // 4 bytes
  float audio_intensity; // 4 bytes
};
static_assert(sizeof(CommonPostProcessUniforms) == 32, 
              "CommonPostProcessUniforms must be 32 bytes for WGSL alignment");
```

**Example (C++ GaussianBlurParams):**

```cpp
struct GaussianBlurParams {
  float strength = 2.0f;
  float _pad = 0.0f;
};
static_assert(sizeof(GaussianBlurParams) == 8, 
              "GaussianBlurParams must be 8 bytes for WGSL alignment");
```

## Handling Common Pitfalls

- **`vec3<f32>` Padding:** Avoid using `vec3<f32>` for padding in WGSL, as it has a 16-byte alignment. If padding is needed, use `vec2<f32>` for 8 bytes or individual `f32`s for 4-byte alignment.
- **C++ vs. WGSL Alignment:** Always rely on `static_assert` in C++ and verify against WGSL alignment rules. C++ padding rules might differ, and the `static_assert` is the ultimate arbiter.
- **Unmatched Structs:** Ensure every WGSL uniform struct has a corresponding C++ struct with a matching `static_assert`.

## Validation Tool

The `tools/validate_uniforms.py` script is integrated into the build system. It automatically checks for inconsistencies between WGSL and C++ uniform struct definitions and reports any size mismatches. Ensure this script passes for all new or modified uniform definitions.