OpenGL pixel shader

From Rosetta Code
OpenGL pixel shader is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Using the triangle geometry from OpenGL. But instead of using a mechanism such as glColor3d, use a pixel shader for each pixel in the triangle. The pixel shader should pick a random RGB color for each pixel. Most pixels should have colors which are different from that of most other pixels.

Optional: provide an update mechanism, to repeatedly re-render the triangle. (Possibilities include a mouse event handler, a timer event handler or an infinite loop, or even window expose events.) Shader generated color for each pixel should be different in each render.

Optional: after updating the opengl rendering target but before rendering the triangle, query the opengl implementation to determine which versions of shaders are supported by the rendering target, list the tested shaders and the available shaders and then use a supported shader.

See also: opengl.org's gl shader language documentation, and lighthouse3d.com's glsl tutorial.

C

Library: GLUT
Getting a true (pseudo) random number is surprisingly tricky. The following code makes something noisy, but not at all random:
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GL/glut.h>

GLuint ps, vs, prog, r_mod;
float angle = 0;
void render(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glUniform1f(r_mod, rand() / (float)RAND_MAX);

	glLoadIdentity();
	glRotatef(angle, angle * .1, 1, 0);
	glBegin(GL_TRIANGLES);
		glVertex3f(-1, -.5, 0);
		glVertex3f(0, 1, 0);
		glVertex3f(1, 0, 0);
	glEnd();
	angle += .02;
	glutSwapBuffers();
}

void set_shader()
{
	const char *f =
		"varying float x, y, z;"
		"uniform float r_mod;"
		"float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341.0, 1.0); }"
		"void main() {"
		"	gl_FragColor = vec4(rand(gl_FragCoord.x, x), rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1.0);"
		"}";
	const char *v =
		"varying float x, y, z;"
		"void main() {"
		"	gl_Position = ftransform();"
		"	x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;"
		"	x += y; y -= x; z += x - y;"
		"}";

	vs = glCreateShader(GL_VERTEX_SHADER);
	ps = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(ps, 1, &f, 0);
	glShaderSource(vs, 1, &v, 0);

	glCompileShader(vs);
	glCompileShader(ps);

	prog = glCreateProgram();
	glAttachShader(prog, ps);
	glAttachShader(prog, vs);

	glLinkProgram(prog);
	glUseProgram(prog);
	r_mod = glGetUniformLocation(prog, "r_mod");
}

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(200, 200);
	glutCreateWindow("Stuff");
	glutIdleFunc(render);

	glewInit();
	if (!glewIsSupported("GL_VERSION_2_0")) {
		fprintf(stderr, "GL 2.0 unsupported\n");
		return 1;
	}

	set_shader();
	glutMainLoop();

	return 0;
}

Go

Translation of: C
Library: FreeGLUT
Library: GLEW


The following uses 'cgo' to bind to the above C libraries. As C macro functions cannot be invoked directly from Go code, it has been necessary to wrap them first in 'normal' C functions and then invoke those.

package main

/*
#cgo LDFLAGS: -lglut -lGLEW -lGL -lGLU
#include <stdlib.h>
#include <GL/glew.h>
#include <GL/glut.h>

extern void render();

typedef void (*callback) ();

static inline callback idleFunc() {
    return render;
}

static inline void glUniform1f_macro(GLint location, GLfloat v0) {
    glUniform1f(location, v0);
}

static inline GLuint glCreateShader_macro(GLenum _type) {
    return glCreateShader(_type);
}

static inline void glShaderSource_macro(GLuint shader, GLsizei count, const GLchar *const* string, const GLint* length) {
    glShaderSource(shader, count, string, length);
}

static inline void glCompileShader_macro(GLuint shader) {
    glCompileShader(shader);
}

static inline GLuint glCreateProgram_macro() {
    return glCreateProgram();
}

static inline void glAttachShader_macro(GLuint program, GLuint shader) {
    glAttachShader(program, shader);
}

static inline void glLinkProgram_macro(GLuint program) {
    glLinkProgram(program);
}

static inline void glUseProgram_macro(GLuint program) {
    glUseProgram(program);
}

static inline GLint glGetUniformLocation_macro(GLuint program, const GLchar* name) {
    return glGetUniformLocation(program, name);
}

*/
import "C"
import "log"
import "unsafe"

var ps, vs, prog, r_mod C.GLuint
var angle = float32(0)

