added an xmobile backend that can be generated from the gogl backend
This commit is contained in:
parent
93c75a9b61
commit
77a9d14867
12 changed files with 2404 additions and 9 deletions
|
@ -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
|
||||
|
|
|
@ -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
66
backend/xmobile/clip.go
Executable 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
305
backend/xmobile/fill.go
Executable 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
401
backend/xmobile/gen/gen.go
Normal 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
116
backend/xmobile/gradients.go
Executable 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
94
backend/xmobile/imagedata.go
Executable 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
263
backend/xmobile/images.go
Executable 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
180
backend/xmobile/shader.go
Executable 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
468
backend/xmobile/shaders.go
Executable 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
498
backend/xmobile/xmobile.go
Executable 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
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue