go.mobile/sprite: replace Sheet/Texture with Texture/SubTex

LGTM=nigeltao
R=nigeltao
CC=golang-codereviews
https://golang.org/cl/172050044
This commit is contained in:
David Crawshaw 2014-11-11 20:46:47 -05:00
parent 50b4d219bf
commit 10b4dcd412
5 changed files with 99 additions and 116 deletions

View File

@ -21,29 +21,35 @@ type node struct {
relTransform f32.Affine relTransform f32.Affine
} }
type sheet struct { type texture struct {
glImage *glutil.Image glImage *glutil.Image
b image.Rectangle b image.Rectangle
} }
type texture struct { func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() }
sheet sprite.Sheet
b image.Rectangle func (t *texture) Download(r image.Rectangle, dst draw.Image) {
panic("TODO")
}
func (t *texture) Upload(r image.Rectangle, src image.Image) {
draw.Draw(t.glImage.RGBA, r, src, src.Bounds().Min, draw.Src)
t.glImage.Upload()
}
func (t *texture) Unload() {
panic("TODO")
} }
func Engine() sprite.Engine { func Engine() sprite.Engine {
return &engine{ return &engine{
nodes: []*node{nil}, nodes: []*node{nil},
sheets: []sheet{{}},
textures: []texture{{}},
} }
} }
type engine struct { type engine struct {
glImages map[sprite.Texture]*glutil.Image glImages map[sprite.Texture]*glutil.Image
nodes []*node nodes []*node
sheets []sheet
textures []texture
absTransforms []f32.Affine absTransforms []f32.Affine
} }
@ -63,41 +69,20 @@ func (e *engine) Unregister(n *sprite.Node) {
panic("todo") panic("todo")
} }
func (e *engine) LoadSheet(a image.Image) (sprite.Sheet, error) { func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) {
b := a.Bounds() b := src.Bounds()
glImage := glutil.NewImage(b.Dx(), b.Dy()) t := &texture{glutil.NewImage(b.Dx(), b.Dy()), b}
draw.Draw(glImage.RGBA, glImage.Bounds(), a, b.Min, draw.Src) t.Upload(b, src)
glImage.Upload()
// TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more. // TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more.
e.sheets = append(e.sheets, sheet{ return t, nil
glImage: glImage,
b: b,
})
return sprite.Sheet(len(e.sheets) - 1), nil
} }
func (e *engine) LoadTexture(s sprite.Sheet, bounds image.Rectangle) (sprite.Texture, error) { func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
e.textures = append(e.textures, texture{
sheet: s,
b: bounds,
})
return sprite.Texture(len(e.textures) - 1), nil
}
func (e *engine) UnloadSheet(s sprite.Sheet) error {
panic("todo")
}
func (e *engine) UnloadTexture(x sprite.Texture) error {
panic("todo")
}
func (e *engine) SetTexture(n *sprite.Node, t clock.Time, x sprite.Texture) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
n.EngineFields.Texture = x n.EngineFields.SubTex = x
} }
func (e *engine) SetTransform(n *sprite.Node, t clock.Time, m f32.Affine) { func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
e.nodes[n.EngineFields.Index].relTransform = m e.nodes[n.EngineFields.Index].relTransform = m
} }
@ -125,24 +110,22 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
e.absTransforms = append(e.absTransforms, m) e.absTransforms = append(e.absTransforms, m)
if x := e.textures[n.EngineFields.Texture]; x.sheet != 0 { if x := n.EngineFields.SubTex; x.T != nil {
if 0 < x.sheet && int(x.sheet) < len(e.sheets) { x.T.(*texture).glImage.Draw(
e.sheets[x.sheet].glImage.Draw( geom.Point{
geom.Point{ geom.Pt(m[0][2]),
geom.Pt(m[0][2]), geom.Pt(m[1][2]),
geom.Pt(m[1][2]), },
}, geom.Point{
geom.Point{ geom.Pt(m[0][2] + m[0][0]),
geom.Pt(m[0][2] + m[0][0]), geom.Pt(m[1][2] + m[1][0]),
geom.Pt(m[1][2] + m[1][0]), },
}, geom.Point{
geom.Point{ geom.Pt(m[0][2] + m[0][1]),
geom.Pt(m[0][2] + m[0][1]), geom.Pt(m[1][2] + m[1][1]),
geom.Pt(m[1][2] + m[1][1]), },
}, x.R,
x.b, )
)
}
} }
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {

View File

@ -25,12 +25,11 @@ import (
// //
// will produce a dst that is half the size of src. To perform a // will produce a dst that is half the size of src. To perform a
// traditional affine transform, use the inverse of the affine matrix. // traditional affine transform, use the inverse of the affine matrix.
func affine(dst *image.RGBA, src image.Image, a *f32.Affine, mask image.Image, op draw.Op) { func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
srcb := src.Bounds()
b := dst.Bounds() b := dst.Bounds()
var maskb image.Rectangle var maskb image.Rectangle
if mask != nil { if mask != nil {
maskb = mask.Bounds() maskb = mask.Bounds().Add(srcb.Min)
} }
for y := b.Min.Y; y < b.Max.Y; y++ { for y := b.Min.Y; y < b.Max.Y; y++ {

View File

@ -48,7 +48,6 @@ func TestAffine(t *testing.T) {
b := src.Bounds() b := src.Bounds()
b.Min.X += 10 b.Min.X += 10
b.Max.Y /= 2 b.Max.Y /= 2
src = src.SubImage(b).(*image.RGBA)
var a f32.Affine var a f32.Affine
a.Identity() a.Identity()
@ -56,10 +55,10 @@ func TestAffine(t *testing.T) {
a.Translate(&a, 0, 24) a.Translate(&a, 0, 24)
a.Rotate(&a, float32(math.Asin(12./20))) a.Rotate(&a, float32(math.Asin(12./20)))
// See commentary in the render method defined in portable.go. // See commentary in the render method defined in portable.go.
a.Scale(&a, 40/float32(src.Rect.Dx()), 20/float32(src.Rect.Dy())) a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
a.Inverse(&a) a.Inverse(&a)
affine(got, src, &a, nil, draw.Over) affine(got, src, b, nil, &a, draw.Over)
ptTopLeft := geom.Point{0, 24} ptTopLeft := geom.Point{0, 24}
ptBottomRight := geom.Point{12 + 32, 16} ptBottomRight := geom.Point{12 + 32, 16}
@ -120,7 +119,7 @@ func TestAffineMask(t *testing.T) {
a := new(f32.Affine) a := new(f32.Affine)
a.Identity() a.Identity()
got := image.NewRGBA(b) got := image.NewRGBA(b)
affine(got, src, a, mask, draw.Src) affine(got, src, b, mask, a, draw.Src)
if !imageEq(got, want) { if !imageEq(got, want) {
gotPath, err := writeTempPNG("testpattern-mask-got", got) gotPath, err := writeTempPNG("testpattern-mask-got", got)

View File

@ -10,7 +10,6 @@
package portable package portable
import ( import (
"fmt"
"image" "image"
"image/draw" "image/draw"
@ -23,10 +22,8 @@ import (
// Engine builds a sprite Engine that renders onto dst. // Engine builds a sprite Engine that renders onto dst.
func Engine(dst *image.RGBA) sprite.Engine { func Engine(dst *image.RGBA) sprite.Engine {
return &engine{ return &engine{
dst: dst, dst: dst,
nodes: []*node{nil}, nodes: []*node{nil},
sheets: []*image.RGBA{nil},
textures: []*image.RGBA{nil},
} }
} }
@ -35,11 +32,28 @@ type node struct {
relTransform f32.Affine relTransform f32.Affine
} }
type texture struct {
m *image.RGBA
}
func (t *texture) Bounds() (w, h int) {
b := t.m.Bounds()
return b.Dx(), b.Dy()
}
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src)
}
func (t *texture) Upload(r image.Rectangle, src image.Image) {
draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src)
}
func (t *texture) Unload() { panic("TODO") }
type engine struct { type engine struct {
dst *image.RGBA dst *image.RGBA
nodes []*node nodes []*node
sheets []*image.RGBA
textures []*image.RGBA
absTransforms []f32.Affine absTransforms []f32.Affine
} }
@ -59,39 +73,21 @@ func (e *engine) Unregister(n *sprite.Node) {
panic("todo") panic("todo")
} }
func (e *engine) LoadSheet(a image.Image) (sprite.Sheet, error) { func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) {
rgba, ok := a.(*image.RGBA) b := m.Bounds()
if !ok { w, h := b.Dx(), b.Dy()
b := a.Bounds()
rgba = image.NewRGBA(b) t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))}
draw.Draw(rgba, b, a, b.Min, draw.Src) t.Upload(b, m)
} return t, nil
e.sheets = append(e.sheets, rgba)
return sprite.Sheet(len(e.sheets) - 1), nil
} }
func (e *engine) LoadTexture(s sprite.Sheet, bounds image.Rectangle) (sprite.Texture, error) { func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
if s < 0 || len(e.sheets) <= int(s) {
return 0, fmt.Errorf("portable: LoadTexture: sheet %d is out of bounds", s)
}
e.textures = append(e.textures, e.sheets[s].SubImage(bounds).(*image.RGBA))
return sprite.Texture(len(e.textures) - 1), nil
}
func (e *engine) UnloadSheet(s sprite.Sheet) error {
panic("todo")
}
func (e *engine) UnloadTexture(x sprite.Texture) error {
panic("todo")
}
func (e *engine) SetTexture(n *sprite.Node, t clock.Time, x sprite.Texture) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
n.EngineFields.Texture = x n.EngineFields.SubTex = x
} }
func (e *engine) SetTransform(n *sprite.Node, t clock.Time, m f32.Affine) { func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
e.nodes[n.EngineFields.Index].relTransform = m e.nodes[n.EngineFields.Index].relTransform = m
} }
@ -122,9 +118,7 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
e.absTransforms = append(e.absTransforms, m) e.absTransforms = append(e.absTransforms, m)
if x := e.textures[n.EngineFields.Texture]; x != nil { if x := n.EngineFields.SubTex; x.T != nil {
b := x.Bounds()
// Affine transforms work in geom.Pt, which is entirely // Affine transforms work in geom.Pt, which is entirely
// independent of the number of pixels in a texture. A texture // independent of the number of pixels in a texture. A texture
// of any image.Rectangle bounds rendered with // of any image.Rectangle bounds rendered with
@ -135,10 +129,12 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
// by the pixel width and height, reducing the texture to // by the pixel width and height, reducing the texture to
// (1px, 1px) of the destination image. Multiplying by // (1px, 1px) of the destination image. Multiplying by
// geom.PixelsPerPt, done in Render above, makes it (1pt, 1pt). // geom.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
m.Scale(&m, 1/float32(b.Dx()), 1/float32(b.Dy())) dx, dy := x.R.Dx(), x.R.Dy()
if dx > 0 && dy > 0 {
m.Inverse(&m) // See the documentation on the affine function. m.Scale(&m, 1/float32(dx), 1/float32(dy))
affine(e.dst, x, &m, nil, draw.Over) m.Inverse(&m) // See the documentation on the affine function.
affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
}
} }
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {

View File

@ -26,6 +26,7 @@ package sprite
import ( import (
"image" "image"
"image/draw"
"golang.org/x/mobile/f32" "golang.org/x/mobile/f32"
"golang.org/x/mobile/sprite/clock" "golang.org/x/mobile/sprite/clock"
@ -35,21 +36,26 @@ type Arranger interface {
Arrange(e Engine, n *Node, t clock.Time) Arrange(e Engine, n *Node, t clock.Time)
} }
type Sheet int32 type Texture interface {
Bounds() (w, h int)
Download(r image.Rectangle, dst draw.Image)
Upload(r image.Rectangle, src image.Image)
Unload()
}
type Texture int32 type SubTex struct {
T Texture
R image.Rectangle
}
type Engine interface { type Engine interface {
Register(n *Node) Register(n *Node)
Unregister(n *Node) Unregister(n *Node)
LoadSheet(a image.Image) (Sheet, error) LoadTexture(a image.Image) (Texture, error)
LoadTexture(s Sheet, bounds image.Rectangle) (Texture, error)
UnloadSheet(s Sheet) error
UnloadTexture(x Texture) error
SetTexture(n *Node, t clock.Time, x Texture) SetSubTex(n *Node, x SubTex)
SetTransform(n *Node, t clock.Time, m f32.Affine) // sets transform relative to parent. SetTransform(n *Node, m f32.Affine) // sets transform relative to parent.
Render(scene *Node, t clock.Time) Render(scene *Node, t clock.Time)
} }
@ -65,9 +71,9 @@ type Node struct {
// in other packages. // in other packages.
EngineFields struct { EngineFields struct {
// TODO: separate TexDirty and TransformDirty bits? // TODO: separate TexDirty and TransformDirty bits?
Dirty bool Dirty bool
Index int32 Index int32
Texture Texture SubTex SubTex
} }
} }