Noah Petherbridge
5893daba58
* Refactor texture caching in render.Engine: * New interface method: NewTexture(filename string, image.Image) * WASM immediately encodes the image to PNG and generates a JavaScript `Image()` object to load it with a data URI and keep it in memory. * SDL2 saves the bitmap to disk as it did before. * WASM: deprecate the sessionStorage for holding image data. Session storage methods panic if called. The image data is directly kept in Go memory as a js.Value holding an Image(). * Shared Memory workaround: the level.Chunk.ToBitmap() function is where chunk textures get cached, but it had no access to the render.Engine used in the game. The `pkg/shmem` package holds global pointers to common structures like the CurrentRenderEngine as a work-around. * Also shmem.Flash() so Doodle can make its d.Flash() function globally available, any sub-package can now flash text to the screen regardless of source code location. * JavaScript API for Doodads now has a global Flash() function available. * WASM: Handle window resize so Doodle can recompute its dimensions instead of scaling/shrinking the view.
174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
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
|
|
// WebAssembly targets.
|
|
type Engine struct {
|
|
canvas Canvas
|
|
startTime time.Time
|
|
width int
|
|
height int
|
|
ticks uint32
|
|
|
|
// Private fields.
|
|
events *events.State
|
|
running bool
|
|
textures map[string]*Texture // cached texture PNG images
|
|
|
|
// Event channel. WASM subscribes to events asynchronously using the
|
|
// JavaScript APIs, whereas SDL2 polls the event queue which orders them
|
|
// all up for processing. This channel will order and queue the events.
|
|
queue chan Event
|
|
}
|
|
|
|
// New creates the Canvas Engine.
|
|
func New(canvasID string) (*Engine, error) {
|
|
canvas := GetCanvas(canvasID)
|
|
|
|
engine := &Engine{
|
|
canvas: canvas,
|
|
startTime: time.Now(),
|
|
events: events.New(),
|
|
width: canvas.ClientW(),
|
|
height: canvas.ClientH(),
|
|
queue: make(chan Event, 1024),
|
|
textures: map[string]*Texture{},
|
|
}
|
|
|
|
return engine, nil
|
|
}
|
|
|
|
// WindowSize returns the size of the canvas window.
|
|
func (e *Engine) WindowSize() (w, h int) {
|
|
// Good time to recompute it first?
|
|
var (
|
|
window = js.Global().Get("window")
|
|
width = window.Get("innerWidth").Int()
|
|
height = window.Get("innerHeight").Int()
|
|
)
|
|
e.canvas.Value.Set("width", width)
|
|
e.canvas.Value.Set("height", height)
|
|
return e.canvas.ClientW(), e.canvas.ClientH()
|
|
}
|
|
|
|
// GetTicks returns the number of milliseconds since the engine started.
|
|
func (e *Engine) GetTicks() uint32 {
|
|
ms := time.Since(e.startTime) * time.Millisecond
|
|
return uint32(ms)
|
|
}
|
|
|
|
// TO BE IMPLEMENTED...
|
|
|
|
func (e *Engine) Setup() error {
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Teardown tasks.
|
|
func (e *Engine) Teardown() {}
|
|
|
|
func (e *Engine) Loop() error {
|
|
return nil
|
|
}
|