Browse Source

WASM Texture Caching

* 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.
master
Noah Petherbridge 2 years ago
parent
commit
5893daba58
5 changed files with 106 additions and 26 deletions
  1. +4
    -2
      canvas/canvas.go
  2. +69
    -24
      canvas/engine.go
  3. +16
    -0
      canvas/events.go
  4. +2
    -0
      interface.go
  5. +15
    -0
      sdl/texture.go

+ 4
- 2
canvas/canvas.go View File

@@ -28,10 +28,12 @@ func GetCanvas(id string) Canvas {

// ClientW returns the client width.
func (c Canvas) ClientW() int {
return c.Value.Get("clientWidth").Int()
return js.Global().Get("window").Get("innerWidth").Int()
// return c.Value.Get("clientWidth").Int()
}

// ClientH returns the client height.
func (c Canvas) ClientH() int {
return c.Value.Get("clientHeight").Int()
return js.Global().Get("window").Get("innerHeight").Int()
// return c.Value.Get("clientHeight").Int()
}

+ 69
- 24
canvas/engine.go View File

@@ -1,13 +1,16 @@
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"
"git.kirsle.net/apps/doodle/pkg/wasm"
)

// Engine implements a rendering engine targeting an HTML canvas for
@@ -20,8 +23,9 @@ type Engine struct {
ticks uint32

// Private fields.
events *events.State
running bool
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
@@ -40,6 +44,7 @@ func New(canvasID string) (*Engine, error) {
width: canvas.ClientW(),
height: canvas.ClientH(),
queue: make(chan Event, 1024),
textures: map[string]*Texture{},
}

return engine, nil
@@ -47,6 +52,14 @@ func New(canvasID string) (*Engine, error) {

// 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()
}

@@ -70,10 +83,58 @@ func (e *Engine) Present() error {
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))
@@ -82,37 +143,21 @@ func (t *Texture) Size() render.Rect {
// 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 data, ok := wasm.GetSession(filename); ok {
img := js.Global().Get("document").Call("createElement", "img")
img.Set("src", data)
return &Texture{
data: data,
image: img,
width: 60, // TODO
height: 60,
}, nil
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)

}

var TODO int

// Copy a texturer bitmap onto the canvas.
func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
tex := t.(*Texture)

// image := js.Global().Get("document").Call("createElement", "img")
// image.Set("src", tex.data)
// e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y)
e.canvas.ctx2d.Call("drawImage", tex.canvas, dist.X, dist.Y)

// log.Info("drawing image just this once")
e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y)
// TODO++
// if TODO > 200 {
// log.Info("I exited at engine.Copy for canvas engine")
// os.Exit(0)
// }
}

// Delay for a moment.


+ 16
- 0
canvas/events.go View File

@@ -15,6 +15,7 @@ const (
ClickEvent
KeyEvent
ResizeEvent
WindowEvent
)

// Event object queues up asynchronous JavaScript events to be processed linearly.
@@ -36,6 +37,19 @@ type Event struct {

// AddEventListeners sets up bindings to collect events from the browser.
func (e *Engine) AddEventListeners() {
// Window resize.
js.Global().Get("window").Call(
"addEventListener",
"resize",
js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e.queue <- Event{
Name: "resize",
Class: WindowEvent,
}
return nil
}),
)

// Mouse movement.
e.canvas.Value.Call(
"addEventListener",
@@ -149,6 +163,8 @@ func (e *Engine) Poll() (*events.State, error) {

for event := e.PollEvent(); event != nil; event = e.PollEvent() {
switch event.Class {
case WindowEvent:
s.Resized.Push(true)
case MouseEvent:
s.CursorX.Push(int32(event.X))
s.CursorY.Push(int32(event.Y))


+ 2
- 0
interface.go View File

@@ -2,6 +2,7 @@ package render

import (
"fmt"
"image"
"math"

"git.kirsle.net/apps/doodle/lib/events"
@@ -32,6 +33,7 @@ type Engine interface {

// Texture caching.
NewBitmap(filename string) (Texturer, error)
NewTexture(filename string, img image.Image) (Texturer, error)
Copy(t Texturer, src, dst Rect)

// Delay for a moment using the render engine's delay method,


+ 15
- 0
sdl/texture.go View File

@@ -2,9 +2,12 @@ package sdl

import (
"fmt"
"image"
"os"

"git.kirsle.net/apps/doodle/lib/render"
"github.com/veandco/go-sdl2/sdl"
"golang.org/x/image/bmp"
)

// Copy a texture into the renderer.
@@ -25,6 +28,18 @@ 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) {
fh, err := os.Create(filename)
if err != nil {
return nil, err
}
defer fh.Close()

err = bmp.Encode(fh, img)
return nil, err
}

// Size returns the dimensions of the texture.
func (t *Texture) Size() render.Rect {
return render.NewRect(t.width, t.height)


Loading…
Cancel
Save