added an xmobile backend that can be generated from the gogl backend

This commit is contained in:
Thomas Friedel 2019-02-24 13:19:21 +01:00
parent 93c75a9b61
commit 77a9d14867
12 changed files with 2404 additions and 9 deletions

View file

@ -64,8 +64,6 @@ func New(x, y, w, h int) (*GoGLBackend, error) {
return nil, err
}
gl.GetError() // clear error state
b := &GoGLBackend{
w: w,
h: h,
@ -74,6 +72,8 @@ func New(x, y, w, h int) (*GoGLBackend, error) {
ptsBuf: make([]float32, 0, 4096),
}
gl.GetError() // clear error state
err = loadShader(solidVS, solidFS, &b.sr.shaderProgram)
if err != nil {
return nil, err
@ -450,7 +450,7 @@ func (b *GoGLBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, int32(b.w), int32(b.h))
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf)
gl.FramebufferTexture(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, offscr.tex, 0)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, offscr.tex, 0)
if err := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); err != gl.FRAMEBUFFER_COMPLETE {
// todo this should maybe not panic

View file

@ -13,6 +13,7 @@ import (
type shaderProgram struct {
ID, vs, fs uint32
attribs map[string]uint32
uniforms map[string]int32
}

66
backend/xmobile/clip.go Executable file
View file

@ -0,0 +1,66 @@
package xmobilebackend
import (
"unsafe"
"golang.org/x/mobile/gl"
)
func (b *XMobileBackend) ClearClip() {
b.activate()
b.glctx.StencilMask(0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
}
func (b *XMobileBackend) Clip(pts [][2]float64) {
b.activate()
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := gl.Enum(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform4f(b.sr.Color, 1, 1, 1, 1)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(false, false, false, false)
b.glctx.StencilMask(0x04)
b.glctx.StencilFunc(gl.ALWAYS, 4, 0x04)
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.StencilMask(0x02)
b.glctx.StencilFunc(gl.EQUAL, 0, 0x06)
b.glctx.StencilOp(gl.KEEP, gl.INVERT, gl.INVERT)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.StencilMask(0x04)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0x04)
b.glctx.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(true, true, true, true)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilMask(0xFF)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
}

305
backend/xmobile/fill.go Executable file
View file

@ -0,0 +1,305 @@
package xmobilebackend
import (
"image"
"math"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
func (b *XMobileBackend) Clear(pts [4][2]float64) {
b.activate()
// first check if the four points are aligned to form a nice rectangle, which can be more easily
// cleared using glScissor and glClear
aligned := pts[0][0] == pts[1][0] && pts[2][0] == pts[3][0] && pts[0][1] == pts[3][1] && pts[1][1] == pts[2][1]
if !aligned {
aligned = pts[0][0] == pts[3][0] && pts[1][0] == pts[2][0] && pts[0][1] == pts[1][1] && pts[2][1] == pts[3][1]
}
if aligned {
minX := math.Floor(math.Min(pts[0][0], pts[2][0]))
maxX := math.Ceil(math.Max(pts[0][0], pts[2][0]))
minY := math.Floor(math.Min(pts[0][1], pts[2][1]))
maxY := math.Ceil(math.Max(pts[0][1], pts[2][1]))
b.clearRect(int(minX), int(minY), int(maxX)-int(minX), int(maxY)-int(minY))
return
}
data := [8]float32{
float32(pts[0][0]), float32(pts[0][1]),
float32(pts[1][0]), float32(pts[1][1]),
float32(pts[2][0]), float32(pts[2][1]),
float32(pts[3][0]), float32(pts[3][1])}
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform4f(b.sr.Color, 0, 0, 0, 0)
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
b.glctx.Disable(gl.BLEND)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.Enable(gl.BLEND)
}
func (b *XMobileBackend) clearRect(x, y, w, h int) {
b.glctx.Enable(gl.SCISSOR_TEST)
var box [4]int32
b.glctx.GetIntegerv(box[:], gl.SCISSOR_BOX)
b.glctx.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h))
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT)
b.glctx.Scissor(box[0], box[1], box[2], box[3])
b.glctx.Disable(gl.SCISSOR_TEST)
}
func (b *XMobileBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) {
b.activate()
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := gl.Enum(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
if style.Color.A >= 255 {
vertex := b.useShader(style)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.DisableVertexAttribArray(vertex)
} else {
b.glctx.ColorMask(false, false, false, false)
b.glctx.StencilFunc(gl.ALWAYS, 1, 0xFF)
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
b.glctx.StencilMask(0x01)
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform4f(b.sr.Color, 0, 0, 0, 0)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(true, true, true, true)
b.glctx.StencilFunc(gl.EQUAL, 1, 0xFF)
vertex := b.useShader(style)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.ptsBuf = append(b.ptsBuf[:0], 0, 0, float32(b.fw), 0, float32(b.fw), float32(b.fh), 0, float32(b.fh))
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(vertex)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
b.glctx.StencilMask(0xFF)
}
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *XMobileBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [][2]float64) {
b.activate()
w, h := mask.Rect.Dx(), mask.Rect.Dy()
b.glctx.ActiveTexture(gl.TEXTURE1)
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
for y := 0; y < h; y++ {
off := y * mask.Stride
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, alphaTexSize-1-y, w, 1, gl.ALPHA, gl.UNSIGNED_BYTE, mask.Pix[off:])
}
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
vertex, alphaTexCoord := b.useAlphaShader(style, 1)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.EnableVertexAttribArray(alphaTexCoord)
tw := float64(w) / alphaTexSize
th := float64(h) / alphaTexSize
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data, 0, 1, 0, float32(1-th), float32(tw), float32(1-th), float32(tw), 1)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(alphaTexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(vertex)
b.glctx.DisableVertexAttribArray(alphaTexCoord)
b.glctx.ActiveTexture(gl.TEXTURE1)
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
for y := 0; y < h; y++ {
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, alphaTexSize-1-y, w, 1, gl.ALPHA, gl.UNSIGNED_BYTE, zeroes[0:])
}
b.glctx.ActiveTexture(gl.TEXTURE0)
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *XMobileBackend) drawBlurred(blur float64) {
b.glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
var kernel []float32
var kernelBuf [255]float32
var gs *gaussianShader
if blur < 3 {
gs = &b.gauss15r
kernel = kernelBuf[:15]
} else if blur < 12 {
gs = &b.gauss63r
kernel = kernelBuf[:63]
} else {
gs = &b.gauss127r
kernel = kernelBuf[:127]
}
gaussianKernel(blur, kernel)
b.offscr2.alpha = true
b.enableTextureRenderTarget(&b.offscr2)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data := [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
b.glctx.UseProgram(gs.ID)
b.glctx.Uniform1i(gs.Image, 0)
b.glctx.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(gs.KernelScale, 1.0/float32(b.fw), 0.0)
b.glctx.Uniform1fv(gs.Kernel, kernel)
b.glctx.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(gs.Vertex)
b.glctx.EnableVertexAttribArray(gs.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(gs.Vertex)
b.glctx.DisableVertexAttribArray(gs.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.disableTextureRenderTarget()
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data = [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
b.glctx.UseProgram(gs.ID)
b.glctx.Uniform1i(gs.Image, 0)
b.glctx.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(gs.KernelScale, 0.0, 1.0/float32(b.fh))
b.glctx.Uniform1fv(gs.Kernel, kernel)
b.glctx.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(gs.Vertex)
b.glctx.EnableVertexAttribArray(gs.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(gs.Vertex)
b.glctx.DisableVertexAttribArray(gs.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
}
func gaussianKernel(stddev float64, target []float32) {
stddevSqr := stddev * stddev
center := float64(len(target) / 2)
factor := 1.0 / math.Sqrt(2*math.Pi*stddevSqr)
for i := range target {
x := float64(i) - center
target[i] = float32(factor * math.Pow(math.E, -x*x/(2*stddevSqr)))
}
// normalizeKernel(target)
}
func normalizeKernel(kernel []float32) {
var sum float32
for _, v := range kernel {
sum += v
}
factor := 1.0 / sum
for i := range kernel {
kernel[i] *= factor
}
}

401
backend/xmobile/gen/gen.go Normal file
View file

@ -0,0 +1,401 @@
package main
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
func main() {
{ // make sure we are in the right directory
dir, err := os.Getwd()
if err != nil {
log.Fatalf("Failed to get working directory: %v", err)
}
d1 := filepath.Base(dir)
d2 := filepath.Base(filepath.Dir(dir))
if d2 != "backend" || d1 != "xmobile" {
log.Fatalln("This must be run in the backend/xmobile directory")
}
}
{ // delete existing files
fis, err := ioutil.ReadDir(".")
if err != nil {
log.Fatalf("Failed to read current dir: %v", err)
}
for _, fi := range fis {
if fi.IsDir() || fi.Name() == "shader.go" {
continue
}
err = os.Remove(fi.Name())
if err != nil {
log.Fatalf("Failed to delete file %s: %v", fi.Name(), err)
}
}
}
{ // copy gogl files
fis, err := ioutil.ReadDir("../gogl")
if err != nil {
log.Fatalf("Failed to read dir ../gogl: %v", err)
}
for _, fi := range fis {
if !strings.HasSuffix(fi.Name(), ".go") || fi.Name() == "shader.go" {
continue
}
path := filepath.Join("../gogl", fi.Name())
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("Failed to read file %s: %v", path, err)
}
filename, rewritten := rewrite(fi.Name(), string(data))
err = ioutil.WriteFile(filename, ([]byte)(rewritten), 0777)
if err != nil {
log.Fatalf("Failed to write file %s: %v", fi.Name(), err)
}
}
}
err := exec.Command("go", "fmt").Run()
if err != nil {
log.Fatalf("Failed to run go fmt: %v", err)
}
}
func rewrite(filename, src string) (string, string) {
src = strings.Replace(src, `package goglbackend`, `package xmobilebackend`, 1)
src = strings.Replace(src, `"github.com/go-gl/gl/v3.2-core/gl"`, `"golang.org/x/mobile/gl"`, 1)
src = strings.Replace(src, "\tgl.", "\tb.glctx.", -1)
src = strings.Replace(src, "GoGLBackend", "XMobileBackend", -1)
src = strings.Replace(src, "uint32(gl.TRIANGLES)", "gl.Enum(gl.TRIANGLES)", -1)
src = strings.Replace(src, `func (g *gradient) Delete() {`,
`func (g *gradient) Delete() {
b := g.b`, -1)
src = strings.Replace(src, `func (g *gradient) load(stops backendbase.Gradient) {`,
`func (g *gradient) load(stops backendbase.Gradient) {
b := g.b`, -1)
src = strings.Replace(src, `func (img *Image) Delete() {`,
`func (img *Image) Delete() {
b := img.b`, -1)
src = strings.Replace(src, `func (img *Image) Replace(src image.Image) error {`,
`func (img *Image) Replace(src image.Image) error {
b := img.b`, -1)
src = strings.Replace(src, `imageBufTex == 0`, `imageBufTex.Value == 0`, -1)
src = strings.Replace(src,
`loadImage(src image.Image, tex uint32)`,
`loadImage(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageRGBA(src *image.RGBA, tex uint32)`,
`loadImageRGBA(b *XMobileBackend, src *image.RGBA, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageGray(src *image.Gray, tex uint32)`,
`loadImageGray(b *XMobileBackend, src *image.Gray, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageConverted(src image.Image, tex uint32)`,
`loadImageConverted(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
src = strings.Replace(src,
`func loadShader(vs, fs string, sp *shaderProgram) error {`,
`func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
sp.b = b`, -1)
src = strings.Replace(src, `func glError() error {
glErr := gl.GetError()
`, `func glError(b *XMobileBackend) error {
glErr := b.glctx.GetError()
`, -1)
src = rewriteCalls(src, "glError", func(params []string) string {
return "glError(b)"
})
src = regexp.MustCompile(`[ \t]+tex[ ]+uint32`).ReplaceAllString(src, "\ttex gl.Texture")
src = rewriteCalls(src, "b.glctx.BufferData", func(params []string) string {
return "b.glctx.BufferData(" + params[0] + ", byteSlice(" + params[2] + ", " + params[1] + "), " + params[3] + ")"
})
src = rewriteCalls(src, "b.glctx.VertexAttribPointer", func(params []string) string {
params[5] = strings.Replace(params[5], "gl.PtrOffset(", "", 1)
params[5] = strings.Replace(params[5], ")", "", 1)
params[5] = strings.Replace(params[5], "nil", "0", 1)
return "b.glctx.VertexAttribPointer(" + strings.Join(params, ",") + ")"
})
src = rewriteCalls(src, "b.glctx.DrawArrays", func(params []string) string {
if strings.HasPrefix(params[2], "int32(") {
params[2] = params[2][6 : len(params[2])-1]
}
return "b.glctx.DrawArrays(" + strings.Join(params, ",") + ")"
})
src = rewriteCalls(src, "b.glctx.Uniform1fv", func(params []string) string {
params[2] = params[2][1 : len(params[2])-3]
return "b.glctx.Uniform1fv(" + params[0] + ", " + params[2] + ")"
})
src = rewriteCalls(src, "b.glctx.TexImage2D", func(params []string) string {
params = append(params[:5], params[6:]...)
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.TexImage2D(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.TexSubImage2D", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.TexSubImage2D(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.GetIntegerv", func(params []string) string {
return "b.glctx.GetIntegerv(" + params[1][1:len(params[1])-2] + ":], " + params[0] + ")"
})
src = rewriteCalls(src, "b.glctx.GenTextures", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateTexture()"
})
src = rewriteCalls(src, "b.glctx.DeleteTextures", func(params []string) string {
return "b.glctx.DeleteTexture(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.GenBuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateBuffer()"
})
src = rewriteCalls(src, "b.glctx.GenFramebuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateFramebuffer()"
})
src = rewriteCalls(src, "b.glctx.DeleteFramebuffers", func(params []string) string {
return "b.glctx.DeleteFramebuffer(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.GenRenderbuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateRenderbuffer()"
})
src = rewriteCalls(src, "b.glctx.DeleteRenderbuffers", func(params []string) string {
return "b.glctx.DeleteRenderbuffer(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.RenderbufferStorage", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
}
}
return "b.glctx.RenderbufferStorage(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "gl.CheckFramebufferStatus", func(params []string) string {
return "b.glctx.CheckFramebufferStatus(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.BindFramebuffer", func(params []string) string {
if params[1] == "0" {
params[1] = "gl.Framebuffer{Value: 0}"
}
return "b.glctx.BindFramebuffer(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.ReadPixels", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.ReadPixels(" + params[6] + ", " + strings.Join(params[:len(params)-1], ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.Viewport", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
}
}
return "b.glctx.Viewport(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImage", func(params []string) string {
return "loadImage(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageRGBA", func(params []string) string {
return "loadImageRGBA(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageGray", func(params []string) string {
return "loadImageGray(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageConverted", func(params []string) string {
return "loadImageConverted(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadShader", func(params []string) string {
return "loadShader(b, " + strings.Join(params, ", ") + ")"
})
if filename == "gogl.go" {
filename = "xmobile.go"
src = rewriteMain(src)
} else if filename == "shaders.go" {
src = rewriteShaders(src)
}
return filename, src
}
func rewriteMain(src string) string {
src = strings.Replace(src, "type XMobileBackend struct {\n",
"type XMobileBackend struct {\n\tglctx gl.Context\n\n", 1)
src = strings.Replace(src, "func New(x, y, w, h int) (*XMobileBackend, error) {",
"func New(glctx gl.Context, x, y, w, h int) (*XMobileBackend, error) {", 1)
src = strings.Replace(src, "func NewOffscreen(w, h int, alpha bool) (*XMobileBackend, error)",
"func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackend, error)", 1)
src = rewriteCalls(src, "New", func(params []string) string {
return "New(glctx, " + strings.Join(params, ", ") + ")"
})
src = strings.Replace(src,
` err := gl.Init()
if err != nil {
return nil, err
}
`, ` var err error
`, 1)
src = strings.Replace(src, "\tb := &XMobileBackend{\n",
"\tb := &XMobileBackend{\n\t\tglctx: glctx,\n\n", 1)
src = strings.Replace(src,
` buf uint32
shadowBuf uint32
alphaTex uint32
`,
` buf gl.Buffer
shadowBuf gl.Buffer
alphaTex gl.Texture
`, 1)
src = strings.Replace(src, `imageBufTex uint32`, `imageBufTex gl.Texture`, 1)
src = strings.Replace(src,
`type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf uint32
frameBuf uint32
alpha bool
}
`,
`type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf gl.Renderbuffer
frameBuf gl.Framebuffer
alpha bool
}
`, 1)
src = src + `
func byteSlice(ptr unsafe.Pointer, size int) []byte {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = size
sh.Len = size
sh.Data = uintptr(ptr)
return buf
}
`
src = strings.Replace(src, "import (\n",
`import (
"unsafe"
"reflect"
`, 1)
src = strings.Replace(src, "vertexLoc uint32", "vertexLoc gl.Attrib", 1)
src = strings.Replace(src, "alphaTexSlot int32", "alphaTexSlot int", 1)
src = strings.Replace(src, "vertexLoc, alphaTexCoordLoc uint32", "vertexLoc, alphaTexCoordLoc gl.Attrib", 1)
return src
}
func rewriteShaders(src string) string {
src = strings.Replace(src,
`import (
"bytes"
"fmt"
"strings"
)
`,
`import (
"bytes"
"fmt"
"strings"
"golang.org/x/mobile/gl"
)
`, 1)
src = strings.Replace(src, "uint32", "gl.Attrib", -1)
src = strings.Replace(src, "int32", "gl.Uniform", -1)
return src
}
func rewriteCalls(src, funcName string, fn func([]string) string) string {
rewritten := ""
pos := 0
for {
idx := strings.Index(src[pos:], funcName)
if idx == -1 {
rewritten += src[pos:]
break
}
idx += pos
rewritten += src[pos:idx]
parenStart := idx + len(funcName)
if idx > 5 && src[idx-5:idx] == "func " {
rewritten += src[idx:parenStart]
pos = parenStart
continue
}
if src[parenStart] != '(' {
rewritten += src[idx:parenStart]
pos = parenStart
continue
}
params := make([]string, 0, 10)
parenDepth := 0
paramStart := 0
paramsStr := src[parenStart+1:]
paramloop:
for i, rn := range paramsStr {
switch rn {
case '(':
parenDepth++
case ')':
parenDepth--
if parenDepth == -1 {
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
pos = parenStart + i + 2
break paramloop
}
case ',':
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
paramStart = i + 1
}
}
rewritten += fn(params)
}
return rewritten
}

116
backend/xmobile/gradients.go Executable file
View file

@ -0,0 +1,116 @@
package xmobilebackend
import (
"runtime"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
// LinearGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a straight line
type LinearGradient struct {
gradient
}
// RadialGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a circle
type RadialGradient struct {
gradient
}
type gradient struct {
b *XMobileBackend
tex gl.Texture
loaded bool
deleted bool
opaque bool
}
func (b *XMobileBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
b.activate()
lg := &LinearGradient{
gradient: gradient{b: b, opaque: true},
}
lg.tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
lg.load(data)
runtime.SetFinalizer(lg, func(lg *LinearGradient) {
b.glChan <- func() {
b.glctx.DeleteTexture(lg.tex)
}
})
return lg
}
func (b *XMobileBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
b.activate()
rg := &RadialGradient{
gradient: gradient{b: b, opaque: true},
}
rg.tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
rg.load(data)
runtime.SetFinalizer(rg, func(rg *RadialGradient) {
b.glChan <- func() {
b.glctx.DeleteTexture(rg.tex)
}
})
return rg
}
// Delete explicitly deletes the gradient
func (g *gradient) Delete() {
b := g.b
g.b.activate()
b.glctx.DeleteTexture(g.tex)
g.deleted = true
}
func (g *gradient) IsDeleted() bool { return g.deleted }
func (g *gradient) IsOpaque() bool { return g.opaque }
func (lg *LinearGradient) Replace(data backendbase.Gradient) { lg.load(data) }
func (rg *RadialGradient) Replace(data backendbase.Gradient) { rg.load(data) }
func (g *gradient) load(stops backendbase.Gradient) {
b := g.b
if g.loaded {
return
}
g.b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, g.tex)
var pixels [2048 * 4]byte
pp := 0
for i := 0; i < 2048; i++ {
c := stops.ColorAt(float64(i) / 2047)
pixels[pp] = c.R
pixels[pp+1] = c.G
pixels[pp+2] = c.B
pixels[pp+3] = c.A
pp += 4
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels[0:])
g.loaded = true
}

94
backend/xmobile/imagedata.go Executable file
View file

@ -0,0 +1,94 @@
package xmobilebackend
import (
"image"
"image/color"
"unsafe"
"golang.org/x/mobile/gl"
)
// GetImageData returns an RGBA image of the current image
func (b *XMobileBackend) GetImageData(x, y, w, h int) *image.RGBA {
b.activate()
if x < 0 {
w += x
x = 0
}
if y < 0 {
h += y
y = 0
}
if w > b.w {
w = b.w
}
if h > b.h {
h = b.h
}
if len(b.imageBuf) < w*h*3 {
b.imageBuf = make([]byte, w*h*3)
}
b.glctx.ReadPixels(b.imageBuf[0:], x, y, w, h, gl.RGB, gl.UNSIGNED_BYTE)
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
bp := 0
for cy := y; cy < y+h; cy++ {
for cx := x; cx < x+w; cx++ {
rgba.SetRGBA(cx, y+h-1-cy, color.RGBA{R: b.imageBuf[bp], G: b.imageBuf[bp+1], B: b.imageBuf[bp+2], A: 255})
bp += 3
}
}
return rgba
}
// PutImageData puts the given image at the given x/y coordinates
func (b *XMobileBackend) PutImageData(img *image.RGBA, x, y int) {
b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
if b.imageBufTex.Value == 0 {
b.imageBufTex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
} else {
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
}
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if img.Stride == img.Bounds().Dx()*4 {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix[0:])
} else {
data := make([]uint8, 0, w*h*4)
for cy := 0; cy < h; cy++ {
start := cy * img.Stride
end := start + w*4
data = append(data, img.Pix[start:end]...)
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
}
dx, dy := float32(x), float32(y)
dw, dh := float32(w), float32(h)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
0, 0, 1, 0, 1, 1, 0, 1}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.UseProgram(b.ir.ID)
b.glctx.Uniform1i(b.ir.Image, 0)
b.glctx.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.ir.GlobalAlpha, 1)
b.glctx.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(b.ir.Vertex)
b.glctx.EnableVertexAttribArray(b.ir.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.ir.Vertex)
b.glctx.DisableVertexAttribArray(b.ir.TexCoord)
}

263
backend/xmobile/images.go Executable file
View file

@ -0,0 +1,263 @@
package xmobilebackend
import (
"errors"
"image"
"runtime"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
// Image represents a loaded image that can be used in various drawing functions
type Image struct {
b *XMobileBackend
w, h int
tex gl.Texture
deleted bool
opaque bool
}
func (b *XMobileBackend) LoadImage(src image.Image) (backendbase.Image, error) {
b.activate()
var tex gl.Texture
tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, tex)
if src == nil {
return &Image{tex: tex}, nil
}
img, err := loadImage(b, src, tex)
if err != nil {
return nil, err
}
runtime.SetFinalizer(img, func(img *Image) {
if !img.deleted {
b.glChan <- func() {
b.glctx.DeleteTexture(img.tex)
}
}
})
return img, nil
}
func loadImage(b *XMobileBackend, src image.Image, tex gl.Texture) (*Image, error) {
var img *Image
var err error
switch v := src.(type) {
case *image.RGBA:
img, err = loadImageRGBA(b, v, tex)
if err != nil {
return nil, err
}
case *image.Gray:
img, err = loadImageGray(b, v, tex)
if err != nil {
return nil, err
}
case image.Image:
img, err = loadImageConverted(b, v, tex)
if err != nil {
return nil, err
}
default:
return nil, errors.New("Unsupported source type")
}
return img, nil
}
func loadImageRGBA(b *XMobileBackend, src *image.RGBA, tex gl.Texture) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
checkOpaque:
for y := 0; y < img.h; y++ {
off := src.PixOffset(0, y) + 3
for x := 0; x < img.w; x++ {
if src.Pix[off] < 255 {
img.opaque = false
break checkOpaque
}
off += 4
}
}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if err := glError(b); err != nil {
return nil, err
}
if src.Stride == img.w*4 {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, src.Pix[0:])
} else {
data := make([]uint8, 0, img.w*img.h*4)
for y := 0; y < img.h; y++ {
start := y * src.Stride
end := start + img.w*4
data = append(data, src.Pix[start:end]...)
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
}
if err := glError(b); err != nil {
return nil, err
}
b.glctx.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(b); err != nil {
return nil, err
}
return img, nil
}
func loadImageGray(b *XMobileBackend, src *image.Gray, tex gl.Texture) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if err := glError(b); err != nil {
return nil, err
}
if src.Stride == img.w {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, img.w, img.h, gl.RED, gl.UNSIGNED_BYTE, src.Pix[0:])
} else {
data := make([]uint8, 0, img.w*img.h)
for y := 0; y < img.h; y++ {
start := y * src.Stride
end := start + img.w
data = append(data, src.Pix[start:end]...)
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, img.w, img.h, gl.RED, gl.UNSIGNED_BYTE, data[0:])
}
if err := glError(b); err != nil {
return nil, err
}
b.glctx.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(b); err != nil {
return nil, err
}
return img, nil
}
func loadImageConverted(b *XMobileBackend, src image.Image, tex gl.Texture) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if err := glError(b); err != nil {
return nil, err
}
data := make([]uint8, 0, img.w*img.h*4)
for y := 0; y < img.h; y++ {
for x := 0; x < img.w; x++ {
ir, ig, ib, ia := src.At(x, y).RGBA()
r, g, b, a := uint8(ir>>8), uint8(ig>>8), uint8(ib>>8), uint8(ia>>8)
data = append(data, r, g, b, a)
if a < 255 {
img.opaque = false
}
}
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
if err := glError(b); err != nil {
return nil, err
}
b.glctx.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(b); err != nil {
return nil, err
}
return img, nil
}
// Width returns the width of the image
func (img *Image) Width() int { return img.w }
// Height returns the height of the image
func (img *Image) Height() int { return img.h }
// Size returns the width and height of the image
func (img *Image) Size() (int, int) { return img.w, img.h }
// Delete deletes the image from memory. Any draw calls
// with a deleted image will not do anything
func (img *Image) Delete() {
b := img.b
img.b.activate()
b.glctx.DeleteTexture(img.tex)
img.deleted = true
}
// IsDeleted returns true if the Delete function has been
// called on this image
func (img *Image) IsDeleted() bool { return img.deleted }
// Replace replaces the image with the new one
func (img *Image) Replace(src image.Image) error {
b := img.b
img.b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
newImg, err := loadImage(b, src, img.tex)
if err != nil {
return err
}
*img = *newImg
return nil
}
// IsOpaque returns true if all pixels in the image
// have a full alpha value
func (img *Image) IsOpaque() bool { return img.opaque }
func (b *XMobileBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64) {
b.activate()
img := dimg.(*Image)
sx /= float64(img.w)
sy /= float64(img.h)
sw /= float64(img.w)
sh /= float64(img.h)
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data,
float32(sx), float32(sy),
float32(sx), float32(sy+sh),
float32(sx+sw), float32(sy+sh),
float32(sx+sw), float32(sy),
)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.UseProgram(b.ir.ID)
b.glctx.Uniform1i(b.ir.Image, 0)
b.glctx.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.ir.GlobalAlpha, float32(alpha))
b.glctx.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(b.ir.Vertex)
b.glctx.EnableVertexAttribArray(b.ir.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.ir.Vertex)
b.glctx.DisableVertexAttribArray(b.ir.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
}

180
backend/xmobile/shader.go Executable file
View file

@ -0,0 +1,180 @@
package xmobilebackend
import (
"errors"
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/mobile/gl"
)
type shaderProgram struct {
b *XMobileBackend
ID gl.Program
vs, fs gl.Shader
attribs map[string]gl.Attrib
uniforms map[string]gl.Uniform
}
func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
sp.b = b
glError(b) // clear the current error
// compile vertex shader
{
sp.vs = b.glctx.CreateShader(gl.VERTEX_SHADER)
b.glctx.ShaderSource(sp.vs, vs)
b.glctx.CompileShader(sp.vs)
status := b.glctx.GetShaderi(sp.vs, gl.COMPILE_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetShaderInfoLog(sp.vs)
b.glctx.DeleteShader(sp.vs)
return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after compiling vertex shader: %v", err)
}
}
// compile fragment shader
{
sp.fs = b.glctx.CreateShader(gl.FRAGMENT_SHADER)
b.glctx.ShaderSource(sp.fs, fs)
b.glctx.CompileShader(sp.fs)
status := b.glctx.GetShaderi(sp.fs, gl.COMPILE_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetShaderInfoLog(sp.fs)
b.glctx.DeleteShader(sp.fs)
return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after compiling fragment shader: %v", err)
}
}
// link shader program
{
sp.ID = b.glctx.CreateProgram()
b.glctx.AttachShader(sp.ID, sp.vs)
b.glctx.AttachShader(sp.ID, sp.fs)
b.glctx.LinkProgram(sp.ID)
status := b.glctx.GetProgrami(sp.ID, gl.LINK_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetProgramInfoLog(sp.ID)
b.glctx.DeleteProgram(sp.ID)
b.glctx.DeleteShader(sp.vs)
b.glctx.DeleteShader(sp.fs)
return fmt.Errorf("failed to link shader program:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after linking shader: %v", err)
}
}
b.glctx.UseProgram(sp.ID)
// load the attributes
count := b.glctx.GetProgrami(sp.ID, gl.ACTIVE_ATTRIBUTES)
sp.attribs = make(map[string]gl.Attrib, int(count))
for i := 0; i < count; i++ {
name, _, _ := b.glctx.GetActiveAttrib(sp.ID, uint32(i))
sp.attribs[name] = b.glctx.GetAttribLocation(sp.ID, name)
}
// load the uniforms
count = b.glctx.GetProgrami(sp.ID, gl.ACTIVE_UNIFORMS)
sp.uniforms = make(map[string]gl.Uniform, int(count))
for i := 0; i < count; i++ {
name, _, _ := b.glctx.GetActiveUniform(sp.ID, uint32(i))
sp.uniforms[name] = b.glctx.GetUniformLocation(sp.ID, name)
}
return nil
}
func (sp *shaderProgram) use() {
sp.b.glctx.UseProgram(sp.ID)
}
func (sp *shaderProgram) delete() {
sp.b.glctx.DeleteProgram(sp.ID)
sp.b.glctx.DeleteShader(sp.vs)
sp.b.glctx.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")
}
sp.b.glctx.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(gl.Attrib{}) {
fmt.Fprintf(&errs, "field for attribute \"%s\" must have type gl.Attrib; ", name)
} else {
field.Set(reflect.ValueOf(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(gl.Uniform{}) {
fmt.Fprintf(&errs, "field for uniform \"%s\" must have type gl.Uniform; ", name)
} else {
field.Set(reflect.ValueOf(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 {
sp.b.glctx.EnableVertexAttribArray(loc)
}
}
func (sp *shaderProgram) disableAllVertexAttribArrays() {
for _, loc := range sp.attribs {
sp.b.glctx.DisableVertexAttribArray(loc)
}
}

468
backend/xmobile/shaders.go Executable file
View file

@ -0,0 +1,468 @@
package xmobilebackend
import (
"bytes"
"fmt"
"strings"
"golang.org/x/mobile/gl"
)
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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform float globalAlpha;
void main() {
vec4 col = texture2D(image, mod(v_cp / 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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform sampler2D alphaTex;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec4 col = texture2D(image, mod(v_cp / 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 gl.Attrib
CanvasSize gl.Uniform
Color gl.Uniform
GlobalAlpha gl.Uniform
}
type imageShader struct {
shaderProgram
Vertex gl.Attrib
TexCoord gl.Attrib
CanvasSize gl.Uniform
Image gl.Uniform
GlobalAlpha gl.Uniform
}
type linearGradientShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
Dir gl.Uniform
Len gl.Uniform
GlobalAlpha gl.Uniform
}
type radialGradientShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
To gl.Uniform
RadFrom gl.Uniform
RadTo gl.Uniform
GlobalAlpha gl.Uniform
}
type imagePatternShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
ImageSize gl.Uniform
Image gl.Uniform
GlobalAlpha gl.Uniform
}
type solidAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Color gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type linearGradientAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
Dir gl.Uniform
Len gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type radialGradientAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
To gl.Uniform
RadFrom gl.Uniform
RadTo gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type imagePatternAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
ImageSize gl.Uniform
Image gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type gaussianShader struct {
shaderProgram
Vertex gl.Attrib
TexCoord gl.Attrib
CanvasSize gl.Uniform
KernelScale gl.Uniform
Image gl.Uniform
Kernel gl.Uniform
}

498
backend/xmobile/xmobile.go Executable file
View file

@ -0,0 +1,498 @@
package xmobilebackend
import (
"fmt"
"image/color"
"math"
"reflect"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
const alphaTexSize = 2048
var zeroes [alphaTexSize]byte
type XMobileBackend struct {
glctx gl.Context
x, y, w, h int
fx, fy, fw, fh float64
buf gl.Buffer
shadowBuf gl.Buffer
alphaTex gl.Texture
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
imageBufTex gl.Texture
imageBuf []byte
ptsBuf []float32
offscreen bool
offscrBuf offscreenBuffer
offscrImg Image
glChan chan func()
}
type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf gl.Renderbuffer
frameBuf gl.Framebuffer
alpha bool
}
func New(glctx gl.Context, x, y, w, h int) (*XMobileBackend, error) {
var err error
b := &XMobileBackend{
glctx: glctx,
w: w,
h: h,
fw: float64(w),
fh: float64(h),
ptsBuf: make([]float32, 0, 4096),
}
b.glctx.GetError() // clear error state
err = loadShader(b, solidVS, solidFS, &b.sr.shaderProgram)
if err != nil {
return nil, err
}
b.sr.shaderProgram.mustLoadLocations(&b.sr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, linearGradientVS, linearGradientFS, &b.lgr.shaderProgram)
if err != nil {
return nil, err
}
b.lgr.shaderProgram.mustLoadLocations(&b.lgr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, radialGradientVS, radialGradientFS, &b.rgr.shaderProgram)
if err != nil {
return nil, err
}
b.rgr.shaderProgram.mustLoadLocations(&b.rgr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imagePatternVS, imagePatternFS, &b.ipr.shaderProgram)
if err != nil {
return nil, err
}
b.ipr.shaderProgram.mustLoadLocations(&b.ipr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, solidAlphaVS, solidAlphaFS, &b.sar.shaderProgram)
if err != nil {
return nil, err
}
b.sar.shaderProgram.mustLoadLocations(&b.sar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, linearGradientAlphaVS, linearGradientFS, &b.lgar.shaderProgram)
if err != nil {
return nil, err
}
b.lgar.shaderProgram.mustLoadLocations(&b.lgar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, radialGradientAlphaVS, radialGradientAlphaFS, &b.rgar.shaderProgram)
if err != nil {
return nil, err
}
b.rgar.shaderProgram.mustLoadLocations(&b.rgar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imagePatternAlphaVS, imagePatternAlphaFS, &b.ipar.shaderProgram)
if err != nil {
return nil, err
}
b.ipar.shaderProgram.mustLoadLocations(&b.ipar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imageVS, imageFS, &b.ir.shaderProgram)
if err != nil {
return nil, err
}
b.ir.shaderProgram.mustLoadLocations(&b.ir)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian15VS, gaussian15FS, &b.gauss15r.shaderProgram)
if err != nil {
return nil, err
}
b.gauss15r.shaderProgram.mustLoadLocations(&b.gauss15r)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian63VS, gaussian63FS, &b.gauss63r.shaderProgram)
if err != nil {
return nil, err
}
b.gauss63r.shaderProgram.mustLoadLocations(&b.gauss63r)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian127VS, gaussian127FS, &b.gauss127r.shaderProgram)
if err != nil {
return nil, err
}
b.gauss127r.shaderProgram.mustLoadLocations(&b.gauss127r)
if err = glError(b); err != nil {
return nil, err
}
b.buf = b.glctx.CreateBuffer()
if err = glError(b); err != nil {
return nil, err
}
b.shadowBuf = b.glctx.CreateBuffer()
if err = glError(b); err != nil {
return nil, err
}
b.glctx.ActiveTexture(gl.TEXTURE0)
b.alphaTex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, alphaTexSize, alphaTexSize, gl.ALPHA, gl.UNSIGNED_BYTE, nil)
b.glctx.Enable(gl.BLEND)
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
b.glctx.Enable(gl.STENCIL_TEST)
b.glctx.StencilMask(0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.Disable(gl.SCISSOR_TEST)
return b, nil
}
func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackend, error) {
b, err := New(glctx, 0, 0, w, h)
if err != nil {
return nil, err
}
b.offscreen = true
b.offscrBuf.alpha = alpha
return b, nil
}
// SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized
func (b *XMobileBackend) SetBounds(x, y, w, h int) {
if !b.offscreen {
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)
if b == activeContext {
b.glctx.Viewport(0, 0, b.w, b.h)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
}
}
func (b *XMobileBackend) Size() (int, int) {
return b.w, b.h
}
func glError(b *XMobileBackend) error {
glErr := b.glctx.GetError()
if glErr != gl.NO_ERROR {
return fmt.Errorf("GL Error: %x", glErr)
}
return nil
}
var activeContext *XMobileBackend
func (b *XMobileBackend) activate() {
if activeContext != b {
activeContext = b
if b.offscreen {
b.glctx.Viewport(0, 0, b.w, b.h)
b.enableTextureRenderTarget(&b.offscrBuf)
b.offscrImg.w = b.offscrBuf.w
b.offscrImg.h = b.offscrBuf.h
b.offscrImg.tex = b.offscrBuf.tex
} else {
b.glctx.Viewport(b.x, b.y, b.w, b.h)
b.disableTextureRenderTarget()
}
}
loop:
for {
select {
case f := <-b.glChan:
f()
default:
break loop
}
}
}
func (b *XMobileBackend) DeleteOffscreen() {
if !b.offscreen {
return
}
b.glctx.DeleteTexture(b.offscrBuf.tex)
b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf)
b.glctx.DeleteRenderbuffer(b.offscrBuf.renderStencilBuf)
b.offscreen = false
b.activate()
}
type glColor struct {
r, g, b, a float64
}
func colorGoToGL(c color.RGBA) glColor {
var glc glColor
glc.r = float64(c.R) / 255
glc.g = float64(c.G) / 255
glc.b = float64(c.B) / 255
glc.a = float64(c.A) / 255
return glc
}
func (b *XMobileBackend) useShader(style *backendbase.FillStyle) (vertexLoc gl.Attrib) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.UseProgram(b.lgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
b.glctx.Uniform2f(b.lgr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.lgr.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.lgr.Dir, float32(dir[0]), float32(dir[1]))
b.glctx.Uniform1f(b.lgr.Len, float32(length))
b.glctx.Uniform1i(b.lgr.Gradient, 0)
b.glctx.Uniform1f(b.lgr.GlobalAlpha, float32(style.Color.A)/255)
return b.lgr.Vertex
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.UseProgram(b.rgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
b.glctx.Uniform2f(b.rgr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.rgr.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.rgr.To, float32(to[0]), float32(to[1]))
b.glctx.Uniform1f(b.rgr.RadFrom, float32(style.Gradient.RadFrom))
b.glctx.Uniform1f(b.rgr.RadTo, float32(style.Gradient.RadTo))
b.glctx.Uniform1i(b.rgr.Gradient, 0)
b.glctx.Uniform1f(b.rgr.GlobalAlpha, float32(style.Color.A)/255)
return b.rgr.Vertex
}
if img := style.Image; img != nil {
img := img.(*Image)
b.glctx.UseProgram(b.ipr.ID)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.Uniform2f(b.ipr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.ipr.ImageSize, float32(img.w), float32(img.h))
b.glctx.Uniform1i(b.ipr.Image, 0)
b.glctx.Uniform1f(b.ipr.GlobalAlpha, float32(style.Color.A)/255)
return b.ipr.Vertex
}
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
b.glctx.Uniform4f(b.sr.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
return b.sr.Vertex
}
func (b *XMobileBackend) useAlphaShader(style *backendbase.FillStyle, alphaTexSlot int) (vertexLoc, alphaTexCoordLoc gl.Attrib) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.UseProgram(b.lgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
b.glctx.Uniform2f(b.lgar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.lgar.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.lgar.Dir, float32(dir[0]), float32(dir[1]))
b.glctx.Uniform1f(b.lgar.Len, float32(length))
b.glctx.Uniform1i(b.lgar.Gradient, 0)
b.glctx.Uniform1i(b.lgar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.lgar.GlobalAlpha, float32(style.Color.A)/255)
return b.lgar.Vertex, b.lgar.AlphaTexCoord
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.UseProgram(b.rgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
b.glctx.Uniform2f(b.rgar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.rgar.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.rgar.To, float32(to[0]), float32(to[1]))
b.glctx.Uniform1f(b.rgar.RadFrom, float32(style.Gradient.RadFrom))
b.glctx.Uniform1f(b.rgar.RadTo, float32(style.Gradient.RadTo))
b.glctx.Uniform1i(b.rgar.Gradient, 0)
b.glctx.Uniform1i(b.rgar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.rgar.GlobalAlpha, float32(style.Color.A)/255)
return b.rgar.Vertex, b.rgar.AlphaTexCoord
}
if img := style.Image; img != nil {
img := img.(*Image)
b.glctx.UseProgram(b.ipar.ID)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.Uniform2f(b.ipar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.ipar.ImageSize, float32(img.w), float32(img.h))
b.glctx.Uniform1i(b.ipar.Image, 0)
b.glctx.Uniform1i(b.ipar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.ipar.GlobalAlpha, float32(style.Color.A)/255)
return b.ipar.Vertex, b.ipar.AlphaTexCoord
}
b.glctx.UseProgram(b.sar.ID)
b.glctx.Uniform2f(b.sar.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
b.glctx.Uniform4f(b.sar.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
b.glctx.Uniform1i(b.sar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.sar.GlobalAlpha, 1)
return b.sar.Vertex, b.sar.AlphaTexCoord
}
func (b *XMobileBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
if offscr.w != b.w || offscr.h != b.h {
if offscr.w != 0 && offscr.h != 0 {
b.glctx.DeleteTexture(offscr.tex)
b.glctx.DeleteFramebuffer(offscr.frameBuf)
b.glctx.DeleteRenderbuffer(offscr.renderStencilBuf)
}
offscr.w = b.w
offscr.h = b.h
b.glctx.ActiveTexture(gl.TEXTURE0)
offscr.tex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, offscr.tex)
// todo do non-power-of-two textures work everywhere?
if offscr.alpha {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, b.w, b.h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
} else {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, b.w, b.h, gl.RGB, gl.UNSIGNED_BYTE, nil)
}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
offscr.frameBuf = b.glctx.CreateFramebuffer()
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
offscr.renderStencilBuf = b.glctx.CreateRenderbuffer()
b.glctx.BindRenderbuffer(gl.RENDERBUFFER, offscr.renderStencilBuf)
b.glctx.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, b.w, b.h)
b.glctx.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf)
b.glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, offscr.tex, 0)
if err := b.glctx.CheckFramebufferStatus(gl.FRAMEBUFFER); err != gl.FRAMEBUFFER_COMPLETE {
// todo this should maybe not panic
panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err))
}
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
} else {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
}
}
func (b *XMobileBackend) disableTextureRenderTarget() {
if b.offscreen {
b.enableTextureRenderTarget(&b.offscrBuf)
} else {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
}
}
type vec [2]float64
func (v1 vec) sub(v2 vec) vec {
return vec{v1[0] - v2[0], v1[1] - v2[1]}
}
func (v vec) len() float64 {
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
}
func (v vec) scale(f float64) vec {
return vec{v[0] * f, v[1] * f}
}
func byteSlice(ptr unsafe.Pointer, size int) []byte {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = size
sh.Len = size
sh.Data = uintptr(ptr)
return buf
}

View file

@ -4,7 +4,7 @@ import (
"log"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/xmobile"
"github.com/tfriedel6/canvas/backend/xmobile"
"golang.org/x/exp/shiny/driver/gldriver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget"
@ -19,10 +19,13 @@ func main() {
gldriver.Main(func(s screen.Screen) {
glw := glwidget.NewGL(draw)
sheet = widget.NewSheet(glw)
canvas.LoadGL(glimplxmobile.New(glw.Ctx))
cv = canvas.New(0, 0, 600, 600)
backend, err := xmobilebackend.New(glw.Ctx, 0, 0, 600, 600)
if err != nil {
log.Fatal(err)
}
cv = canvas.New(backend)
err := widget.RunWindow(s, sheet, &widget.RunWindowOptions{
err = widget.RunWindow(s, sheet, &widget.RunWindowOptions{
NewWindowOptions: screen.NewWindowOptions{
Title: "Shiny Canvas Example",
Width: 600,