@vgpu/wgsl-std 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vercel, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @vgpu/wgsl-std
2
+
3
+ Pure WGSL utility snippets for use with `@vgpu/wgsl` import resolution.
4
+
5
+ This package intentionally ships raw `.wgsl` modules instead of JavaScript wrappers or a macro/include system. Import the explicit utility area you need:
6
+
7
+ ```wgsl
8
+ import { saturate, remap, safeNormalize3, rotate2d } from "@vgpu/wgsl-std/math";
9
+ import { srgbToLinear3, linearToSrgb3, luminance, applyExposure } from "@vgpu/wgsl-std/color";
10
+ import { vogelDisk, hammersley2d } from "@vgpu/wgsl-std/sampling";
11
+ import { pi, tau, goldenAngle } from "@vgpu/wgsl-std/constants";
12
+ ```
13
+
14
+ There is no root WGSL export. Subpath exports resolve to physical WGSL files:
15
+
16
+ - `@vgpu/wgsl-std/math` -> `src/math/index.wgsl`
17
+ - `@vgpu/wgsl-std/color` -> `src/color/index.wgsl`
18
+ - `@vgpu/wgsl-std/sampling` -> `src/sampling/index.wgsl`
19
+ - `@vgpu/wgsl-std/constants` -> `src/constants/index.wgsl`
20
+
21
+ WGSL snippets in this package must stay pure declaration modules: functions, constants, structs, and aliases only. They must not introduce hidden bindings, resource variables, overrides, or entry points.
22
+
23
+ ## Constants
24
+
25
+ `@vgpu/wgsl-std/constants` includes common math constants as plain WGSL `const` declarations:
26
+
27
+ - `pi: f32`: π.
28
+ - `tau: f32`: 2π.
29
+ - `halfPi: f32`: π / 2.
30
+ - `quarterPi: f32`: π / 4.
31
+ - `invPi: f32`: 1 / π.
32
+ - `invTau: f32`: 1 / 2π.
33
+ - `goldenRatio: f32`: φ.
34
+ - `goldenAngle: f32`: golden angle in radians.
35
+
36
+ The constants module is intentionally small so importing it does not add much WGSL text. `resolveShader()` also performs conservative declaration-level dead-code elimination, so unused declarations from larger WGSL utility modules are pruned from shaders with entry points before minification. `goldenAngle` remains available from `@vgpu/wgsl-std/sampling` for sampling-only shaders.
37
+
38
+ See `src/constants/index.docs.md` for examples and precision notes.
39
+
40
+ ## Math utilities
41
+
42
+ `@vgpu/wgsl-std/math` includes scalar range helpers and vector safety helpers:
43
+
44
+ - `saturate(value: f32) -> f32`: clamp to `[0.0, 1.0]`.
45
+ - `clamp01(value: f32) -> f32`: alias for `saturate`.
46
+ - `inverseLerp(rangeStart: f32, rangeEnd: f32, value: f32) -> f32`: unclamped inverse lerp; returns `0.0` when `rangeStart == rangeEnd`.
47
+ - `remap(inMin: f32, inMax: f32, outMin: f32, outMax: f32, value: f32) -> f32`: unclamped range remap; returns `outMin` when the input range is zero-length.
48
+ - `safeNormalize2/3/4(value, fallback)`: normalize non-zero vectors and return `fallback` for zero-length vectors. WGSL has no user-defined generic overloads, so dimensions are explicit in v1.
49
+ - `rotate2d(value: vec2f, radians: f32) -> vec2f`: counter-clockwise 2D rotation by radians.
50
+
51
+ See `src/math/index.docs.md` for examples and edge-case notes.
52
+
53
+ ## Color utilities
54
+
55
+ `@vgpu/wgsl-std/color` includes non-PBR color helpers:
56
+
57
+ - `srgbToLinear(value: f32) -> f32`: standard IEC/sRGB decode transfer for one channel.
58
+ - `srgbToLinear3(color: vec3f) -> vec3f`: decode RGB channels from sRGB to linear light.
59
+ - `srgbToLinear4(color: vec4f) -> vec4f`: decode RGB channels and preserve alpha.
60
+ - `linearToSrgb(value: f32) -> f32`: standard IEC/sRGB encode transfer for one channel.
61
+ - `linearToSrgb3(color: vec3f) -> vec3f`: encode RGB channels from linear light to sRGB.
62
+ - `linearToSrgb4(color: vec4f) -> vec4f`: encode RGB channels and preserve alpha.
63
+ - `luminance(color: vec3f) -> f32`: relative luminance using Rec.709/sRGB coefficients `(0.2126, 0.7152, 0.0722)`. Pass linear-light color.
64
+ - `applyExposure(color: vec3f, exposure: f32) -> vec3f`: multiply by `exp2(exposure)`, where exposure is measured in stops/EV.
65
+
66
+ WGSL has no user-defined generics, so vector transfer helpers use `3`/`4` suffixes while scalar transfer helpers keep the base names. Color transfer helpers expect normal `[0.0, 1.0]` color-channel inputs but do not clamp; clamp explicitly with `@vgpu/wgsl-std/math` when desired. The transfer and luminance formulas are mathematically standard and useful for physically meaningful conversions, but they may not match artistic/tuned approximations in an existing shader without deliberate visual review. The color module intentionally defers PBR helpers and tonemappers (ACES/Hable/Filament/Reinhard) so applications choose their own display transform.
67
+
68
+ See `src/color/index.docs.md` for formulas, examples, and performance notes.
69
+
70
+ ## Sampling utilities
71
+
72
+ `@vgpu/wgsl-std/sampling` includes deterministic sampling helpers for unit-disk kernels and low-discrepancy 2D point sets:
73
+
74
+ - `goldenAngle: f32`: golden angle in radians, rounded to WGSL `f32` precision (`2.3999631`).
75
+ - `vogelDisk(index: u32, count: u32, phi: f32) -> vec2f`: Vogel spiral sample in the unit disk for `index < count`; `phi` rotates the pattern in radians. Returns `vec2f(0.0)` when `count == 0u` to avoid division by zero.
76
+ - `radicalInverseVdc(bits: u32) -> f32`: standard base-2 Van der Corput radical inverse via deterministic bit reversal, clamped to the largest `f32` below `1.0` for high reversed-bit values whose WGSL `f32` product would otherwise round to `1.0`.
77
+ - `hammersley2d(index: u32, count: u32) -> vec2f`: Hammersley point `(index / count, radicalInverseVdc(index))`; returns `vec2f(0.0)` when `count == 0u`.
78
+
79
+ Before, shader code often repeats the sampling math manually:
80
+
81
+ ```wgsl
82
+ fn localVogelDisk(index: u32, count: u32, phi: f32) -> vec2f {
83
+ if (count == 0u) {
84
+ return vec2f(0.0);
85
+ }
86
+ let angle = f32(index) * 2.3999631 + phi;
87
+ let radius = sqrt((f32(index) + 0.5) / f32(count));
88
+ return vec2f(cos(angle), sin(angle)) * radius;
89
+ }
90
+ ```
91
+
92
+ With the utility module, import the helper explicitly:
93
+
94
+ ```wgsl
95
+ import { vogelDisk } from "@vgpu/wgsl-std/sampling";
96
+
97
+ fn localVogelDisk(index: u32, count: u32, phi: f32) -> vec2f {
98
+ return vogelDisk(index, count, phi);
99
+ }
100
+ ```
101
+
102
+ Performance note: `vogelDisk` uses `sqrt`, `cos`, and `sin`; precompute fixed kernels if they are reused heavily. `hammersley2d`/`radicalInverseVdc` are stateless integer/float math and are not random-number generators.
103
+
104
+ Provenance: Vogel disk sampling is an original WGSL transcription of Vogel's 1979 published golden-angle phyllotaxis model. Van der Corput and Hammersley samples are standard low-discrepancy sequence formulas implemented here with original WGSL bit operations; they are deliberate reviewed additions beyond the original minimal `vogelDisk` requirement to satisfy the updated user preference for fewer deferrals while staying provenance-clean. `concentricDisk` is deferred for separate API review of disk-mapping conventions and edge behavior, and Perlin/simplex/fBM/value noise plus shader-magic hash/random snippets are deferred for separate API and provenance review.
105
+
106
+ See `src/sampling/index.docs.md` for examples, input ranges, and edge-case notes.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@vgpu/wgsl-std",
3
+ "version": "0.0.5",
4
+ "description": "Pure WGSL utility snippets for vgpu shaders.",
5
+ "keywords": [
6
+ "webgpu",
7
+ "graphics",
8
+ "rendering",
9
+ "wgsl",
10
+ "shader",
11
+ "utilities"
12
+ ],
13
+ "homepage": "https://github.com/vercel-labs/vgpu#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/vercel-labs/vgpu/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/vercel-labs/vgpu.git",
20
+ "directory": "packages/wgsl-std"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Matias Gonzalez <matiasngf@hotmail.com>",
24
+ "private": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "sideEffects": false,
29
+ "type": "module",
30
+ "exports": {
31
+ "./math": "./src/math/index.wgsl",
32
+ "./color": "./src/color/index.wgsl",
33
+ "./sampling": "./src/sampling/index.wgsl",
34
+ "./constants": "./src/constants/index.wgsl"
35
+ },
36
+ "files": [
37
+ "src/**/*.wgsl",
38
+ "src/**/*.docs.md",
39
+ "README.md",
40
+ "LICENSE"
41
+ ]
42
+ }
@@ -0,0 +1,72 @@
1
+ # @vgpu/wgsl-std/color
2
+
3
+ Raw WGSL color utility module for `@vgpu/wgsl` imports. The module contains pure declarations only: no bindings, overrides, hidden state, or entry points.
4
+
5
+ ```wgsl
6
+ import { srgbToLinear3, linearToSrgb3, luminance, applyExposure } from "@vgpu/wgsl-std/color";
7
+
8
+ fn shade(baseColorSrgb: vec3f, exposureStops: f32) -> vec3f {
9
+ let linear = srgbToLinear3(baseColorSrgb);
10
+ let exposed = applyExposure(linear, exposureStops);
11
+ return linearToSrgb3(exposed);
12
+ }
13
+ ```
14
+
15
+ ## API
16
+
17
+ WGSL has no user-defined generics, so v1 uses an explicit dimensional suffix for vector transfer helpers and unsuffixed scalar helpers:
18
+
19
+ - `srgbToLinear(value: f32) -> f32`
20
+ - `srgbToLinear3(color: vec3f) -> vec3f`
21
+ - `srgbToLinear4(color: vec4f) -> vec4f`
22
+ - `linearToSrgb(value: f32) -> f32`
23
+ - `linearToSrgb3(color: vec3f) -> vec3f`
24
+ - `linearToSrgb4(color: vec4f) -> vec4f`
25
+ - `luminance(color: vec3f) -> f32`
26
+ - `applyExposure(color: vec3f, exposure: f32) -> vec3f`
27
+
28
+ The `vec4f` transfer helpers convert RGB channels and preserve alpha unchanged.
29
+
30
+ ## sRGB transfer functions
31
+
32
+ `srgbToLinear*` and `linearToSrgb*` implement the standard IEC/sRGB piecewise transfer formulas:
33
+
34
+ ```text
35
+ srgbToLinear(c) = c / 12.92 when c <= 0.04045
36
+ = ((c + 0.055) / 1.055) ^ 2.4 otherwise
37
+
38
+ linearToSrgb(c) = 12.92 * c when c <= 0.0031308
39
+ = 1.055 * c ^ (1 / 2.4) - 0.055 otherwise
40
+ ```
41
+
42
+ The expected input and output range is `[0.0, 1.0]` for color channels. These helpers do **not** clamp inputs or outputs; callers that need display-range saturation should import `saturate` or `clamp01` from `@vgpu/wgsl-std/math` explicitly. The formulas are mathematically standard; replacing an artistic or tuned approximation with these helpers can still change the look of a shader and should be reviewed visually.
43
+
44
+ ## Luminance
45
+
46
+ `luminance(color)` returns relative luminance for a linear RGB color using Rec.709/sRGB coefficients:
47
+
48
+ ```text
49
+ dot(color, vec3f(0.2126, 0.7152, 0.0722))
50
+ ```
51
+
52
+ Pass linear-light color values. If your source is sRGB encoded, decode with `srgbToLinear3` before computing luminance.
53
+
54
+ ## Exposure
55
+
56
+ `applyExposure(color, exposure)` treats `exposure` as photographic stops/EV and returns:
57
+
58
+ ```text
59
+ color * exp2(exposure)
60
+ ```
61
+
62
+ Examples: `0.0` leaves the color unchanged, `1.0` doubles it, and `-2.0` multiplies it by `0.25`. The function is intentionally unclamped so HDR pipelines can keep values above `1.0` until an explicit later display transform.
63
+
64
+ ## Performance notes
65
+
66
+ - Scalar transfer helpers branch once and use `pow` only above the sRGB breakpoint.
67
+ - Vector transfer helpers call the scalar helper per RGB channel. This keeps behavior identical for scalar and vector paths and avoids hidden approximation tables.
68
+ - `luminance` is a single dot product; `applyExposure` is a scalar `exp2` and vector multiply.
69
+
70
+ ## Deferred helpers
71
+
72
+ This v1 color module intentionally does not include PBR helpers, BRDF/Fresnel/IBL routines, ACES/Hable/Filament tonemappers, or a default tonemap. Tonemapping and PBR-specific utilities are deferred so applications choose their own display transform and resource model explicitly.
@@ -0,0 +1,37 @@
1
+ export fn luminance(value: vec3f) -> f32 {
2
+ return dot(value, vec3f(0.2126, 0.7152, 0.0722));
3
+ }
4
+
5
+ export fn applyExposure(value: vec3f, exposure: f32) -> vec3f {
6
+ return value * exp2(exposure);
7
+ }
8
+
9
+ export fn srgbToLinear(value: f32) -> f32 {
10
+ if (value <= 0.04045) {
11
+ return value / 12.92;
12
+ }
13
+ return pow((value + 0.055) / 1.055, 2.4);
14
+ }
15
+
16
+ export fn srgbToLinear3(value: vec3f) -> vec3f {
17
+ return vec3f(srgbToLinear(value.r), srgbToLinear(value.g), srgbToLinear(value.b));
18
+ }
19
+
20
+ export fn srgbToLinear4(value: vec4f) -> vec4f {
21
+ return vec4f(srgbToLinear3(value.rgb), value.a);
22
+ }
23
+
24
+ export fn linearToSrgb(value: f32) -> f32 {
25
+ if (value <= 0.0031308) {
26
+ return value * 12.92;
27
+ }
28
+ return 1.055 * pow(value, 1.0 / 2.4) - 0.055;
29
+ }
30
+
31
+ export fn linearToSrgb3(value: vec3f) -> vec3f {
32
+ return vec3f(linearToSrgb(value.r), linearToSrgb(value.g), linearToSrgb(value.b));
33
+ }
34
+
35
+ export fn linearToSrgb4(value: vec4f) -> vec4f {
36
+ return vec4f(linearToSrgb3(value.rgb), value.a);
37
+ }
@@ -0,0 +1,53 @@
1
+ # @vgpu/wgsl-std/constants
2
+
3
+ Raw WGSL math constants for `@vgpu/wgsl` imports. The module contains pure `const` declarations only: no functions, bindings, overrides, hidden state, resources, or entry points.
4
+
5
+ ```wgsl
6
+ import { pi, tau, goldenAngle } from "@vgpu/wgsl-std/constants";
7
+
8
+ fn angleForIndex(index: u32) -> f32 {
9
+ return f32(index) * goldenAngle + tau * 0.25;
10
+ }
11
+ ```
12
+
13
+ ## API
14
+
15
+ - `pi: f32`: π, rounded to WGSL `f32` precision (`3.1415927`).
16
+ - `tau: f32`: 2π, rounded to WGSL `f32` precision (`6.2831855`).
17
+ - `halfPi: f32`: π / 2, rounded to WGSL `f32` precision (`1.5707964`).
18
+ - `quarterPi: f32`: π / 4, rounded to WGSL `f32` precision (`0.7853982`).
19
+ - `invPi: f32`: 1 / π, rounded to WGSL `f32` precision (`0.3183099`).
20
+ - `invTau: f32`: 1 / 2π, rounded to WGSL `f32` precision (`0.15915494`).
21
+ - `goldenRatio: f32`: φ, rounded to WGSL `f32` precision (`1.618034`).
22
+ - `goldenAngle: f32`: golden angle in radians, rounded to WGSL `f32` precision (`2.3999631`).
23
+
24
+ ## Native WGSL before
25
+
26
+ ```wgsl
27
+ const PI: f32 = 3.1415927;
28
+ const TAU: f32 = 6.2831855;
29
+
30
+ fn radiansFromTurns(turns: f32) -> f32 {
31
+ return turns * TAU;
32
+ }
33
+ ```
34
+
35
+ ## Utility import after
36
+
37
+ ```wgsl
38
+ import { tau } from "@vgpu/wgsl-std/constants";
39
+
40
+ fn radiansFromTurns(turns: f32) -> f32 {
41
+ return turns * tau;
42
+ }
43
+ ```
44
+
45
+ ## Notes
46
+
47
+ These constants are plain WGSL `const` declarations. The module is intentionally small so importing it does not add much WGSL text. Declaration-level dead-code elimination for larger WGSL utility modules is tracked in https://github.com/vercel-labs/vgpu/issues/98.
48
+
49
+ `goldenAngle` is also exported by `@vgpu/wgsl-std/sampling` for sampling-only imports. Prefer this constants module when sharing the value across non-sampling shader code.
50
+
51
+ ## Provenance
52
+
53
+ These are standard mathematical constants transcribed directly into WGSL `f32` literals and rounded to the same precision style as the rest of `@vgpu/wgsl-std`.
@@ -0,0 +1,8 @@
1
+ export const pi: f32 = 3.1415927;
2
+ export const tau: f32 = 6.2831855;
3
+ export const halfPi: f32 = 1.5707964;
4
+ export const quarterPi: f32 = 0.7853982;
5
+ export const invPi: f32 = 0.3183099;
6
+ export const invTau: f32 = 0.15915494;
7
+ export const goldenRatio: f32 = 1.618034;
8
+ export const goldenAngle: f32 = 2.3999631;
@@ -0,0 +1,54 @@
1
+ # @vgpu/wgsl-std/math
2
+
3
+ Raw WGSL math utility module for `@vgpu/wgsl` imports.
4
+
5
+ All exports are pure WGSL declarations. Import only the helpers you use:
6
+
7
+ ```wgsl
8
+ import { saturate, remap, safeNormalize3, rotate2d } from "@vgpu/wgsl-std/math";
9
+
10
+ fn shade(normal: vec3f, uv: vec2f) -> vec3f {
11
+ let n = safeNormalize3(normal, vec3f(0.0, 0.0, 1.0));
12
+ let rotatedUv = rotate2d(uv, 0.78539816339);
13
+ let weight = saturate(remap(-1.0, 1.0, 0.0, 1.0, n.z));
14
+ return vec3f(rotatedUv, weight);
15
+ }
16
+ ```
17
+
18
+ ## Catalog
19
+
20
+ ### `saturate(value: f32) -> f32`
21
+
22
+ Clamps a scalar `f32` to `[0.0, 1.0]` using WGSL `clamp`.
23
+
24
+ ### `clamp01(value: f32) -> f32`
25
+
26
+ Alias for `saturate`. Use whichever name is clearer at the call site.
27
+
28
+ ### `inverseLerp(rangeStart: f32, rangeEnd: f32, value: f32) -> f32`
29
+
30
+ Returns `(value - rangeStart) / (rangeEnd - rangeStart)` without clamping the result. Values outside the input range produce values outside `[0.0, 1.0]`.
31
+
32
+ If `rangeStart == rangeEnd`, the input range has no length and the helper returns `0.0` deterministically instead of dividing by zero.
33
+
34
+ ### `remap(inMin: f32, inMax: f32, outMin: f32, outMax: f32, value: f32) -> f32`
35
+
36
+ Maps `value` from input range `[inMin, inMax]` into output range `[outMin, outMax]` using `inverseLerp`, without clamping.
37
+
38
+ If `inMin == inMax`, `inverseLerp` returns `0.0`, so `remap` returns `outMin`.
39
+
40
+ ### `safeNormalize2(value: vec2f, fallback: vec2f) -> vec2f`
41
+ ### `safeNormalize3(value: vec3f, fallback: vec3f) -> vec3f`
42
+ ### `safeNormalize4(value: vec4f, fallback: vec4f) -> vec4f`
43
+
44
+ Normalizes non-zero vectors and returns `fallback` for zero-length vectors. The fallback is returned exactly as supplied; normalize it first if the fallback must be unit length.
45
+
46
+ WGSL does not provide user-defined generic functions for multiple vector dimensions, so v1 exports explicit `2`, `3`, and `4` dimensional names instead of a single overloaded `safeNormalize`.
47
+
48
+ ### `rotate2d(value: vec2f, radians: f32) -> vec2f`
49
+
50
+ Rotates a `vec2f` by `radians` counter-clockwise around the origin:
51
+
52
+ ```wgsl
53
+ let up = rotate2d(vec2f(1.0, 0.0), 1.57079632679);
54
+ ```
@@ -0,0 +1,50 @@
1
+ export fn saturate(value: f32) -> f32 {
2
+ return clamp(value, 0.0, 1.0);
3
+ }
4
+
5
+ export fn clamp01(value: f32) -> f32 {
6
+ return saturate(value);
7
+ }
8
+
9
+ export fn inverseLerp(rangeStart: f32, rangeEnd: f32, value: f32) -> f32 {
10
+ let denominator = rangeEnd - rangeStart;
11
+ if (denominator == 0.0) {
12
+ return 0.0;
13
+ }
14
+ return (value - rangeStart) / denominator;
15
+ }
16
+
17
+ export fn remap(inMin: f32, inMax: f32, outMin: f32, outMax: f32, value: f32) -> f32 {
18
+ let t = inverseLerp(inMin, inMax, value);
19
+ return outMin + t * (outMax - outMin);
20
+ }
21
+
22
+ export fn safeNormalize2(value: vec2f, fallback: vec2f) -> vec2f {
23
+ let lengthSquared = dot(value, value);
24
+ if (lengthSquared <= 0.0) {
25
+ return fallback;
26
+ }
27
+ return value * inverseSqrt(lengthSquared);
28
+ }
29
+
30
+ export fn safeNormalize3(value: vec3f, fallback: vec3f) -> vec3f {
31
+ let lengthSquared = dot(value, value);
32
+ if (lengthSquared <= 0.0) {
33
+ return fallback;
34
+ }
35
+ return value * inverseSqrt(lengthSquared);
36
+ }
37
+
38
+ export fn safeNormalize4(value: vec4f, fallback: vec4f) -> vec4f {
39
+ let lengthSquared = dot(value, value);
40
+ if (lengthSquared <= 0.0) {
41
+ return fallback;
42
+ }
43
+ return value * inverseSqrt(lengthSquared);
44
+ }
45
+
46
+ export fn rotate2d(value: vec2f, radians: f32) -> vec2f {
47
+ let c = cos(radians);
48
+ let s = sin(radians);
49
+ return mat2x2f(c, s, -s, c) * value;
50
+ }
@@ -0,0 +1,86 @@
1
+ # @vgpu/wgsl-std/sampling
2
+
3
+ Raw WGSL sampling utility module for `@vgpu/wgsl` imports. The module contains pure declarations only: no bindings, overrides, hidden state, resources, or entry points.
4
+
5
+ ```wgsl
6
+ import { goldenAngle, vogelDisk, hammersley2d } from "@vgpu/wgsl-std/sampling";
7
+
8
+ fn sampleKernel(index: u32, count: u32, rotation: f32) -> vec3f {
9
+ let disk = vogelDisk(index, count, rotation);
10
+ let sequence = hammersley2d(index, count);
11
+ return vec3f(disk, sequence.y);
12
+ }
13
+ ```
14
+
15
+ ## API
16
+
17
+ - `goldenAngle: f32`: golden angle in radians, rounded to WGSL `f32` precision (`2.3999631`).
18
+ - `vogelDisk(index: u32, count: u32, phi: f32) -> vec2f`: deterministic Vogel spiral point in the unit disk.
19
+ - `radicalInverseVdc(bits: u32) -> f32`: base-2 Van der Corput radical inverse using bit reversal, clamped to the largest `f32` below `1.0` for high reversed-bit values whose WGSL `f32` product would otherwise round to `1.0`.
20
+ - `hammersley2d(index: u32, count: u32) -> vec2f`: 2D Hammersley point `(index / count, radicalInverseVdc(index))`.
21
+
22
+ ## Native WGSL before
23
+
24
+ ```wgsl
25
+ fn localVogelDisk(index: u32, count: u32, phi: f32) -> vec2f {
26
+ if (count == 0u) {
27
+ return vec2f(0.0);
28
+ }
29
+ let goldenAngle = 2.3999631;
30
+ let angle = f32(index) * goldenAngle + phi;
31
+ let radius = sqrt((f32(index) + 0.5) / f32(count));
32
+ return vec2f(cos(angle), sin(angle)) * radius;
33
+ }
34
+ ```
35
+
36
+ ## Utility import after
37
+
38
+ ```wgsl
39
+ import { vogelDisk } from "@vgpu/wgsl-std/sampling";
40
+
41
+ fn localVogelDisk(index: u32, count: u32, phi: f32) -> vec2f {
42
+ return vogelDisk(index, count, phi);
43
+ }
44
+ ```
45
+
46
+ ## Vogel disk samples
47
+
48
+ `vogelDisk(index, count, phi)` computes:
49
+
50
+ ```text
51
+ angle = f32(index) * goldenAngle + phi
52
+ radius = sqrt((f32(index) + 0.5) / f32(count))
53
+ point = vec2f(cos(angle), sin(angle)) * radius
54
+ ```
55
+
56
+ Use `index < count` for samples bounded by the unit disk. `phi` is an angular offset in radians; pass a per-frame or per-pixel rotation when you want to rotate the entire pattern without changing radial placement.
57
+
58
+ If `count == 0u`, the function returns `vec2f(0.0)` defensively instead of dividing by zero.
59
+
60
+ ## Low-discrepancy sequence samples
61
+
62
+ `radicalInverseVdc(bits)` implements the standard base-2 Van der Corput radical inverse by reversing the 32 bits of `bits` and scaling by `1 / 2^32`. Because WGSL returns `f32`, the final value is clamped to `0.99999994` (the largest `f32` below `1.0`) for the highest reversed-bit values whose scaled product would otherwise round up to `1.0`. This preserves the `[0.0, 1.0)` sequence contract even when the reversed `u32` value is near the top of its representable range.
63
+
64
+ `hammersley2d(index, count)` returns:
65
+
66
+ ```text
67
+ vec2f(f32(index) / f32(count), radicalInverseVdc(index))
68
+ ```
69
+
70
+ Use `index < count` for the conventional Hammersley point set over `[0.0, 1.0) x [0.0, 1.0)`. If `count == 0u`, the function returns `vec2f(0.0)` defensively instead of dividing by zero.
71
+
72
+ ## Performance notes
73
+
74
+ - `vogelDisk` uses one `sqrt`, one `cos`, and one `sin` per sample. Precompute or reuse points when a fixed kernel is sampled many times.
75
+ - `radicalInverseVdc` uses integer shifts, masks, and one float conversion; it does not use trigonometry or hidden state.
76
+ - `hammersley2d` is deterministic and stateless. It is not a random number generator and does not decorrelate samples across pixels by itself.
77
+
78
+ ## Provenance
79
+
80
+ Vogel disk sampling comes from Vogel's 1979 published mathematical model for phyllotaxis using the golden-angle spiral. The implementation here is an original transcription of the formula into WGSL.
81
+
82
+ The Van der Corput radical inverse and Hammersley point set are standard low-discrepancy sequence definitions. The bit-reversal implementation is an original WGSL implementation of those mathematical definitions and uses only ordinary integer mask/shift operations. These helpers are deliberate reviewed additions beyond the original minimal `vogelDisk` requirement, included to satisfy the updated user preference for fewer deferrals while staying provenance-clean.
83
+
84
+ ## Deferred helpers
85
+
86
+ This module intentionally does not include `concentricDisk`; that helper needs separate API review for disk-mapping conventions and edge behavior. It also does not include Perlin noise, simplex noise, fBM, value noise, or shader-magic hash/random snippets. Those helpers need separate API, quality, and provenance review so v1 avoids copying unattributed shader snippets or baking in a hidden random/resource model.
@@ -0,0 +1,31 @@
1
+ export const goldenAngle: f32 = 2.3999631;
2
+
3
+ const inverseU32Range: f32 = 2.3283064e-10;
4
+
5
+ export fn vogelDisk(index: u32, count: u32, phi: f32) -> vec2f {
6
+ if (count == 0u) {
7
+ return vec2f(0.0);
8
+ }
9
+
10
+ let angle = f32(index) * goldenAngle + phi;
11
+ let radius = sqrt((f32(index) + 0.5) / f32(count));
12
+ return vec2f(cos(angle), sin(angle)) * radius;
13
+ }
14
+
15
+ export fn radicalInverseVdc(bits: u32) -> f32 {
16
+ var value = bits;
17
+ value = (value << 16u) | (value >> 16u);
18
+ value = ((value & 0x55555555u) << 1u) | ((value & 0xaaaaaaaau) >> 1u);
19
+ value = ((value & 0x33333333u) << 2u) | ((value & 0xccccccccu) >> 2u);
20
+ value = ((value & 0x0f0f0f0fu) << 4u) | ((value & 0xf0f0f0f0u) >> 4u);
21
+ value = ((value & 0x00ff00ffu) << 8u) | ((value & 0xff00ff00u) >> 8u);
22
+ return min(f32(value) * inverseU32Range, 0.99999994);
23
+ }
24
+
25
+ export fn hammersley2d(index: u32, count: u32) -> vec2f {
26
+ if (count == 0u) {
27
+ return vec2f(0.0);
28
+ }
29
+
30
+ return vec2f(f32(index) / f32(count), radicalInverseVdc(index));
31
+ }