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.
This commit is contained in:
parent
03b4441eaa
commit
27b908e40a
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
func (e *Engine) NewBitmap(filename string) (render.Texturer, error) {
|
||||
return nil, 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
|
||||
}
|
||||
|
||||
func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
|
||||
// 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 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)
|
||||
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
@ -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…
Reference in New Issue
Block a user