Skip to content

Shader source from expressions: MathS.Shaders.Hlsl and Glsl #2293

Open

Description

@Rafael-SOWNet

What it is. Print expressions as straight-line HLSL or GLSL: one assignment per output, each repeated subexpression computed once into a local, sincos for a sin/cos pair in HLSL.

Entity r = "sqrt(x^2 + y^2)";
MathS.Shaders.Hlsl(new Dictionary<string, Entity> { ["g.x"] = r.Differentiate("x"), ["g.y"] = r.Differentiate("y") }.ToList(),
                   new Dictionary<Entity.Variable, string> { ["x"] = "p.x", ["y"] = "p.y" }).Code;
// float am0 = (0.5 * rsqrt(((p.x * p.x) + (p.y * p.y))));
// g.x = (am0 * (2.0 * p.x));
// g.y = (am0 * (2.0 * p.y));

Why. Exact gradients and Hessians of signed distance fields, and closed forms of physical laws, derived on the CPU and run on the GPU. Measured in practice: an exact SDF normal replaced a 4-sample finite difference at 1509 → 466 instructions, and a second one ran 1.23× faster while being exact where the finite difference was 30° off at creases. #552 (Unity) is the same audience.

Contract.

  • Computes the real-valued reading that Compile<double, …> computes. The tests hold it to that: the same printer writes C# against helpers named as HLSL's intrinsics, Roslyn compiles that text, and it is compared with Compile<double, double, double> at 200 points per case (28 cases).
  • One difference to agree on: if A compiled division by zero over double has no value #2288 lands, Compile<double> answers NaN for a division by zero, where x / 0.0 on a GPU is ±inf. The printer writes / as is; guarding every division would cost a branch per quotient. The proposal is to document the difference rather than pay for it.
  • Declines, naming the node, what has no faithful form: complex constants, non-identifier names, Providedf (a shader cannot say "no value here"; Differentiate attaches one to tanh's exponential form, so the caller unwraps it where the condition cannot fail), piecewise without a final always-true case, Roundf (tie rules differ), factorial, sets.
  • Adds no referenced assembly: KernelDependenciesTest caught a first draft that used Regex.
  • Integer powers multiplied out to the 4th; higher as pow(abs(x), n) with the sign restored for odd n, because pow of a negative base is undefined in both languages.
  • mod printed floored, as Modf is (fmod truncates).
  • Nothing is simplified.

Packaging. No clause fires (pure managed, string output) → kernel, beside IntoLinq. Arguably (C) "consumes rather than composes"; the SymPy exporter is the precedent for an output format in the kernel.

Implementation, for judging the design against working code. Branch feat/shader-source, one commit on master 87a0eff: Functions/Compilation/IntoSource/*, Convenience/MathS.Shaders.cs, Tests/UnitTests/Convenience/ShadersTest.cs, +17 lines of PublicApi.txt. UnitTests 18332 passed / 13 skipped, FSharpWrapperUnitTests 134 passed. The pull request will follow Accepted.

Worked use, together with dimensional analysis (#2292). A water wave from ω² = g k tanh(k h): the emitted HLSL matches the library to 4e-16 in double, and to 3e-5 of the amplitude in fp32 out to k h = 300.

Milestone: 2.7.0 (additive).

Activity

  1. Rafael-SOWNet commented on Oct 31, 2026

    @Rafael-SOWNet
    MemberAuthor

    #2288 has landed (e238512). Compiled over double, a zero divisor and zero raised to a power that is not positive now have no value (NaN), so the difference described under Contract is now real, and a little wider than the division stated there:

    at Compile<double> since #2288 the printed shader
    a / b with b = 0 NaN a / b: ±inf under IEEE 754
    x^(-n), x^(-1/2) at x = 0 NaN 1.0 / (x * x), rsqrt(x): ±inf
    an unsimplified x^0 at x = 0 NaN 1.0

    Wherever the compiled function has a value, the two still agree: feat/shader-source rebased onto master 17cd9da passes ShadersTest, which compares the printed code with Compile<double, …> at 200 points for each of 28 cases.

    The proposal stands: print / and pow as they are rather than guard every quotient. The branch's documentation already says that where Compile<double> has no value the shader's is NaN or unspecified; the pull request will name these cases in it, so that ±inf at a zero divisor is documented, not discovered.

  2. Happypig375 commented on Nov 1, 2026

    @Happypig375
    Member

    Hypothetically can speed up compiled functions too. Precise contract, behaviour, settings TBD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions