ComputeSharp icon indicating copy to clipboard operation
ComputeSharp copied to clipboard

Method outside of shader can't access its own `static readonly` field

Open rickbrew opened this issue 3 years ago • 3 comments

The error:

1>D:\src\github\PdnGpuEffectSamples\NightCircuitShaderEffect.cs(180,28,180,34): error CMPSD2D0034: The shader of type PaintDotNet.Effects.Gpu.Samples.NightCircuitShaderEffect.Shader failed to compile due to an HLSL compiler error (Message: "The FXC compiler encountered one or more errors while trying to compile the shader: [error X3004]: undeclared identifier 'TAU'. Make sure to only be using supported features by checking the README file in the ComputeSharp repository: https://github.com/Sergio0694/ComputeSharp. If you're sure that your C# shader code is valid, please open an issue an include a working repro and this error message.")

ShaderImpl is hosting the methods for the shader because I'm experimenting with having two ID2D1PixelShader impls for this: one w/ IEEE strictness, the other without, and the user can toggle between the two. At the top of ShaderImpl is the TAU field, but for some reason a method within ShaderImpl can't access it.

The code (warning, long!) :

[D2DInputCount(1)]
[D2DInputSimple(0)]
[D2DInputDescription(0, D2D1Filter.MinMagMipPoint)]
[D2DRequiresScenePosition]
[D2DEmbeddedBytecode(D2D1ShaderProfile.PixelShader50)]
[D2DCompileOptions(D2D1CompileOptions.Default | D2D1CompileOptions.IeeeStrictness)]
[AutoConstructor]
private partial struct Shader
    : ID2D1PixelShader
{
    // Standard input constants for ShaderToy shaders
    private float iTime;
    private readonly float2 iResolution;

    // This Execute() method adapts ShaderToy's mainImage() to work with ComputeSharp.D2D1's expected method signature
    public float4 Execute()
    {
        float2 scenePos = D2D.GetInput(0).XY;
        ShaderImpl.mainImage(out float4 fragColor, scenePos, this.iTime, this.iResolution);
        return fragColor;
    }
}

private static class ShaderImpl
{
    //#define TAU atan(1.)*8.
    private static readonly float TAU = Hlsl.Atan(1.0f) * 8.0f;

    static void lookAt(ref float3 rd, float3 ro, float3 ta, float3 up)
    {
        float3 w = Hlsl.Normalize(ta - ro);
        float3 u = Hlsl.Normalize(Hlsl.Cross(w, up));
        rd = rd.X * u + rd.Y * Hlsl.Cross(u, w) + rd.Z * w;
    }

    private static void pointAt(ref float3 p, float3 dir, float3 up)
    {
        float3 u = Hlsl.Normalize(Hlsl.Cross(dir, up));
        p = new float3(Hlsl.Dot(p, u), Hlsl.Dot(p, Hlsl.Cross(u, dir)), Hlsl.Dot(p, dir));
    }

    private static void rot(ref float3 p, float3 a, float t)
    {
        a = Hlsl.Normalize(a);
        float3 u = Hlsl.Cross(a, p), v = Hlsl.Cross(a, u);
        p = u * Hlsl.Sin(t) + v * Hlsl.Cos(t) + a * Hlsl.Dot(a, p);
    }

    private static void rot(ref float2 p, float t)
    {
        p = p * Hlsl.Cos(t) + new float2(-p.Y, p.X) * Hlsl.Sin(t);
    }

    // https://www.shadertoy.com/view/WdfcWr
    private static void pSFold(ref float2 p, float n)
    {
        float h = Hlsl.Floor(Hlsl.Log2(n)), a = TAU * Hlsl.Exp2(h) / n;
        for (float i = 0; i < h + 2; i++)
        {
            float2 v = new float2(-Hlsl.Cos(a), Hlsl.Sin(a));
            float g = Hlsl.Dot(p, v);
            p -= (g - Hlsl.Sqrt(g * g + 2e-3f)) * v;
            a *= 0.5f;
        }
    }

    private const float seed = 2576;

    // The #define is translated into a method overload for each type that it's needed for
    //#define hash(p)fract(sin(p*12345.5))
    private static float hash(float p)
    {
        return Hlsl.Frac(Hlsl.Sin(p * 12345.5f));
    }

    private static float2 hash(float2 p)
    {
        return Hlsl.Frac(Hlsl.Sin(p * 12345.5f));
    }

    private static float3 hash(float3 p)
    {
        return Hlsl.Frac(Hlsl.Sin(p * 12345.5f));
    }

