From c7cc40a3390c34b3c762966008ead7bbdc8ada63 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 27 Jun 2019 13:01:01 -0700 Subject: [PATCH] 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. --- lib/render/canvas/engine.go | 87 ---------------------------------- lib/render/canvas/texture.go | 91 ++++++++++++++++++++++++++++++++++++ lib/render/interface.go | 4 +- lib/render/sdl/texture.go | 14 +++--- lib/ui/image.go | 2 +- pkg/level/chunk.go | 39 +++++----------- pkg/wallpaper/texture.go | 2 +- 7 files changed, 113 insertions(+), 126 deletions(-) create mode 100644 lib/render/canvas/texture.go diff --git a/lib/render/canvas/engine.go b/lib/render/canvas/engine.go index 10d5563..5f7f49e 100644 --- a/lib/render/canvas/engine.go +++ b/lib/render/canvas/engine.go @@ -1,16 +1,10 @@ package canvas import ( - "bytes" - "encoding/base64" - "errors" - "image" - "image/png" "syscall/js" "time" "git.kirsle.net/apps/doodle/lib/events" - "git.kirsle.net/apps/doodle/lib/render" ) // Engine implements a rendering engine targeting an HTML canvas for @@ -79,87 +73,6 @@ func (e *Engine) Present() error { 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. func (e *Engine) Delay(delay uint32) { time.Sleep(time.Duration(delay) * time.Millisecond) diff --git a/lib/render/canvas/texture.go b/lib/render/canvas/texture.go new file mode 100644 index 0000000..be6da8a --- /dev/null +++ b/lib/render/canvas/texture.go @@ -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) + +} diff --git a/lib/render/interface.go b/lib/render/interface.go index 5f912df..82d3966 100644 --- a/lib/render/interface.go +++ b/lib/render/interface.go @@ -32,8 +32,8 @@ type Engine interface { ComputeTextRect(Text) (Rect, error) // Texture caching. - NewBitmap(filename string) (Texturer, error) - NewTexture(filename string, img image.Image) (Texturer, error) + StoreTexture(name string, img image.Image) (Texturer, error) + LoadTexture(name string) (Texturer, error) Copy(t Texturer, src, dst Rect) // Delay for a moment using the render engine's delay method, diff --git a/lib/render/sdl/texture.go b/lib/render/sdl/texture.go index 677ef72..27b0cc7 100644 --- a/lib/render/sdl/texture.go +++ b/lib/render/sdl/texture.go @@ -28,8 +28,8 @@ type Texture struct { height int32 } -// NewTexture caches an SDL texture from a bitmap. -func (r *Renderer) NewTexture(filename string, img image.Image) (render.Texturer, error) { +// StoreTexture caches an SDL texture from a bitmap. +func (r *Renderer) StoreTexture(name string, img image.Image) (render.Texturer, error) { var ( fh = bytes.NewBuffer([]byte{}) ) @@ -65,7 +65,7 @@ func (r *Renderer) NewTexture(filename string, img image.Image) (render.Texturer height: surface.H, tex: texture, } - r.textures[filename] = tex + r.textures[name] = tex return tex, nil } @@ -75,10 +75,10 @@ func (t *Texture) Size() render.Rect { return render.NewRect(t.width, t.height) } -// NewBitmap initializes a texture from a bitmap image. -func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) { - if tex, ok := r.textures[filename]; ok { +// LoadTexture initializes a texture from a bitmap image. +func (r *Renderer) LoadTexture(name string) (render.Texturer, error) { + if tex, ok := r.textures[name]; ok { 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) } diff --git a/lib/ui/image.go b/lib/ui/image.go index 32d57fd..8530f51 100644 --- a/lib/ui/image.go +++ b/lib/ui/image.go @@ -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) } - tex, err := e.NewBitmap(filename) + tex, err := e.LoadTexture(filename) if err != nil { return nil, err } diff --git a/pkg/level/chunk.go b/pkg/level/chunk.go index 39f7a09..b8dda84 100644 --- a/pkg/level/chunk.go +++ b/pkg/level/chunk.go @@ -5,14 +5,11 @@ import ( "fmt" "image" "math" - "os" - "runtime" "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/shmem" - "git.kirsle.net/apps/doodle/pkg/userdir" "github.com/satori/go.uuid" "github.com/vmihailenco/msgpack" ) @@ -80,11 +77,7 @@ func NewChunk() *Chunk { func (c *Chunk) Texture(e render.Engine) render.Texturer { if c.texture == nil || c.dirty { // Generate the normal bitmap and one with a color mask if applicable. - bitmap := c.toBitmap(render.Invisible) - if runtime.GOOS != "js" { // WASM - defer os.Remove(bitmap) - } - tex, err := e.NewBitmap(bitmap) + tex, err := c.toBitmap(render.Invisible) if err != nil { 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 { if c.textureMasked == nil || c.textureMaskedColor != mask { // Generate the normal bitmap and one with a color mask if applicable. - bitmap := c.toBitmap(mask) - if runtime.GOOS != "js" { // WASM - defer os.Remove(bitmap) - } - tex, err := e.NewBitmap(bitmap) + tex, err := c.toBitmap(mask) if err != nil { 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. -func (c *Chunk) toBitmap(mask render.Color) string { - // Generate a unique filename for this chunk cache. - var filename string +func (c *Chunk) toBitmap(mask render.Color) (render.Texturer, error) { + // Generate a unique name for this chunk cache. + var name string if c.uuid == uuid.Nil { c.uuid = uuid.Must(uuid.NewV4()) } - filename = c.uuid.String() + name = c.uuid.String() 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, ) } // Get the temp bitmap image. - bitmap := userdir.CacheFilename("chunk", filename+".bmp") - err := c.ToBitmap(bitmap, mask) - if err != nil { - log.Error("Texture: %s", err) - } - - return bitmap + return c.ToBitmap(name, mask) } // 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() imgSize := image.Rectangle{ 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. - _, err := shmem.CurrentRenderEngine.NewTexture(filename, img) - return err + tex, err := shmem.CurrentRenderEngine.StoreTexture(filename, img) + return tex, err } // Set proxies to the accessor and flags the texture as dirty. diff --git a/pkg/wallpaper/texture.go b/pkg/wallpaper/texture.go index 30e1eda..7ad83b2 100644 --- a/pkg/wallpaper/texture.go +++ b/pkg/wallpaper/texture.go @@ -58,6 +58,6 @@ func (wp *Wallpaper) RepeatTexture(e render.Engine) (render.Texturer, error) { // texture creates or returns a cached texture for a wallpaper. func texture(e render.Engine, img *image.RGBA, name string) (render.Texturer, error) { filename := userdir.CacheFilename("wallpaper", name+".bmp") - texture, err := shmem.CurrentRenderEngine.NewTexture(filename, img) + texture, err := shmem.CurrentRenderEngine.StoreTexture(filename, img) return texture, err }