Refactor Render Texture-Cache Interface

Since SDL2 is using in-memory bitmaps the same as Canvas engine, the
function names of the render.Engine interface have been cleaned up:

* NewTexture(filename, image) -> StoreTexture(name, image)
  Create a new cached texture with a given name.
* NewBitmap(filename) -> LoadTexture(name)
  Recall a stored texture with a given name.
* level.Chunk.ToBitmap uses simpler names for the textures instead of
  userdir.CacheFilename file-like paths.
physics
Noah 2019-06-27 13:01:01 -07:00
parent 3d199ca263
commit c7cc40a339
7 changed files with 113 additions and 126 deletions

View File

@ -1,16 +1,10 @@
package canvas package canvas
import ( import (
"bytes"
"encoding/base64"
"errors"
"image"
"image/png"
"syscall/js" "syscall/js"
"time" "time"
"git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
) )
// Engine implements a rendering engine targeting an HTML canvas for // Engine implements a rendering engine targeting an HTML canvas for
@ -79,87 +73,6 @@ func (e *Engine) Present() error {
return nil return nil
} }
// Texture can hold on to cached image textures.
type Texture struct {
data string // data:image/png URI
image js.Value // DOM image element
canvas js.Value // Warmed up canvas element
ctx2d js.Value // 2D drawing context for the canvas.
width int
height int
}
// NewTexture caches a texture from a bitmap.
func (e *Engine) NewTexture(filename string, img image.Image) (render.Texturer, error) {
var (
fh = bytes.NewBuffer([]byte{})
imageSize = img.Bounds().Size()
width = imageSize.X
height = imageSize.Y
)
// Encode to PNG format.
if err := png.Encode(fh, img); err != nil {
return nil, err
}
var dataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fh.Bytes())
tex := &Texture{
data: dataURI,
width: width,
height: height,
}
// Preheat a cached Canvas object.
canvas := js.Global().Get("document").Call("createElement", "canvas")
canvas.Set("width", width)
canvas.Set("height", height)
tex.canvas = canvas
ctx2d := canvas.Call("getContext", "2d")
tex.ctx2d = ctx2d
// Load as a JS Image object.
image := js.Global().Call("eval", "new Image()")
image.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ctx2d.Call("drawImage", image, 0, 0)
return nil
}))
image.Set("src", tex.data)
tex.image = image
// Cache the texture in memory.
e.textures[filename] = tex
return tex, nil
}
// Size returns the dimensions of the texture.
func (t *Texture) Size() render.Rect {
return render.NewRect(int32(t.width), int32(t.height))
}
// NewBitmap initializes a texture from a bitmap image. The image is stored
// in HTML5 Session Storage.
func (e *Engine) NewBitmap(filename string) (render.Texturer, error) {
if tex, ok := e.textures[filename]; ok {
return tex, nil
}
panic("no bitmap for " + filename)
return nil, errors.New("no bitmap data stored for " + filename)
}
// Copy a texturer bitmap onto the canvas.
func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
tex := t.(*Texture)
// e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y)
e.canvas.ctx2d.Call("drawImage", tex.canvas, dist.X, dist.Y)
}
// Delay for a moment. // Delay for a moment.
func (e *Engine) Delay(delay uint32) { func (e *Engine) Delay(delay uint32) {
time.Sleep(time.Duration(delay) * time.Millisecond) time.Sleep(time.Duration(delay) * time.Millisecond)

View File

@ -0,0 +1,91 @@
package canvas
import (
"bytes"
"encoding/base64"
"errors"
"image"
"image/png"
"syscall/js"
"git.kirsle.net/apps/doodle/lib/render"
)
// Texture can hold on to cached image textures.
type Texture struct {
data string // data:image/png URI
image js.Value // DOM image element
canvas js.Value // Warmed up canvas element
ctx2d js.Value // 2D drawing context for the canvas.
width int
height int
}
// StoreTexture caches a texture from a bitmap.
func (e *Engine) StoreTexture(name string, img image.Image) (render.Texturer, error) {
var (
fh = bytes.NewBuffer([]byte{})
imageSize = img.Bounds().Size()
width = imageSize.X
height = imageSize.Y
)
// Encode to PNG format.
if err := png.Encode(fh, img); err != nil {
return nil, err
}
var dataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fh.Bytes())
tex := &Texture{
data: dataURI,
width: width,
height: height,
}
// Preheat a cached Canvas object.
canvas := js.Global().Get("document").Call("createElement", "canvas")
canvas.Set("width", width)
canvas.Set("height", height)
tex.canvas = canvas
ctx2d := canvas.Call("getContext", "2d")
tex.ctx2d = ctx2d
// Load as a JS Image object.
image := js.Global().Call("eval", "new Image()")
image.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ctx2d.Call("drawImage", image, 0, 0)
return nil
}))
image.Set("src", tex.data)
tex.image = image
// Cache the texture in memory.
e.textures[name] = tex
return tex, nil
}
// Size returns the dimensions of the texture.
func (t *Texture) Size() render.Rect {
return render.NewRect(int32(t.width), int32(t.height))
}
// LoadTexture recalls a cached texture image.
func (e *Engine) LoadTexture(name string) (render.Texturer, error) {
if tex, ok := e.textures[name]; ok {
return tex, nil
}
return nil, errors.New("no bitmap data stored for " + name)
}
// Copy a texturer bitmap onto the canvas.
func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
tex := t.(*Texture)
// e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y)
e.canvas.ctx2d.Call("drawImage", tex.canvas, dist.X, dist.Y)
}

