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.
This commit is contained in:
parent
3d199ca263
commit
c7cc40a339
|
@ -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)
|
||||||
|
|
91
lib/render/canvas/texture.go
Normal file
91
lib/render/canvas/texture.go
Normal 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)
|
||||||
|
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user