offscreen backend now has its own type, can be used as image when drawing images

This commit is contained in:
Thomas Friedel 2019-02-27 13:05:23 +01:00
parent 122488e64c
commit 9c3cccabdd
9 changed files with 176 additions and 94 deletions

View file

@ -26,6 +26,9 @@ type Backend interface {
GetImageData(x, y, w, h int) *image.RGBA GetImageData(x, y, w, h int) *image.RGBA
PutImageData(img *image.RGBA, x, y int) PutImageData(img *image.RGBA, x, y int)
CanUseAsImage(b Backend) bool
AsImage() Image // can return nil if not supported
} }
// FillStyle is the color and other details on how to fill // FillStyle is the color and other details on how to fill

View file

@ -42,11 +42,10 @@ type GoGLBackend struct {
ptsBuf []float32 ptsBuf []float32
offscreen bool
offscrBuf offscreenBuffer
offscrImg Image
glChan chan func() glChan chan func()
activateFn func()
disableTextureRenderTarget func()
} }
type offscreenBuffer struct { type offscreenBuffer struct {
@ -70,6 +69,7 @@ func New(x, y, w, h int) (*GoGLBackend, error) {
fw: float64(w), fw: float64(w),
fh: float64(h), fh: float64(h),
ptsBuf: make([]float32, 0, 4096), ptsBuf: make([]float32, 0, 4096),
glChan: make(chan func()),
} }
gl.GetError() // clear error state gl.GetError() // clear error state
@ -211,26 +211,54 @@ func New(x, y, w, h int) (*GoGLBackend, error) {
gl.Disable(gl.SCISSOR_TEST) gl.Disable(gl.SCISSOR_TEST)
b.activateFn = func() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h))
}
b.disableTextureRenderTarget = func() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
}
return b, nil return b, nil
} }
func NewOffscreen(w, h int, alpha bool) (*GoGLBackend, error) { type GoGLBackendOffscreen struct {
GoGLBackend
offscrBuf offscreenBuffer
offscrImg Image
}
func NewOffscreen(w, h int, alpha bool) (*GoGLBackendOffscreen, error) {
b, err := New(0, 0, w, h) b, err := New(0, 0, w, h)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b.offscreen = true bo := &GoGLBackendOffscreen{}
b.offscrBuf.alpha = alpha bo.offscrBuf.alpha = alpha
return b, nil
b.activateFn = func() {
b.enableTextureRenderTarget(&bo.offscrBuf)
gl.Viewport(0, 0, int32(b.w), int32(b.h))
bo.offscrImg.w = bo.offscrBuf.w
bo.offscrImg.h = bo.offscrBuf.h
bo.offscrImg.tex = bo.offscrBuf.tex
bo.offscrImg.flip = true
}
b.disableTextureRenderTarget = func() {
b.enableTextureRenderTarget(&bo.offscrBuf)
}
bo.GoGLBackend = *b
return bo, nil
} }
// SetBounds updates the bounds of the canvas. This would // SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized // usually be called for example when the window is resized
func (b *GoGLBackend) SetBounds(x, y, w, h int) { func (b *GoGLBackend) SetBounds(x, y, w, h int) {
if !b.offscreen {
b.x, b.y = x, y b.x, b.y = x, y
b.fx, b.fy = float64(x), float64(y) b.fx, b.fy = float64(x), float64(y)
}
b.w, b.h = w, h b.w, b.h = w, h
b.fw, b.fh = float64(w), float64(h) b.fw, b.fh = float64(w), float64(h)
if b == activeContext { if b == activeContext {
@ -239,6 +267,11 @@ func (b *GoGLBackend) SetBounds(x, y, w, h int) {
} }
} }
// SetBounds updates the size of the offscreen texture
func (b *GoGLBackendOffscreen) SetBounds(w, h int) {
b.GoGLBackend.SetBounds(0, 0, w, h)
}
func (b *GoGLBackend) Size() (int, int) { func (b *GoGLBackend) Size() (int, int) {
return b.w, b.h return b.w, b.h
} }
@ -256,39 +289,39 @@ var activeContext *GoGLBackend
func (b *GoGLBackend) activate() { func (b *GoGLBackend) activate() {
if activeContext != b { if activeContext != b {
activeContext = b activeContext = b
if b.offscreen { b.activateFn()
gl.Viewport(0, 0, int32(b.w), int32(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 {
gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h))
b.disableTextureRenderTarget()
}
} }
b.runGLQueue()
}
loop: func (b *GoGLBackend) runGLQueue() {
for { for {
select { select {
case f := <-b.glChan: case f := <-b.glChan:
f() f()
default: default:
break loop return
} }
} }
} }
func (b *GoGLBackend) DeleteOffscreen() { func (b *GoGLBackendOffscreen) Delete() {
if !b.offscreen {
return
}
gl.DeleteTextures(1, &b.offscrBuf.tex) gl.DeleteTextures(1, &b.offscrBuf.tex)
gl.DeleteFramebuffers(1, &b.offscrBuf.frameBuf) gl.DeleteFramebuffers(1, &b.offscrBuf.frameBuf)
gl.DeleteRenderbuffers(1, &b.offscrBuf.renderStencilBuf) gl.DeleteRenderbuffers(1, &b.offscrBuf.renderStencilBuf)
b.offscreen = false }
b.activate() func (b *GoGLBackend) CanUseAsImage(b2 backendbase.Backend) bool {
_, ok := b2.(*GoGLBackendOffscreen)
return ok
}
func (b *GoGLBackend) AsImage() backendbase.Image {
return nil
}
func (b *GoGLBackendOffscreen) AsImage() backendbase.Image {
return &b.offscrImg
} }
type glColor struct { type glColor struct {
@ -463,14 +496,6 @@ func (b *GoGLBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
} }
} }
func (b *GoGLBackend) disableTextureRenderTarget() {
if b.offscreen {
b.enableTextureRenderTarget(&b.offscrBuf)
} else {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
}
}
type vec [2]float64 type vec [2]float64
func (v1 vec) sub(v2 vec) vec { func (v1 vec) sub(v2 vec) vec {

View file

@ -17,6 +17,7 @@ type Image struct {
tex uint32 tex uint32
deleted bool deleted bool
opaque bool opaque bool
flip bool
} }
func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) { func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) {
@ -34,6 +35,7 @@ func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
img.b = b
runtime.SetFinalizer(img, func(img *Image) { runtime.SetFinalizer(img, func(img *Image) {
if !img.deleted { if !img.deleted {
@ -207,6 +209,7 @@ func (img *Image) Replace(src image.Image) error {
if err != nil { if err != nil {
return err return err
} }
newImg.b = img.b
*img = *newImg *img = *newImg
return nil return nil
} }
@ -225,6 +228,11 @@ func (b *GoGLBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64,
sw /= float64(img.w) sw /= float64(img.w)
sh /= float64(img.h) sh /= float64(img.h)
if img.flip {
sy += sh
sh = -sh
}
var buf [16]float32 var buf [16]float32
data := buf[:0] data := buf[:0]
for _, pt := range pts { for _, pt := range pts {

View file

@ -252,8 +252,8 @@ func rewriteMain(src string) string {
"type XMobileBackend struct {\n\tglctx gl.Context\n\n", 1) "type XMobileBackend struct {\n\tglctx gl.Context\n\n", 1)
src = strings.Replace(src, "func New(x, y, w, h int) (*XMobileBackend, error) {", 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) "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)", src = strings.Replace(src, "func NewOffscreen(w, h int, alpha bool) (*XMobileBackendOffscreen, error)",
"func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackend, error)", 1) "func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackendOffscreen, error)", 1)
src = rewriteCalls(src, "New", func(params []string) string { src = rewriteCalls(src, "New", func(params []string) string {
return "New(glctx, " + strings.Join(params, ", ") + ")" return "New(glctx, " + strings.Join(params, ", ") + ")"

View file

@ -17,6 +17,7 @@ type Image struct {
tex gl.Texture tex gl.Texture
deleted bool deleted bool
opaque bool opaque bool
flip bool
} }
func (b *XMobileBackend) LoadImage(src image.Image) (backendbase.Image, error) { func (b *XMobileBackend) LoadImage(src image.Image) (backendbase.Image, error) {
@ -34,6 +35,7 @@ func (b *XMobileBackend) LoadImage(src image.Image) (backendbase.Image, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
img.b = b
runtime.SetFinalizer(img, func(img *Image) { runtime.SetFinalizer(img, func(img *Image) {
if !img.deleted { if !img.deleted {
@ -209,6 +211,7 @@ func (img *Image) Replace(src image.Image) error {
if err != nil { if err != nil {
return err return err
} }
newImg.b = img.b
*img = *newImg *img = *newImg
return nil return nil
} }
@ -227,6 +230,11 @@ func (b *XMobileBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float6
sw /= float64(img.w) sw /= float64(img.w)
sh /= float64(img.h) sh /= float64(img.h)
if img.flip {
sy += sh
sh = -sh
}
var buf [16]float32 var buf [16]float32
data := buf[:0] data := buf[:0]
for _, pt := range pts { for _, pt := range pts {

View file

@ -46,11 +46,10 @@ type XMobileBackend struct {
ptsBuf []float32 ptsBuf []float32
offscreen bool
offscrBuf offscreenBuffer
offscrImg Image
glChan chan func() glChan chan func()
activateFn func()
disableTextureRenderTarget func()
} }
type offscreenBuffer struct { type offscreenBuffer struct {
@ -73,6 +72,7 @@ func New(glctx gl.Context, x, y, w, h int) (*XMobileBackend, error) {
fw: float64(w), fw: float64(w),
fh: float64(h), fh: float64(h),
ptsBuf: make([]float32, 0, 4096), ptsBuf: make([]float32, 0, 4096),
glChan: make(chan func()),
} }
b.glctx.GetError() // clear error state b.glctx.GetError() // clear error state
@ -214,26 +214,54 @@ func New(glctx gl.Context, x, y, w, h int) (*XMobileBackend, error) {
b.glctx.Disable(gl.SCISSOR_TEST) b.glctx.Disable(gl.SCISSOR_TEST)
b.activateFn = func() {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
b.glctx.Viewport(b.x, b.y, b.w, b.h)
}
b.disableTextureRenderTarget = func() {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
}
return b, nil return b, nil
} }
func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackend, error) { type XMobileBackendOffscreen struct {
XMobileBackend
offscrBuf offscreenBuffer
offscrImg Image
}
func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackendOffscreen, error) {
b, err := New(glctx, 0, 0, w, h) b, err := New(glctx, 0, 0, w, h)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b.offscreen = true bo := &XMobileBackendOffscreen{}
b.offscrBuf.alpha = alpha bo.offscrBuf.alpha = alpha
return b, nil
b.activateFn = func() {
b.enableTextureRenderTarget(&bo.offscrBuf)
b.glctx.Viewport(0, 0, b.w, b.h)
bo.offscrImg.w = bo.offscrBuf.w
bo.offscrImg.h = bo.offscrBuf.h
bo.offscrImg.tex = bo.offscrBuf.tex
bo.offscrImg.flip = true
}
b.disableTextureRenderTarget = func() {
b.enableTextureRenderTarget(&bo.offscrBuf)
}
bo.XMobileBackend = *b
return bo, nil
} }
// SetBounds updates the bounds of the canvas. This would // SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized // usually be called for example when the window is resized
func (b *XMobileBackend) SetBounds(x, y, w, h int) { func (b *XMobileBackend) SetBounds(x, y, w, h int) {
if !b.offscreen {
b.x, b.y = x, y b.x, b.y = x, y
b.fx, b.fy = float64(x), float64(y) b.fx, b.fy = float64(x), float64(y)
}
b.w, b.h = w, h b.w, b.h = w, h
b.fw, b.fh = float64(w), float64(h) b.fw, b.fh = float64(w), float64(h)
if b == activeContext { if b == activeContext {
@ -242,6 +270,11 @@ func (b *XMobileBackend) SetBounds(x, y, w, h int) {
} }
} }
// SetBounds updates the size of the offscreen texture
func (b *XMobileBackendOffscreen) SetBounds(w, h int) {
b.XMobileBackend.SetBounds(0, 0, w, h)
}
func (b *XMobileBackend) Size() (int, int) { func (b *XMobileBackend) Size() (int, int) {
return b.w, b.h return b.w, b.h
} }
@ -259,39 +292,39 @@ var activeContext *XMobileBackend
func (b *XMobileBackend) activate() { func (b *XMobileBackend) activate() {
if activeContext != b { if activeContext != b {
activeContext = b activeContext = b
if b.offscreen { b.activateFn()
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()
}
} }
b.runGLQueue()
}
loop: func (b *XMobileBackend) runGLQueue() {
for { for {
select { select {
case f := <-b.glChan: case f := <-b.glChan:
f() f()
default: default:
break loop return
} }
} }
} }
func (b *XMobileBackend) DeleteOffscreen() { func (b *XMobileBackendOffscreen) Delete() {
if !b.offscreen {
return
}
b.glctx.DeleteTexture(b.offscrBuf.tex) b.glctx.DeleteTexture(b.offscrBuf.tex)
b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf) b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf)
b.glctx.DeleteRenderbuffer(b.offscrBuf.renderStencilBuf) b.glctx.DeleteRenderbuffer(b.offscrBuf.renderStencilBuf)
b.offscreen = false }
b.activate() func (b *XMobileBackend) CanUseAsImage(b2 backendbase.Backend) bool {
_, ok := b2.(*XMobileBackendOffscreen)
return ok
}
func (b *XMobileBackend) AsImage() backendbase.Image {
return nil
}
func (b *XMobileBackendOffscreen) AsImage() backendbase.Image {
return &b.offscrImg
} }
type glColor struct { type glColor struct {
@ -466,14 +499,6 @@ func (b *XMobileBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
} }
} }
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 type vec [2]float64
func (v1 vec) sub(v2 vec) vec { func (v1 vec) sub(v2 vec) vec {

View file

@ -20,9 +20,6 @@ import (
type Canvas struct { type Canvas struct {
b backendbase.Backend b backendbase.Backend
w, h int
fw, fh float64
path Path2D path Path2D
convex bool convex bool
rect bool rect bool
@ -138,8 +135,6 @@ func New(backend backendbase.Backend) *Canvas {
stateStack: make([]drawState, 0, 20), stateStack: make([]drawState, 0, 20),
images: make(map[interface{}]*Image), images: make(map[interface{}]*Image),
} }
w, h := backend.Size()
cv.setBounds(w, h)
cv.state.lineWidth = 1 cv.state.lineWidth = 1
cv.state.lineAlpha = 1 cv.state.lineAlpha = 1
cv.state.miterLimitSqr = 100 cv.state.miterLimitSqr = 100
@ -150,19 +145,20 @@ func New(backend backendbase.Backend) *Canvas {
return cv return cv
} }
func (cv *Canvas) setBounds(w, h int) { // Width returns the internal width of the canvas
cv.w, cv.h = w, h func (cv *Canvas) Width() int {
cv.fw, cv.fh = float64(w), float64(h) w, _ := cv.b.Size()
return w
} }
// Width returns the internal width of the canvas
func (cv *Canvas) Width() int { return cv.w }
// Height returns the internal height of the canvas // Height returns the internal height of the canvas
func (cv *Canvas) Height() int { return cv.h } func (cv *Canvas) Height() int {
_, h := cv.b.Size()
return h
}
// Size returns the internal width and height of the canvas // Size returns the internal width and height of the canvas
func (cv *Canvas) Size() (int, int) { return cv.w, cv.h } func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
func (cv *Canvas) tf(v vec) vec { func (cv *Canvas) tf(v vec) vec {
v, _ = v.mulMat(cv.state.transform) v, _ = v.mulMat(cv.state.transform)

View file

@ -42,7 +42,8 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
return nil, err return nil, err
} }
case *Canvas: case *Canvas:
src = cv.GetImageData(0, 0, cv.Width(), cv.Height()) w, h := cv.b.Size()
src = cv.GetImageData(0, 0, w, h)
default: default:
return nil, errors.New("Unsupported source type") return nil, errors.New("Unsupported source type")
} }
@ -82,6 +83,19 @@ func (cv *Canvas) getImage(src interface{}) *Image {
} }
cv.images[v] = img cv.images[v] = img
return img return img
case *Canvas:
if !cv.b.CanUseAsImage(v.b) {
w, h := v.Size()
return cv.getImage(v.GetImageData(0, 0, w, h))
}
bimg := v.b.AsImage()
if bimg == nil {
w, h := v.Size()
return cv.getImage(v.GetImageData(0, 0, w, h))
}
img := &Image{cv: cv, img: bimg}
cv.images[v] = img
return img
} }
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src) fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
cv.images[src] = nil cv.images[src] = nil

View file

@ -96,14 +96,17 @@ func (cv *Canvas) FillText(str string, x, y float64) {
curX += float64(kern) / 64 curX += float64(kern) / 64
} }
w, h := cv.b.Size()
fw, fh := float64(w), float64(h)
p0 := cv.tf(vec{float64(bounds.Min.X) + curX, float64(bounds.Min.Y) + y}) p0 := cv.tf(vec{float64(bounds.Min.X) + curX, float64(bounds.Min.Y) + y})
p1 := cv.tf(vec{float64(bounds.Min.X) + curX, float64(bounds.Max.Y) + y}) p1 := cv.tf(vec{float64(bounds.Min.X) + curX, float64(bounds.Max.Y) + y})
p2 := cv.tf(vec{float64(bounds.Max.X) + curX, float64(bounds.Max.Y) + y}) p2 := cv.tf(vec{float64(bounds.Max.X) + curX, float64(bounds.Max.Y) + y})
p3 := cv.tf(vec{float64(bounds.Max.X) + curX, float64(bounds.Min.Y) + y}) p3 := cv.tf(vec{float64(bounds.Max.X) + curX, float64(bounds.Min.Y) + y})
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) && inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) && (p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
(p0[0] < cv.fw || p1[0] < cv.fw || p2[0] < cv.fw || p3[0] < cv.fw) && (p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
(p0[1] < cv.fh || p1[1] < cv.fh || p2[1] < cv.fh || p3[1] < cv.fh) (p0[1] < fh || p1[1] < fh || p2[1] < fh || p3[1] < fh)
if !curInside && inside { if !curInside && inside {
curInside = true curInside = true