WIP Finishing Up Texture Caching System

Apart from putting the cached bitmaps in a better place, this about
finishes up the texture caching optimization and IT IS FAST!

When I spam drag a lot of pixels around the FPS may drop to the 40's but
once the caches are warmed up the FPS returns to 60 and stays there,
even if the screen is very busy with pixels.

An undocumented debug feature: set the environment variable
DEBUG_CHUNK_COLOR='#00FFFF' to set a bitmap background color besides
white to be used when caching the chunks. It helps to visualize where on
the screen the bitmaps are being used. May go away in the future.

Changes:

- Found that the old default chunk size of 1000 was slow to generate
  bitmap images to cache. The 100px test size was fast and 128 sounds
  like a good middle ground number to pick for now.
- Fixed all the problems with scroll behavior and offset by inverting
  the sign of the scroll behavior. Scrolling to the Right and Down
  actually subtracts X,Y values instead of adds them.
This commit is contained in:
Noah 2018-10-17 23:01:21 -07:00
parent 279a980106
commit 97394f6cdb
7 changed files with 50 additions and 42 deletions

26
balance/debug.go Normal file
View File

@ -0,0 +1,26 @@
package balance
import (
"os"
"git.kirsle.net/apps/doodle/render"
)
// Debug related variables that can toggle on or off certain features and
// overlays within the game.
var (
/***************
* Visualizers *
***************/
// Background color to use when exporting a drawing Chunk as a bitmap image
// on disk. Default is white. Setting this to translucent yellow is a great
// way to visualize the chunks loaded from cache on your screen.
DebugChunkBitmapBackground = render.White // XXX: export $DEBUG_CHUNK_COLOR
)
func init() {
if color := os.Getenv("DEBUG_CHUNK_COLOR"); color != "" {
DebugChunkBitmapBackground = render.MustHexColor(color)
}
}

View File