    private static float3 randVec(float s)
    {
        float2 n = hash(new float2(s, s + 215.3f));
        return new float3(Hlsl.Cos(n.Y) * Hlsl.Cos(n.X), Hlsl.Sin(n.Y), Hlsl.Cos(n.Y) * Hlsl.Sin(n.X));
    }

    private static float3 randCurve(float t, float n)
    {
        float3 p = default;
        for (int i = 0; i < 3; i++)
        {
            p += randVec(n += 365.0f) * Hlsl.Sin((t *= 1.3f) + Hlsl.Sin(t * 0.6f) * 0.5f);
        }
        return p;
    }

    private static float3 orbit(float t, float n, float iTime)
    {
        float3 p = randCurve(-t * 1.5f + iTime, seed) * 5;
        float3 off = randVec(n) * (t + 0.05f) * 0.6f;
        float time = iTime + hash(n) * 5.0f;
        return p + off * Hlsl.Sin(time + 0.5f * Hlsl.Sin(0.5f * time));
    }

    // rewrote 20/12/01
    private static void sFold45(ref float2 p)
    {
        float2 v = Hlsl.Normalize(new float2(1, -1));
        float g = Hlsl.Dot(p, v);
        p -= (g - Hlsl.Sqrt(g * g + 5e-5f)) * v;
    }

    private static float stella(float3 p, float s)
    {
        p = Hlsl.Sqrt(p * p + 5e-5f); // https://iquilezles.org/articles/functions
        sFold45(ref p.XZ);
        sFold45(ref p.YZ);
        return Hlsl.Dot(p, Hlsl.Normalize(new float3(1, 1, -1))) - s;
    }

    /*
    float stella(float3 p, float s)
    {
        p=abs(p);
        if(p.x<p.z)p.xz=p.zx;
        if(p.y<p.z)p.yz=p.zy;
        return dot(p,normalize(new float3(1,1,-1)))-s;
    }
    */

    private static float stellas(float3 p, float iTime)
    {
        p.Y -= -iTime;
        float c = 2.0f;
        float3 e = Hlsl.Floor(p / c);
        e = Hlsl.Sin(11.0f * (2.5f * e + 3.0f * e.YZX + 1.345f));
        p -= e * 0.5f;
        p = Hlsl.Fmod(p, c) - c * 0.5f;
        rot(ref p, hash(e + 166.887f) - 0.5f, iTime * 1.5f);
        return Hlsl.Min(0.7f, stella(p, 0.08f));
    }

    private static float structure(float3 p, ref float g1, ref float g2, float iTime)
    {
        float d = 1e3f, d0;
        for (int i = 0; i < 12; i++)
        {
            float3 q = p;
            float3 w = Hlsl.Normalize(new float3(Hlsl.Sqrt(5.0f) * 0.5f + 0.5f, 1, 0));
            w.XY *= new float2(i >> 1 & 1, i & 1) * 2.0f - 1.0f;

            //w = new float3[] { w, w.YZX, w.ZXY }[i % 3];
            uint imod3 = (uint)i % 3;
            w = (imod3 == 0) ? w :
                (imod3 == 1) ? w.YZX :
                w.ZXY;

            pointAt(ref q, w, -Hlsl.Sign(w.X + w.Y + w.Z) * Hlsl.Sign(w) * w.ZXY);

            d0 = Hlsl.Length(q - new float3(0, 0, Hlsl.Clamp(q.Z, 2.0f, 8.0f))) - 0.4f + q.Z * 0.05f;
            d = Hlsl.Min(d, d0);
            g2 += 0.1f / (0.1f + d0 * d0); // Distance glow by balkhan

            float c = 0.8f;
            float e = Hlsl.Floor(q.Z / c - c * 0.5f);
            q.Z -= c * Hlsl.Clamp(Hlsl.Round(q.Z / c), 3.0f, 9.0f);

            q.Z -= Hlsl.Clamp(q.Z, -0.05f, 0.05f);
            pSFold(ref q.XY, 5.0f);
            q.Y -= 1.4f - e * 0.2f + Hlsl.Sin(iTime * 10.0f + e + (float)i) * 0.05f;
            q.X -= Hlsl.Clamp(q.X, -2.0f, 2.0f);
            q.Y -= Hlsl.Clamp(q.Y, 0.0f, 0.2f);

            d0 = Hlsl.Length(q) * 0.7f - 0.05f;
            d = Hlsl.Min(d, d0);
            if (e == 2.0f + Hlsl.Floor(Hlsl.Fmod(iTime * 5.0f, 7.0f)))
                g1 += 0.1f / (0.1f + d0 * d0);
        }
        return d;
    }

    private static float rabbit(float3 p, ref float g3, float iTime)
    {
        p -= randCurve(iTime, seed) * 5.0f;
        rot(ref p, new float3(1, 1, 1), iTime);
        float d = stella(p, 0.2f);
        g3 += 0.1f / (0.1f + d * d);
        return d;
    }

    private static float map(float3 p, ref float g1, ref float g2, ref float g3, float iTime)
    {
        return Hlsl.Min(Hlsl.Min(stellas(p, iTime), structure(p, ref g1, ref g2, iTime)), rabbit(p, ref g3, iTime));
    }

    private static float3 calcNormal(float3 p, ref float g1, ref float g2, ref float g3, float iTime)
    {
        float3 n = default;
        for (int i = 0; i < 4; i++)
        {
            float3 e = 0.001f * (new float3(9 >> i & 1, i >> 1 & 1, i & 1) * 2.0f - 1.0f);
            n += e * map(p + e, ref g1, ref g2, ref g3, iTime);
        }
        return Hlsl.Normalize(n);
    }

    private static float3 doColor(float3 p, float iTime)
    {
        if (stellas(p, iTime) < 0.001f) return new float3(0.7f, 0.7f, 1);
        return new float3(1, 1, 1);
    }

    private static float3 hue(float h)
    {
        return Hlsl.Cos((new float3(0, 2, -2) / 3.0f + h) * TAU) * 0.5f + 0.5f;
    }

    private static float3 cLine(float3 ro, float3 rd, float3 a, float3 b)
    {
        float3 ab = Hlsl.Normalize(b - a), ao = a - ro;
        float d0 = Hlsl.Dot(rd, ab), d1 = Hlsl.Dot(rd, ao), d2 = Hlsl.Dot(ab, ao);
        float t = (d0 * d1 - d2) / (1.0f - d0 * d0) / Hlsl.Length(b - a);
        t = Hlsl.Clamp(t, 0.0f, 1.0f);
        float3 p = a + (b - a) * t - ro;
        return new float3(Hlsl.Length(Hlsl.Cross(p, rd)), Hlsl.Dot(p, rd), t);
    }

    private static float int7_10_12_15(int index)
    {
        return index == 0 ? 7
            : index == 1 ? 10
            : index == 2 ? 12
            : 15;
    }