//export render
func render() {
    C.glClear(C.GL_COLOR_BUFFER_BIT)
    C.glUniform1f_macro(C.GLint(r_mod), C.GLfloat(C.rand())/C.GLfloat(C.RAND_MAX))
    C.glLoadIdentity()
    C.glRotatef(C.float(angle), C.float(angle*0.1), 1, 0)
    C.glBegin(C.GL_TRIANGLES)
    C.glVertex3f(-1, -0.5, 0)
    C.glVertex3f(0, 1, 0)
    C.glVertex3f(1, 0, 0)
    C.glEnd()
    angle += 0.02
    C.glutSwapBuffers()
}

func setShader() {
    f := "varying float x, y, z;" +
        "uniform float r_mod;" +
        "float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341.0, 1.0); }" +
        "void main() {" +
        "    gl_FragColor = vec4(rand(gl_FragCoord.x, x), rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1.0);" +
        "}"

    v := "varying float x, y, z;" +
        "void main() {" +
        "    gl_Position = ftransform();" +
        "    x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;" +
        "    x += y; y -= x; z += x - y;" +
        "}"

    fc, vc := C.CString(f), C.CString(v)
    defer C.free(unsafe.Pointer(fc))
    defer C.free(unsafe.Pointer(vc))

    vs = C.glCreateShader_macro(C.GL_VERTEX_SHADER)
    ps = C.glCreateShader_macro(C.GL_FRAGMENT_SHADER)
    C.glShaderSource_macro(ps, 1, &fc, nil)
    C.glShaderSource_macro(vs, 1, &vc, nil)

    C.glCompileShader_macro(vs)
    C.glCompileShader_macro(ps)

    prog = C.glCreateProgram_macro()
    C.glAttachShader_macro(prog, ps)
    C.glAttachShader_macro(prog, vs)

    C.glLinkProgram_macro(prog)
    C.glUseProgram_macro(prog)
    rms := C.CString("r_mod")
    r_mod = C.GLuint(C.glGetUniformLocation_macro(prog, rms))
    C.free(unsafe.Pointer(rms))
}

func main() {
    argc := C.int(0)
    C.glutInit(&argc, nil)
    C.glutInitDisplayMode(C.GLUT_DOUBLE | C.GLUT_RGB)
    C.glutInitWindowSize(200, 200)
    tl := "Pixel Shader"
    tlc := C.CString(tl)
    C.glutCreateWindow(tlc)
    defer C.free(unsafe.Pointer(tlc))
    C.glutIdleFunc(C.idleFunc())

    C.glewInit()
    glv := C.CString("GL_VERSION_2_0")
    if C.glewIsSupported(glv) == 0 {
        log.Fatal("GL 2.0 unsupported")
    }
    defer C.free(unsafe.Pointer(glv))

    setShader()
    C.glutMainLoop()
}

JavaScript

(WebGL)

<html style="margin: 0;">
  <head>
    <title>Fragment Shader WebGL Example</title>
    <!-- This use of <script> elements is so that we can have multiline text
         without quoting it inside of JavaScript; the web browser doesn't
         actually do anything besides store the text of these. -->
    <script id="shader-fs" type="text/x-fragment_shader">
      precision highp float;
      uniform float u_time;
      void main(void) {
        // some gobbledegook
        vec3 foo = vec3(pow(gl_FragCoord.xy, vec2(1.0 + sin(dot(vec4(1.0, 100.0, 0.0, 0.0), gl_FragCoord)))), 0.0);
        foo *= mat3(1.2, 3.9, 1.4, 4.1, 0.2, 1.4, 2.5, 1.6, 7.2);
 
        gl_FragColor = vec4(mod(foo + vec3(u_time), 1.0), 1.0);
      }
    </script>
    <script id="shader-vs" type="text/x-vertex_shader">
      attribute vec3 a_position;
      attribute vec4 a_color;
      varying vec4 v_color;
      void main(void) {
        gl_Position = vec4(a_position, 1.0);
        v_color = a_color;
      }
    </script>
    <script type="text/javascript">
      function getShader(gl, id) {
        var scriptElement = document.getElementById(id);
        // Create shader object
        var shader;
        shader= gl.createShader(gl[scriptElement.type.replace('text/x-','').toUpperCase()]);
        // Compile shader from source
        gl.shaderSource(shader, scriptElement.textContent);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
          throw new Error(gl.getShaderInfoLog(shader));
        return shader;
      }
    </script>
  </head>
  <body style="margin: 0;">
    <canvas id="glcanvas" style="border: none; margin: auto; display: block;" width="640" height="480"></canvas>
    <script type="text/javascript">
      var canvas = document.getElementById("glcanvas");
 
      // Get WebGL context.
      var gl = canvas.getContext("webgl")
            || canvas.getContext("experimental-webgl");
      if (!gl)
        throw new Error("WebGL context not found");
 
      // Create shader program from vertex and fragment shader code.
      var shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, getShader(gl, "shader-vs"));
      gl.attachShader(shaderProgram, getShader(gl, "shader-fs"));
      gl.linkProgram(shaderProgram);
      if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
        throw new Error(gl.getProgramInfoLog(shaderProgram));
 
      // Specify to render using that program.
      gl.useProgram(shaderProgram);
 
      // Get the indexes to communicate vertex attributes to the program.
      var positionAttr = gl.getAttribLocation(shaderProgram, "a_position");
      // And specify that we will be actually delivering data to those attributes.
      gl.enableVertexAttribArray(positionAttr);
 
      var timeUniform = gl.getUniformLocation(shaderProgram, "u_time");
 
      // Store vertex positions and colors in array buffer objects.
      var vertices;
      var positionBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices = [
        -0.5, -0.5, 0,
        +0.5, -0.5, 0,
        -0.5, +0.5, 0
      ]), gl.STATIC_DRAW);
      var numVertices = vertices.length / 3; // 3 coordinates per vertex
 
      // Set GL state
      gl.clearColor(0.3, 0.3, 0.3, 1.0);
      gl.enable(gl.DEPTH_TEST);
      gl.viewport(0, 0, gl.drawingBufferWidth || canvas.width,
                        gl.drawingBufferHeight || canvas.height);
 
      //Specify the array data to render. 
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      gl.vertexAttribPointer(positionAttr, 3, gl.FLOAT, false, 0, 0);
 
      var t0 = Date.now();
      function frame() {
        gl.uniform1f(timeUniform, (Date.now() - t0) / 1000);
 
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLES, 0, numVertices);
 
        var e;
        while (e = gl.getError())
          console.log("GL error", e);
 
      }
      setInterval(frame, 1000/20);
    </script>
  </body>
