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
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

View file

@ -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.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 {

View file

@ -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 {

View file

@ -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, ", ") + ")"

View file

@ -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 {

View file

@ -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.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 {

View file

@ -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)

View file

@ -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

View file

@ -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