From 55d3a6ab00241955c98bd7159415a1a4bb5eec6e Mon Sep 17 00:00:00 2001
From: Brandon <thegtproject@live.com>
Date: Tue, 2 Oct 2018 08:59:37 -0600
Subject: [PATCH] implemented custom fragment shader support

---
 pixelgl/attr.go   | 102 +++++++++++++++++++++++++++++
 pixelgl/canvas.go | 157 +++++++++++++++++---------------------------
 pixelgl/shader.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++
 pixelgl/window.go |   5 ++
 4 files changed, 329 insertions(+), 97 deletions(-)
 create mode 100644 pixelgl/attr.go
 create mode 100644 pixelgl/shader.go

diff --git a/pixelgl/attr.go b/pixelgl/attr.go
new file mode 100644
index 0000000..c540bd1
--- /dev/null
+++ b/pixelgl/attr.go
@@ -0,0 +1,102 @@
+package pixelgl
+
+import "github.com/go-gl/mathgl/mgl32"
+
+// AttrType is the attribute's identifier
+type AttrType int
+
+// List of all possible attribute types.
+const (
+	Int AttrType = iota
+	Float
+	Vec2
+	Vec3
+	Vec4
+	Mat2
+	Mat23
+	Mat24
+	Mat3
+	Mat32
+	Mat34
+	Mat4
+	Mat42
+	Mat43
+	Intp // pointers
+	Floatp
+	Vec2p
+	Vec3p
+	Vec4p
+	Mat2p
+	Mat23p
+	Mat24p
+	Mat3p
+	Mat32p
+	Mat34p
+	Mat4p
+	Mat42p
+	Mat43p
+)
+
+// Returns the type identifier for any (supported) variable type
+func getAttrType(v interface{}) AttrType {
+	switch v.(type) {
+	case int32:
+		return Int
+	case float32:
+		return Float
+	case mgl32.Vec2:
+		return Vec2
+	case mgl32.Vec3:
+		return Vec3
+	case mgl32.Vec4:
+		return Vec4
+	case mgl32.Mat2:
+		return Mat2
+	case mgl32.Mat2x3:
+		return Mat23
+	case mgl32.Mat2x4:
+		return Mat24
+	case mgl32.Mat3:
+		return Mat3
+	case mgl32.Mat3x2:
+		return Mat32
+	case mgl32.Mat3x4:
+		return Mat34
+	case mgl32.Mat4:
+		return Mat4
+	case mgl32.Mat4x2:
+		return Mat42
+	case mgl32.Mat4x3:
+		return Mat43
+	case *mgl32.Vec2:
+		return Vec2p
+	case *mgl32.Vec3:
+		return Vec3p
+	case *mgl32.Vec4:
+		return Vec4p
+	case *mgl32.Mat2:
+		return Mat2p
+	case *mgl32.Mat2x3:
+		return Mat23p
+	case *mgl32.Mat2x4:
+		return Mat24p
+	case *mgl32.Mat3:
+		return Mat3p
+	case *mgl32.Mat3x2:
+		return Mat32p
+	case *mgl32.Mat3x4:
+		return Mat34p
+	case *mgl32.Mat4:
+		return Mat4p
+	case *mgl32.Mat4x2:
+		return Mat42p
+	case *mgl32.Mat4x3:
+		return Mat43p
+	case *int32:
+		return Intp
+	case *float32:
+		return Floatp
+	default:
+		panic("invalid AttrType")
+	}
+}
diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go
index b8e4057..66c2ac4 100644
--- a/pixelgl/canvas.go
+++ b/pixelgl/canvas.go
@@ -17,7 +17,7 @@ import (
 // It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
 type Canvas struct {
 	gf     *GLFrame
-	shader *glhf.Shader
+	shader *GLShader
 
 	cmp    pixel.ComposeMethod
 	mat    mgl32.Mat3
@@ -37,24 +37,29 @@ func NewCanvas(bounds pixel.Rect) *Canvas {
 		col: mgl32.Vec4{1, 1, 1, 1},
 	}
 
+	baseShader(c)
 	c.SetBounds(bounds)
+	c.shader.update()
+	return c
+}
 
-	var shader *glhf.Shader
-	mainthread.Call(func() {
-		var err error
-		shader, err = glhf.NewShader(
-			canvasVertexFormat,
-			canvasUniformFormat,
-			canvasVertexShader,
-			canvasFragmentShader,
-		)
-		if err != nil {
-			panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
-		}
-	})
-	c.shader = shader
+// BindUniform will add a uniform with any supported underlying variable
+// if the uniform already exists, including defaults, they will be reassigned
+// to the new value
+func (c *Canvas) BindUniform(Name string, Value interface{}) {
+	c.shader.AddUniform(Name, Value)
+}
 
-	return c
+// UpdateShader needs to be called after any changes to the underlying GLShader
+// are made (ie, BindUniform(), SetFragmentShader()...)
+func (c *Canvas) UpdateShader() {
+	c.shader.update()
+}
+
+// SetFragmentShader allows you to define a new fragment shader on the underlying
+// GLShader. fs is the GLSL source, not a filename
+func (c *Canvas) SetFragmentShader(fs string) {
+	c.shader.fs = fs
 }
 
 // MakeTriangles creates a specialized copy of the supplied Triangles that draws onto this Canvas.
@@ -62,7 +67,7 @@ func NewCanvas(bounds pixel.Rect) *Canvas {
 // TrianglesPosition, TrianglesColor and TrianglesPicture are supported.
 func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
 	return &canvasTriangles{
-		GLTriangles: NewGLTriangles(c.shader, t),
+		GLTriangles: NewGLTriangles(c.shader.s, t),
 		dst:         c,
 	}
 }
@@ -184,6 +189,25 @@ func setBlendFunc(cmp pixel.ComposeMethod) {
 	}
 }
 
