exp/gl/glutil: remove global texture cache
It is now the user's job to track the lifetime of a glutil.Image relative to a (currently implicit, soon to be explicit) GL context. This is an attempt to move glutil.Image closer to the model for buffers and textures in shiny. Long-term, I would like to adopt that model, and this is a step in that direction. It also makes the introduction of *gl.Context possible, so this is a pre-req for cl/13431. Change-Id: I8e6855211b3e67c97d5831c5c4e443e857c83d50 Reviewed-on: https://go-review.googlesource.com/14795 Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
Родитель
afa8a11c1d
Коммит
0064789433
|
@ -44,9 +44,9 @@ import (
|
|||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/app/debug"
|
||||
"golang.org/x/mobile/exp/audio"
|
||||
"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/exp/sprite/glsprite"
|
||||
|
@ -61,8 +61,9 @@ const (
|
|||
var (
|
||||
startTime = time.Now()
|
||||
|
||||
eng = glsprite.Engine()
|
||||
scene *sprite.Node
|
||||
images *glutil.Images
|
||||
eng sprite.Engine
|
||||
scene *sprite.Node
|
||||
|
||||
player *audio.Player
|
||||
|
||||
|
@ -98,6 +99,10 @@ func main() {
|
|||
}
|
||||
|
||||
func onStart() {
|
||||
images = glutil.NewImages()
|
||||
eng = glsprite.Engine(images)
|
||||
loadScene()
|
||||
|
||||
rc, err := asset.Open("boing.wav")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -109,18 +114,16 @@ func onStart() {
|
|||
}
|
||||
|
||||
func onStop() {
|
||||
eng.Release()
|
||||
images.Release()
|
||||
player.Close()
|
||||
}
|
||||
|
||||
func onPaint() {
|
||||
if scene == nil {
|
||||
loadScene()
|
||||
}
|
||||
gl.ClearColor(1, 1, 1, 1)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
now := clock.Time(time.Since(startTime) * 60 / time.Second)
|
||||
eng.Render(scene, now, sz)
|
||||
debug.DrawFPS(sz)
|
||||
}
|
||||
|
||||
func newNode() *sprite.Node {
|
||||
|
|
|
@ -44,6 +44,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
images *glutil.Images
|
||||
fps *debug.FPS
|
||||
program gl.Program
|
||||
position gl.Attrib
|
||||
offset gl.Uniform
|
||||
|
@ -109,13 +111,15 @@ func onStart() {
|
|||
color = gl.GetUniformLocation(program, "color")
|
||||
offset = gl.GetUniformLocation(program, "offset")
|
||||
|
||||
// TODO(crawshaw): the debug package needs to put GL state init here
|
||||
// Can this be an app.RegisterFilter call now??
|
||||
images = glutil.NewImages()
|
||||
fps = debug.NewFPS(images)
|
||||
}
|
||||
|
||||
func onStop() {
|
||||
gl.DeleteProgram(program)
|
||||
gl.DeleteBuffer(buf)
|
||||
fps.Release()
|
||||
images.Release()
|
||||
}
|
||||
|
||||
func onPaint(sz size.Event) {
|
||||
|
@ -138,7 +142,7 @@ func onPaint(sz size.Event) {
|
|||
gl.DrawArrays(gl.TRIANGLES, 0, vertexCount)
|
||||
gl.DisableVertexAttribArray(position)
|
||||
|
||||
debug.DrawFPS(sz)
|
||||
fps.Draw(sz)
|
||||
}
|
||||
|
||||
var triangleData = f32.Bytes(binary.LittleEndian,
|
||||
|
|
|
@ -43,6 +43,7 @@ import (
|
|||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/app/debug"
|
||||
"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/exp/sprite/glsprite"
|
||||
|
@ -51,8 +52,10 @@ import (
|
|||
|
||||
var (
|
||||
startTime = time.Now()
|
||||
eng = glsprite.Engine()
|
||||
images *glutil.Images
|
||||
eng sprite.Engine
|
||||
scene *sprite.Node
|
||||
fps *debug.FPS
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -83,13 +86,16 @@ func main() {
|
|||
|
||||
func onPaint(sz size.Event) {
|
||||
if scene == nil {
|
||||
images = glutil.NewImages()
|
||||
fps = debug.NewFPS(images)
|
||||
eng = glsprite.Engine(images)
|
||||
loadScene()
|
||||
}
|
||||
gl.ClearColor(1, 1, 1, 1)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
now := clock.Time(time.Since(startTime) * 60 / time.Second)
|
||||
eng.Render(scene, now, sz)
|
||||
debug.DrawFPS(sz)
|
||||
fps.Draw(sz)
|
||||
}
|
||||
|
||||
func newNode() *sprite.Node {
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
|
@ -19,24 +18,37 @@ import (
|
|||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
var lastDraw = time.Now()
|
||||
|
||||
var fps struct {
|
||||
mu sync.Mutex
|
||||
sz size.Event
|
||||
m *glutil.Image
|
||||
// FPS draws a count of the frames rendered per second.
|
||||
type FPS struct {
|
||||
sz size.Event
|
||||
images *glutil.Images
|
||||
m *glutil.Image
|
||||
lastDraw time.Time
|
||||
// TODO: store *gl.Context
|
||||
}
|
||||
|
||||
// DrawFPS draws the per second framerate in the bottom-left of the screen.
|
||||
func DrawFPS(sz size.Event) {
|
||||
// NewFPS creates an FPS tied to the current GL context.
|
||||
func NewFPS(images *glutil.Images) *FPS {
|
||||
return &FPS{
|
||||
lastDraw: time.Now(),
|
||||
images: images,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws the per second framerate in the bottom-left of the screen.
|
||||
func (p *FPS) Draw(sz size.Event) {
|
||||
const imgW, imgH = 7*(fontWidth+1) + 1, fontHeight + 2
|
||||
|
||||
fps.mu.Lock()
|
||||
if fps.sz != sz || fps.m == nil {
|
||||
fps.sz = sz
|
||||
fps.m = glutil.NewImage(imgW, imgH)
|
||||
if sz.WidthPx == 0 && sz.HeightPx == 0 {
|
||||
return
|
||||
}
|
||||
if p.sz != sz {
|
||||
p.sz = sz
|
||||
if p.m != nil {
|
||||
p.m.Release()
|
||||
}
|
||||
p.m = p.images.NewImage(imgW, imgH)
|
||||
}
|
||||
fps.mu.Unlock()
|
||||
|
||||
display := [7]byte{
|
||||
4: 'F',
|
||||
|
@ -45,13 +57,13 @@ func DrawFPS(sz size.Event) {
|
|||
}
|
||||
now := time.Now()
|
||||
f := 0
|
||||
if dur := now.Sub(lastDraw); dur > 0 {
|
||||
if dur := now.Sub(p.lastDraw); dur > 0 {
|
||||
f = int(time.Second / dur)
|
||||
}
|
||||
display[2] = '0' + byte((f/1e0)%10)
|
||||
display[1] = '0' + byte((f/1e1)%10)
|
||||
display[0] = '0' + byte((f/1e2)%10)
|
||||
draw.Draw(fps.m.RGBA, fps.m.RGBA.Bounds(), image.White, image.Point{}, draw.Src)
|
||||
draw.Draw(p.m.RGBA, p.m.RGBA.Bounds(), image.White, image.Point{}, draw.Src)
|
||||
for i, c := range display {
|
||||
glyph := glyphs[c]
|
||||
if len(glyph) != fontWidth*fontHeight {
|
||||
|
@ -62,21 +74,29 @@ func DrawFPS(sz size.Event) {
|
|||
if glyph[fontWidth*y+x] == ' ' {
|
||||
continue
|
||||
}
|
||||
fps.m.RGBA.SetRGBA((fontWidth+1)*i+x+1, y+1, color.RGBA{A: 0xff})
|
||||
p.m.RGBA.SetRGBA((fontWidth+1)*i+x+1, y+1, color.RGBA{A: 0xff})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fps.m.Upload()
|
||||
fps.m.Draw(
|
||||
p.m.Upload()
|
||||
p.m.Draw(
|
||||
sz,
|
||||
geom.Point{0, sz.HeightPt - imgH},
|
||||
geom.Point{imgW, sz.HeightPt - imgH},
|
||||
geom.Point{0, sz.HeightPt},
|
||||
fps.m.RGBA.Bounds(),
|
||||
p.m.RGBA.Bounds(),
|
||||
)
|
||||
|
||||
lastDraw = now
|
||||
p.lastDraw = now
|
||||
}
|
||||
|
||||
func (f *FPS) Release() {
|
||||
if f.m != nil {
|
||||
f.m.Release()
|
||||
f.m = nil
|
||||
f.images = nil
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -8,20 +8,18 @@ package glutil
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/mobile/app"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/geom"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
var glimage struct {
|
||||
// Images maintains the shared state used by a set of *Image objects.
|
||||
type Images struct {
|
||||
quadXY gl.Buffer
|
||||
quadUV gl.Buffer
|
||||
program gl.Program
|
||||
|
@ -30,142 +28,59 @@ var glimage struct {
|
|||
uvp gl.Uniform
|
||||
inUV gl.Attrib
|
||||
textureSample gl.Uniform
|
||||
|
||||
// TODO(crawshaw): store *gl.Context
|
||||
|
||||
mu sync.Mutex
|
||||
activeImages int
|
||||
}
|
||||
|
||||
func init() {
|
||||
app.RegisterFilter(func(e interface{}) interface{} {
|
||||
if e, ok := e.(lifecycle.Event); ok {
|
||||
switch e.Crosses(lifecycle.StageVisible) {
|
||||
case lifecycle.CrossOn:
|
||||
start()
|
||||
case lifecycle.CrossOff:
|
||||
stop()
|
||||
}
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
var err error
|
||||
glimage.program, err = CreateProgram(vertexShader, fragmentShader)
|
||||
// NewImages creates an *Images.
|
||||
// TODO(crawshaw): take *gl.Context parameter
|
||||
func NewImages() *Images {
|
||||
program, err := CreateProgram(vertexShader, fragmentShader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
glimage.quadXY = gl.CreateBuffer()
|
||||
glimage.quadUV = gl.CreateBuffer()
|
||||
p := &Images{
|
||||
quadXY: gl.CreateBuffer(),
|
||||
quadUV: gl.CreateBuffer(),
|
||||
program: program,
|
||||
pos: gl.GetAttribLocation(program, "pos"),
|
||||
mvp: gl.GetUniformLocation(program, "mvp"),
|
||||
uvp: gl.GetUniformLocation(program, "uvp"),
|
||||
inUV: gl.GetAttribLocation(program, "inUV"),
|
||||
textureSample: gl.GetUniformLocation(program, "textureSample"),
|
||||
}
|
||||
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, p.quadXY)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, p.quadUV)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
|
||||
|
||||
glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
|
||||
glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
|
||||
glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
|
||||
glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
|
||||
glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
|
||||
|
||||
texmap.Lock()
|
||||
defer texmap.Unlock()
|
||||
for key, tex := range texmap.texs {
|
||||
texmap.init(key)
|
||||
tex.needsUpload = true
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func stop() {
|
||||
gl.DeleteProgram(glimage.program)
|
||||
gl.DeleteBuffer(glimage.quadXY)
|
||||
gl.DeleteBuffer(glimage.quadUV)
|
||||
|
||||
texmap.Lock()
|
||||
for _, t := range texmap.texs {
|
||||
if t.gltex.Value != 0 {
|
||||
gl.DeleteTexture(t.gltex)
|
||||
}
|
||||
t.gltex = gl.Texture{}
|
||||
}
|
||||
texmap.Unlock()
|
||||
}
|
||||
|
||||
type texture struct {
|
||||
gltex gl.Texture
|
||||
width int
|
||||
height int
|
||||
needsUpload bool
|
||||
}
|
||||
|
||||
var texmap = &texmapCache{
|
||||
texs: make(map[texmapKey]*texture),
|
||||
next: 1, // avoid using 0 to aid debugging
|
||||
}
|
||||
|
||||
type texmapKey int
|
||||
|
||||
type texmapCache struct {
|
||||
sync.Mutex
|
||||
texs map[texmapKey]*texture
|
||||
next texmapKey
|
||||
|
||||
// TODO(crawshaw): This is a workaround for having nowhere better to clean up deleted textures.
|
||||
// Better: app.UI(func() { gl.DeleteTexture(t) } in texmap.delete
|
||||
// Best: Redesign the gl package to do away with this painful notion of a UI thread.
|
||||
toDelete []gl.Texture
|
||||
}
|
||||
|
||||
func (tm *texmapCache) create(dx, dy int) *texmapKey {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
key := tm.next
|
||||
tm.next++
|
||||
tm.texs[key] = &texture{
|
||||
width: dx,
|
||||
height: dy,
|
||||
}
|
||||
tm.init(key)
|
||||
return &key
|
||||
}
|
||||
|
||||
// init creates an underlying GL texture for a key.
|
||||
// Must be called with a valid GL context.
|
||||
// Must hold tm.Mutex before calling.
|
||||
func (tm *texmapCache) init(key texmapKey) {
|
||||
tex := tm.texs[key]
|
||||
if tex.gltex.Value != 0 {
|
||||
panic(fmt.Sprintf("attempting to init key (%v) with valid texture", key))
|
||||
}
|
||||
tex.gltex = gl.CreateTexture()
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
|
||||
for _, t := range tm.toDelete {
|
||||
gl.DeleteTexture(t)
|
||||
}
|
||||
tm.toDelete = nil
|
||||
}
|
||||
|
||||
func (tm *texmapCache) delete(key texmapKey) {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
tex := tm.texs[key]
|
||||
delete(tm.texs, key)
|
||||
if tex == nil {
|
||||
// Release releases any held OpenGL resources.
|
||||
// All *Image objects must be released first, or this function panics.
|
||||
func (p *Images) Release() {
|
||||
if p.program == (gl.Program{}) {
|
||||
return
|
||||
}
|
||||
tm.toDelete = append(tm.toDelete, tex.gltex)
|
||||
}
|
||||
|
||||
func (tm *texmapCache) get(key texmapKey) *texture {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
return tm.texs[key]
|
||||
p.mu.Lock()
|
||||
rem := p.activeImages
|
||||
p.mu.Unlock()
|
||||
if rem > 0 {
|
||||
panic("glutil.Images.Release called, but active *Image objects remain")
|
||||
}
|
||||
|
||||
gl.DeleteProgram(p.program)
|
||||
gl.DeleteBuffer(p.quadXY)
|
||||
gl.DeleteBuffer(p.quadUV)
|
||||
|
||||
p.program = gl.Program{}
|
||||
}
|
||||
|
||||
// Image bridges between an *image.RGBA and an OpenGL texture.
|
||||
|
@ -177,13 +92,17 @@ func (tm *texmapCache) get(key texmapKey) *texture {
|
|||
// The typical use of an Image is as a texture atlas.
|
||||
type Image struct {
|
||||
RGBA *image.RGBA
|
||||
key *texmapKey
|
||||
|
||||
gltex gl.Texture
|
||||
width int
|
||||
height int
|
||||
images *Images
|
||||
}
|
||||
|
||||
// NewImage creates an Image of the given size.
|
||||
//
|
||||
// Both a host-memory *image.RGBA and a GL texture are created.
|
||||
func NewImage(w, h int) *Image {
|
||||
func (p *Images) NewImage(w, h int) *Image {
|
||||
dx := roundToPower2(w)
|
||||
dy := roundToPower2(h)
|
||||
|
||||
|
@ -193,12 +112,26 @@ func NewImage(w, h int) *Image {
|
|||
m := image.NewRGBA(image.Rect(0, 0, dx, dy))
|
||||
|
||||
img := &Image{
|
||||
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
|
||||
key: texmap.create(dx, dy),
|
||||
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
|
||||
images: p,
|
||||
width: dx,
|
||||
height: dy,
|
||||
}
|
||||
runtime.SetFinalizer(img.key, func(key *texmapKey) {
|
||||
texmap.delete(*key)
|
||||
})
|
||||
|
||||
p.mu.Lock()
|
||||
p.activeImages++
|
||||
p.mu.Unlock()
|
||||
|
||||
img.gltex = gl.CreateTexture()
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
|
||||
runtime.SetFinalizer(img, (*Image).Release)
|
||||
return img
|
||||
}
|
||||
|
||||
|
@ -212,28 +145,32 @@ func roundToPower2(x int) int {
|
|||
|
||||
// Upload copies the host image data to the GL device.
|
||||
func (img *Image) Upload() {
|
||||
tex := texmap.get(*img.key)
|
||||
gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
|
||||
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix)
|
||||
gl.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix)
|
||||
}
|
||||
|
||||
// Delete invalidates the Image and removes any underlying data structures.
|
||||
// Release invalidates the Image and removes any underlying data structures.
|
||||
// The Image cannot be used after being deleted.
|
||||
func (img *Image) Delete() {
|
||||
texmap.delete(*img.key)
|
||||
func (img *Image) Release() {
|
||||
if img.gltex == (gl.Texture{}) {
|
||||
return
|
||||
}
|
||||
|
||||
gl.DeleteTexture(img.gltex)
|
||||
img.gltex = gl.Texture{}
|
||||
|
||||
img.images.mu.Lock()
|
||||
img.images.activeImages--
|
||||
img.images.mu.Unlock()
|
||||
}
|
||||
|
||||
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
|
||||
// three of its corners, in the current GL framebuffer.
|
||||
func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
|
||||
glimage := img.images
|
||||
|
||||
// TODO(crawshaw): Adjust viewport for the top bar on android?
|
||||
gl.UseProgram(glimage.program)
|
||||
tex := texmap.get(*img.key)
|
||||
if tex.needsUpload {
|
||||
img.Upload()
|
||||
tex.needsUpload = false
|
||||
}
|
||||
|
||||
{
|
||||
// We are drawing a parallelogram PQRS, defined by three of its
|
||||
// corners, onto the entire GL framebuffer ABCD. The two quads may
|
||||
|
@ -309,8 +246,8 @@ func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point,
|
|||
//
|
||||
// and the PQRS quad is always axis-aligned. First of all, convert
|
||||
// from pixel space to texture space.
|
||||
w := float32(tex.width)
|
||||
h := float32(tex.height)
|
||||
w := float32(img.width)
|
||||
h := float32(img.height)
|
||||
px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w
|
||||
py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h
|
||||
qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w
|
||||
|
@ -336,7 +273,7 @@ func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point,
|
|||
}
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
|
||||
gl.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
gl.Uniform1i(glimage.textureSample, 0)
|
||||
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
|
||||
|
|
|
@ -28,6 +28,7 @@ type node struct {
|
|||
}
|
||||
|
||||
type texture struct {
|
||||
e *engine
|
||||
glImage *glutil.Image
|
||||
b image.Rectangle
|
||||
}
|
||||
|
@ -43,18 +44,23 @@ func (t *texture) Upload(r image.Rectangle, src image.Image) {
|
|||
t.glImage.Upload()
|
||||
}
|
||||
|
||||
func (t *texture) Unload() {
|
||||
panic("TODO")
|
||||
func (t *texture) Release() {
|
||||
t.glImage.Release()
|
||||
delete(t.e.textures, t)
|
||||
}
|
||||
|
||||
func Engine() sprite.Engine {
|
||||
// Engine creates an OpenGL-based sprite.Engine.
|
||||
func Engine(images *glutil.Images) sprite.Engine {
|
||||
return &engine{
|
||||
nodes: []*node{nil},
|
||||
nodes: []*node{nil},
|
||||
images: images,
|
||||
textures: make(map[*texture]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type engine struct {
|
||||
glImages map[sprite.Texture]*glutil.Image
|
||||
images *glutil.Images
|
||||
textures map[*texture]struct{}
|
||||
nodes []*node
|
||||
|
||||
absTransforms []f32.Affine
|
||||
|
@ -77,7 +83,12 @@ func (e *engine) Unregister(n *sprite.Node) {
|
|||
|
||||
func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) {
|
||||
b := src.Bounds()
|
||||
t := &texture{glutil.NewImage(b.Dx(), b.Dy()), b}
|
||||
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
|
||||
|
@ -142,3 +153,9 @@ func (e *engine) render(n *sprite.Node, t clock.Time, sz size.Event) {
|
|||
// Pop absTransforms.
|
||||
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
|
||||
}
|
||||
|
||||
func (e *engine) Release() {
|
||||
for img := range e.textures {
|
||||
img.Release()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ 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") }
|
||||
func (t *texture) Release() {}
|
||||
|
||||
type engine struct {
|
||||
dst *image.RGBA
|
||||
|
@ -149,6 +149,8 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
|
|||
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
|
||||
}
|
||||
|
||||
func (e *engine) Release() {}
|
||||
|
||||
// affine draws each pixel of dst using bilinear interpolation of the
|
||||
// affine-transformed position in src. This is equivalent to:
|
||||
//
|
||||
|
|
|
@ -41,7 +41,7 @@ type Texture interface {
|
|||
Bounds() (w, h int)
|
||||
Download(r image.Rectangle, dst draw.Image)
|
||||
Upload(r image.Rectangle, src image.Image)
|
||||
Unload()
|
||||
Release()
|
||||
}
|
||||
|
||||
type SubTex struct {
|
||||
|
@ -61,6 +61,8 @@ type Engine interface {
|
|||
// Render renders the scene arranged at the given time, for the given
|
||||
// window configuration (dimensions and resolution).
|
||||
Render(scene *Node, t clock.Time, sz size.Event)
|
||||
|
||||
Release()
|
||||
}
|
||||
|
||||
// A Node is a renderable element and forms a tree of Nodes.
|
||||
|
|
Загрузка…
Ссылка в новой задаче