View File

@ -32,8 +32,8 @@ type Engine interface {
ComputeTextRect(Text) (Rect, error) ComputeTextRect(Text) (Rect, error)
// Texture caching. // Texture caching.
NewBitmap(filename string) (Texturer, error) StoreTexture(name string, img image.Image) (Texturer, error)
NewTexture(filename string, img image.Image) (Texturer, error) LoadTexture(name string) (Texturer, error)
Copy(t Texturer, src, dst Rect) Copy(t Texturer, src, dst Rect)
// Delay for a moment using the render engine's delay method, // Delay for a moment using the render engine's delay method,

View File

@ -28,8 +28,8 @@ type Texture struct {
height int32 height int32
} }
// NewTexture caches an SDL texture from a bitmap. // StoreTexture caches an SDL texture from a bitmap.
func (r *Renderer) NewTexture(filename string, img image.Image) (render.Texturer, error) { func (r *Renderer) StoreTexture(name string, img image.Image) (render.Texturer, error) {
var ( var (
fh = bytes.NewBuffer([]byte{}) fh = bytes.NewBuffer([]byte{})
) )
@ -65,7 +65,7 @@ func (r *Renderer) NewTexture(filename string, img image.Image) (render.Texturer
height: surface.H, height: surface.H,
tex: texture, tex: texture,
} }
r.textures[filename] = tex r.textures[name] = tex
return tex, nil return tex, nil
} }
@ -75,10 +75,10 @@ func (t *Texture) Size() render.Rect {
return render.NewRect(t.width, t.height) return render.NewRect(t.width, t.height)
} }
// NewBitmap initializes a texture from a bitmap image. // LoadTexture initializes a texture from a bitmap image.
func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) { func (r *Renderer) LoadTexture(name string) (render.Texturer, error) {
if tex, ok := r.textures[filename]; ok { if tex, ok := r.textures[name]; ok {
return tex, nil return tex, nil
} }
return nil, fmt.Errorf("NewBitmap(%s): not found in texture cache", filename) return nil, fmt.Errorf("LoadTexture(%s): not found in texture cache", name)
} }

View File