</html>

Kotlin

Translation of: C
Library: FreeGLUT
Library: GLEW
Works with: Ubuntu 16.04

Assuming that freeglut and GLEW are already installed on your system in the default location(s), you first need to build opengl2.klib using the following .def file and the cinterop tool:

// opengl2.def
headers = /usr/include/GL/glew.h /usr/include/GL/glut.h /usr/include/GL/glext.h
compilerOpts = -I/usr/include
linkerOpts = -L/usr/lib/x86_64-linux-gnu -lglut -lGLEW -lGL -lGLU

You then need to compile the following Kotlin program, linking against opengl2.klib, and run the resulting .kexe file to view the rotating triangle.

// Kotlin Native v0.6

import kotlinx.cinterop.*
import opengl2.*

var rMod = 0
var angle = 0f

fun render() {
    glClear(GL_COLOR_BUFFER_BIT)
    __glewUniform1f!!(rMod, rand() / RAND_MAX.toFloat()) 
    glLoadIdentity()
    glRotatef(angle, angle * 0.1f, 1f, 0f)
    glBegin(GL_TRIANGLES)
    glVertex3f(-1f, -0.5f, 0f)
    glVertex3f(0f, 1f, 0f)
    glVertex3f(1f, 0f, 0f)
    glEnd()
    angle += 0.02f
    glutSwapBuffers()
}

fun setShader() {
    val f = 
        "varying float x, y, z;" +
        "uniform float r_mod;" +
        "float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341, 1); }" +
        "void main() {" +
        "	 gl_FragColor = vec4(rand(gl_FragCoord.x, x), " +
        "rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1);" +
        "}"

    val v = 
        "varying float x, y, z;" +
        "void main() {" +
        "   gl_Position = ftransform();" +
        "   x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;" +
        "   x += y; y -= x; z += x - y;" +
        "}"
    
    val vs = __glewCreateShader!!(GL_VERTEX_SHADER) 
    val ps = __glewCreateShader!!(GL_FRAGMENT_SHADER)
    

    memScoped { 
        val fp = allocPointerTo<ByteVar>()
        fp.value = f.cstr.getPointer(memScope) 
        __glewShaderSource!!(ps, 1, fp.ptr, null)
        val vp = allocPointerTo<ByteVar>()
        vp.value = v.cstr.getPointer(memScope) 
        __glewShaderSource!!(vs, 1, vp.ptr, null)
    
        __glewCompileShader!!(vs)
        __glewCompileShader!!(ps)
 
        val prog = __glewCreateProgram!!()
        __glewAttachShader!!(prog, ps)
        __glewAttachShader!!(prog, vs)
 
        __glewLinkProgram!!(prog)
        __glewUseProgram!!(prog)
    
        val sp = allocPointerTo<ByteVar>()
        sp.value = "r_mod".cstr.getPointer(memScope) 
        rMod = __glewGetUniformLocation!!(prog, sp.value)
    }
}

