Skip to content

Instantly share code, notes, and snippets.

@schroffl
Last active January 6, 2019 20:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schroffl/17150efeedf18f5ca3ef3a724344f247 to your computer and use it in GitHub Desktop.
Save schroffl/17150efeedf18f5ca3ef3a724344f247 to your computer and use it in GitHub Desktop.

I'm trying to create an API to describe an OpenGL Shader Pipeline in Zig. The goal of all this is to have type-safe functions for e.g. setting uniforms and automatically generating variable declarations based on the types.

My current (semi-working) implementation looks like this:
Edit: Actually, it's working, but there's still a lot of stuff done by manually calling the OpenGL functions (like setting uniforms). So basically only the code generation is being used right now.

/// For now it only supports vertex and fragment shaders.
/// At some point a generic Stage type could be created which would
/// contain the information about input and output. This allows
/// for creating arbitrary pipelines where Tesselation/Geometry shaders
/// can easily be added or removed.
pub const Pipeline = struct {
    Vertex: type,
    Uniforms: type,
    Varyings: type,
    
    /// These contain the source code of the shader
    /// as written by the user
    vertex_shader: []const u8,
    fragment_shader: []const u8,
    
    /// This function puts together the actual code that gets passed to glShaderSource from
    /// the user code, Vertex-Type, Uniform-Type, etc. and does the OpenGL setup.
    pub fn compile(comptime self: Pipeline, allocator: *Allocator) ShaderProgram
};

// As you can see, this is rather sparse right now, which is the reason
// so much stuff still has to be done manually.
pub const ShaderProgram = struct {
    gl_id: u32, // OpenGL GLuint    
};

The result of calling Pipeline.compile can then be used to create a Mesh that is rendered with the compiled program.

pub const Mesh = struct {
    pub fn create(program: ShaderProgram) Mesh
    
    // Functions to draw the mesh or use it in some other way. You get the idea
    pub fn draw() void
};

I'm not really experienced with OpenGL so it could very well be that I am trying to use it in a way it's not supposed to be used.


Example

A small, non-working excerpt from my code :)

/// The Pipeline
const test_pipeline = Pipeline {
    .Vertex = struct {
        aPosition: Vec2,
        aColor: Vec3,
    },
    .Uniforms = struct {
        uProjection: Mat3,
        uModel: Mat3,
        uScale: Vec2
    },
    .Varyings = struct {
        fColor: Vec3
    },
    
    vertex_shader =
        \\void main()
        \\  vec3 position = uProjection * uModel * vec3(aPosition * uScale, 1.0);
        \\
        \\  fColor = aColor;
        \\
        \\  gl_Position = vec4(position, 1.0)
        \\}
        ,
     
    .fragment_shader =
        \\void main() {
        \\  outColor = vec4(fColor, 1.0);
        \\}
};

// Later, when creating the mesh
const program = test_pipeline.compile(std.debug.global_allocator);
const mesh = Mesh.create(program);

// Set uniforms (projection matrix, etc.)
// ...

// Render the mesh
mesh.draw();

Here's a gif of the running application, which is just a triangle that can be moved around using WASD. GIF of the running application


The generated shader code looks like this:

Vertex Shader

#version 330
in vec2 aPosition;
in vec3 aColor;

uniform mat3 uProjection;
uniform mat3 uModel;
uniform vec2 uScale;

out vec3 fColor;

void main() {
  vec3 position = uProjection * uModel * vec3(aPosition * uScale, 1.0);

  fColor = aColor;

  gl_Position = vec4(position, 1.0);
}

Fragment Shader

#version 330
in vec3 fColor;

uniform mat3 uProjection;
uniform mat3 uModel;
uniform vec2 uScale;

out vec4 outColor;

void main() {
  outColor = vec4(fColor, 1.0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment