| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build darwin linux windows |
| |
| // Package glsprite implements a sprite Engine using OpenGL ES 2. |
| // |
| // Each sprite.Texture is loaded as a GL texture object and drawn |
| // to the screen via an affine transform done in a simple shader. |
| package glsprite // import "golang.org/x/mobile/exp/sprite/glsprite" |
| |
| import ( |
| "image" |
| "image/draw" |
| |
| "golang.org/x/mobile/event/size" |
| "golang.org/x/mobile/exp/f32" |
| "golang.org/x/mobile/exp/gl/glutil" |
| "golang.org/x/mobile/exp/sprite" |
| "golang.org/x/mobile/exp/sprite/clock" |
| "golang.org/x/mobile/geom" |
| ) |
| |
| type node struct { |
| // TODO: move this into package sprite as Node.EngineFields.RelTransform?? |
| relTransform f32.Affine |
| } |
| |
| type texture struct { |
| e *engine |
| glImage *glutil.Image |
| b image.Rectangle |
| } |
| |
| func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() } |
| |
| 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) Release() { |
| t.glImage.Release() |
| delete(t.e.textures, t) |
| } |
| |
| // Engine creates an OpenGL-based sprite.Engine. |
| func Engine(images *glutil.Images) sprite.Engine { |
| return &engine{ |
| nodes: []*node{nil}, |
| images: images, |
| textures: make(map[*texture]struct{}), |
| } |
| } |
| |
| type engine struct { |
| images *glutil.Images |
| textures map[*texture]struct{} |
| nodes []*node |
| |
| absTransforms []f32.Affine |
| } |
| |
| func (e *engine) Register(n *sprite.Node) { |
| if n.EngineFields.Index != 0 { |
| panic("glsprite: sprite.Node already registered") |
| } |
| o := &node{} |
| o.relTransform.Identity() |
| |
| e.nodes = append(e.nodes, o) |
| n.EngineFields.Index = int32(len(e.nodes) - 1) |
| } |
| |
| func (e *engine) Unregister(n *sprite.Node) { |
| panic("todo") |
| } |
| |
| func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) { |
| b := src.Bounds() |
| t := &texture{ |
| e: e, |
| glImage: e.images.NewImage(b.Dx(), b.Dy()), |
| b: b, |
| } |
| e.textures[t] = struct{}{} |
| t.Upload(b, src) |
| // TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more. |
| return t, nil |
| } |
| |
| func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) { |
| n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree? |
| n.EngineFields.SubTex = x |
| } |
| |
| 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? |
| e.nodes[n.EngineFields.Index].relTransform = m |
| } |
| |
| func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) { |
| e.absTransforms = append(e.absTransforms[:0], f32.Affine{ |
| {1, 0, 0}, |
| {0, 1, 0}, |
| }) |
| e.render(scene, t, sz) |
| } |
| |
| func (e *engine) render(n *sprite.Node, t clock.Time, sz size.Event) { |
| if n.EngineFields.Index == 0 { |
| panic("glsprite: sprite.Node not registered") |
| } |
| if n.Arranger != nil { |
| n.Arranger.Arrange(e, n, t) |
| } |
| |
| // Push absTransforms. |
| // TODO: cache absolute transforms and use EngineFields.Dirty? |
| rel := &e.nodes[n.EngineFields.Index].relTransform |
| m := f32.Affine{} |
| m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) |
| e.absTransforms = append(e.absTransforms, m) |
| |
| if x := n.EngineFields.SubTex; x.T != nil { |
| x.T.(*texture).glImage.Draw( |
| sz, |
| geom.Point{ |
| geom.Pt(m[0][2]), |
| geom.Pt(m[1][2]), |
| }, |
| geom.Point{ |
| geom.Pt(m[0][2] + m[0][0]), |
| geom.Pt(m[1][2] + m[1][0]), |
| }, |
| geom.Point{ |
| geom.Pt(m[0][2] + m[0][1]), |
| geom.Pt(m[1][2] + m[1][1]), |
| }, |
| x.R, |
| ) |
| } |
| |
| for c := n.FirstChild; c != nil; c = c.NextSibling { |
| e.render(c, t, sz) |
| } |
| |
| // Pop absTransforms. |
| e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] |
| } |
| |
| func (e *engine) Release() { |
| for img := range e.textures { |
| img.Release() |
| } |
| } |