@ -10,7 +10,7 @@ var (
CanvasScrollSpeed int32 = 8 CanvasScrollSpeed int32 = 8
// Default chunk size for canvases. // Default chunk size for canvases.
ChunkSize = 100 ChunkSize = 128
// Default size for a new Doodad. // Default size for a new Doodad.
DoodadSize = 100 DoodadSize = 100

8
level/base_test.go Normal file
View File

@ -0,0 +1,8 @@
package level_test
import "github.com/kirsle/golog"
func init() {
log := golog.GetLogger("doodle")
log.Config.Level = golog.ErrorLevel
}

View File

@ -7,6 +7,7 @@ import (
"math" "math"
"os" "os"
"git.kirsle.net/apps/doodle/balance"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
) )
@ -103,15 +104,15 @@ func (c *Chunk) ToBitmap(filename string) error {
// Blank out the pixels. // Blank out the pixels.
for x := 0; x < img.Bounds().Max.X; x++ { for x := 0; x < img.Bounds().Max.X; x++ {
for y := 0; y < img.Bounds().Max.Y; y++ { for y := 0; y < img.Bounds().Max.Y; y++ {
img.Set(x, y, render.RGBA(255, 255, 0, 153).ToColor()) img.Set(x, y, balance.DebugChunkBitmapBackground.ToColor())
} }
} }
// Pixel coordinate offset to map the Chunk World Position to the // Pixel coordinate offset to map the Chunk World Position to the
// smaller image boundaries. // smaller image boundaries.
pointOffset := render.Point{ pointOffset := render.Point{
X: int32(math.Abs(float64(c.Point.X * int32(c.Size)))), X: int32(c.Point.X * int32(c.Size)),
Y: int32(math.Abs(float64(c.Point.Y * int32(c.Size)))), Y: int32(c.Point.Y * int32(c.Size)),
} }
// Blot all the pixels onto it. // Blot all the pixels onto it.
@ -181,8 +182,6 @@ func (c *Chunk) Rect() render.Rect {
func (c *Chunk) SizePositive() render.Rect { func (c *Chunk) SizePositive() render.Rect {
S := c.Rect() S := c.Rect()
return render.Rect{ return render.Rect{
X: c.Point.X * int32(c.Size),
Y: c.Point.Y * int32(c.Size),
W: int32(math.Abs(float64(S.X))) + S.W, W: int32(math.Abs(float64(S.X))) + S.W,
H: int32(math.Abs(float64(S.Y))) + S.H, H: int32(math.Abs(float64(S.Y))) + S.H,
} }

View File

@ -74,6 +74,7 @@ func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point {
pipe := make(chan render.Point) pipe := make(chan render.Point)
go func() { go func() {
sent := make(map[render.Point]interface{}) sent := make(map[render.Point]interface{})
for x := viewport.X; x < viewport.W; x += int32(c.Size / 4) { for x := viewport.X; x < viewport.W; x += int32(c.Size / 4) {
for y := viewport.Y; y < viewport.H; y += int32(c.Size / 4) { for y := viewport.Y; y < viewport.H; y += int32(c.Size / 4) {
@ -94,27 +95,17 @@ func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point {
// Translate to a chunk coordinate, dedupe and send it. // Translate to a chunk coordinate, dedupe and send it.
coord := c.ChunkCoordinate(render.NewPoint(x, y)) coord := c.ChunkCoordinate(render.NewPoint(x, y))
// fmt.Printf("IterViewportChunks: x=%d y=%d chunk=%s\n", x, y, coord)
if _, ok := sent[coord]; ok { if _, ok := sent[coord]; ok {
continue continue
} }
sent[coord] = nil sent[coord] = nil
if _, ok := c.GetChunk(coord); ok { if _, ok := c.GetChunk(coord); ok {
fmt.Printf("Iter: send chunk %s for point %s\n", coord, point)
pipe <- coord pipe <- coord
} }
} }
} }
// for cx := topLeft.X; cx <= bottomRight.X; cx++ {
// for cy := topLeft.Y; cy <= bottomRight.Y; cy++ {
// pt := render.NewPoint(cx, cy)
// if _, ok := c.GetChunk(pt); ok {
// pipe <- pt
// }
// }
// }
close(pipe) close(pipe)
}() }()
return pipe return pipe

View File

@ -32,8 +32,6 @@ func (t *Texture) Size() render.Rect {
// NewBitmap initializes a texture from a bitmap image. // NewBitmap initializes a texture from a bitmap image.
func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) { func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) {
log.Debug("NewBitmap: open from file %s", filename)
surface, err := sdl.LoadBMP(filename) surface, err := sdl.LoadBMP(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("NewBitmap: LoadBMP: %s", err) return nil, fmt.Errorf("NewBitmap: LoadBMP: %s", err)
@ -45,7 +43,6 @@ func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) {
return nil, fmt.Errorf("NewBitmap: create texture: %s", err) return nil, fmt.Errorf("NewBitmap: create texture: %s", err)
} }
log.Debug("Created texture")
return &Texture{ return &Texture{
width: surface.W, width: surface.W,
height: surface.H, height: surface.H,

View File

@ -107,14 +107,14 @@ func (w *Canvas) Loop(ev *events.State) error {
// Arrow keys to scroll the view. // Arrow keys to scroll the view.
scrollBy := render.Point{} scrollBy := render.Point{}
if ev.Right.Now { if ev.Right.Now {
scrollBy.X += balance.CanvasScrollSpeed
} else if ev.Left.Now {
scrollBy.X -= balance.CanvasScrollSpeed scrollBy.X -= balance.CanvasScrollSpeed
} else if ev.Left.Now {
scrollBy.X += balance.CanvasScrollSpeed
} }
if ev.Down.Now { if ev.Down.Now {
scrollBy.Y += balance.CanvasScrollSpeed
} else if ev.Up.Now {
scrollBy.Y -= balance.CanvasScrollSpeed scrollBy.Y -= balance.CanvasScrollSpeed
} else if ev.Up.Now {
scrollBy.Y += balance.CanvasScrollSpeed
} }
if !scrollBy.IsZero() { if !scrollBy.IsZero() {
w.ScrollBy(scrollBy) w.ScrollBy(scrollBy)
@ -136,8 +136,8 @@ func (w *Canvas) Loop(ev *events.State) error {
if ev.Button1.Now { if ev.Button1.Now {
lastPixel := w.lastPixel lastPixel := w.lastPixel
cursor := render.Point{ cursor := render.Point{
X: ev.CursorX.Now - P.X + w.Scroll.X, X: ev.CursorX.Now - P.X - w.Scroll.X,
Y: ev.CursorY.Now - P.Y + w.Scroll.Y, Y: ev.CursorY.Now - P.Y - w.Scroll.Y,
} }
pixel := &level.Pixel{ pixel := &level.Pixel{
X: cursor.X, X: cursor.X,
@ -184,10 +184,10 @@ func (w *Canvas) Loop(ev *events.State) error {
func (w *Canvas) Viewport() render.Rect { func (w *Canvas) Viewport() render.Rect {
var S = w.Size() var S = w.Size()
return render.Rect{ return render.Rect{
X: w.Scroll.X, X: -w.Scroll.X,
Y: w.Scroll.Y, Y: -w.Scroll.Y,
W: S.W - w.BoxThickness(2) + w.Scroll.X, W: S.W - w.Scroll.X,
H: S.H - w.BoxThickness(2) + w.Scroll.Y, H: S.H - w.Scroll.Y,
} }
} }
@ -258,13 +258,6 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
H: src.H, // - w.Scroll.Y, H: src.H, // - w.Scroll.Y,
} }
// log.Warn(
// "chunk: %s src: %s dst: %s",
// coord,
// src,
// dst,
// )
// If the destination width will cause it to overflow the widget // If the destination width will cause it to overflow the widget
// box, trim off the right edge of the destination rect. // box, trim off the right edge of the destination rect.
// //
@ -316,12 +309,6 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
src.Y += delta src.Y += delta
} }
// If the destination rect would overflow our widget bounds, trim
// it off.
// if w.Name == "edit-canvas" {
// log.Info("%s: copy %+v -> %+v", w.Name, src, dst)
// }
e.Copy(tex, src, dst) e.Copy(tex, src, dst)
} }
} }