diff --git a/backend.go b/backend.go new file mode 100644 index 0000000..28940c2 --- /dev/null +++ b/backend.go @@ -0,0 +1,5 @@ +package canvas + +type Backend interface { + ClearRect(x, y, w, h int) +} diff --git a/backend/goglbackend/gogl.go b/backend/goglbackend/gogl.go new file mode 100644 index 0000000..3ad8158 --- /dev/null +++ b/backend/goglbackend/gogl.go @@ -0,0 +1,215 @@ +package goglbackend + +import ( + "fmt" + + "github.com/go-gl/gl/v3.2-core/gl" + "github.com/tfriedel6/canvas" +) + +const alphaTexSize = 2048 + +type GoGLBackend struct { + x, y, w, h int + fx, fy, fw, fh float64 + + buf uint32 + shadowBuf uint32 + alphaTex uint32 + sr solidShader + lgr linearGradientShader + rgr radialGradientShader + ipr imagePatternShader + sar solidAlphaShader + rgar radialGradientAlphaShader + lgar linearGradientAlphaShader + ipar imagePatternAlphaShader + ir imageShader + gauss15r gaussianShader + gauss63r gaussianShader + gauss127r gaussianShader + offscr1 offscreenBuffer + offscr2 offscreenBuffer + glChan chan func() +} + +type offscreenBuffer struct { + tex uint32 + w int + h int + renderStencilBuf uint32 + frameBuf uint32 + alpha bool +} + +func New(x, y, w, h int) (canvas.Backend, error) { + err := gl.Init() + if err != nil { + return nil, err + } + + gl.GetError() // clear error state + + b := &GoGLBackend{ + w: w, + h: h, + fw: float64(w), + fh: float64(h), + } + + err = loadShader(solidVS, solidFS, &b.sr.shaderProgram) + if err != nil { + return nil, err + } + b.sr.shaderProgram.mustLoadLocations(&b.sr) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(linearGradientVS, linearGradientFS, &b.lgr.shaderProgram) + if err != nil { + return nil, err + } + b.lgr.shaderProgram.mustLoadLocations(&b.lgr) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(radialGradientVS, radialGradientFS, &b.rgr.shaderProgram) + if err != nil { + return nil, err + } + b.rgr.shaderProgram.mustLoadLocations(&b.rgr) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(imagePatternVS, imagePatternFS, &b.ipr.shaderProgram) + if err != nil { + return nil, err + } + b.ipr.shaderProgram.mustLoadLocations(&b.ipr) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(solidAlphaVS, solidAlphaFS, &b.sar.shaderProgram) + if err != nil { + return nil, err + } + b.sar.shaderProgram.mustLoadLocations(&b.sar) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(linearGradientAlphaVS, linearGradientFS, &b.lgar.shaderProgram) + if err != nil { + return nil, err + } + b.lgar.shaderProgram.mustLoadLocations(&b.lgar) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(radialGradientAlphaVS, radialGradientAlphaFS, &b.rgar.shaderProgram) + if err != nil { + return nil, err + } + b.rgar.shaderProgram.mustLoadLocations(&b.rgar) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(imagePatternAlphaVS, imagePatternAlphaFS, &b.ipar.shaderProgram) + if err != nil { + return nil, err + } + b.ipar.shaderProgram.mustLoadLocations(&b.ipar) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(imageVS, imageFS, &b.ir.shaderProgram) + if err != nil { + return nil, err + } + b.ir.shaderProgram.mustLoadLocations(&b.ir) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(gaussian15VS, gaussian15FS, &b.gauss15r.shaderProgram) + if err != nil { + return nil, err + } + b.gauss15r.shaderProgram.mustLoadLocations(&b.gauss15r) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(gaussian63VS, gaussian63FS, &b.gauss63r.shaderProgram) + if err != nil { + return nil, err + } + b.gauss63r.shaderProgram.mustLoadLocations(&b.gauss63r) + if err = glError(); err != nil { + return nil, err + } + + err = loadShader(gaussian127VS, gaussian127FS, &b.gauss127r.shaderProgram) + if err != nil { + return nil, err + } + b.gauss127r.shaderProgram.mustLoadLocations(&b.gauss127r) + if err = glError(); err != nil { + return nil, err + } + + gl.GenBuffers(1, &b.buf) + if err = glError(); err != nil { + return nil, err + } + + gl.GenBuffers(1, &b.shadowBuf) + if err = glError(); err != nil { + return nil, err + } + + gl.ActiveTexture(gl.TEXTURE0) + gl.GenTextures(1, &b.alphaTex) + gl.BindTexture(gl.TEXTURE_2D, b.alphaTex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, alphaTexSize, alphaTexSize, 0, gl.ALPHA, gl.UNSIGNED_BYTE, nil) + + gl.Enable(gl.BLEND) + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + gl.Enable(gl.STENCIL_TEST) + gl.StencilMask(0xFF) + gl.Clear(gl.STENCIL_BUFFER_BIT) + gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) + gl.StencilFunc(gl.EQUAL, 0, 0xFF) + + gl.Enable(gl.SCISSOR_TEST) + + return b, nil +} + +// SetBounds updates the bounds of the canvas. This would +// usually be called for example when the window is resized +func (b *GoGLBackend) SetBounds(x, y, w, h int) { + b.x, b.y = x, y + b.fx, b.fy = float64(x), float64(y) + b.w, b.h = w, h + b.fw, b.fh = float64(w), float64(h) +} + +func glError() error { + glErr := gl.GetError() + if glErr != gl.NO_ERROR { + return fmt.Errorf("GL Error: %x", glErr) + } + return nil +} diff --git a/backend/goglbackend/rects.go b/backend/goglbackend/rects.go new file mode 100644 index 0000000..8c62f08 --- /dev/null +++ b/backend/goglbackend/rects.go @@ -0,0 +1,57 @@ +package goglbackend + +import ( + "github.com/go-gl/gl/v3.2-core/gl" +) + +/* +// FillRect fills a rectangle with the active fill style +func (b *GoGLBackend) FillRect(x, y, w, h float64) { + cv.activate() + + p0 := cv.tf(vec{x, y}) + p1 := cv.tf(vec{x, y + h}) + p2 := cv.tf(vec{x + w, y + h}) + p3 := cv.tf(vec{x + w, y}) + + if cv.state.shadowColor.a != 0 { + tris := [24]float32{ + 0, 0, + float32(cv.fw), 0, + float32(cv.fw), float32(cv.fh), + 0, 0, + float32(cv.fw), float32(cv.fh), + 0, float32(cv.fh), + float32(p0[0]), float32(p0[1]), + float32(p3[0]), float32(p3[1]), + float32(p2[0]), float32(p2[1]), + float32(p0[0]), float32(p0[1]), + float32(p2[0]), float32(p2[1]), + float32(p1[0]), float32(p1[1]), + } + cv.drawShadow(tris[:]) + } + + gl.BindBuffer(gl_ARRAY_BUFFER, buf) + data := [8]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1])} + gl.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW) + + gl.StencilFunc(gl_EQUAL, 0, 0xFF) + + vertex := cv.useShader(&cv.state.fill) + gl.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) + gl.EnableVertexAttribArray(vertex) + gl.DrawArrays(gl_TRIANGLE_FAN, 0, 4) + gl.DisableVertexAttribArray(vertex) + + gl.StencilFunc(gl_ALWAYS, 0, 0xFF) +} +*/ + +// ClearRect sets the color of the rectangle to transparent black +func (b *GoGLBackend) ClearRect(x, y, w, h int) { + gl.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h)) + gl.ClearColor(0, 0, 0, 0) + gl.Clear(gl.COLOR_BUFFER_BIT) + // cv.applyScissor() +} diff --git a/backend/goglbackend/shader.go b/backend/goglbackend/shader.go new file mode 100644 index 0000000..f7e62da --- /dev/null +++ b/backend/goglbackend/shader.go @@ -0,0 +1,201 @@ +package goglbackend + +import ( + "errors" + "fmt" + "reflect" + "strings" + "unicode" + "unicode/utf8" + + "github.com/go-gl/gl/v3.2-core/gl" +) + +type shaderProgram struct { + ID, vs, fs uint32 + attribs map[string]uint32 + uniforms map[string]int32 +} + +func loadShader(vs, fs string, sp *shaderProgram) error { + glError() // clear the current error + + // compile vertex shader + { + sp.vs = gl.CreateShader(gl.VERTEX_SHADER) + csrc, freeFunc := gl.Strs(vs + "\x00") + defer freeFunc() + gl.ShaderSource(sp.vs, 1, csrc, nil) + gl.CompileShader(sp.vs) + + var status int32 + gl.GetShaderiv(sp.vs, gl.COMPILE_STATUS, &status) + if status != gl.TRUE { + var buf [65536]byte + var length int32 + gl.GetShaderInfoLog(sp.vs, int32(len(buf)), &length, &buf[0]) + clog := string(buf[:length]) + gl.DeleteShader(sp.vs) + return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog) + } + if err := glError(); err != nil { + return fmt.Errorf("gl error after compiling vertex shader: %v", err) + } + } + + // compile fragment shader + { + sp.fs = gl.CreateShader(gl.FRAGMENT_SHADER) + csrc, freeFunc := gl.Strs(fs + "\x00") + defer freeFunc() + gl.ShaderSource(sp.fs, 1, csrc, nil) + gl.CompileShader(sp.fs) + + var status int32 + gl.GetShaderiv(sp.fs, gl.COMPILE_STATUS, &status) + if status != gl.TRUE { + var buf [65536]byte + var length int32 + gl.GetShaderInfoLog(sp.fs, int32(len(buf)), &length, &buf[0]) + clog := string(buf[:length]) + gl.DeleteShader(sp.fs) + return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog) + } + if err := glError(); err != nil { + return fmt.Errorf("gl error after compiling fragment shader: %v", err) + } + } + + // link shader program + { + sp.ID = gl.CreateProgram() + gl.AttachShader(sp.ID, sp.vs) + gl.AttachShader(sp.ID, sp.fs) + gl.LinkProgram(sp.ID) + + var status int32 + gl.GetProgramiv(sp.ID, gl.LINK_STATUS, &status) + if status != gl.TRUE { + var buf [65536]byte + var length int32 + gl.GetProgramInfoLog(sp.ID, int32(len(buf)), &length, &buf[0]) + clog := string(buf[:length]) + gl.DeleteProgram(sp.ID) + gl.DeleteShader(sp.vs) + gl.DeleteShader(sp.fs) + return fmt.Errorf("failed to link shader program:\n\n%s", clog) + } + if err := glError(); err != nil { + return fmt.Errorf("gl error after linking shader: %v", err) + } + } + + gl.UseProgram(sp.ID) + var nameBuf [256]byte + var length, size int32 + var xtype uint32 + var count int32 + + // load the attributes + gl.GetProgramiv(sp.ID, gl.ACTIVE_ATTRIBUTES, &count) + sp.attribs = make(map[string]uint32, int(count)) + for i := int32(0); i < count; i++ { + gl.GetActiveAttrib(sp.ID, uint32(i), int32(len(nameBuf)), &length, &size, &xtype, &nameBuf[0]) + name := string(nameBuf[:length]) + loc := gl.GetAttribLocation(sp.ID, &nameBuf[0]) + sp.attribs[name] = uint32(loc) + } + + // load the uniforms + gl.GetProgramiv(sp.ID, gl.ACTIVE_UNIFORMS, &count) + sp.uniforms = make(map[string]int32, int(count)) + for i := int32(0); i < count; i++ { + gl.GetActiveUniform(sp.ID, uint32(i), int32(len(nameBuf)), &length, &size, &xtype, &nameBuf[0]) + name := string(nameBuf[:length]) + loc := gl.GetUniformLocation(sp.ID, &nameBuf[0]) + sp.uniforms[name] = loc + } + + return nil +} + +func (sp *shaderProgram) use() { + gl.UseProgram(sp.ID) +} + +func (sp *shaderProgram) delete() { + gl.DeleteProgram(sp.ID) + gl.DeleteShader(sp.vs) + gl.DeleteShader(sp.fs) +} + +func (sp *shaderProgram) loadLocations(target interface{}) error { + val := reflect.ValueOf(target) + if val.Kind() != reflect.Ptr { + panic("target must be a pointer to a struct") + } + val = val.Elem() + if val.Kind() != reflect.Struct { + panic("target must be a pointer to a struct") + } + + gl.UseProgram(sp.ID) + + var errs strings.Builder + + for name, loc := range sp.attribs { + field := val.FieldByName(sp.structName(name)) + if field == (reflect.Value{}) { + fmt.Fprintf(&errs, "field for attribute \"%s\" not found; ", name) + } else if field.Type() != reflect.TypeOf(uint32(0)) { + fmt.Fprintf(&errs, "field for attribute \"%s\" must have type uint32; ", name) + } else { + field.Set(reflect.ValueOf(uint32(loc))) + } + } + + for name, loc := range sp.uniforms { + field := val.FieldByName(sp.structName(name)) + if field == (reflect.Value{}) { + fmt.Fprintf(&errs, "field for uniform \"%s\" not found; ", name) + } else if field.Type() != reflect.TypeOf(int32(0)) { + fmt.Fprintf(&errs, "field for uniform \"%s\" must have type int32; ", name) + } else { + field.Set(reflect.ValueOf(int32(loc))) + } + } + + if errs.Len() > 0 { + return errors.New(strings.TrimSpace(errs.String())) + } + return nil +} + +func (sp *shaderProgram) structName(name string) string { + rn, sz := utf8.DecodeRuneInString(name) + name = fmt.Sprintf("%c%s", unicode.ToUpper(rn), name[sz:]) + idx := strings.IndexByte(name, '[') + if idx > 0 { + name = name[:idx] + } + return name +} + +func (sp *shaderProgram) mustLoadLocations(target interface{}) { + err := sp.loadLocations(target) + if err != nil { + panic(err) + } +} + +func (sp *shaderProgram) enableAllVertexAttribArrays() { + for _, loc := range sp.attribs { + gl.EnableVertexAttribArray(loc) + } +} + +func (sp *shaderProgram) disableAllVertexAttribArrays() { + for _, loc := range sp.attribs { + gl.DisableVertexAttribArray(loc) + } +} diff --git a/backend/goglbackend/shaders.go b/backend/goglbackend/shaders.go new file mode 100755 index 0000000..9656ca3 --- /dev/null +++ b/backend/goglbackend/shaders.go @@ -0,0 +1,490 @@ +package goglbackend + +import ( + "bytes" + "fmt" + "strings" +) + +var imageVS = ` +attribute vec2 vertex, texCoord; +uniform vec2 canvasSize; +varying vec2 v_texCoord; +void main() { + v_texCoord = texCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var imageFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_texCoord; +uniform sampler2D image; +uniform float globalAlpha; +void main() { + vec4 col = texture2D(image, v_texCoord); + col.a *= globalAlpha; + gl_FragColor = col; +}` + +var solidVS = ` +attribute vec2 vertex; +uniform vec2 canvasSize; +void main() { + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var solidFS = ` +#ifdef GL_ES +precision mediump float; +#endif +uniform vec4 color; +uniform float globalAlpha; +void main() { + vec4 col = color; + col.a *= globalAlpha; + gl_FragColor = col; +}` + +var linearGradientVS = ` +attribute vec2 vertex; +uniform vec2 canvasSize; +varying vec2 v_cp; +void main() { + v_cp = vertex; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var linearGradientFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +uniform mat3 invmat; +uniform sampler2D gradient; +uniform vec2 from, dir; +uniform float len; +uniform float globalAlpha; +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + vec2 v = untf.xy - from; + float r = dot(v, dir) / len; + r = clamp(r, 0.0, 1.0); + vec4 col = texture2D(gradient, vec2(r, 0.0)); + col.a *= globalAlpha; + gl_FragColor = col; +}` + +var radialGradientVS = ` +attribute vec2 vertex; +uniform vec2 canvasSize; +varying vec2 v_cp; +void main() { + v_cp = vertex; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var radialGradientFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +uniform mat3 invmat; +uniform sampler2D gradient; +uniform vec2 from, to, dir; +uniform float radFrom, radTo; +uniform float len; +uniform float globalAlpha; +bool isNaN(float v) { + return v < 0.0 || 0.0 < v || v == 0.0 ? false : true; +} +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + float o_a = 0.5 * sqrt( + pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0) + -4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom) + *(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo) + ); + float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo); + float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo); + float o1 = (-o_a + o_b) / o_c; + float o2 = (o_a + o_b) / o_c; + if (isNaN(o1) && isNaN(o2)) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + float o = max(o1, o2); + //float r = radFrom + o * (radTo - radFrom); + vec4 col = texture2D(gradient, vec2(o, 0.0)); + col.a *= globalAlpha; + gl_FragColor = col; +}` + +var imagePatternVS = ` +attribute vec2 vertex; +uniform vec2 canvasSize; +varying vec2 v_cp; +void main() { + v_cp = vertex; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var imagePatternFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +uniform vec2 imageSize; +uniform mat3 invmat; +uniform sampler2D image; +uniform float globalAlpha; +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0)); + col.a *= globalAlpha; + gl_FragColor = col; +}` + +var solidAlphaVS = ` +attribute vec2 vertex, alphaTexCoord; +uniform vec2 canvasSize; +varying vec2 v_atc; +void main() { + v_atc = alphaTexCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var solidAlphaFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_atc; +uniform vec4 color; +uniform sampler2D alphaTex; +uniform float globalAlpha; +void main() { + vec4 col = color; + col.a *= texture2D(alphaTex, v_atc).a * globalAlpha; + gl_FragColor = col; +}` + +var linearGradientAlphaVS = ` +attribute vec2 vertex, alphaTexCoord; +uniform vec2 canvasSize; +varying vec2 v_cp; +varying vec2 v_atc; +void main() { + v_cp = vertex; + v_atc = alphaTexCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var linearGradientAlphaFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +varying vec2 v_atc; +varying vec2 v_texCoord; +uniform mat3 invmat; +uniform sampler2D gradient; +uniform vec2 from, dir; +uniform float len; +uniform sampler2D alphaTex; +uniform float globalAlpha; +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + vec2 v = untf.xy - from; + float r = dot(v, dir) / len; + r = clamp(r, 0.0, 1.0); + vec4 col = texture2D(gradient, vec2(r, 0.0)); + col.a *= texture2D(alphaTex, v_atc).a * globalAlpha; + gl_FragColor = col; +}` + +var radialGradientAlphaVS = ` +attribute vec2 vertex, alphaTexCoord; +uniform vec2 canvasSize; +varying vec2 v_cp; +varying vec2 v_atc; +void main() { + v_cp = vertex; + v_atc = alphaTexCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var radialGradientAlphaFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +varying vec2 v_atc; +uniform mat3 invmat; +uniform sampler2D gradient; +uniform vec2 from, to, dir; +uniform float radFrom, radTo; +uniform float len; +uniform sampler2D alphaTex; +uniform float globalAlpha; +bool isNaN(float v) { + return v < 0.0 || 0.0 < v || v == 0.0 ? false : true; +} +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + float o_a = 0.5 * sqrt( + pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0) + -4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom) + *(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo) + ); + float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo); + float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo); + float o1 = (-o_a + o_b) / o_c; + float o2 = (o_a + o_b) / o_c; + if (isNaN(o1) && isNaN(o2)) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + float o = max(o1, o2); + float r = radFrom + o * (radTo - radFrom); + vec4 col = texture2D(gradient, vec2(o, 0.0)); + col.a *= texture2D(alphaTex, v_atc).a * globalAlpha; + gl_FragColor = col; +}` + +var imagePatternAlphaVS = ` +attribute vec2 vertex, alphaTexCoord; +uniform vec2 canvasSize; +varying vec2 v_cp; +varying vec2 v_atc; +void main() { + v_cp = vertex; + v_atc = alphaTexCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var imagePatternAlphaFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +varying vec2 v_atc; +uniform vec2 imageSize; +uniform mat3 invmat; +uniform sampler2D image; +uniform sampler2D alphaTex; +uniform float globalAlpha; +void main() { + vec3 untf = vec3(v_cp, 1.0) * invmat; + vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0)); + col.a *= texture2D(alphaTex, v_atc).a * globalAlpha; + gl_FragColor = col; +}` + +var gaussian15VS = ` +attribute vec2 vertex, texCoord; +uniform vec2 canvasSize; +varying vec2 v_texCoord; +void main() { + v_texCoord = texCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var gaussian15FS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_texCoord; +uniform vec2 kernelScale; +uniform sampler2D image; +uniform float kernel[15]; +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); +_SUM_ + gl_FragColor = color; +}` + +var gaussian63VS = ` +attribute vec2 vertex, texCoord; +uniform vec2 canvasSize; +varying vec2 v_texCoord; +void main() { + v_texCoord = texCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var gaussian63FS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_texCoord; +uniform vec2 kernelScale; +uniform sampler2D image; +uniform float kernel[63]; +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); +_SUM_ + gl_FragColor = color; +}` + +var gaussian127VS = ` +attribute vec2 vertex, texCoord; +uniform vec2 canvasSize; +varying vec2 v_texCoord; +void main() { + v_texCoord = texCoord; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var gaussian127FS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_texCoord; +uniform vec2 kernelScale; +uniform sampler2D image; +uniform float kernel[127]; +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); +_SUM_ + gl_FragColor = color; +}` + +func init() { + fstr := "\tcolor += texture2D(image, v_texCoord + vec2(%.1f * kernelScale.x, %.1f * kernelScale.y)) * kernel[%d];\n" + bb := bytes.Buffer{} + for i := 0; i < 127; i++ { + off := float64(i) - 63 + fmt.Fprintf(&bb, fstr, off, off, i) + } + gaussian127FS = strings.Replace(gaussian127FS, "_SUM_", bb.String(), -1) + bb.Reset() + for i := 0; i < 63; i++ { + off := float64(i) - 31 + fmt.Fprintf(&bb, fstr, off, off, i) + } + gaussian63FS = strings.Replace(gaussian63FS, "_SUM_", bb.String(), -1) + bb.Reset() + for i := 0; i < 15; i++ { + off := float64(i) - 7 + fmt.Fprintf(&bb, fstr, off, off, i) + } + gaussian15FS = strings.Replace(gaussian15FS, "_SUM_", bb.String(), -1) +} + +type solidShader struct { + shaderProgram + Vertex uint32 + CanvasSize int32 + Color int32 + GlobalAlpha int32 +} + +type imageShader struct { + shaderProgram + Vertex uint32 + TexCoord uint32 + CanvasSize int32 + Image int32 + GlobalAlpha int32 +} + +type linearGradientShader struct { + shaderProgram + Vertex uint32 + CanvasSize int32 + Invmat int32 + Gradient int32 + From int32 + Dir int32 + Len int32 + GlobalAlpha int32 +} + +type radialGradientShader struct { + shaderProgram + Vertex uint32 + CanvasSize int32 + Invmat int32 + Gradient int32 + From int32 + To int32 + Dir int32 + RadFrom int32 + RadTo int32 + Len int32 + GlobalAlpha int32 +} + +type imagePatternShader struct { + shaderProgram + Vertex uint32 + CanvasSize int32 + ImageSize int32 + Invmat int32 + Image int32 + GlobalAlpha int32 +} + +type solidAlphaShader struct { + shaderProgram + Vertex uint32 + AlphaTexCoord uint32 + CanvasSize int32 + Color int32 + AlphaTex int32 + GlobalAlpha int32 +} + +type linearGradientAlphaShader struct { + shaderProgram + Vertex uint32 + AlphaTexCoord uint32 + CanvasSize int32 + Invmat int32 + Gradient int32 + From int32 + Dir int32 + Len int32 + AlphaTex int32 + GlobalAlpha int32 +} + +type radialGradientAlphaShader struct { + shaderProgram + Vertex uint32 + AlphaTexCoord uint32 + CanvasSize int32 + Invmat int32 + Gradient int32 + From int32 + To int32 + Dir int32 + RadFrom int32 + RadTo int32 + Len int32 + AlphaTex int32 + GlobalAlpha int32 +} + +type imagePatternAlphaShader struct { + shaderProgram + Vertex uint32 + AlphaTexCoord uint32 + CanvasSize int32 + ImageSize int32 + Invmat int32 + Image int32 + AlphaTex int32 + GlobalAlpha int32 +} + +type gaussianShader struct { + shaderProgram + Vertex uint32 + TexCoord uint32 + CanvasSize int32 + KernelScale int32 + Image int32 + Kernel int32 +} diff --git a/canvas.go b/canvas.go index cee2eb3..a04a472 100644 --- a/canvas.go +++ b/canvas.go @@ -16,6 +16,8 @@ import ( // Canvas represents an area on the viewport on which to draw // using a set of functions very similar to the HTML5 canvas type Canvas struct { + b Backend + x, y, w, h int fx, fy, fw, fh float64 @@ -134,11 +136,11 @@ var Performance = struct { // While all functions on the canvas use the top left point as // the origin, since GL uses the bottom left coordinate, the // coordinates given here also use the bottom left as origin -func New(x, y, w, h int) *Canvas { +func New(backend Backend, x, y, w, h int) *Canvas { if gli == nil { panic("LoadGL must be called before a canvas can be created") } - cv := &Canvas{stateStack: make([]drawState, 0, 20)} + cv := &Canvas{b: backend, stateStack: make([]drawState, 0, 20)} cv.SetBounds(x, y, w, h) cv.state.lineWidth = 1 cv.state.lineAlpha = 1 @@ -154,8 +156,8 @@ func New(x, y, w, h int) *Canvas { // does not render directly to the screen but renders to a // texture instead. If alpha is set to true, the offscreen // canvas will have an alpha channel -func NewOffscreen(w, h int, alpha bool) *Canvas { - cv := New(0, 0, w, h) +func NewOffscreen(backend Backend, w, h int, alpha bool) *Canvas { + cv := New(backend, 0, 0, w, h) cv.offscreen = true cv.offscrBuf.alpha = alpha return cv diff --git a/paths.go b/paths.go index ff5d856..4c13fea 100644 --- a/paths.go +++ b/paths.go @@ -666,9 +666,7 @@ func (cv *Canvas) ClearRect(x, y, w, h float64) { cv.activate() if cv.state.transform == matIdentity() { - gli.Scissor(int32(x+0.5), int32(cv.fh-y-h+0.5), int32(w+0.5), int32(h+0.5)) - gli.ClearColor(0, 0, 0, 0) - gli.Clear(gl_COLOR_BUFFER_BIT) + cv.b.ClearRect(int(x+0.5), int(y+0.5), int(w+0.5), int(h+0.5)) cv.applyScissor() return }