From 9c3cccabdd422309683ea1c467240bb40aa8531b Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Wed, 27 Feb 2019 13:05:23 +0100 Subject: [PATCH] offscreen backend now has its own type, can be used as image when drawing images --- backend/backendbase/base.go | 3 ++ backend/gogl/gogl.go | 101 ++++++++++++++++++++++-------------- backend/gogl/images.go | 8 +++ backend/xmobile/gen/gen.go | 4 +- backend/xmobile/images.go | 8 +++ backend/xmobile/xmobile.go | 101 ++++++++++++++++++++++-------------- canvas.go | 22 ++++---- images.go | 16 +++++- text.go | 7 ++- 9 files changed, 176 insertions(+), 94 deletions(-) diff --git a/backend/backendbase/base.go b/backend/backendbase/base.go index d077592..fcb4d11 100644 --- a/backend/backendbase/base.go +++ b/backend/backendbase/base.go @@ -26,6 +26,9 @@ type Backend interface { GetImageData(x, y, w, h int) *image.RGBA 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 diff --git a/backend/gogl/gogl.go b/backend/gogl/gogl.go index bf6bf99..350e40e 100644 --- a/backend/gogl/gogl.go +++ b/backend/gogl/gogl.go @@ -42,11 +42,10 @@ type GoGLBackend struct { ptsBuf []float32 - offscreen bool - offscrBuf offscreenBuffer - offscrImg Image - glChan chan func() + + activateFn func() + disableTextureRenderTarget func() } type offscreenBuffer struct { @@ -70,6 +69,7 @@ func New(x, y, w, h int) (*GoGLBackend, error) { fw: float64(w), fh: float64(h), ptsBuf: make([]float32, 0, 4096), + glChan: make(chan func()), } gl.GetError() // clear error state @@ -211,26 +211,54 @@ func New(x, y, w, h int) (*GoGLBackend, error) { 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 } -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) if err != nil { return nil, err } - b.offscreen = true - b.offscrBuf.alpha = alpha - return b, nil + bo := &GoGLBackendOffscreen{} + bo.offscrBuf.alpha = alpha + + 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 // usually be called for example when the window is resized func (b *GoGLBackend) SetBounds(x, y, w, h int) { - if !b.offscreen { - b.x, b.y = x, y - b.fx, b.fy = float64(x), float64(y) - } + 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 { @@ -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) { return b.w, b.h } @@ -256,39 +289,39 @@ var activeContext *GoGLBackend func (b *GoGLBackend) activate() { if activeContext != b { activeContext = b - if b.offscreen { - 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.activateFn() } + b.runGLQueue() +} -loop: +func (b *GoGLBackend) runGLQueue() { for { select { case f := <-b.glChan: f() default: - break loop + return } } } -func (b *GoGLBackend) DeleteOffscreen() { - if !b.offscreen { - return - } +func (b *GoGLBackendOffscreen) Delete() { gl.DeleteTextures(1, &b.offscrBuf.tex) gl.DeleteFramebuffers(1, &b.offscrBuf.frameBuf) 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 { @@ -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 func (v1 vec) sub(v2 vec) vec { diff --git a/backend/gogl/images.go b/backend/gogl/images.go index 3306e07..33beec6 100644 --- a/backend/gogl/images.go +++ b/backend/gogl/images.go @@ -17,6 +17,7 @@ type Image struct { tex uint32 deleted bool opaque bool + flip bool } 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 { return nil, err } + img.b = b runtime.SetFinalizer(img, func(img *Image) { if !img.deleted { @@ -207,6 +209,7 @@ func (img *Image) Replace(src image.Image) error { if err != nil { return err } + newImg.b = img.b *img = *newImg return nil } @@ -225,6 +228,11 @@ func (b *GoGLBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, sw /= float64(img.w) sh /= float64(img.h) + if img.flip { + sy += sh + sh = -sh + } + var buf [16]float32 data := buf[:0] for _, pt := range pts { diff --git a/backend/xmobile/gen/gen.go b/backend/xmobile/gen/gen.go index 7b08d87..2d54cc4 100644 --- a/backend/xmobile/gen/gen.go +++ b/backend/xmobile/gen/gen.go @@ -252,8 +252,8 @@ func rewriteMain(src string) string { "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 = strings.Replace(src, "func NewOffscreen(w, h int, alpha bool) (*XMobileBackendOffscreen, error)", + "func NewOffscreen(glctx gl.Context, w, h int, alpha bool) (*XMobileBackendOffscreen, error)", 1) src = rewriteCalls(src, "New", func(params []string) string { return "New(glctx, " + strings.Join(params, ", ") + ")" diff --git a/backend/xmobile/images.go b/backend/xmobile/images.go index c05d552..c88d3b2 100755 --- a/backend/xmobile/images.go +++ b/backend/xmobile/images.go @@ -17,6 +17,7 @@ type Image struct { tex gl.Texture deleted bool opaque bool + flip bool } 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 { return nil, err } + img.b = b runtime.SetFinalizer(img, func(img *Image) { if !img.deleted { @@ -209,6 +211,7 @@ func (img *Image) Replace(src image.Image) error { if err != nil { return err } + newImg.b = img.b *img = *newImg return nil } @@ -227,6 +230,11 @@ func (b *XMobileBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float6 sw /= float64(img.w) sh /= float64(img.h) + if img.flip { + sy += sh + sh = -sh + } + var buf [16]float32 data := buf[:0] for _, pt := range pts { diff --git a/backend/xmobile/xmobile.go b/backend/xmobile/xmobile.go index 2d1f515..d2010df 100755 --- a/backend/xmobile/xmobile.go +++ b/backend/xmobile/xmobile.go @@ -46,11 +46,10 @@ type XMobileBackend struct { ptsBuf []float32 - offscreen bool - offscrBuf offscreenBuffer - offscrImg Image - glChan chan func() + + activateFn func() + disableTextureRenderTarget func() } type offscreenBuffer struct { @@ -73,6 +72,7 @@ func New(glctx gl.Context, x, y, w, h int) (*XMobileBackend, error) { fw: float64(w), fh: float64(h), ptsBuf: make([]float32, 0, 4096), + glChan: make(chan func()), } 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.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 } -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) if err != nil { return nil, err } - b.offscreen = true - b.offscrBuf.alpha = alpha - return b, nil + bo := &XMobileBackendOffscreen{} + bo.offscrBuf.alpha = alpha + + 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 // 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.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 { @@ -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) { return b.w, b.h } @@ -259,39 +292,39 @@ 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() - } + b.activateFn() } + b.runGLQueue() +} -loop: +func (b *XMobileBackend) runGLQueue() { for { select { case f := <-b.glChan: f() default: - break loop + return } } } -func (b *XMobileBackend) DeleteOffscreen() { - if !b.offscreen { - return - } +func (b *XMobileBackendOffscreen) Delete() { b.glctx.DeleteTexture(b.offscrBuf.tex) b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf) 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 { @@ -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 func (v1 vec) sub(v2 vec) vec { diff --git a/canvas.go b/canvas.go index c6c1c45..e1cef59 100644 --- a/canvas.go +++ b/canvas.go @@ -20,9 +20,6 @@ import ( type Canvas struct { b backendbase.Backend - w, h int - fw, fh float64 - path Path2D convex bool rect bool @@ -138,8 +135,6 @@ func New(backend backendbase.Backend) *Canvas { stateStack: make([]drawState, 0, 20), images: make(map[interface{}]*Image), } - w, h := backend.Size() - cv.setBounds(w, h) cv.state.lineWidth = 1 cv.state.lineAlpha = 1 cv.state.miterLimitSqr = 100 @@ -150,19 +145,20 @@ func New(backend backendbase.Backend) *Canvas { return cv } -func (cv *Canvas) setBounds(w, h int) { - cv.w, cv.h = w, h - cv.fw, cv.fh = float64(w), float64(h) +// Width returns the internal width of the canvas +func (cv *Canvas) Width() int { + 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 -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 -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 { v, _ = v.mulMat(cv.state.transform) diff --git a/images.go b/images.go index 5980682..33398b9 100644 --- a/images.go +++ b/images.go @@ -42,7 +42,8 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) { return nil, err } case *Canvas: - src = cv.GetImageData(0, 0, cv.Width(), cv.Height()) + w, h := cv.b.Size() + src = cv.GetImageData(0, 0, w, h) default: return nil, errors.New("Unsupported source type") } @@ -82,6 +83,19 @@ func (cv *Canvas) getImage(src interface{}) *Image { } cv.images[v] = 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) cv.images[src] = nil diff --git a/text.go b/text.go index 730ddf8..26cabdd 100644 --- a/text.go +++ b/text.go @@ -96,14 +96,17 @@ func (cv *Canvas) FillText(str string, x, y float64) { 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}) 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}) 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) && (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[1] < cv.fh || p1[1] < cv.fh || p2[1] < cv.fh || p3[1] < cv.fh) + (p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) && + (p0[1] < fh || p1[1] < fh || p2[1] < fh || p3[1] < fh) if !curInside && inside { curInside = true