fun main(args: Array<String>) {
    memScoped {
        val argc = alloc<IntVar>().apply { value = 0 }
        glutInit(argc.ptr, null) 
    }
    glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB)
    glutInitWindowSize(200, 200)
    glutCreateWindow("Stuff")
    glutIdleFunc(staticCFunction(::render))
    glewInit()
    if (glewIsSupported("GL_VERSION_2_0") == 0.toByte()) {
        println("GL 2.0 unsupported\n")
        return
    }
    setShader()
    glutMainLoop()
}

Ol

#!/usr/bin/ol
(import (lib gl2))

; init
(glShadeModel GL_SMOOTH)
(glClearColor 0.11 0.11 0.11 1)

(define po (gl:CreateProgram
"#version 120 // OpenGL 2.1
	varying float x, y, z;
	void main(void) {
		gl_Position = ftransform();
		x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;
		x += y; y -= x; z += x - y;
	}"
"#version 120 // OpenGL 2.1
	varying float x, y, z;
	uniform float r_mod;
	float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341, 1); }
	
	void main() {
		gl_FragColor = vec4(rand(gl_FragCoord.x, x), rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1);
	}"))

; draw
(gl:set-renderer (lambda (mouse)
   (glClear GL_COLOR_BUFFER_BIT)

   (glUseProgram po)
   (glUniform1f (glGetUniformLocation po (c-string "r_mod")) 1)

   (glColor3f 1 1 1)
   (glBegin GL_TRIANGLES)
      (glVertex2f -0.6 -0.6)
      (glVertex2f +0.6 -0.6)
      (glVertex2f -0.0 +0.7)
   (glEnd)))

Phix

Translation of: JavaScript
Library: Phix/pGUI
Library: Phix/online

You can run this online here.

--
-- demo\rosetta\OpenGLShader.exw
-- =============================
--
--  Works identically to the rc JavaScript entry under pwa/p2js, but on desktop/Phix
--  the colours cycle round white/blue/orange once per second. Beyond my ken anyway.
--
with javascript_semantics
requires("1.0.1")
include pGUI.e
include opengl.e

Ihandln dlg
Ihandle canvas

function resize_cb(Ihandle /*ih*/, integer width, height)
    glViewport(0, 0, width, height)
    return IUP_DEFAULT
end function

constant fragment_shader = """
precision highp float;
uniform float u_time;
void main(void) {
    // some gobbledegook
    vec3 foo = vec3(pow(gl_FragCoord.xy, vec2(1.0 + sin(dot(vec4(1.0, 100.0, 0.0, 0.0), gl_FragCoord)))), 0.0);
    foo *= mat3(1.2, 3.9, 1.4, 4.1, 0.2, 1.4, 2.5, 1.6, 7.2);

    gl_FragColor = vec4(mod(foo + vec3(u_time), 1.0), 1.0);
}
"""
constant vertex_shader = """
attribute vec3 a_position;
attribute vec4 a_color;
varying vec4 v_color;
void main(void) {
    gl_Position = vec4(a_position, 1.0);
    v_color = a_color;
}
"""

constant vertices = {
                      -0.5, -0.5, 0,
                      +0.5, -0.5, 0,
                      -0.5, +0.5, 0
                    }

function getShader(string src, integer shader_type)
    integer shader = glCreateShader(shader_type)
    // Compile shader from source
    glShaderSource(shader, src)
    glCompileShader(shader)
    if not glGetShaderParameter(shader, GL_COMPILE_STATUS) then
        crash(glGetShaderInfoLog(shader))
    end if
    return shader
end function

integer timeUniform

procedure set_shader()
    IupGLMakeCurrent(canvas)
    integer prog = glCreateProgram()
    glAttachShader(prog, getShader(vertex_shader,GL_VERTEX_SHADER))
    glAttachShader(prog, getShader(fragment_shader,GL_FRAGMENT_SHADER))
    glLinkProgram(prog)
    if not glGetProgramParameter(prog, GL_LINK_STATUS) then
        crash(glGetProgramInfoLog(prog))
    end if
    glUseProgram(prog)

    // Get the indexes to communicate vertex attributes to the program.
    integer positionAttr = glGetAttribLocation(prog, "a_position")
    // And specify that we will be actually delivering data to those attributes.
    glEnableVertexAttribArray(positionAttr)
 
    timeUniform = glGetUniformLocation(prog, "u_time")

    // Store vertex positions and colors in array buffer objects.
    integer positionBuffer = glCreateBuffer()
    glBindBuffer(GL_ARRAY_BUFFER, positionBuffer)
    {integer size, atom pData} = glFloat32Array(vertices)
    glBufferData(GL_ARRAY_BUFFER, size, pData, GL_STATIC_DRAW)
      
 
    glEnable(GL_DEPTH_TEST);

    //Specify the array data to render. 
    glBindBuffer(GL_ARRAY_BUFFER, positionBuffer)
    glVertexAttribPointer(positionAttr, 3, GL_FLOAT, false, 0, 0)
 
end procedure

atom t0 = time()

function timer_cb(Ihandle /*ih*/)
    glClearColor(0.3, 0.3, 0.3, 1.0)
    integer {w,h} = IupGetIntInt(canvas,"DRAWSIZE")
    glViewport(0, 0, w, h)
    glUniform1f(timeUniform, (time()-t0))
    glClear(GL_COLOR_BUFFER_BIT || GL_DEPTH_BUFFER_BIT)

    integer numVertices = length(vertices)/3; // 3 coordinates per vertex
    glDrawArrays(GL_TRIANGLES, 0, numVertices);

    glFlush()

    return IUP_DEFAULT
end function

procedure main()
    IupOpen()
    IupGLCanvasOpen()
    canvas = IupGLCanvas("RESIZE_CB", Icallback("resize_cb"), "RASTERSIZE=640x480")
    dlg = IupDialog(canvas, "TITLE=OpenGLShader, SHRINK=YES")
    IupMap(dlg)
    set_shader()
    IupShow(dlg)
    Ihandle timer = IupTimer(Icallback("timer_cb"), 1000/20)
    if platform()!=JS then
        IupMainLoop()
        dlg = IupDestroy(dlg)
        IupClose()
    end if
end procedure
main()

Racket

#lang racket/gui
(require typed/opengl)
 
(define (resize w h)
  (glViewport 0 0 w h))

;; shaders gotten from [#C]
(define shader-source:fragment
  #<<<
varying float x, y, z;
uniform float r_mod;
float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341, 1); }
void main() {
  gl_FragColor = vec4(rand(gl_FragCoord.x, x),
                      rand(gl_FragCoord.y, y),
                      rand(gl_FragCoord.z, z),
                      1);
}
<
  )

(define shader-source:vertex
  #<<<
varying float x, y, z;
void main() {
  gl_Position = ftransform();
  x = gl_Position.x;
  y = gl_Position.y;
  z = gl_Position.z;
  x += y;
  y -= x;
  z += x - y;
}
<
  )

(define (draw-opengl prg)
  (glClearColor 0.0 0.0 0.0 0.0)
  (glClear GL_COLOR_BUFFER_BIT)
  (glShadeModel GL_SMOOTH)
  (glMatrixMode GL_PROJECTION)
  (glLoadIdentity)
  (glOrtho 0.0 1.0 0.0 1.0 -1.0 1.0)
  (glMatrixMode GL_MODELVIEW)
  (glLoadIdentity)
  (glBegin GL_TRIANGLES)
  (glVertex3d 0.25 0.25 0.0)
  (glVertex3d 0.75 0.25 0.0)
  (glVertex3d 0.75 0.75 0.0)
  (glEnd))
 
 
