Code Monkey home page Code Monkey logo

shadergen's Introduction

ShaderGen

Gitter Chat

A proof-of-concept library which generates shader code from C#. Currently, the project can generate HLSL (D3D11), GLSL-330 (core-GL-compatible), GLSLES-300 (OpenGL ES-compatible), GLSL-450 (Vulkan-compatible), and Metal shader code from a single shader source specified in C#.

Shaders in C#

Writing shader code in C# could have quite a few benefits:

  • Easily share type definitions between graphics code in C# and shaders.
    • For example, one could re-use the same structure to describe the input to a vertex shader, as well as to store the actual vertex data in your C# program.
    • Shader uniforms ("constant buffers") can be shared as well.
  • Analysis done at code-generation time can be used to build extra metadata about the shaders, enabling reflection-like capabilities.
    • The full vertex input specification can be generated when doing the C# analysis for code generation.
    • The layouts and order for all global shader resources can be captured.
    • Validation can be performed to ensure, for example, uniforms are multiples of 16-bytes in size, etc.
  • C# refactoring tools can be used.
  • C# niceties like inheritance, composition, partial declarations, etc. can be leveraged for easier shader writing (speculative).

Example Shader

Here is an example vertex and fragment shader, written in C# with ShaderGen:

public class MinExample
{
    public Matrix4x4 Projection;
    public Matrix4x4 View;
    public Matrix4x4 World;
    public Texture2DResource SurfaceTexture;
    public SamplerResource Sampler;

    public struct VertexInput
    {
        [PositionSemantic] public Vector3 Position;
        [TextureCoordinateSemantic] public Vector2 TextureCoord;
    }

    public struct FragmentInput
    {
        [SystemPositionSemanticAttribute] public Vector4 Position;
        [TextureCoordinateSemantic] public Vector2 TextureCoord;
    }

    [VertexShader]
    public FragmentInput VertexShaderFunc(VertexInput input)
    {
        FragmentInput output;
        Vector4 worldPosition = Mul(World, new Vector4(input.Position, 1));
        Vector4 viewPosition = Mul(View, worldPosition);
        output.Position = Mul(Projection, viewPosition);
        output.TextureCoord = input.TextureCoord;
        return output;
    }

    [FragmentShader]
    public Vector4 FragmentShaderFunc(FragmentInput input)
    {
        return Sample(SurfaceTexture, Sampler, input.TextureCoord);
    }
}

Here is some representative output from the library (subject to change, etc.):

HLSL Vertex Shader

struct MinExample_VertexInput
{
    float3 Position : POSITION0;
    float2 TextureCoord : TEXCOORD0;
};

struct MinExample_FragmentInput
{
    float4 Position : SV_Position;
    float2 TextureCoord : TEXCOORD0;
};

cbuffer ProjectionBuffer : register(b0)
{
    float4x4 Projection;
}

cbuffer ViewBuffer : register(b1)
{
    float4x4 View;
}

cbuffer WorldBuffer : register(b2)
{
    float4x4 World;
}

MinExample_FragmentInput VertexShaderFunc( MinExample_VertexInput input)
{
    MinExample_FragmentInput output;
    float4 worldPosition = mul(World, float4(input.Position, 1));
    float4 viewPosition = mul(View, worldPosition);
    output.Position = mul(Projection, viewPosition);
    output.TextureCoord = input.TextureCoord;
    return output;
}

HLSL Fragment Shader

struct MinExample_VertexInput
{
    float3 Position : POSITION0;
    float2 TextureCoord : TEXCOORD0;
};

struct MinExample_FragmentInput
{
    float4 Position : SV_Position;
    float2 TextureCoord : TEXCOORD0;
};

Texture2D SurfaceTexture : register(t0);

SamplerState Sampler : register(s0);

float4 FragmentShaderFunc( MinExample_FragmentInput input) : SV_Target
{
    return SurfaceTexture.Sample(Sampler, input.TextureCoord);
}

GLSL (450) Vertex Shader

#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
struct MinExample_VertexInput
{
    vec3 Position;
    vec2 TextureCoord;
};

struct MinExample_FragmentInput
{
    vec4 Position;
    vec2 TextureCoord;
};

layout(set = 0, binding = 0) uniform Projection
{
    mat4 field_Projection;
};

layout(set = 0, binding = 1) uniform View
{
    mat4 field_View;
};

layout(set = 0, binding = 2) uniform World
{
    mat4 field_World;
};

layout(set = 0, binding = 3) uniform texture2D SurfaceTexture;
layout(set = 0, binding = 4) uniform sampler Sampler;
MinExample_FragmentInput VertexShaderFunc( MinExample_VertexInput input_)
{
    MinExample_FragmentInput output_;
    vec4 worldPosition = field_World * vec4(input_.Position, 1);
    vec4 viewPosition = field_View * worldPosition;
    output_.Position = field_Projection * viewPosition;
    output_.TextureCoord = input_.TextureCoord;
    return output_;
}


layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 TextureCoord;
layout(location = 0) out vec2 fsin_0;

void main()
{
    MinExample_VertexInput input_;
    input_.Position = Position;
    input_.TextureCoord = TextureCoord;
    MinExample_FragmentInput output_ = VertexShaderFunc(input_);
    fsin_0 = output_.TextureCoord;
    gl_Position = output_.Position;
        gl_Position.y = -gl_Position.y; // Correct for Vulkan clip coordinates
}

GLSL (450) Fragment Shader

#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
struct MinExample_VertexInput
{
    vec3 Position;
    vec2 TextureCoord;
};

struct MinExample_FragmentInput
{
    vec4 Position;
    vec2 TextureCoord;
};

layout(set = 0, binding = 0) uniform Projection
{
    mat4 field_Projection;
};

layout(set = 0, binding = 1) uniform View
{
    mat4 field_View;
};

layout(set = 0, binding = 2) uniform World
{
    mat4 field_World;
};

layout(set = 0, binding = 3) uniform texture2D SurfaceTexture;
layout(set = 0, binding = 4) uniform sampler Sampler;
vec4 FragmentShaderFunc( MinExample_FragmentInput input_)
{
    return texture(sampler2D(SurfaceTexture, Sampler), input_.TextureCoord);
}


layout(location = 0) in vec2 fsin_0;
layout(location = 0) out vec4 _outputColor_;

void main()
{
    MinExample_FragmentInput input_;
    input_.Position = gl_FragCoord;
    input_.TextureCoord = fsin_0;
    vec4 output_ = FragmentShaderFunc(input_);
    _outputColor_ = output_;
}

shadergen's People

Contributors

amerkoleci avatar clonedeath avatar feliwir avatar jiuyong avatar mellinoe avatar pathogendavid avatar perksey avatar tgjones avatar thargy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shadergen's Issues

[SUGGGESTION] ShaderBuiltins should be split

Further to the conversations in #65, #67 and GH-77, we should consider reserving ShaderBuiltins for members that will throw a ShaderBuiltinException as they cannot reasonably be implemented on the CPU - therefore code that uses them cannot be run on the CPU.

Methods that can have CPU equivalents should be in a seperate class (Builtins) to indicate that they can run on the GPU & CPU. This makes it easy to see if a file is using GPU only functionality (as it will reference ShaderBuiltins. By only including Builtins you can be sure the members used will be available when running the same code directly on the CPU.

Note, before GH-77 all members of ShaderBuiltins throw a ShaderBuiltinException anyway when executed on the CPU, even when they could be implemented to run on the CPU, so this wasn't as big an issue. However, going forward, the ability to share code between GPU & CPU is a USP of the library, and so consideration should be given to creating CPU implementations of members whenever feasible, so the distinction becomes more important.

[Enhancement] Support instance methods.

Currently, calling instance methods doesn't work. To support, we need to rewrite providing the instance as the first parameter, e.g. (extending the example in #72):

    public struct SimpleConstructedStruct
    {
        public readonly float OutFloat;

        public SimpleConstructedStruct(float outFloat)
        {
            OutFloat = outFloat;
        }

        public SimpleConstructedStruct Increment()
        {
            return new SimpleConstructedStruct (OutFloat + 1.0f);
        }
    }

...
    SimpleConstructedStruct s = new SimpleConstructedStruct(1.0f);
    s = s.SimpleConstructedStruct();

Should be written as:

    struct SimpleConstructedStruct
    {
        float OutFloat;
    };

    SimpleConstructedStruct_0_ctor(float outFloat)
    {
        SimpleConsructedStruct this;
        this.OutFloat = outFloat;
        return this;
    }

    SimpleConstructedStruct_Increment(SimpleConstructedStruct this)
    {
        return SimpleConstructedStruct_0_ctor(this.OutFloat + 1.0);
    }

...
    SimpleConstructedStruct s = SimpleConstructedStruct_0_ctor(1.0);
    s = SimpleConstructedStruct_Increment(s);

[BUG] GlslEs300 Compute shaders require es profile with version 310 or above

After adding more automated backend testing I received the following error on compiling a compute shader across all backends:

ERROR: #version: compute shaders require es profile with version 310 or above, or non-es profile with version 420 or above

Currently the GlslEs300 backend doesn't check to see if the function is a compute shader when compiling.

[Bug] Hlsl.bytes no longer produced.

Upgrading ShaderGen to 1.2.0.beta-3 stops creation of "hlsl.bytes" which I suspect is due to #89, though no warnings or errors are produced. I can confirm from the Build logs that SharpDx.D3DCompiler.dll is being found.

[SUGGESTION] Remove ShaderBuiltins for VertexID, InstanceID, DispatchThreadID, GroupThreadID, IsFrontFace

Further to the extensive discussion in #65, a core goal would be to share shader code between the CPU and GPU, allowing ray-tracing, AI, etc. to be run on the CPU where compute shaders are not available, or even to maximise throughput by utilising both the CPU and GPU.

The ShaderBuiltins for VertexID, InstanceID, DispatchThreadID, GroupThreadID, IsFrontFace allow shader code to access these variables in an 'OpenGL style' as opposed to the 'Direct3D' style where the variables are passed into shaders as parameters.

For example DispatchThreadID is simply replaced with gl_GlobalInvocationID in OpenGL, but is added as an input parameter to the compute shader e.g.

[numthreads(1, 1, 1)]
void CS(uint3 _builtins_DispatchThreadID : SV_DispatchThreadID) { ... }

Although, this is a valid sylistic choice, as the aspiration is to be able to ultimately execute CS from the CPU it would be much easier to achieve that goal replacing code such as:

        // Current 'OpenGL' style
        [ComputeShader(1, 1, 1)]
        public void CS()
        {
            uint index = DispatchThreadID.X;
            ...
        }

with a pattern like:

        // Proposed 'Direct3D' style - with parameter name matching
        [ComputeShader(1, 1, 1)]
        public void CS(UInt3 dispatchThreadID)
        {
            uint index = dispatchThreadID.X;
            ...
        }

or, to future proof, prevent collisions, be less 'magic' and far more intelligible to consumers, allow a semantic attribute that supports parameter attachment:

        // Proposed 'Direct3D' style - with semantic attribute indicating which parameter is the dispatch id
        [ComputeShader(1, 1, 1)]
        public void CS([DispatchIDSemantic] UInt3 id)
        {
            uint index = id.X;
            ...
        }

This approach would make it much easier to call the compute shader directly from .NET code on the CPU. Currently ShaderBuiltins.DispatchID (et al) throw a ShaderBuiltinException on execution on the CPU. Technically, I could change them to pull out a value from the Thread context, however maintaining a thread context is notoriously annoying in an async world and impacts on performance, and compute shaders (in particular) are destined to be run asynchronously even on the CPU. Further, as the code can't easily tell which properties will be accessed in the code, all of them would need adding to the context immediately prior to execution, which is wasteful and slow. Anyone implementing a CPU executor for the shaders would likewise need to understand how to setup the contexts (possibly by adding methods such as ShaderBuiltins.InitialiseComputeContext(...), ShaderBuiltins.InitialiseVertexContext(...), etc.) but this is far less obvious and discoverable, and far less performant than adopting the Direct3D style.

ShaderGen.Build does not generate any shader

Environment

ShaderGen/ShaderGen.Build 1.2.0-beta3 from nuget.org
Windows 10 Pro 18362.418 (1903)
.NET Core SDK 3.0
JetBrains Rider 2019.3

Description

I was trying to use ShaderGen.Build to build my shaders, but it didn't generate anything in bin. No MSBuild messages were shown. I did spot ShaderGen files in obj, but _sggeneratedfiles.txt was empty.

Reproducing

Here's a minimal repro project zipped with artifacts generated by my build. Shader copied from veldrid-raytracer.

Foo.zip

ShaderGen doesn't escape paths when passing to HLSL Compiler

When a shader path has a space in it then HLSL compilation fails:

Failed to compile HLSL: StdOut: , StdErr: Too many files specified ('Control\Veldrid-Samples\bin\obj\TexturedCube\Cube-vertex.hlsl' was the last one), use /? to get usage information.

(Full path was "D:\Source Control\..." so is splitting on space

The issue is due to line 308 of ShaderGen.App.Program's CompileHlsl method which sets the args:
string args = $"/T {profile} /E {entryPoint} {shaderPath} /Fo {outputPath}";

Quoting the parameters fixes, so I will submit a pull-request.

Feature request: support calling static functions in separate files

I'd like to be able to do this:

File1.cs

using static CommonHelperFunctions;

[VertexShader]
Vector4 VS(Vector4 input)
{
    return FunctionInAnotherClass(input);
}

File2.cs

public static class CommonHelperFunctions
{
    public static Vector4 FunctionInAnotherClass(Vector4 value)
    {
        return value;
    }
}

I imagine this could be a common pattern for helper functions.

But currently, it crashes in the language backend. For example, in HlslBackend it crashes here. This Single call doesn't match anything, because setContext.Functions only contains functions defined in the local file:

ShaderFunctionAndBlockSyntax f = setContext.Functions.Single(
    sfabs => sfabs.Function.DeclaringType == name.TypeName && sfabs.Function.Name == name.MethodName);

I had a quick go at trying to fix the code but it got a bit complicated. I can keep going, but I wanted to check first: is this something you'd like to support? If so, any implementation suggestions would be appreciated :)

[BUG] Constructor name collision

"ctor" should not be used to identify constructors, ".ctor" should be used (as .NET does) to prevent collision with legitimate members named "ctor".

Support 0->1 depth range in GLSL

In order to support the reverse-depth-buffer optimization, I would like to support a 0->1 depth range, even in GLSL. Given that the glClipControl extension isn't universally supported, and given that folks will still likely use the default depth range in OpenGL, we should support both depth ranges somehow. Also, OpenGL ES and WebGL do not support glClipControl at all. Perhaps the best way to accomplish this is conditional compilation -- if DEPTH_RANGE_ZERO_TO_ONE is defined, we will not transform the depth range. Otherwise we will, as we do today.

We should think about how such a preprocessor define could be utilized in other intrinsic functions that could allow you to write code that is portable to both depth range schemes.

@tgjones Any thoughts here?

[ENHANCEMENT] Parse GLSLangValidator version output in ToolChain

Running GlslangValidator -v produces an output similar to this:

Glslang Version: Overload400-PrecQual.1817 08-Feb-2017
ESSL Version: OpenGL ES GLSL 3.00 glslang LunarG Khronos.Overload400-PrecQual.1817 08-Feb-2017
GLSL Version: 4.20 glslang LunarG Khronos.Overload400-PrecQual.1817 08-Feb-2017
SPIR-V Version 0x00010000, Revision 8
GLSL.std.450 Version 100, Revision 1
Khronos Tool ID 8
GL_KHR_vulkan_glsl version 100
ARB_GL_gl_spirv version 100

When multiple versions of GlslangValidator are found on the same system (local directory, path, Vulkan SDK, etc.) it would be nice to pick the latest automatically by picking the latest version (by parsing this output). For bonus points, we can see which version of each spec. is supported.

Catch StructuredBuffer alignment issues

Due to alignment requirements in GLSL, there are some very subtle bugs that can occur when using StructuredBuffers with custom structure types. We should run some analysis on the structure types used in StructuredBuffer resources and ensure that the exact layout will be preserved when used in a GLSL shader. The important alignment requirements that I am aware of are:

  • Structures must be aligned to the size of their largest member. For example, a structure containing a Vector4 needs to be a multiple of 16-bytes, or it will be misaligned when consumed from GLSL.
  • Fields need to be aligned to their size. For example, a Vector4 field needs to be placed on a 16-byte boundary or it will be misaligned when consumed from GLSL.

To facilitate this, we should just do some basic analysis and step over each field when we encounter a StructuredBuffer resource. We already do some analysis like this in order to determine struct sizes. We can simply check if the alignment requirements are being met and throw an exception if not.

@thargy

Help needed: Problems with compute shaders

I've been trying to get the Particle Compute shader example working using ShaderGen, and I'm not pretty much stuck. If managed to find my way around a lot of idiosyncracies but I could do with some help if anyone has time?

I've managed to create the code shown in #62 (and I won't reproduce here as it's quite large), and, so long as I split the class files into their own files (see #62), the Compute, Vertex and Fragment shaders all compile without error.

When using GraphicsBackend.Direct3D11 the particles zoom around the screen much faster than before (possibly the cause of the next issues), but clearly the shaders are doing something.

When changing to GraphicsBackend.OpenGL, the shaders 'load' but I get the following error:

VeldridException: Unable to compile shader code for shader [] of type VertexShader: 0(25) : error C0000: syntax error, unexpected identifier, expecting reserved word or reserved word "in" or reserved word "out" or reserved word "uniform" at token "buffer"

When I get to the first GraphicsDevice.SwapBuffers(MainSwapchain); call in Draw. (see here)

From what I can gather this is probably due to the following section of the 330.glsl shader, being invalid syntax:

layout(std140) readonly buffer Particles
{
    Apocraphy_Shaders_ParticleInfo field_Particles[];
};

This is generated from the following field in the Vertex/Fragment shader file:
public StructuredBuffer<ParticleInfo> Particles;

When changing to `GraphicsBackend.Vulkan', I get a different error:

System.AccessViolationException
HResult=0x80004003
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Source=
StackTrace:

When I get to the first GraphicsDevice.SwapBuffers(MainSwapchain); call in Draw (see here).

This might be related to whatever's causing the particles to zoom around on Direct3D11, which is possibly due to preceeding two lines no longer pointing to the correct places in the output shaders:

            _cl.SetComputeResourceSet(0, _computeResourceSet);
            _cl.SetComputeResourceSet(1, _computeScreenSizeResourceSet);

(also here)

For completeness, below are the generated Vertex shaders:

OpenGL

#version 330 core

struct SamplerDummy { int _dummyValue; };

struct Apocraphy_Shaders_ScreenSize
{
    float Width;
    float Height;
    vec2 _padding;
};

struct Apocraphy_Shaders_FragmentInput
{
    vec4 Position;
    vec4 Color;
};

struct Apocraphy_Shaders_ParticleInfo
{
    vec2 Position;
    vec2 Velocity;
    vec4 Color;
};

layout(std140) readonly buffer Particles
{
    Apocraphy_Shaders_ParticleInfo field_Particles[];
};
layout(std140) uniform ScreenSize
{
    Apocraphy_Shaders_ScreenSize field_ScreenSize;
};

Apocraphy_Shaders_FragmentInput VS()
{
    Apocraphy_Shaders_ParticleInfo input_ = field_Particles[uint(gl_VertexID)];
    Apocraphy_Shaders_FragmentInput output_;
    output_.Position.x = 2 * (input_.Position.x / field_ScreenSize.Width - 0.5f);
    output_.Position.y = 2 * (input_.Position.y / field_ScreenSize.Height - 0.5f);
    output_.Position.z = 0.f;
    output_.Position.w = 1.f;
    output_.Color = input_.Color;
    return output_;
}


out vec4 fsin_0;

void main()
{
    Apocraphy_Shaders_FragmentInput output_ = VS();
    fsin_0 = output_.Color;
    gl_Position = output_.Position;
        gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;
}

Vulkan

#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
struct Apocraphy_Shaders_ScreenSize
{
    float Width;
    float Height;
    vec2 _padding;
};

struct Apocraphy_Shaders_FragmentInput
{
    vec4 Position;
    vec4 Color;
};

struct Apocraphy_Shaders_ParticleInfo
{
    vec2 Position;
    vec2 Velocity;
    vec4 Color;
};

layout(std140, set = 0, binding = 0) readonly buffer Particles
{
    Apocraphy_Shaders_ParticleInfo field_Particles[];
};
layout(set = 0, binding = 1) uniform ScreenSize
{
    Apocraphy_Shaders_ScreenSize field_ScreenSize;
};

Apocraphy_Shaders_FragmentInput VS()
{
    Apocraphy_Shaders_ParticleInfo input_ = field_Particles[gl_VertexIndex];
    Apocraphy_Shaders_FragmentInput output_;
    output_.Position.x = 2 * (input_.Position.x / field_ScreenSize.Width - 0.5f);
    output_.Position.y = 2 * (input_.Position.y / field_ScreenSize.Height - 0.5f);
    output_.Position.z = 0.f;
    output_.Position.w = 1.f;
    output_.Color = input_.Color;
    return output_;
}


layout(location = 0) out vec4 fsin_0;

void main()
{
    Apocraphy_Shaders_FragmentInput output_ = VS();
    fsin_0 = output_.Color;
    gl_Position = output_.Position;
        gl_Position.y = -gl_Position.y; // Correct for Vulkan clip coordinates
}

Direct3D:

struct Apocraphy_Shaders_ScreenSize
{
    float Width;
    float Height;
    float2 _padding;
};

struct Apocraphy_Shaders_FragmentInput
{
    float4 Position : SV_Position;
    float4 Color : COLOR0;
};

struct Apocraphy_Shaders_ParticleInfo
{
    float2 Position : POSITION0;
    float2 Velocity : POSITION1;
    float4 Color : COLOR0;
};

StructuredBuffer<Apocraphy_Shaders_ParticleInfo> Particles: register(t0);
cbuffer ScreenSizeBuffer : register(b0)
{
    Apocraphy_Shaders_ScreenSize ScreenSize;
}

Apocraphy_Shaders_FragmentInput VS(uint _builtins_VertexID : SV_VertexID)
{
    Apocraphy_Shaders_ParticleInfo input = Particles[_builtins_VertexID];
    Apocraphy_Shaders_FragmentInput output;
    output.Position.x = 2 * (input.Position.x / ScreenSize.Width - 0.5f);
    output.Position.y = 2 * (input.Position.y / ScreenSize.Height - 0.5f);
    output.Position.z = 0.f;
    output.Position.w = 1.f;
    output.Color = input.Color;
    return output;
}

embedding generated shaders as part of the compilation process.

Right now the compiled shaders are generad as content files that are copied to the output bin directory, so the executable can load the shaders from the directory.

Is there a way to tell ShaderGen to treat the generated shaders as embedded resources instead of content files?

Vertex shader with multiple vertex data buffers outputs incorrect buffer indices in Metal backend

I've got this shader input structure:

public struct VertexInput
{
    [PositionSemantic] public Vector3 Position;
    [NormalSemantic] public Vector3 Normal;

    [TextureCoordinateSemantic] public Vector2 UV;
}

On the application side, the data comes from two separate buffers:

  • One buffer for Position and Normal
  • One buffer for UV

I also have some uniform buffers in this shader. To simplify, let's say there's one buffer named GlobalConstants.

ShaderGen writes out (something like) the following Metal code:

vertex VertexOutput VS(
    VertexInput input [[ stage_in ]],
    constant GlobalConstantsType &GlobalConstants [[ buffer(1) ]]) ...

The problem is in the binding location for GlobalConstants. It should be [[ buffer(2) ]], because the first two slots are taken up by the vertex data.

Here's the code responsible:

if (function.Type == ShaderFunctionType.VertexEntryPoint
&& function.Parameters.Length > 0)
{
bufferBinding = 1;
}

For this example, it needs to offset bufferBinding by 2, not 1.

I don't seen an obvious solution. Perhaps there could be an attribute on struct VertexInput to tell ShaderGen how many vertex buffers are used (with a default of 1 if there's no attribute)? Something like:

[VertexBufferCount(2)]
public struct VertexInput { ... }

Once we have a solution, I can do a PR for it.

I can't output some file.

when i use it in veldrid-raytracer on window server 2019,it con't output *.hlsl.bytes and *.450.glsl.spv file.
but it can output *.glsl *.glsle *.hlsl *.metal file well . and the demo work on OpenGL and Vulkan as fine.

How do you normalize?

Trying to work out how to normalize a vector?

Is there any documentation as also trying to understand the register attribute and why numbers can be repeated?

[Bug] Tests fail on computers without OpenGLES

Spotted this one in passing.

The use of ToolFeatures.HeadlessGraphicsDevice here should be ToolFeatures.ToHeadless; which will ensure that the backend supports compilation and transpilation on the test run, as well as running in headless mode.

Shader permutation

This is something that creates panic inside me, how would be possible to handle shader variants or permutation, maybe having the ShaderSet that sends preprocessor defines to roslyn process. Unity has some concept about shader permutation, instead of:

if(NormalMapEnabled) // Create branch on gpu shader generation
#if NORMAL_MAP_ENABLED // Macro

[SUGGESTION] Control compatibility level for method implementations.

As I've been going through #65, (see GH-77 and GH-80), and making methods consistent, it has occurred to me that, there is a concept of how compatible you want implementations to be across backends.

For example, the Pow method on Vulkan ignores the sign of the first parameter, whereas all other backends will return a NaN for -ve values. To make compatible, I had to wrap the input parameter with an abs().

In some scenarios, this optimisation is a cost that developers may not wish to pay - particularly if the input is never intended to be negative. There are numerous other examples.

There are several ways to resolve this:

  • Along with #78 (which I still think we shoul do to distinguish GPU-exclusive methods from GPU/CPU methods), add a new FastBuiltins class which implements the same methods as Builtins but where implementations are only consistent within certain input ranges (which can be documented in the intelli-sense for those functions). This allows for quick switching between 'accurate' and 'fast' compatibility in a single file, but you can still mix and match by using explicit static invocation, or by explicit using statements.
  • Provide method overloads which are less fussy, e.g. PowFast() as well as Pow(), this prevents name collisions when using using static, and is more visible, it requires manually changing invocations when required, which is no bad thing as care should be taken when using a fast implementation anyway.
  • Use an attribute on the class/method to indicate compatability level.
  • Add transpiler options for compatability, which can be passed in on build.

[Bug] Comparison between UInt and Int fails on GlslEs300

When compiling code such as

    uint index = 3;
    if (index >= 2)
        return ;

Against the GlslEs300 backend, the following error occurs (from glslangvalidator:

'>=' : wrong operand types: no operation '>=' exists that takes a left-hand operand of type ' temp mediump uint' and a right operand of type ' const int' (or there is no acceptable conversion)

This implies we need add am explicit cast in this scenario for this specific backend (compilation succeeds on other backends, can't test Metal).

[Enhancement] Add tests for ShaderGen.App

The current tests don't cover ShaderGen.App, which meant that bugs introduced by #89 were not detected. Making it testable should reduce such issues.

Further, some of the logic in ToolChain.cs would more usefully sit in ShaderGen.App anyway, where it can be used by other tools in the future.

[Enhancement] Add ability to run compute shaders in test project

There are lots of subtle differences between the implementations of the different backends that mean we can't guarantee equivalence between compilations unless we actually execute the shader code. Further to the discussion in #65, using Veldrid in the test project should allow us to create compute shaders to automatically flag differences in code execution.

[Enhancement] Asynchronously compile shaders

Compiling Shaders for HLSL, OpenGL, Metal, etc. often involves File IO and invocation of separate processes which can happen asynchronously for potential speed gains on using as part of a Build process.

ShaderGen.App should separate the various backend pathways into ToolChains (which could be moved out of the current test project as part of #98) to make more extensible and facilitate asynchronous invocation.

Converting ShaderGen to work as a Roslyn Source Generator

I am trying to strip all of the build functionality of ShaderGen into a source generator that can produce SPIR-V byte code at compile time for a ShaderGen shader.

I have created this small project to test if I can get the backend code to generate: https://github.com/ScottKane/ShaderGenTest

For some reason the following code doesn't produce any shader sets:

var hlsl      = new HlslBackend(compilation);
var glsl330   = new Glsl330Backend(compilation);
var glsles300 = new GlslEs300Backend(compilation);
var glsl450   = new Glsl450Backend(compilation);
var metal     = new MetalBackend(compilation);

var languages = new LanguageBackend[]
{
    hlsl,
    glsl330,
    glsles300,
    glsl450,
    metal,
};
    
var generator = new ShaderGen.ShaderGenerator(compilation, languages);
var result    = generator.GenerateShaders();

foreach (var language in languages)
{
    var extension = BackendExtension(language);
    var sets      = result.GetOutput(language);

    foreach (var set in sets)
    {
        var name = set.Name;
        if (set.VertexShaderCode != null)
        {
            name = name + "-vertex." + extension;
            c.AddSource(name, ToCommented(set.VertexShaderCode));
        }
        if (set.FragmentShaderCode != null)
        {
            name = name + "-fragment." + extension;
            c.AddSource(name, ToCommented(set.FragmentShaderCode));
        }
        if (set.ComputeShaderCode != null)
        {
            name = name + "-compute." + extension;
            c.AddSource(name, ToCommented(set.ComputeShaderCode));
        }
    }
}

@mellinoe If you have any capacity to take a quick look, any help would be greatly appreciated. The DebugRoslynComponent run profile should allow you to debug the generator to see what's going on if you run it from Visual Studio (Rider doesn't support debugging roslyn components yet).

I think if I can get this working as a generator it could breathe new life into the project.

Thanks

About findFXC

I find that if we change the default windows kits installation path,the function can't work correctly.
I think we can search from system environment variables.
Or we can let the user manually set a fxcpath string.

private static string FindFxcExe()
{
const string WindowsKitsFolder = @"C:\Program Files (x86)\Windows Kits";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Directory.Exists(WindowsKitsFolder))
{
IEnumerable<string> paths = Directory.EnumerateFiles(
WindowsKitsFolder,
"fxc.exe",
SearchOption.AllDirectories);
string path = paths.FirstOrDefault(s => !s.Contains("arm"));
return path;
}
return null;
}

Suggestion: Drop ShaderSet assembly attribute

I have a suggestion/question regarding the ShaderSetAttribute assembly attribute. It strikes me that the current design does not allow for easy refactoring.

Rather than have the assembly attribute, would it be better to make the VertexShaderAttribute and FragmentShaderAttribute attributes accept a params string[] shaderSets parameter? Although finding Assembly attributes is very quick compared to looking for member attributes, the latter is still incredibly fast in reality, is more readible, and is impervious to method renaming. Also, having to specify the shaderset attribute as well as adding the FragmentShader and VertexShader attributes seems redundant. The minor build time cost seems worth it?

Please forgive me if I am missing something?

Add support for full MathF library

These are pretty important functions so need to add support. However, I don't believe we can just use the SimpleNameTranslator as apparently HLSL swaps the arguments.

MathF.Atan2 it takes float y, float x which is the same as GLSL, however, I believe HLSL takes float x, float y.

[BUG] Using bool field type in buffer causes stack overflow

When using the bool field type in a struct that is itself used in a buffer, ShaderGen crashes due to a stack overflow. This can be traced to TypeSizeCache.GetCSharpSize not having a known size for System.Boolean, to fix that issue requires reference to the conversation in #14, as the size is different depending on the backend in question.

This bug then causes it to try to determine the size manually, there are only two fields in a bool, and both are static strings (FalseString and TrueString). As they are statics they should be skipped as they don't contribute to the size.

However, this bug causes TypeSizeCahce.GetCSharpAlignment to be called for the symbol System.String. This two is not a known type, and also has a single field, which is also a static string (Empty), so TypeSizeCahce.GetCSharpAlignment is called again for the symbol System.String. And we get recursion.

The fixes therefore are:

  1. Add a size of 4 bytes for bool, which should be correct for everything but Metal (see #14). May need to add support to specify a size of 1 for Metal.
  2. Skip consideration of static fields in structs when calculating Size.
  3. Change recursive methods to use stack based recursion and detect repeats.
  4. Consider adding support for System.String by treating as a byte[] or similar.

[Enhancement] Support method overloading

Currently, method overloading doesn't work. To support, method numbering would help, e.g. (extending the example in #73):

    public struct SimpleConstructedStruct
    {
        public readonly float OutFloat;

        public SimpleConstructedStruct(float outFloat)
        {
            OutFloat = outFloat;
        }

        public SimpleConstructedStruct Increment()
        {
            return new SimpleConstructedStruct (OutFloat + 1.0f);
        }

        public SimpleConstructedStruct Increment(float amount)
        {
            return new SimpleConstructedStruct (OutFloat + amount);
        }
    }

...
    SimpleConstructedStruct s = new SimpleConstructedStruct(1.0f);
    s = s.SimpleConstructedStruct();
    s = s.SimpleConstructedStruct(1.0f);

Should be written as:

    struct SimpleConstructedStruct
    {
        float OutFloat;
    };

    SimpleConstructedStruct_0_ctor(float outFloat)
    {
        SimpleConsructedStruct this;
        this.OutFloat = outFloat;
        return this;
    }

    SimpleConstructedStruct_Increment(SimpleConstructedStruct this)
    {
        return SimpleConstructedStruct_0_ctor(this.OutFloat + 1.0);
    }

    SimpleConstructedStruct_Increment_2(SimpleConstructedStruct this, float amount)
    {
        return SimpleConstructedStruct_0_ctor(this.OutFloat + amount);
    }

...
    SimpleConstructedStruct s = SimpleConstructedStruct_0_ctor(1.0);
    s = SimpleConstructedStruct_Increment(s);
    s = SimpleConstructedStruct_Increment_2(s, 1.0);

Reserved Keywords collision prevention

Finally got round to trying ShaderGen and Veldrid out as I like the idea of the project. I am something of a novice when it comes to writing shaders, but I thought I'd give it a go. I got everything set up (not entirely plug and play and I might raise a few suggestions for improving new user experience, but I guess the projects not at that stage yet).

Being a novice, I called my Vertex Shader method, VertexShader. Everything compiled, but a closer look at the build output revealed the following (note it didn't fail the build):

------ Rebuild All started: Project: Apocraphy, Configuration: Debug Any CPU ------
Failed to compile HLSL: StdOut: Microsoft (R) Direct3D Shader Compiler 10.1
Copyright (C) 2013 Microsoft. All rights reserved.

, StdErr: C:\Users\Craig.Dean\OneDrive - Web Applications UK Ltd\Sandboxes\Apocraphy\Apocraphy\obj\Shaders\Simple-vertex.hlsl(13,40-51): error X3000: syntax error: unexpected token 'VertexShader'

compilation failed; no code produced
.
Apocraphy -> C:\Users\Craig.Dean\OneDrive - Web Applications UK Ltd\Sandboxes\Apocraphy\Apocraphy\bin\Debug\netcoreapp2.0\Apocraphy.dll
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

And, on inspection, the Shaders folder did not contain a hlsl.bytes file for the vertex shader.

After some Google-Fu I finally figured out that this was because VertexShader is a reserved keyword in hlsl (see HLSL Keywords). It's not obvious from the error, but that's not your fault. Again - I'm a bit of a novice when it comes to shaders.

The first question is, should the final HLSL compilation failure not ideally fail the build (I note an option in the ShaderGen targets that for the build to fail when ShaderGen itself fails, should we have a similar option or respect that option for HLSL compilation failures)?

Moreover, one of the benefits of ShaderGen is the ability it will give to perform more useful static analysis. It seems like it would be relatively easy to add reserved keyword collision checking for the various languages and to auto-rename colliding tokens in the generated output, or error. At the moment the code seems to assume that valid C# identifiers are also valid in the other languages, and that is not the case according to the various specifications I've quickly reviewed (opengl has reservations too).

Thanks for a great project.

Build targets escape path backslash

Compile target doesnt escape paths, this causes an issue on some windows machines. I did solve it by adding another backslash after $(BaseIntermediateOutputPath).

This would have to be solved while keeping crossplatform in mind.

<!-- File: project.csproj -->
<!-- $(BaseIntermediateOutputPath) == "obj\" -->
<ShaderOutputPath>$(BaseIntermediateOutputPath)</ShaderOutputPath>
<!-- File: ShaderGen.targets -->
<_SGArgs>$(_SGArgs) --out "$(ShaderOutputPath)"</_SGArgs>

So that would generate something like this:

dotnet ...\packages\shadergen.build\1.0.30\build\ShaderGen.App.dll  --ref "obj\Debug\netcoreapp2.0\_sgreferences.txt" --src "obj\Debug\netcoreapp2.0\_sgsources.txt" --out "obj\" --genlist "obj\Debug\netcoreapp2.0\_sggeneratedfiles.txt"

Excellent work

Hi,

Recently I started also adding shader generation logic using C#, similar as you, how would you handle the generation logic? as MSBuild task? I suppose that the generation should be not deployed with existing C# project, also reference to Microsoft.CodeAnalysis.CSharp is not usuable prefered on final target?

Just asking how you plan to handle this.
Thanks,

Amer

[Enhancement] Use GLSLangValidator to validate OpenGL Shaders

Currently ShaderGen.App only uses glslangvalidator to compile Vulkan. It would be nice to have the option to use it to validate the OpenGL ES and 330 too.

We could do this by extending #93 to include a new option:

V | Validate Validates OpenGLES And OpenGL330 shaders, and in the case of OpenGL450 it only validates and doesn't compile spirv, just outputting the shader code. For other backends is the same as R except does not compile (so requires the output of uncompiled shader).

[Enhancement] Support constructor invocation on structs

For example:

    public struct SimpleConstructedStruct
    {
        public readonly float OutFloat;

        public SimpleConstructedStruct(float outFloat)
        {
            OutFloat = outFloat;
        }
    }

...
    SimpleConstructedStruct s = new SimpleConstructedStruct(1.0f);

Should become:

    struct SimpleConstructedStruct
    {
        float OutFloat;
    };

    SimpleConstructedStruct_0_ctor(float outFloat)
    {
        SimpleConsructedStruct this;
        this.OutFloat = outFloat;
        return this;
    }

...
    SimpleConstructedStruct s = SimpleConstructedStruct_0_ctor(1.0);

Field name collision when classes in the same file.

In an attempt to better understand ShaderGen, I've had a go at converting the ComputeParticles shaders from the Veldrid-Samples project. After lots of trial and error, I got to the following:

using System.Numerics;
using ShaderGen;
using static ShaderGen.ShaderBuiltins;

[assembly: ShaderSet("Particle", "Apocraphy.Shaders.Particle.VS", "Apocraphy.Shaders.Particle.FS")]
[assembly: ComputeShaderSet("ParticleCompute", "Apocraphy.Shaders.ParticleCompute.CS")]
namespace Apocraphy.Shaders
{
    public class ParticleCompute
    {
        public RWStructuredBuffer<ParticleInfo> Particles;
        public ScreenSize ScreenSize;

        [ComputeShader(1, 1, 1)]
        public void CS()
        {
            uint index = DispatchThreadID.X;
            if (index > 1024)
                return;

            ParticleInfo particleInfo = Particles[index];
            Vector2 pos = particleInfo.Position;
            Vector2 vel = particleInfo.Velocity;

            Vector2 newPos = pos + vel;
            Vector2 newVel = vel;
            if (newPos.X > ScreenSize.Width)
            {
                newPos.X -= newPos.X - ScreenSize.Width;
                newVel.X *= -1;
            }
            if (newPos.X < 0)
            {
                newPos.X *= -1;
                newVel.X *= -1;
            }
            if (newPos.Y > ScreenSize.Height)
            {
                newPos.Y -= newPos.Y - ScreenSize.Height;
                newVel.Y *= -1;
            }
            if (newPos.Y < 0)
            {
                newPos.Y = -newPos.Y;
                newVel.Y *= -1;
            }

            particleInfo.Position = newPos;
            particleInfo.Velocity = newVel;
            Particles[index] = particleInfo;
        }

    }

    public class Particle
    {
        public StructuredBuffer<ParticleInfo> Particles;
        public ScreenSize ScreenSize;

        [VertexShader]
        public FragmentInput VS()
        {
            ParticleInfo input = Particles[VertexID];
            FragmentInput output;
            output.Position.X = 2 * (input.Position.X / ScreenSize.Width - 0.5f);
            output.Position.Y = 2 * (input.Position.Y / ScreenSize.Height - 0.5f);
            output.Position.Z = 0f;
            output.Position.W = 1f;
            output.Color = input.Color;
            return output;
        }

        [FragmentShader]
        public Vector4 FS(FragmentInput input)
        {
            return input.Color;
        }

    }

    public struct ScreenSize
    {
        public float Width;
        public float Height;

#pragma warning disable 169
        private Vector2 _padding;
#pragma warning restore 169
    }

    public struct FragmentInput
    {
        [SystemPositionSemantic]
        public Vector4 Position;

        [ColorSemantic]
        public Vector4 Color;
    }

    public struct ParticleInfo
    {
        [PositionSemantic]
        public Vector2 Position;

        [PositionSemantic]
        public Vector2 Velocity;

        [ColorSemantic]
        public Vector4 Color;
    };
}

However, this throws the following error on build:

------ Rebuild All started: Project: Apocraphy, Configuration: Debug Any CPU ------
An error was encountered while generating shader code:
System.InvalidOperationException: Sequence contains more than one matching element
at System.Linq.Enumerable.Single[TSource](IEnumerable1 source, Func2 predicate)
at ShaderGen.ShaderMethodVisitor.VisitIdentifierName(IdentifierNameSyntax node) in C:\projects\shadergen\src\ShaderGen\ShaderMethodVisitor.cs:line 347
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor1.Visit(SyntaxNode node) at ShaderGen.ShaderMethodVisitor.VisitElementAccessExpression(ElementAccessExpressionSyntax node) in C:\projects\shadergen\src\ShaderGen\ShaderMethodVisitor.cs:line 520 at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor1.Visit(SyntaxNode node)
at ShaderGen.ShaderMethodVisitor.VisitVariableDeclaration(VariableDeclarationSyntax node) in C:\projects\shadergen\src\ShaderGen\ShaderMethodVisitor.cs:line 505
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor1.Visit(SyntaxNode node) at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor1.Visit(SyntaxNode node)
at ShaderGen.ShaderMethodVisitor.VisitBlock(BlockSyntax node) in C:\projects\shadergen\src\ShaderGen\ShaderMethodVisitor.cs:line 73
at ShaderGen.ShaderMethodVisitor.VisitFunction(MethodDeclarationSyntax node) in C:\projects\shadergen\src\ShaderGen\ShaderMethodVisitor.cs:line 38
at ShaderGen.Hlsl.HlslBackend.GenerateFullTextCore(String setName, ShaderFunction function) in C:\projects\shadergen\src\ShaderGen\Hlsl\HlslBackend.cs:line 182
at ShaderGen.LanguageBackend.ProcessEntryFunction(String setName, ShaderFunction function) in C:\projects\shadergen\src\ShaderGen\LanguageBackend.cs:line 148
at ShaderGen.LanguageBackend.GetShaderModel(String setName) in C:\projects\shadergen\src\ShaderGen\LanguageBackend.cs:line 78
at ShaderGen.ShaderGenerator.GenerateShaders(ShaderSetInfo ss, ShaderGenerationResult result) in C:\projects\shadergen\src\ShaderGen\ShaderGenerator.cs:line 198
at ShaderGen.ShaderGenerator.GenerateShaders() in C:\projects\shadergen\src\ShaderGen\ShaderGenerator.cs:line 144
at ShaderGen.App.Program.Main(String[] args) in C:\projects\shadergen\src\ShaderGen.App\Program.cs:line 167

C:\Users\Craig.nuget\packages\shadergen.build\1.2.0-gf5792ed443\build\ShaderGen.targets(35,5): error MSB3073: The command "dotnet C:\Users\Craig.nuget\packages\shadergen.build\1.2.0-gf5792ed443\build\ShaderGen.App.dll --ref " obj\Debug\netcoreapp2.0_sgreferences.txt " --src " obj\Debug\netcoreapp2.0_sgsources.txt " --out " obj\Shaders " --genlist "obj\Debug\netcoreapp2.0_sggeneratedfiles.txt"" exited with code -1.
Done building project "Apocraphy.csproj" -- FAILED.
------ Rebuild All started: Project: Apocraphy.App, Configuration: Debug Any CPU ------

Which refers to:

ResourceDefinition referencedResource = _backend.GetContext(_setName).Resources.Single(rd => rd.Name == symbolName);

Moving the ParticleCompute class into it's own file fixes the issue. I'm raising as a bug as classes can be created in the same file (even if generally considered bad practice).

NuGet packages out of order

Following on from the discussion in #63, although the version.js file appears to be correct now, NuGet's are still being published out of order on MyGet, which makes for a confusing experience when using VisualStudio, and can lead to devs downloading the wrong version unless they are manually inspecting based on modification date.

[Suggestion] Add JetBrains.Annotations

Hi,

JetBrains Resharper is very popular, and something I've used extensively over the years. It's particularly good at providing warnings for code issues like potential null reference exceptions, and works with the Rider IDE.

To enhance static analysis you can mark a library with attributes such as [NotNull], [CanBeNull] and the incredibly powerful [ContractAnnotation] all of which allow R# to spot potential code issues.

These attributes are available via the JetBrains.Annotations NuGet and are effectively transparent to anyone not using R#. Source is available on GitHub and uses the MIT license.

Is it something that we could add in?

[Enhancement] Add more options to ShaderGen.App to require shader compilations

When using ShaderGen as part of a task it tends to fail silently which is not a great experience for end users who can take time trying to figure out why their code isn't running correctly. I suggest adding options to ShaderGen.App for each backend type, to allow them to be specified as:

X | Exclude - Prevents the creation of the specific Shader - this allows for faster builds where certain Shaders are not required.
A | Auto - [Default] Build if possible, but fails silently if not (current behavior).
R | Require - Fail if the shader isn't successfully built.

The supported backends include hlsl, glsl330, glsles300, glsl450, metal and iOS (note this also requires some simple logic tweaks in the way metal currently builds both MacOS and iOS).

A suggested format could be:
--glsl330 X --glsles300 Exclude --hlsl Require --ios X

Which would fail if HLSL failed, would not attempt OpenGL 330, ES, or iOS metal and would build MacOS Metal and Vulkan (glsl450) if possible.

This would then be backed by new build properties such as $(ShaderGenHlslMode) which are imported in ShaderGen.targets, e.g.

<_SGArgs Condition="'$(ShaderGenHlslMode)' != ''">$(_SGArgs) --hlsl "$(ShaderGenHlslMode)"</_SGArgs>

Developers can then get the desired behaviour by adding the properties to their .csproj, e.g.

<ShaderGenHlslMode>Require</ShaderGenHlslMode>
<ShaderGenIosMode>Exclude</ShaderGenIosMode>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.