From 4250dd28d57096fce4e5ea0203d6938ad0c20687 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 16 Oct 2026 03:02:13 +0200 Subject: [PATCH] cultmath: compile-hlsl.ps1 fails on warnings and on a missing shader without its path; compile-glsl.ps1 passes struct arguments dxc runs with -WX; a ShaderPath that is not a file throws naming the parameter and entry index, never the path; the GLSL wrapper builds a struct parameter from its declaration's field zeros. design.md and README state the struct-argument twin rule, the span twins and the missing shader identity. No C# change; the compiles run on Starfire after the last commit. Co-Authored-By: Claude Opus 5.5 --- packages/cultmath/README.md | 9 +++++---- packages/cultmath/docs/design.md | 25 +++++++++++++++++++----- packages/cultmath/tools/compile-glsl.ps1 | 17 ++++++++++++---- packages/cultmath/tools/compile-hlsl.ps1 | 7 +++++-- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/packages/cultmath/README.md b/packages/cultmath/README.md index 0111949c..8648263b 100644 --- a/packages/cultmath/README.md +++ b/packages/cultmath/README.md @@ -82,9 +82,10 @@ shader semantics. the analytic Hessian of `snoise(float3)` on the same lattice, hash and tie order as `snoise_grad` (a symmetric `float3x3`; GLSL `mat3`), and `integral_clamp_linear(a, b, lo, hi, t0, t1)`, the closed-form integral over `[t0, t1]` of `clamp(a + b t, - lo, hi)`; and, in C# for now (shaders follow), the octave stages `span_fbm(path, stage, - octaves, lacunarity, gain)` and `span_ridged`, over a `SpanPath` and a `SpanStage` (`SpanStage.Identity` - is the plain fbm), which return a `SpanNoise` (`field` as `float4(gradient, value)`, + lo, hi)`; and the octave stages `span_fbm(path, stage, + octaves, lacunarity, gain)` and `span_ridged` (shader twins `cultmath_span_fbm` and `cultmath_span_ridged`, + whose callers build the `SpanStage` themselves), over a `SpanPath` and a `SpanStage` (`SpanStage.Identity` + is the plain fbm in C#), which return a `SpanNoise` (`field` as `float4(gradient, value)`, `curvature`, `fold`) for a stretched, offset, shifted octave sum over a span. Three measured bounds describe `snoise`: `SNOISE_LIPSCHITZ` (gradient length, `NoiseBoundTests.MeasureLipschitz`), `SNOISE_HESSIAN` (Hessian spectral norm, `MeasureHessian`) and `SNOISE_THIRD` (third-derivative operator norm, @@ -178,7 +179,7 @@ CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`: -OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.metal ``` -`compile-hlsl.ps1` compiles both `CultMath.hlsl` copies as a `lib_6_3` library with the x64 DXC. +`compile-hlsl.ps1` compiles both `CultMath.hlsl` copies as a `lib_6_3` library with the x64 DXC; a warning fails it too (`-WX`). DXC emits Vulkan SPIR-V with `-spirv`. `-TargetEnv vulkan1.2` is exposed by the script, but the current Windows DXC release compiled this shader successfully diff --git a/packages/cultmath/docs/design.md b/packages/cultmath/docs/design.md index d11e1518..3511d755 100644 --- a/packages/cultmath/docs/design.md +++ b/packages/cultmath/docs/design.md @@ -113,8 +113,9 @@ modifying a temporary). The HLSL side is checked too: `tools/get-dxc.ps1` fetches Microsoft's DirectXShaderCompiler (x64), and `tools/compile-hlsl.ps1` compiles both copies of -`CultMath.hlsl` with it (`dxc -T lib_6_3 -HV 2021`); the script throws when dxc cannot -launch or exits non-zero, so a stale exit code never reads as a pass. +`CultMath.hlsl` with it (`dxc -T lib_6_3 -HV 2021 -WX`, so a warning fails like an error and the +text must compile cleanly); the script throws when a shader file is missing, when dxc cannot +launch or when it exits non-zero, so a stale exit code never reads as a pass. The mirror test compares bit patterns (any NaN equals any NaN; -0 differs from 0). It proves the text of `CultMath.hlsl` computes the same float32 results as @@ -124,13 +125,20 @@ compiled under `using static CultMath.math;`, so its intrinsics are C# `math` itself: the test proves the composition in the text, not the intrinsic rules, which only `HlslSemanticsTests` pins. +Every mirror function is compared, internals included: its counterpart is the `math` +function of the same name, public or private (`snoise_mod289` and `snoise_permute` are +private), and a function with no counterpart fails by name. A struct parameter maps to the +CultMath type of the same name, and only if both have the same instance fields by name, type +and order (`SpanPath`, `SpanStage`); the argument is built from the case's scalars field by +field and copied to its C# twin by field name. + The comparison also allows exactly one struct return shape: a mirror function whose HLSL return type is a plain struct of `float`/`floatN`/`int`/`intN` fields is compared to its C# counterpart field by field, recursing into each field's own components, down to bit-for-bit scalars, matching each leaf's declaration path (field names, not just position) and declared type as well -as its value. `CultCellular` (`math.cs`, `cellular`) and `CultPhasor` -(`math.Phacelle.cs`, `phacelle`) are its consumers. A struct return with a mismatched, reordered-by-name, or +as its value. `CultCellular` (`math.cs`, `cellular`), `CultPhasor` +(`math.Phacelle.cs`, `phacelle`) and `SpanNoise` (`math.Span.cs`, `span_fbm`) are its consumers. A struct return with a mismatched, reordered-by-name, or retyped field does not get a second comparison path or a shape of its own; it fails the same walk that already handles a bare vector return. @@ -809,7 +817,8 @@ configuration bound. A change to the kernels must re-run the measurements. ### Span stages -`span_fbm` and `span_ridged` (`math.Span.cs`, C# only so far) are the octave stages +`span_fbm` and `span_ridged` (`math.Span.cs`; `cultmath_span_fbm` and `cultmath_span_ridged` in HLSL over +the same struct types, `cultmath_span_stage` behind both, and GLSL derived) are the octave stages of a warp chain, `span_fbm(path, stage, octaves, lacunarity, gain)`, each returning one `SpanNoise { float4 field; float curvature; float fold; }`. The inputs are two structs with named public fields and no positional constructor, so the three floats @@ -831,6 +840,12 @@ Lab's stage is `1 - 2|n|` per octave: with `G` the sum of `gain^o` over the octaves (signed, so the alternating sum for a negative gain), its value is `2 value - G`, and its gradient, curvature and fold are twice `span_ridged`'s. +The shaders have no `SpanStage.Identity`: a shader caller builds every stage field itself (in GLSL, +`SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0))` is the identity). A constant would +need a second transformation rule (C# has no const structs, GLSL wants a constructor) and a mirror +exemption, for one value; the mirror test proves instead that the shader text at a C#-built identity +returns `cultmath_fbm_grad` and `cultmath_ridged_grad`'s field bit for bit. + Bounds come from the three pins only. With `|M| = |frequency| max(1, 1/stretch)` and `nu = f_o |M| speed` the noise-space speed, the Hessian of the noise on the span has operator norm at most `Hl = min(SNOISE_HESSIAN, ||snoise_hessian(x_mid)||_F + e_h + diff --git a/packages/cultmath/tools/compile-glsl.ps1 b/packages/cultmath/tools/compile-glsl.ps1 index 45929cac..8ec2b82c 100644 --- a/packages/cultmath/tools/compile-glsl.ps1 +++ b/packages/cultmath/tools/compile-glsl.ps1 @@ -29,12 +29,21 @@ if ($Phacelle) { if ($Cycles) { $library += Get-Content -LiteralPath (Join-Path $repoRoot "shaders\CultMath.Cycles.glsl") -Raw } +# A parameter of a struct type the library declares is passed as that struct's constructor with each +# field's zero, read from its declaration (a field may be a struct itself); every other type is its zero. +$structs = @{} +foreach ($declaration in [regex]::Matches($library, '(?s)struct (\w+)\s*\{(.*?)\};')) { + $structs[$declaration.Groups[1].Value] = @([regex]::Matches($declaration.Groups[2].Value, '(?m)^\s*(\w+)\s+\w+\s*;') | ForEach-Object { $_.Groups[1].Value }) +} +function Get-Zero([string]$type) { + if ($structs.ContainsKey($type)) { return "$type($((@($structs[$type] | ForEach-Object { Get-Zero $_ })) -join ', '))" } + if ($type -eq 'int' -or $type -like 'ivec*') { return "$type(0)" } + if ($type -eq 'uint' -or $type -like 'uvec*') { return "$type(0u)" } + return "$type(0.0)" +} $calls = foreach ($match in [regex]::Matches($library, '(?m)^\w+\s+(cultmath_\w+)\s*\(([^)]*)\)')) { $arguments = foreach ($parameter in ($match.Groups[2].Value -split ',')) { - $type = ($parameter.Trim() -split '\s+')[0] - if ($type -eq 'int' -or $type -like 'ivec*') { "$type(0)" } - elseif ($type -eq 'uint' -or $type -like 'uvec*') { "$type(0u)" } - else { "$type(0.0)" } + Get-Zero (($parameter.Trim() -split '\s+')[0]) } " $($match.Groups[1].Value)($($arguments -join ', '));" } diff --git a/packages/cultmath/tools/compile-hlsl.ps1 b/packages/cultmath/tools/compile-hlsl.ps1 index 6742503c..6e19ec82 100644 --- a/packages/cultmath/tools/compile-hlsl.ps1 +++ b/packages/cultmath/tools/compile-hlsl.ps1 @@ -29,11 +29,14 @@ New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null $index = 0 foreach ($shader in $ShaderPath) { $index++ + if (-not (Test-Path -LiteralPath $shader -PathType Leaf)) { + throw "shader not found (parameter ShaderPath, entry $index)." + } $shaderFile = Get-Item -LiteralPath $shader $output = Join-Path $outputDirectory "$index-$($shaderFile.BaseName).dxil" - & $DxcPath -T lib_6_3 -HV 2021 -I $shaderFile.DirectoryName $shaderFile.FullName -Fo $output + & $DxcPath -T lib_6_3 -HV 2021 -WX -I $shaderFile.DirectoryName $shaderFile.FullName -Fo $output if ($LASTEXITCODE -ne 0) { throw "dxc failed with exit code $LASTEXITCODE (parameter ShaderPath, entry $index)." } - Write-Host "compiled shader $index of $(@($ShaderPath).Count) (lib_6_3, -HV 2021)" + Write-Host "compiled shader $index of $(@($ShaderPath).Count) (lib_6_3, -HV 2021, -WX)" }