(define my-canvas%
  (class* canvas% ()
    (inherit with-gl-context swap-gl-buffers)
    (define the-program #f)

    (define/override (on-paint)
      (with-gl-context
          (λ()
            (unless the-program
              (set! the-program
                    (create-program (load-shader (open-input-string shader-source:fragment)
                                                 GL_FRAGMENT_SHADER)
                                    (load-shader (open-input-string shader-source:vertex)
                                                 GL_VERTEX_SHADER)))
              (glUseProgram the-program))
            (draw-opengl the-program) (swap-gl-buffers))))
    (define/override (on-size width height)
      (with-gl-context (λ() (resize width height))))
    (super-instantiate () (style '(gl)))))
 
(define win (new frame% [label "Racket Rosetta Code OpenGL example"]
                        [min-width 200] [min-height 200]))
(define gl  (new my-canvas% [parent win]))
 
(send win show #t)

Tcl

Library: tcl3d
Translation of: C

Using the Tcl3d library and liberally borrowing from this pixel shader demo on the wiki, here is a brute translation of the C implementation.

package require tcl3d

proc mkshader {type src} {
    set sh [glCreateShader $type]
    tcl3dOglShaderSource $sh $src
    glCompileShader $sh
    puts "compilation report : [tcl3dOglGetShaderState $sh $::GL_COMPILE_STATUS] [tcl3dOglGetShaderInfoLog $sh]"
    return $sh
}

proc render {{angle 0}} {
    glClear $::GL_COLOR_BUFFER_BIT
    glUniform1f $::uloc_rmod [expr {rand()}]
    glLoadIdentity
    glRotatef $angle 1.0 1.0 1.0
    glBegin GL_TRIANGLES
        glVertex3f -1 -.5 0
        glVertex3f  0  1  0
        glVertex3f  1  0  0
    glEnd

    .w swapbuffers
    after 40 [list render [expr {$angle+.2}]]
}

proc set_shader {} {
    set f {
        varying float x, y, z;
        uniform float rmod;
        float rand(float s, float r) { return mod(mod(s, r + rmod)*112341.0, 1.0); }
        void main() {
            gl_FragColor = vec4(rand(gl_FragCoord.x, x), rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1);
        }
    }
    set v {
        varying float x, y, z;
        void main() {
            gl_Position = ftransform();
            x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;
            x += y; y -= x; z += x - y;
        }
    }

    set vs [mkshader $::GL_VERTEX_SHADER $v]
    set ps [mkshader $::GL_FRAGMENT_SHADER $f]

    set proc [glCreateProgram]
    glAttachShader $proc $ps
    glAttachShader $proc $vs
    glLinkProgram $proc
    glUseProgram $proc

    set ::uloc_rmod [glGetUniformLocation $proc "rmod"]
}

togl .w -w 640 -h 480 -double true
pack .w -expand 1 -fill both
bind .w <Key-Escape> exit
wm protocol . WM_DELETE_WINDOW exit

set_shader
render

Wren

Translation of: C
Library: FreeGLUT
Library: GLEW


As it's not currently possible for Wren-cli to access OpenGL directly, we embed a Wren script in a C application to complete this task. See the OpenGL#Wren task for some of the issues involved here.

/* OpenGL_pixel_shader.wren */

import "random" for Random

var GL_COLOR_BUFFER_BIT = 0x4000

var GL_TRIANGLES = 0x0004

var GL_VERTEX_SHADER = 0x8b31
var GL_FRAGMENT_SHADER = 0x8b30

var GLUT_ACTION_ON_WINDOW_CLOSE = 0x01f9
var GLUT_ACTION_GLUTMAINLOOP_RETURNS = 0x0001

var GLUT_DOUBLE = 0x0002
var GLUT_RGB    = 0x0000

var Rand = Random.new()

var Angle = 0
var R_mod = 0

class GL {
    foreign static clear(mask)

    foreign static uniform1f(location, v0)

    foreign static loadIdentity()

    foreign static rotatef(angle, x, y, z)

    foreign static begin(mode)

    foreign static vertex3f(x, y, z)

    foreign static end()

    foreign static createShader(shaderType)

    foreign static shaderSource(shader, count, string, length)

    foreign static compileShader(shader)

    foreign static createProgram()

    foreign static attachShader(program, shader)

    foreign static linkProgram(program)

    foreign static useProgram(program)

    foreign static getUniformLocation(program, name)
}

class Glut {
    foreign static initDisplayMode(mode)

    foreign static initWindowSize(width, height)

    foreign static createWindow(name)

    foreign static swapBuffers()

    foreign static idleFunc(clazz, signature)

    foreign static setOption(eWhat, value)
}

class Glew {
    foreign static init()

    foreign static isSupported(name)
}

class GLCallbacks {
    static render() {
        GL.clear(GL_COLOR_BUFFER_BIT)
	    GL.uniform1f(R_mod, Rand.float())
	    GL.loadIdentity()
	    GL.rotatef(Angle, Angle * 0.1, 1, 0)
	    GL.begin(GL_TRIANGLES)
		    GL.vertex3f(-1, -0.5, 0)
		    GL.vertex3f(0, 1, 0)
		    GL.vertex3f(1, 0, 0)
	    GL.end()
	    Angle = Angle + 0.02
	    Glut.swapBuffers()
    }
}

var setShader = Fn.new {
    var f = 
"varying float x, y, z;
uniform float r_mod;
float rand(float s, float r) { return mod(mod(s, r + r_mod) * 112341.0, 1.0);}
void main() {
    gl_FragColor = vec4(rand(gl_FragCoord.x, x), rand(gl_FragCoord.y, y), rand(gl_FragCoord.z, z), 1.0);
}"

    var v =
"varying float x, y, z;
void main() {
gl_Position = ftransform();
x = gl_Position.x; y = gl_Position.y; z = gl_Position.z;
x += y; y -= x; z += x - y;
}"

    var vs = GL.createShader(GL_VERTEX_SHADER)
    var ps = GL.createShader(GL_FRAGMENT_SHADER)
    GL.shaderSource(ps, 1, f, null)
    GL.shaderSource(vs, 1, v, null)

    GL.compileShader(vs)
    GL.compileShader(ps)

    var prog = GL.createProgram()
    GL.attachShader(prog, ps)
    GL.attachShader(prog, vs)

    GL.linkProgram(prog)
    GL.useProgram(prog)
    R_mod = GL.getUniformLocation(prog, "r_mod")	
}

Glut.initDisplayMode(GLUT_DOUBLE | GLUT_RGB)
Glut.initWindowSize(200, 200)
Glut.createWindow("Stuff")
Glut.idleFunc("GLCallbacks", "render()")
Glew.init()
if (!Glew.isSupported("GL_VERSION_2_0")) {
    System.print("GL 2.0 unsupported\n")
    return
}
setShader.call()
Glut.setOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS)


We now embed this Wren script in the following C program, compile and run it.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/freeglut.h>
#include "wren.h"

/* C <=> Wren interface functions */

WrenVM *vm;

const char *idleClass, *idleMethod;

void idle() {
    wrenEnsureSlots(vm, 1);
    wrenGetVariable(vm, "main", idleClass, 0);
    WrenHandle *method = wrenMakeCallHandle(vm, idleMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void C_clear(WrenVM* vm) {
    GLbitfield mask = (GLbitfield)wrenGetSlotDouble(vm, 1);
    glClear(mask);
}

void C_uniform1f(WrenVM* vm) {
    GLint location = (GLint)wrenGetSlotDouble(vm, 1);
    GLfloat v0     = (GLfloat)wrenGetSlotDouble(vm, 2);
    glUniform1f(location, v0);
}

void C_loadIdentity(WrenVM* vm) {
    glLoadIdentity();
}

void C_rotatef(WrenVM* vm) {
    GLdouble angle = (GLdouble)wrenGetSlotDouble(vm, 1);
    GLdouble x     = (GLdouble)wrenGetSlotDouble(vm, 2);
    GLdouble y     = (GLdouble)wrenGetSlotDouble(vm, 3);
    GLdouble z     = (GLdouble)wrenGetSlotDouble(vm, 4);
    glRotatef(angle, x, y, z);
}

void C_begin(WrenVM* vm) {
    GLenum mode = (GLenum)wrenGetSlotDouble(vm, 1);
    glBegin(mode);
}

void C_vertex3f(WrenVM* vm) {
    GLfloat x = (GLfloat)wrenGetSlotDouble(vm, 1);
    GLfloat y = (GLfloat)wrenGetSlotDouble(vm, 2);
    GLfloat z = (GLfloat)wrenGetSlotDouble(vm, 3);
    glVertex3f(x, y, z);
}

void C_end(WrenVM* vm) {
    glEnd();
}

void C_createShader(WrenVM* vm) {
    GLenum shaderType = (GLenum)wrenGetSlotDouble(vm, 1);
    GLuint shader = glCreateShader(shaderType);
    wrenSetSlotDouble(vm, 0, (double)shader);
}

void C_shaderSource(WrenVM* vm) {
    GLuint shader = (GLuint)wrenGetSlotDouble(vm, 1);
    GLsizei count = (GLsizei)wrenGetSlotDouble(vm, 2);
    // assume for simplicity there will always be one shader string and the fourth parameter will be null
    const GLchar *string = (const GLchar *)wrenGetSlotString(vm, 3);
    glShaderSource(shader, count, &string, 0);
}

void C_compileShader(WrenVM* vm) {
    GLuint shader = (GLuint)wrenGetSlotDouble(vm, 1);
    glCompileShader(shader);
    // check shader compiled ok
    GLint shader_compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_compiled);
    if (shader_compiled != GL_TRUE) {
        GLsizei log_length = 0;
        GLchar message[1024];
        glGetShaderInfoLog(shader, 1024, &log_length, message);
        printf("%s\n", message);
    }
}

void C_createProgram(WrenVM* vm) {
    GLuint program = glCreateProgram();
    wrenSetSlotDouble(vm, 0, (double)program);
}

void C_attachShader(WrenVM* vm) {
    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);
    GLuint shader  = (GLuint)wrenGetSlotDouble(vm, 2);
    glAttachShader(program, shader);
}

void C_linkProgram(WrenVM* vm) {
    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);
    // check program linked ok
    glBindAttribLocation(program, 0, "position");
    glBindAttribLocation(program, 1, "texcoord");
    glBindAttribLocation(program, 2, "normal");
    glBindAttribLocation(program, 3, "color");
    glLinkProgram(program);
    GLint program_linked;
    glGetProgramiv(program, GL_LINK_STATUS, &program_linked);
    if (program_linked != GL_TRUE) {
        GLsizei log_length = 0;
        GLchar message[1024];
        glGetProgramInfoLog(program, 1024, &log_length, message);
        printf("%s\n", message);
    }
}

void C_useProgram(WrenVM* vm) {
    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);
    glUseProgram(program);
}

void C_getUniformLocation(WrenVM* vm) {
    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);
    const GLchar *name = (const GLchar *)wrenGetSlotString(vm, 2);
    GLint location = glGetUniformLocation(program, name);
    wrenSetSlotDouble(vm, 0, (double)location);
}

void C_initDisplayMode(WrenVM* vm) {
    unsigned int mode = (unsigned int)wrenGetSlotDouble(vm, 1);
    glutInitDisplayMode(mode);
}

void C_initWindowSize(WrenVM* vm) {
    int width  = (int)wrenGetSlotDouble(vm, 1);
    int height = (int)wrenGetSlotDouble(vm, 2);
    glutInitWindowSize(width, height);
}

void C_createWindow(WrenVM* vm) {
    const char *name = wrenGetSlotString(vm, 1);
    glutCreateWindow(name);
}

void C_swapBuffers(WrenVM* vm) {
    glutSwapBuffers();
}

void C_idleFunc(WrenVM* vm) {
    idleClass  = wrenGetSlotString(vm, 1);
    idleMethod = wrenGetSlotString(vm, 2);
    glutIdleFunc(&idle);
}

void C_setOption(WrenVM* vm) {
    GLenum eWhat = (GLenum)wrenGetSlotDouble(vm, 1);
    int value    = (int)wrenGetSlotDouble(vm, 2);
    glutSetOption(eWhat, value);
}

void C_init(WrenVM* vm) {
    glewInit();
}

void C_isSupported(WrenVM* vm) {
    const char *name = (const char *)wrenGetSlotString(vm, 1);
    GLboolean supported = glewIsSupported(name);
    wrenSetSlotBool(vm, 0, (bool)supported);
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "main") == 0) {
        if (strcmp(className, "GL") == 0) {
            if (isStatic && strcmp(signature, "clear(_)") == 0)                return C_clear;
            if (isStatic && strcmp(signature, "uniform1f(_,_)") == 0)          return C_uniform1f;
            if (isStatic && strcmp(signature, "loadIdentity()") == 0)          return C_loadIdentity;
            if (isStatic && strcmp(signature, "rotatef(_,_,_,_)") == 0)        return C_rotatef;
            if (isStatic && strcmp(signature, "begin(_)") == 0)                return C_begin;
            if (isStatic && strcmp(signature, "vertex3f(_,_,_)") == 0)         return C_vertex3f;
            if (isStatic && strcmp(signature, "end()") == 0)                   return C_end;
            if (isStatic && strcmp(signature, "createShader(_)") == 0)         return C_createShader;
            if (isStatic && strcmp(signature, "shaderSource(_,_,_,_)") == 0)   return C_shaderSource;
            if (isStatic && strcmp(signature, "compileShader(_)") == 0)        return C_compileShader;
            if (isStatic && strcmp(signature, "createProgram()") == 0)         return C_createProgram;
            if (isStatic && strcmp(signature, "attachShader(_,_)") == 0)       return C_attachShader;
            if (isStatic && strcmp(signature, "linkProgram(_)") == 0)          return C_linkProgram;
            if (isStatic && strcmp(signature, "useProgram(_)") == 0)           return C_useProgram;
            if (isStatic && strcmp(signature, "getUniformLocation(_,_)") == 0) return C_getUniformLocation;
        } else if (strcmp(className, "Glut") == 0) {
            if (isStatic && strcmp(signature, "initDisplayMode(_)") == 0)      return C_initDisplayMode;
            if (isStatic && strcmp(signature, "initWindowSize(_,_)") == 0)     return C_initWindowSize;
            if (isStatic && strcmp(signature, "createWindow(_)") == 0)         return C_createWindow;
            if (isStatic && strcmp(signature, "swapBuffers()") == 0)           return C_swapBuffers;           
            if (isStatic && strcmp(signature, "idleFunc(_,_)") == 0)           return C_idleFunc;
            if (isStatic && strcmp(signature, "setOption(_,_)") == 0)          return C_setOption;
        } else if (strcmp(className, "Glew") == 0) {
            if (isStatic && strcmp(signature, "init()") == 0)                  return C_init;
            if (isStatic && strcmp(signature, "isSupported(_)") == 0)          return C_isSupported;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    fread(script, 1, fsize, f);
    fclose(f);
    script[fsize] = 0;
    return script;
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignMethodFn = &bindForeignMethod;
    vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = "OpenGL_pixel_shader.wren";
    char *script = readFile(fileName);
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    glutMainLoop();
    wrenFreeVM(vm);
    free(script);
    return 0;
}
Output:
Same as C example.