Document the full HLSL target and its source transformations

design.md states the generated swizzle and mixed-constructor surface, writable
matrix rows, HLSL intrinsic types and NaN behavior, the deferred matrix items
(float4x4, transpose, determinant, _11 names), the exact HLSL-to-C#
transformations the compatibility test applies, and the dxc check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 13:57:03 +02:00
commit 7a1bd1ebb1
2 changed files with 53 additions and 8 deletions

View file

@ -16,7 +16,9 @@ shader semantics.
- `float2`, `float3`, `float4` - `float2`, `float3`, `float4`
- component-wise arithmetic - component-wise arithmetic
- scalar-to-vector splats - scalar-to-vector splats
- swizzles for common lanes - every HLSL swizzle (`xyzw` and `rgba`, repeats readable, non-repeating
masks writable) and every HLSL mixed constructor, generated by
`tools/generate-swizzles.ps1`
- conversions to and from `System.Numerics` - conversions to and from `System.Numerics`
- `Random`, a compact deterministic XORShift stream with scalar and vector draws - `Random`, a compact deterministic XORShift stream with scalar and vector draws
- `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using - `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using

View file

@ -12,20 +12,63 @@ rest of the spelling should survive the move.
That makes these HLSL rules, not options: That makes these HLSL rules, not options:
- Components and swizzles are writable. `v.xz = f2;` and `v[1] = s;` work. - Every swizzle exists. `float2/3/4`, `int2/3/4`, and `bool2/3/4` expose every
Swizzle setters exist only for non-repeating masks, because HLSL rejects read swizzle of length 2 to 4, repeats included (`v.wwxy`), in both `xyzw` and
`v.xx = ...`. `rgba` naming (never mixed), plus single-component `r/g/b/a`. `double2/3` get
lengths 2 and 3 only, because there is no `double4`. Components and swizzles are
writable: `v.xz = f2;`, `c.rgb *= 0.5f;`, and `v[1] = s;` work. Setters exist
only for non-repeating masks, because HLSL rejects `v.xx = ...`.
- Every HLSL mixed constructor exists, as a struct constructor and as a `math`
function: `float4(float2, float2)`, `float4(float, float2, float)`,
`float3(float, float2)`, the copy form `float3(float3)`, and the rest, for the
float, int, and bool families (double gets constructors only; a `math.double3`
function would hide the `double3` type name).
- Swizzles and mixed constructors are generated. `tools/generate-swizzles.ps1`
writes the checked-in `src/CultMath/Swizzles.g.cs` (partial structs plus a
partial `math`). Edit the generator, rerun it, and commit both; never edit the
output by hand.
- Comparison operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) are component-wise and - Comparison operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) are component-wise and
return `bool2`/`bool3`/`bool4`. Reduce them with `any` and `all`. return `bool2`/`bool3`/`bool4`. Reduce them with `any` and `all`.
`Equals`/`GetHashCode` stay value-based so vectors still work as dictionary `Equals`/`GetHashCode` stay value-based so vectors still work as dictionary
keys. keys.
- `select(condition, whenTrue, whenFalse)` uses HLSL's argument order. - `select(condition, whenTrue, whenFalse)` uses HLSL's argument order.
- Matrices are row-major: constructors take rows, `m[i]` is row i, `_mRC` is - Matrices are row-major: constructors take rows, `m[i]` returns a reference to
row R column C, and products go through `mul` (`mul(m, v)` treats `v` as a row i (so `m[1][2] = s;` writes one element), `_mRC` is row R column C, and
column, `mul(v, m)` as a row). `*` on matrices would be component-wise in HLSL, products go through `mul` (`mul(m, v)` treats `v` as a column, `mul(v, m)` as a
so CultMath does not define it. row). `*` on matrices would be component-wise in HLSL, so CultMath does not
define it.
- Scalar/vector arithmetic and comparisons have explicit overloads on both - Scalar/vector arithmetic and comparisons have explicit overloads on both
sides rather than leaning on implicit scalar splat. sides rather than leaning on implicit scalar splat.
- Intrinsics return HLSL's types and edge cases: `sign` returns `int`/`intN`
(NaN gives 0); `step(y, x)` is `x >= y ? 1 : 0` (NaN gives 0), in C# and in the
HLSL mirror; `any`/`all` accept numeric vectors (a component is true when it is
not 0); `min`, `max`, and `abs` on int vectors return int vectors.
Deferred until a consumer needs them: `float4x4`, `transpose`, `determinant`,
and HLSL's one-based `_11`..`_33` element names.
### HLSL Target: Source Transformations
`HlslSourceCompatibilityTests` compiles the real `shaders/CultMath.hlsl` with
Roslyn against CultMath. These transformations are the complete list; if a body
needs anything else, the test fails:
1. Include guards (`#ifndef`/`#define`/`#endif`) are dropped.
2. Functions taking `Texture2D`/`SamplerState` are dropped; GPU resources have
no CultMath analog.
3. File-scope `static const` becomes `const`.
4. Floating literals gain `f` (`0.5` becomes `0.5f`).
5. The file body is wrapped in a class, since C# has no free functions; HLSL
functions become private instance methods.
HLSL constructs that stay outside the target and must not appear in the mirror:
`const` locals of vector type (C# `const` is primitives only; write a plain
local) and writes through a swizzle component (`v.xy.x = s`, which C# rejects as
modifying a temporary).
The HLSL side is checked too: `tools/get-dxc.ps1` fetches Microsoft's
DirectXShaderCompiler, and
`dxc -T lib_6_3 -HV 2021 shaders/CultMath.hlsl` must compile cleanly.
Where HLSL is silent, CultMath keeps its own decisions and does not defer to Where HLSL is silent, CultMath keeps its own decisions and does not defer to
Unity.Mathematics: `normalize` is safe (divides by `max(length, 1e-20)`), Unity.Mathematics: `normalize` is safe (divides by `max(length, 1e-20)`),