    public static void mainImage(out float4 fragColor, float2 fragCoord, float iTime, float2 iResolution)
    {
        float g1 = 0;
        float g2 = 0;
        float g3 = 0;

        float2 p = (2.0f * fragCoord - iResolution.XY) / iResolution.Y;
        float3 col = new float3(0.0f, 0.0f, 0.05f);

        // ComputeSharp.D2D1 does not currently support 'new int[] { ... }', so it has been translated to a method call (see above)
        //float3 ro = new float3(1, 0, new int[] { 7, 10, 12, 15 }[(int)(Hlsl.Abs(4.0f * Hlsl.Sin(iTime * 0.3f + 3.0f * Hlsl.Sin(iTime * 0.2f)))) & 3]);
        float3 ro = new float3(1, 0, int7_10_12_15((int)(Hlsl.Abs(4.0f * Hlsl.Sin(iTime * 0.3f + 3.0f * Hlsl.Sin(iTime * 0.2f)))) & 3));

        rot(ref ro, new float3(1, 1, 1), iTime * 0.2f);
        float3 ta = new float3(2, 1, 2);
        float3 rd = Hlsl.Normalize(new float3(p, 2));
        lookAt(ref rd, ro, ta, new float3(0, 1, 0));
        float z = 0.0f, i, d = 0, ITR = 50.0f;
        for (i = 0.0f; i < ITR; i++)
        {
            z += d = map(ro + rd * z, ref g1, ref g2, ref g3, iTime);
            if (d < 0.001f || z > 30.0f) break;
        }
        if (d < .001f)
        {
            float3 p2 = ro + rd * z;
            float3 nor = calcNormal(p2, ref g1, ref g2, ref g3, iTime);
            float3 li = Hlsl.Normalize(new float3(1, 1, -1));
            col = doColor(p2, iTime);
            col *= Hlsl.Pow(Hlsl.Abs(1.9f - i / ITR), 2.0f);
            col *= Hlsl.Clamp(Hlsl.Dot(nor, li), 0.3f, 1.0f);
            col *= Hlsl.Max(0.5f + 0.5f * nor.Y, 0.2f);
            col += new float3(0.8f, 0.1f, 0.0f) * Hlsl.Pow(Hlsl.Abs(Hlsl.Clamp(Hlsl.Dot(Hlsl.Reflect(Hlsl.Normalize(p2 - ro), nor), Hlsl.Normalize(new float3(-1, -1, -1))), 0.0f, 1.0f)), 30.0f);
            col += new float3(0.1f, 0.2f, 0.5f) * Hlsl.Pow(Hlsl.Abs(Hlsl.Clamp(Hlsl.Dot(Hlsl.Reflect(Hlsl.Normalize(p2 - ro), nor), Hlsl.Normalize(new float3(1, 1, -1))), 0.0f, 1.0f)), 30.0f);
            col = Hlsl.Lerp(new float3(0, 0, 0), col, Hlsl.Exp(-z * z * 0.00001f));
        }
        col += new float3(0.9f, 0.1f, 0.0f) * g1 * 0.05f;
        col += new float3(0.0f, 0.3f, 0.7f) * g2 * 0.08f;
        col += new float3(0.5f, 0.3f, 0.1f) * g3 * 0.15f;

        // https://www.shadertoy.com/view/wtXSzX
        float3 de;
        ITR = 40.0f;
        for (i = 0.0f; i < 1.0f; i += 1.0f / 7.0f)
        {
            de = new float3(1e9f, 1e9f, 1e9f);
            float off = hash(i * 234.6f + 256.0f);
            for (float j = 0.0f; j < 1.0f; j += 1.0f / ITR)
            {
                float t = j + off * 0.5f;
                float3 c = cLine(ro, rd, orbit(t, off, iTime), orbit(t + 1.0f / ITR, off, iTime));
                if (de.X * de.X * de.Y > c.X * c.X * c.Y)
                {
                    de = c;
                    de.Z = j + c.Z / ITR;
                }
            }
            float s = Hlsl.Pow(Hlsl.Max(0.0f, 0.6f - de.Z), 2.0f) * 0.1f;
            if (de.Y > 0.0f && z > de.Y)
                col += Hlsl.Lerp(new float3(1, 1, 1), hue(i), 0.8f) * (1.0f - de.Z * 0.9f) * Hlsl.SmoothStep(s + 0.17f, s, de.X) * 0.7f;
        }
        float col0 = 0.8f + 0.3f * Hlsl.Sin(iTime * 0.5f + 3.0f * Hlsl.Sin(iTime * 0.3f));
        col = Hlsl.Pow(Hlsl.Abs(col), new float3(col0, col0, col0));

        fragColor = new float4(col, 1);
    }
}

rickbrew avatar May 27 '22 23:05 rickbrew

BTW it works if I copy TAU into the shader struct:

[D2DInputCount(1)]
[D2DInputSimple(0)]
[D2DInputDescription(0, D2D1Filter.MinMagMipPoint)]
[D2DRequiresScenePosition]
[D2DEmbeddedBytecode(D2D1ShaderProfile.PixelShader50)]
[D2DCompileOptions(D2D1CompileOptions.Default | D2D1CompileOptions.IeeeStrictness)]
[AutoConstructor]
private partial struct Shader
    : ID2D1PixelShader
{
    //#define TAU atan(1.)*8.
    private static readonly float TAU = Hlsl.Atan(1.0f) * 8.0f;

    // Standard input constants for ShaderToy shaders
    private float iTime;
    private readonly float2 iResolution;

    // This Execute() method adapts ShaderToy's mainImage() to work with ComputeSharp.D2D1's expected method signature
    public float4 Execute()
    {
        float2 scenePos = D2D.GetInput(0).XY;
        ShaderImpl.mainImage(out float4 fragColor, scenePos, this.iTime, this.iResolution);
        return fragColor;
    }
}

rickbrew avatar May 27 '22 23:05 rickbrew

Minimum repro:

[D2DInputCount(0)]
[D2DEmbeddedBytecode(D2D1ShaderProfile.PixelShader50)]
private partial struct Shader : ID2D1PixelShader
{
    public float4 Execute()
    {
        return ShaderImpl.mainImage();
    }
}

private static class ShaderImpl
{
    private static readonly float TAU = Hlsl.Atan(1.0f) * 8.0f;

    public static float4 mainImage()
    {
        return TAU;
    }
}

Sergio0694 avatar Aug 18 '22 11:08 Sergio0694

So, following up the investigation from the other day:

  • Currently, only constants are discovered from static methods. Swapping those fields to a constant will work.
  • We can add support to static readonly fields.
  • Mutable static fields would become too messy. We should emit proper diagnostics though, and an error.

Sergio0694 avatar Aug 19 '22 23:08 Sergio0694