From 55d3a6ab00241955c98bd7159415a1a4bb5eec6e Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 2 Oct 2018 08:59:37 -0600 Subject: [PATCH] implemented custom fragment shader support --- pixelgl/attr.go | 102 +++++++++++++++++++++++++++++ pixelgl/canvas.go | 159 +++++++++++++++++---------------------------- pixelgl/shader.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++ pixelgl/window.go | 5 ++ 4 files changed, 330 insertions(+), 98 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,32 +37,37 @@ func NewCanvas(bounds pixel.Rect) *Canvas { col: mgl32.Vec4{1, 1, 1, 1}, } + baseShader(c) c.SetBounds(bounds) - - 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 - + c.shader.update() return c } +// 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) +} + +// 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. // // 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 +}