From 97394f6cdba2c39b41da78515f6e58ec56b4e008 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 17 Oct 2018 23:01:21 -0700 Subject: [PATCH] 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. --- balance/debug.go | 26 ++++++++++++++++++++++++++ balance/numbers.go | 2 +- level/base_test.go | 8 ++++++++ level/chunk.go | 9 ++++----- level/chunker.go | 11 +---------- render/sdl/texture.go | 3 --- uix/canvas.go | 33 ++++++++++----------------------- 7 files changed, 50 insertions(+), 42 deletions(-) create mode 100644 balance/debug.go create mode 100644 level/base_test.go diff --git a/balance/debug.go b/balance/debug.go new file mode 100644 index 0000000..0b8f86e --- /dev/null +++ b/balance/debug.go @@ -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) + } +} diff --git a/balance/numbers.go b/balance/numbers.go index 49f50af..a491619 100644 --- a/balance/numbers.go +++ b/balance/numbers.go @@ -10,7 +10,7 @@ var ( CanvasScrollSpeed int32 = 8 // Default chunk size for canvases. - ChunkSize = 100 + ChunkSize = 128 // Default size for a new Doodad. DoodadSize = 100 diff --git a/level/base_test.go b/level/base_test.go new file mode 100644 index 0000000..ffcc26a --- /dev/null +++ b/level/base_test.go @@ -0,0 +1,8 @@ +package level_test + +import "github.com/kirsle/golog" + +func init() { + log := golog.GetLogger("doodle") + log.Config.Level = golog.ErrorLevel +} diff --git a/level/chunk.go b/level/chunk.go index efd80e2..5349795 100644 --- a/level/chunk.go +++ b/level/chunk.go @@ -7,6 +7,7 @@ import ( "math" "os" + "git.kirsle.net/apps/doodle/balance" "git.kirsle.net/apps/doodle/render" "golang.org/x/image/bmp" ) @@ -103,15 +104,15 @@ func (c *Chunk) ToBitmap(filename string) error { // Blank out the pixels. for x := 0; x < img.Bounds().Max.X; x++ { 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 // smaller image boundaries. pointOffset := render.Point{ - X: int32(math.Abs(float64(c.Point.X * int32(c.Size)))), - Y: int32(math.Abs(float64(c.Point.Y * int32(c.Size)))), + X: int32(c.Point.X * int32(c.Size)), + Y: int32(c.Point.Y * int32(c.Size)), } // Blot all the pixels onto it. @@ -181,8 +182,6 @@ func (c *Chunk) Rect() render.Rect { func (c *Chunk) SizePositive() render.Rect { S := c.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, H: int32(math.Abs(float64(S.Y))) + S.H, } diff --git a/level/chunker.go b/level/chunker.go index 316fafa..127c5db 100644 --- a/level/chunker.go +++ b/level/chunker.go @@ -74,6 +74,7 @@ func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point { pipe := make(chan render.Point) go func() { sent := make(map[render.Point]interface{}) + for x := viewport.X; x < viewport.W; x += 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. 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 { continue } sent[coord] = nil if _, ok := c.GetChunk(coord); ok { - fmt.Printf("Iter: send chunk %s for point %s\n", coord, point) 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) }() return pipe diff --git a/render/sdl/texture.go b/render/sdl/texture.go index f6e1630..5d7ad39 100644 --- a/render/sdl/texture.go +++ b/render/sdl/texture.go @@ -32,8 +32,6 @@ func (t *Texture) Size() render.Rect { // NewBitmap initializes a texture from a bitmap image. func (r *Renderer) NewBitmap(filename string) (render.Texturer, error) { - log.Debug("NewBitmap: open from file %s", filename) - surface, err := sdl.LoadBMP(filename) if err != nil { 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) } - log.Debug("Created texture") return &Texture{ width: surface.W, height: surface.H, diff --git a/uix/canvas.go b/uix/canvas.go index e25f1ad..82bce3e 100644 --- a/uix/canvas.go +++ b/uix/canvas.go @@ -107,14 +107,14 @@ func (w *Canvas) Loop(ev *events.State) error { // Arrow keys to scroll the view. scrollBy := render.Point{} if ev.Right.Now { - scrollBy.X += balance.CanvasScrollSpeed - } else if ev.Left.Now { scrollBy.X -= balance.CanvasScrollSpeed + } else if ev.Left.Now { + scrollBy.X += balance.CanvasScrollSpeed } if ev.Down.Now { - scrollBy.Y += balance.CanvasScrollSpeed - } else if ev.Up.Now { scrollBy.Y -= balance.CanvasScrollSpeed + } else if ev.Up.Now { + scrollBy.Y += balance.CanvasScrollSpeed } if !scrollBy.IsZero() { w.ScrollBy(scrollBy) @@ -136,8 +136,8 @@ func (w *Canvas) Loop(ev *events.State) error { if ev.Button1.Now { lastPixel := w.lastPixel cursor := render.Point{ - X: ev.CursorX.Now - P.X + w.Scroll.X, - Y: ev.CursorY.Now - P.Y + w.Scroll.Y, + X: ev.CursorX.Now - P.X - w.Scroll.X, + Y: ev.CursorY.Now - P.Y - w.Scroll.Y, } pixel := &level.Pixel{ X: cursor.X, @@ -184,10 +184,10 @@ func (w *Canvas) Loop(ev *events.State) error { func (w *Canvas) Viewport() render.Rect { var S = w.Size() return render.Rect{ - X: w.Scroll.X, - Y: w.Scroll.Y, - W: S.W - w.BoxThickness(2) + w.Scroll.X, - H: S.H - w.BoxThickness(2) + w.Scroll.Y, + X: -w.Scroll.X, + Y: -w.Scroll.Y, + W: S.W - w.Scroll.X, + 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, } - // log.Warn( - // "chunk: %s src: %s dst: %s", - // coord, - // src, - // dst, - // ) - // If the destination width will cause it to overflow the widget // 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 } - // 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) } }