Browse Source

Texture Caching for WASM Canvas Engine

* Add RGBA color blending support in WASM build.
* Initial texture caching API for Canvas renderer engine. The WASM build
  writes the chunk caches as a "data:image/png" base64 URL on the
  browser's sessionStorage, for access to copy into the Canvas.
* Separated the ClickEvent from the MouseEvent (motion) in the WASM
  event queue system, to allow clicking and dragging.
* Added the EscapeKey handler, which will abruptly terminate the WASM
  application, same as it kills the window in the desktop build.
* Optimization fix: I discovered that if the user clicks and holds over
  a single pixel when drawing a level, repeated Set() operations were
  firing meaning multiple cache invalidations. Not noticeable on PC but
  on WebAssembly it crippled the browser. Now if the cursor isn't moving
  it doesn't do anything.
master
Noah Petherbridge 2 years ago
parent
commit
27b908e40a
3 changed files with 75 additions and 21 deletions
  1. +16
    -5
      canvas/draw.go
  2. +46
    -2
      canvas/engine.go
  3. +13
    -14
      canvas/events.go

+ 16
- 5
canvas/draw.go View File

@@ -1,6 +1,7 @@
package canvas

import (
"fmt"
"syscall/js"

"git.kirsle.net/apps/doodle/lib/render"
@@ -8,9 +9,19 @@ import (

// Methods here implement the drawing functions of the render.Engine

// RGBA turns a color into CSS RGBA string.
func RGBA(c render.Color) string {
return fmt.Sprintf("rgba(%d,%d,%d,%f)",
c.Red,
c.Green,
c.Blue,
float64(c.Alpha)/255,
)
}

// Clear the canvas to a certain color.
func (e *Engine) Clear(color render.Color) {
e.canvas.ctx2d.Set("fillStyle", color.ToHex())
e.canvas.ctx2d.Set("fillStyle", RGBA(color))
e.canvas.ctx2d.Call("fillRect", 0, 0, e.width, e.height)
}

@@ -21,7 +32,7 @@ func (e *Engine) SetTitle(title string) {

// DrawPoint draws a pixel.
func (e *Engine) DrawPoint(color render.Color, point render.Point) {
e.canvas.ctx2d.Set("fillStyle", color.ToHex())
e.canvas.ctx2d.Set("fillStyle", RGBA(color))
e.canvas.ctx2d.Call("fillRect",
int(point.X),
int(point.Y),
@@ -32,7 +43,7 @@ func (e *Engine) DrawPoint(color render.Color, point render.Point) {

// DrawLine draws a line between two points.
func (e *Engine) DrawLine(color render.Color, a, b render.Point) {
e.canvas.ctx2d.Set("fillStyle", color.ToHex())
e.canvas.ctx2d.Set("fillStyle", RGBA(color))
for pt := range render.IterLine2(a, b) {
e.canvas.ctx2d.Call("fillRect",
int(pt.X),
@@ -45,7 +56,7 @@ func (e *Engine) DrawLine(color render.Color, a, b render.Point) {

// DrawRect draws a rectangle.
func (e *Engine) DrawRect(color render.Color, rect render.Rect) {
e.canvas.ctx2d.Set("strokeStyle", color.ToHex())
e.canvas.ctx2d.Set("strokeStyle", RGBA(color))
e.canvas.ctx2d.Call("strokeRect",
int(rect.X),
int(rect.Y),
@@ -56,7 +67,7 @@ func (e *Engine) DrawRect(color render.Color, rect render.Rect) {

// DrawBox draws a filled rectangle.
func (e *Engine) DrawBox(color render.Color, rect render.Rect) {
e.canvas.ctx2d.Set("fillStyle", color.ToHex())
e.canvas.ctx2d.Set("fillStyle", RGBA(color))
e.canvas.ctx2d.Call("fillRect",
int(rect.X),
int(rect.Y),


+ 46
- 2
canvas/engine.go View File

@@ -1,10 +1,13 @@
package canvas

import (
"errors"
"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
@@ -63,12 +66,53 @@ 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
width int
height int
}

// 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) {
return nil, nil
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
}

return nil, errors.New("no bitmap data stored for " + filename)

}

func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
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)

// 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.


+ 13
- 14
canvas/events.go View File

@@ -4,7 +4,6 @@ import (
"syscall/js"

"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/pkg/log"
)

// EventClass to categorize JavaScript events.
@@ -13,6 +12,7 @@ type EventClass int
// EventClass values.
const (
MouseEvent EventClass = iota
ClickEvent
KeyEvent
ResizeEvent
)
@@ -69,8 +69,6 @@ func (e *Engine) AddEventListeners() {
which = args[0].Get("which").Int()
)

log.Info("Clicked at %d,%d", x, y)

// Is a mouse button pressed down?
checkDown := func(number int) bool {
if which == number {
@@ -81,7 +79,7 @@ func (e *Engine) AddEventListeners() {

e.queue <- Event{
Name: ev,
Class: MouseEvent,
Class: ClickEvent,
X: x,
Y: y,
LeftClick: checkDown(1),
@@ -149,22 +147,26 @@ func (e *Engine) PollEvent() *Event {
func (e *Engine) Poll() (*events.State, error) {
s := e.events

if e.events.EnterKey.Now {
log.Info("saw enter key here, good")
}
if e.events.KeyName.Now == "h" {
log.Info("saw letter h here, good")
}

for event := e.PollEvent(); event != nil; event = e.PollEvent() {
switch event.Class {
case MouseEvent:
s.CursorX.Push(int32(event.X))
s.CursorY.Push(int32(event.Y))
case ClickEvent:
s.CursorX.Push(int32(event.X))
s.CursorY.Push(int32(event.Y))
s.Button1.Push(event.LeftClick)
s.Button2.Push(event.RightClick)
case KeyEvent:
switch event.KeyName {
case "Escape":
if event.Repeat {
continue
}

if event.State {
s.EscapeKey.Push(true)
}
case "Enter":
if event.Repeat {
continue
@@ -196,15 +198,12 @@ func (e *Engine) Poll() (*events.State, error) {
s.KeyName.Push(`\b`)
}
default:
log.Info("default handler, push key %s", event.KeyName)
if event.State {
s.KeyName.Push(event.KeyName)
} else {
s.KeyName.Push("")
}
}

log.Info("event end, stored key=%s", s.KeyName.Now)
}
}



Loading…
Cancel
Save