@ -55,7 +55,7 @@ func OpenImage(e render.Engine, filename string) (*Image, error) {
return nil, fmt.Errorf("OpenImage: %s: not a supported image type", filename) return nil, fmt.Errorf("OpenImage: %s: not a supported image type", filename)
} }
tex, err := e.NewBitmap(filename) tex, err := e.LoadTexture(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,14 +5,11 @@ import (
"fmt" "fmt"
"image" "image"
"math" "math"
"os"
"runtime"
"git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/userdir"
"github.com/satori/go.uuid" "github.com/satori/go.uuid"
"github.com/vmihailenco/msgpack" "github.com/vmihailenco/msgpack"
) )
@ -80,11 +77,7 @@ func NewChunk() *Chunk {
func (c *Chunk) Texture(e render.Engine) render.Texturer { func (c *Chunk) Texture(e render.Engine) render.Texturer {
if c.texture == nil || c.dirty { if c.texture == nil || c.dirty {
// Generate the normal bitmap and one with a color mask if applicable. // Generate the normal bitmap and one with a color mask if applicable.
bitmap := c.toBitmap(render.Invisible) tex, err := c.toBitmap(render.Invisible)
if runtime.GOOS != "js" { // WASM
defer os.Remove(bitmap)
}
tex, err := e.NewBitmap(bitmap)
if err != nil { if err != nil {
log.Error("Texture: %s", err) log.Error("Texture: %s", err)
} }
@ -100,11 +93,7 @@ func (c *Chunk) Texture(e render.Engine) render.Texturer {
func (c *Chunk) TextureMasked(e render.Engine, mask render.Color) render.Texturer { func (c *Chunk) TextureMasked(e render.Engine, mask render.Color) render.Texturer {
if c.textureMasked == nil || c.textureMaskedColor != mask { if c.textureMasked == nil || c.textureMaskedColor != mask {
// Generate the normal bitmap and one with a color mask if applicable. // Generate the normal bitmap and one with a color mask if applicable.
bitmap := c.toBitmap(mask) tex, err := c.toBitmap(mask)
if runtime.GOOS != "js" { // WASM
defer os.Remove(bitmap)
}
tex, err := e.NewBitmap(bitmap)
if err != nil { if err != nil {
log.Error("Texture: %s", err) log.Error("Texture: %s", err)
} }
@ -116,32 +105,26 @@ func (c *Chunk) TextureMasked(e render.Engine, mask render.Color) render.Texture
} }
// toBitmap puts the texture in a well named bitmap path in the cache folder. // toBitmap puts the texture in a well named bitmap path in the cache folder.
func (c *Chunk) toBitmap(mask render.Color) string { func (c *Chunk) toBitmap(mask render.Color) (render.Texturer, error) {
// Generate a unique filename for this chunk cache. // Generate a unique name for this chunk cache.
var filename string var name string
if c.uuid == uuid.Nil { if c.uuid == uuid.Nil {
c.uuid = uuid.Must(uuid.NewV4()) c.uuid = uuid.Must(uuid.NewV4())
} }
filename = c.uuid.String() name = c.uuid.String()
if mask != render.Invisible { if mask != render.Invisible {
filename += fmt.Sprintf("-%02x%02x%02x%02x", name += fmt.Sprintf("-%02x%02x%02x%02x",
mask.Red, mask.Green, mask.Blue, mask.Alpha, mask.Red, mask.Green, mask.Blue, mask.Alpha,
) )
} }
// Get the temp bitmap image. // Get the temp bitmap image.
bitmap := userdir.CacheFilename("chunk", filename+".bmp") return c.ToBitmap(name, mask)
err := c.ToBitmap(bitmap, mask)
if err != nil {
log.Error("Texture: %s", err)
}
return bitmap
} }
// ToBitmap exports the chunk's pixels as a bitmap image. // ToBitmap exports the chunk's pixels as a bitmap image.
func (c *Chunk) ToBitmap(filename string, mask render.Color) error { func (c *Chunk) ToBitmap(filename string, mask render.Color) (render.Texturer, error) {
canvas := c.SizePositive() canvas := c.SizePositive()
imgSize := image.Rectangle{ imgSize := image.Rectangle{
Min: image.Point{}, Min: image.Point{},
@ -188,8 +171,8 @@ func (c *Chunk) ToBitmap(filename string, mask render.Color) error {
} }
// Cache the texture data with the current renderer. // Cache the texture data with the current renderer.
_, err := shmem.CurrentRenderEngine.NewTexture(filename, img) tex, err := shmem.CurrentRenderEngine.StoreTexture(filename, img)
return err return tex, err
} }
// Set proxies to the accessor and flags the texture as dirty. // Set proxies to the accessor and flags the texture as dirty.

View File

@ -58,6 +58,6 @@ func (wp *Wallpaper) RepeatTexture(e render.Engine) (render.Texturer, error) {
// texture creates or returns a cached texture for a wallpaper. // texture creates or returns a cached texture for a wallpaper.
func texture(e render.Engine, img *image.RGBA, name string) (render.Texturer, error) { func texture(e render.Engine, img *image.RGBA, name string) (render.Texturer, error) {
filename := userdir.CacheFilename("wallpaper", name+".bmp") filename := userdir.CacheFilename("wallpaper", name+".bmp")
texture, err := shmem.CurrentRenderEngine.NewTexture(filename, img) texture, err := shmem.CurrentRenderEngine.StoreTexture(filename, img)
return texture, err return texture, err
} }