+// updates all uniform values for gl to consume
+func (c *Canvas) setUniforms(texbounds pixel.Rect) {
+	mat := c.mat
+	col := c.col
+	c.shader.uniformDefaults.transform = mat
+	c.shader.uniformDefaults.colormask = col
+	dstBounds := c.Bounds()
+	c.shader.uniformDefaults.bounds = mgl32.Vec4{
+		float32(dstBounds.Min.X),
+		float32(dstBounds.Min.Y),
+		float32(dstBounds.W()),
+		float32(dstBounds.H()),
+	}
+
+	for loc, u := range c.shader.uniforms {
+		c.shader.s.SetUniformAttr(loc, u.Value)
+	}
+}
+
 // Clear fills the whole Canvas with a single color.
 func (c *Canvas) Clear(color color.Color) {
 	c.gf.Dirty()
@@ -279,29 +303,41 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
 
 	// save the current state vars to avoid race condition
 	cmp := ct.dst.cmp
+	smt := ct.dst.smooth
 	mat := ct.dst.mat
 	col := ct.dst.col
-	smt := ct.dst.smooth
 
 	mainthread.CallNonBlock(func() {
 		ct.dst.setGlhfBounds()
 		setBlendFunc(cmp)
 
 		frame := ct.dst.gf.Frame()
-		shader := ct.dst.shader
+		shader := ct.dst.shader.s
 
 		frame.Begin()
 		shader.Begin()
 
+		ct.dst.shader.uniformDefaults.transform = mat
+		ct.dst.shader.uniformDefaults.colormask = col
 		dstBounds := ct.dst.Bounds()
-		shader.SetUniformAttr(canvasBounds, mgl32.Vec4{
+		ct.dst.shader.uniformDefaults.bounds = mgl32.Vec4{
 			float32(dstBounds.Min.X),
 			float32(dstBounds.Min.Y),
 			float32(dstBounds.W()),
 			float32(dstBounds.H()),
-		})
-		shader.SetUniformAttr(canvasTransform, mat)
-		shader.SetUniformAttr(canvasColorMask, col)
+		}
+
+		bx, by, bw, bh := intBounds(bounds)
+		ct.dst.shader.uniformDefaults.texbounds = mgl32.Vec4{
+			float32(bx),
+			float32(by),
+			float32(bw),
+			float32(bh),
+		}
+
+		for loc, u := range ct.dst.shader.uniforms {
+			ct.dst.shader.s.SetUniformAttr(loc, u.Value)
+		}
 
 		if tex == nil {
 			ct.vs.Begin()
@@ -310,14 +346,6 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
 		} else {
 			tex.Begin()
 
-			bx, by, bw, bh := intBounds(bounds)
-			shader.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
-				float32(bx),
-				float32(by),
-				float32(bw),
-				float32(bh),
-			})
-
 			if tex.Smooth() != smt {
 				tex.SetSmooth(smt)
 			}
@@ -358,74 +386,9 @@ const (
 	canvasIntensity
 )
 
-var canvasVertexFormat = glhf.AttrFormat{
+var defaultCanvasVertexFormat = glhf.AttrFormat{
 	canvasPosition:  {Name: "position", Type: glhf.Vec2},
 	canvasColor:     {Name: "color", Type: glhf.Vec4},
 	canvasTexCoords: {Name: "texCoords", Type: glhf.Vec2},
 	canvasIntensity: {Name: "intensity", Type: glhf.Float},
 }
-
-const (
-	canvasTransform int = iota
-	canvasColorMask
-	canvasBounds
-	canvasTexBounds
-)
-
-var canvasUniformFormat = glhf.AttrFormat{
-	canvasTransform: {Name: "transform", Type: glhf.Mat3},
-	canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
-	canvasBounds:    {Name: "bounds", Type: glhf.Vec4},
-	canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4},
-}
-
-var canvasVertexShader = `
-#version 330 core
-
-in vec2 position;
-in vec4 color;
-in vec2 texCoords;
-in float intensity;
-
-out vec4 Color;
-out vec2 TexCoords;
-out float Intensity;
-
-uniform mat3 transform;
-uniform vec4 bounds;
-
-void main() {
-	vec2 transPos = (transform * vec3(position, 1.0)).xy;
-	vec2 normPos = (transPos - bounds.xy) / bounds.zw * 2 - vec2(1, 1);
-	gl_Position = vec4(normPos, 0.0, 1.0);
-	Color = color;
-	TexCoords = texCoords;
-	Intensity = intensity;
-}
-`
-
-var canvasFragmentShader = `
-#version 330 core
-
-in vec4 Color;
-in vec2 TexCoords;
-in float Intensity;
-
-out vec4 color;
-
-uniform vec4 colorMask;
-uniform vec4 texBounds;
-uniform sampler2D tex;
-
-void main() {
-	if (Intensity == 0) {
-		color = colorMask * Color;
-	} else {
-		color = vec4(0, 0, 0, 0);
-		color += (1 - Intensity) * Color;
-		vec2 t = (TexCoords - texBounds.xy) / texBounds.zw;
-		color += Intensity * Color * texture(tex, t);
-		color *= colorMask;
-	}
-}
-`
diff --git a/pixelgl/shader.go b/pixelgl/shader.go
new file mode 100644
index 0000000..9d2e8fe
--- /dev/null
+++ b/pixelgl/shader.go
@@ -0,0 +1,162 @@
+package pixelgl
+
+import (
+	"github.com/faiface/glhf"
+	"github.com/faiface/mainthread"
+	"github.com/go-gl/mathgl/mgl32"
+	"github.com/pkg/errors"
+)
+
+// GLShader is a type to assist with managing a canvas's underlying
+// shader configuration. This allows for customization of shaders on
+// a per canvas basis.
+type (
+	GLShader struct {
+		s      *glhf.Shader
+		vf, uf glhf.AttrFormat
+		vs, fs string
+
+		uniforms []gsUniformAttr
+
+		uniformDefaults struct {
+			transform mgl32.Mat3
+			colormask mgl32.Vec4
+			bounds    mgl32.Vec4
+			texbounds mgl32.Vec4
+		}
+	}
+
+	gsUniformAttr struct {
+		Name  string
+		Type  AttrType
+		Value interface{}
+	}
+)
+
+// reinitialize GLShader data and recompile the underlying gl shader object
+func (gs *GLShader) update() {
+	gs.uf = nil
+	for _, u := range gs.uniforms {
+		gs.uf = append(gs.uf, glhf.Attr{
+			Name: u.Name,
+			Type: glhf.AttrType(u.Type),
+		})
+	}
+	var shader *glhf.Shader
+	mainthread.Call(func() {
+		var err error
+		shader, err = glhf.NewShader(
+			gs.vf,
+			gs.uf,
+			gs.vs,
+			gs.fs,
+		)
+		if err != nil {
+			panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
+		}
+	})
+
+	gs.s = shader
+}
+
+// gets the uniform index from GLShader
+func (gs *GLShader) getUniform(Name string) int {
+	for i, u := range gs.uniforms {
+		if u.Name == Name {
+			return i
+		}
+	}
+	return -1
+}
+
+// AddUniform appends a custom uniform name and value to the shader
+//
+// To add a time uniform for example:
+//
+// utime := float32(time.Since(starttime)).Seconds())
+// mycanvas.shader.AddUniform("u_time", &utime)
+//
+func (gs *GLShader) AddUniform(Name string, Value interface{}) {
+	Type := getAttrType(Value)
+	if loc := gs.getUniform(Name); loc > -1 {
+		gs.uniforms[loc].Name = Name
+		gs.uniforms[loc].Type = Type
+		gs.uniforms[loc].Value = Value
+		return
+	}
+	gs.uniforms = append(gs.uniforms, gsUniformAttr{
+		Name:  Name,
+		Type:  Type,
+		Value: Value,
+	})
+}
+
+// Sets up a base shader with everything needed for a pixel
+// canvas to render correctly. The defaults can be overridden
+// by simply using AddUniform()
+func baseShader(c *Canvas) {
+	gs := &GLShader{
+		vf: defaultCanvasVertexFormat,
+		vs: defaultCanvasVertexShader,
+		fs: baseCanvasFragmentShader,
+	}
+
+	gs.AddUniform("u_transform", &gs.uniformDefaults.transform)
+	gs.AddUniform("u_colormask", &gs.uniformDefaults.colormask)
+	gs.AddUniform("u_bounds", &gs.uniformDefaults.bounds)
+	gs.AddUniform("u_texbounds", &gs.uniformDefaults.texbounds)
+
+	c.shader = gs
+}
+
+var defaultCanvasVertexShader = `
+#version 330 core
+
+in vec2 position;
+in vec4 color;
+in vec2 texCoords;
+in float intensity;
+out vec4 Color;
+out vec2 texcoords;
+out vec2 glpos;
+out float Intensity;
+
+uniform mat3 u_transform;
+uniform vec4 u_bounds;
+
+void main() {
+	vec2 transPos = (u_transform * vec3(position, 1.0)).xy;
+	vec2 normPos = (transPos - u_bounds.xy) / u_bounds.zw * 2 - vec2(1, 1);
+	gl_Position = vec4(normPos, 0.0, 1.0);
+	Color = color;
+	texcoords = texCoords;
+	Intensity = intensity;
+	glpos = transPos;
+}
+`
+
+var baseCanvasFragmentShader = `
+#version 330 core
+
+in vec4 Color;
+in vec2 texcoords;
+in float Intensity;
+
+out vec4 fragColor;
+
+uniform vec4 u_colormask;
+uniform vec4 u_texbounds;
+uniform sampler2D u_texture;
+
+void main() {
+	if (Intensity == 0) {
+		fragColor = u_colormask * Color;
+	} else {
+		fragColor = vec4(0, 0, 0, 0);
+		fragColor += (1 - Intensity) * Color;
+		vec2 t = (texcoords - u_texbounds.xy) / u_texbounds.zw;
+		fragColor += Intensity * Color * texture(u_texture, t);
+		fragColor *= u_colormask;
+	}
+}
+`
diff --git a/pixelgl/window.go b/pixelgl/window.go
index ddc5426..de7bb0d 100644
--- a/pixelgl/window.go
+++ b/pixelgl/window.go
@@ -424,3 +424,8 @@ func (w *Window) Clear(c color.Color) {
 func (w *Window) Color(at pixel.Vec) pixel.RGBA {
 	return w.canvas.Color(at)
 }
+
+// GetCanvas returns the window's underlying Canvas
+func (w *Window) GetCanvas() *Canvas {
+	return w.canvas
+}
-- 
GitLab