From 49b9a190f2d969295faa488d53a67d8627d9bbe0 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sat, 28 Dec 2019 13:46:08 -0800 Subject: [PATCH] Render to surface, not ebiten texture (#268) * Render to surface, not ebiten texture * Fix debug text --- d2asset/animation.go | 17 ++--- d2asset/composite.go | 6 +- d2core/d2scene/character_select.go | 11 +-- d2core/d2scene/credits.go | 4 +- d2core/d2scene/game.go | 6 +- d2core/d2scene/main_menu.go | 5 +- d2core/d2scene/map_engine_testing.go | 33 +++++---- d2core/d2scene/select_hero_class.go | 8 +-- d2core/engine.go | 36 ++++++---- d2core/hero.go | 6 +- d2core/npc.go | 6 +- d2corecommon/d2coreinterface/drawable.go | 4 +- d2corecommon/d2coreinterface/scene.go | 6 +- d2player/game_controls.go | 5 +- d2render/animated_entity.go | 15 ++-- d2render/d2mapengine/engine.go | 9 ++- d2render/d2mapengine/region.go | 92 ++++++++++++++---------- d2render/d2surface/surface.go | 57 +++++++++++++-- d2render/d2ui/Scrollbar.go | 3 +- d2render/d2ui/button.go | 52 +++++++------- d2render/d2ui/checkbox.go | 25 ++++--- d2render/d2ui/font.go | 7 +- d2render/d2ui/label.go | 24 ++++--- d2render/d2ui/manager.go | 10 +-- d2render/d2ui/textbox.go | 3 +- d2render/sprite.go | 20 +++--- main.go | 11 ++- 27 files changed, 280 insertions(+), 201 deletions(-) diff --git a/d2asset/animation.go b/d2asset/animation.go index b4fe0345..caf0589f 100644 --- a/d2asset/animation.go +++ b/d2asset/animation.go @@ -9,7 +9,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2data/d2dc6" "github.com/OpenDiablo2/D2Shared/d2data/d2dcc" "github.com/OpenDiablo2/D2Shared/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -192,18 +192,15 @@ func (a *Animation) Advance(elapsed float64) error { return nil } -func (a *Animation) Render(target *ebiten.Image, offsetX, offsetY int) error { +func (a *Animation) Render(target *d2surface.Surface) error { direction := a.directions[a.directionIndex] frame := direction.frames[a.frameIndex] - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(frame.offsetX+offsetX), float64(frame.offsetY+offsetY)) - opts.CompositeMode = a.compositeMode - if a.colorMod != nil { - opts.ColorM = d2corehelper.ColorToColorM(a.colorMod) - } - - return target.DrawImage(frame.image, opts) + target.PushTranslation(frame.offsetX, frame.offsetY) + target.PushCompositeMode(a.compositeMode) + target.PushColor(a.colorMod) + defer target.PopN(3) + return target.Render(frame.image) } func (a *Animation) GetFrameSize(frameIndex int) (int, int, error) { diff --git a/d2asset/composite.go b/d2asset/composite.go index 48e72add..e6647495 100644 --- a/d2asset/composite.go +++ b/d2asset/composite.go @@ -9,7 +9,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2data" "github.com/OpenDiablo2/D2Shared/d2data/d2datadict" "github.com/OpenDiablo2/D2Shared/d2data/d2dcc" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type Composite struct { @@ -45,7 +45,7 @@ func (c *Composite) Advance(elapsed float64) error { return nil } -func (c *Composite) Render(target *ebiten.Image, offsetX, offsetY int) error { +func (c *Composite) Render(target *d2surface.Surface) error { if c.mode == nil { return nil } @@ -53,7 +53,7 @@ func (c *Composite) Render(target *ebiten.Image, offsetX, offsetY int) error { for _, layerIndex := range c.mode.drawOrder[c.mode.frameIndex] { layer := c.mode.layers[layerIndex] if layer != nil { - if err := layer.Render(target, offsetX, offsetY); err != nil { + if err := layer.Render(target); err != nil { return err } } diff --git a/d2core/d2scene/character_select.go b/d2core/d2scene/character_select.go index 8d6c0187..668c83fc 100644 --- a/d2core/d2scene/character_select.go +++ b/d2core/d2scene/character_select.go @@ -6,8 +6,6 @@ import ( "os" "strings" - "github.com/hajimehoshi/ebiten/ebitenutil" - "github.com/OpenDiablo2/D2Shared/d2common" "github.com/OpenDiablo2/D2Shared/d2common/d2resource" dh "github.com/OpenDiablo2/D2Shared/d2helper" @@ -15,6 +13,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core" "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" "github.com/hajimehoshi/ebiten" ) @@ -195,7 +194,7 @@ func (v *CharacterSelect) onExitButtonClicked() { func (v *CharacterSelect) Unload() { } -func (v *CharacterSelect) Render(screen *ebiten.Image) { +func (v *CharacterSelect) Render(screen *d2surface.Surface) { v.background.RenderSegmented(screen, 4, 3, 0) v.d2HeroTitle.Render(screen) actualSelectionIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2) @@ -210,10 +209,12 @@ func (v *CharacterSelect) Render(screen *ebiten.Image) { v.characterNameLabel[i].Render(screen) v.characterStatsLabel[i].Render(screen) v.characterExpLabel[i].Render(screen) - v.characterImage[i].Render(screen, v.characterNameLabel[i].X-40, v.characterNameLabel[i].Y+50) + screen.PushTranslation(v.characterNameLabel[i].X-40, v.characterNameLabel[i].Y+50) + v.characterImage[i].Render(screen) + screen.Pop() } if v.showDeleteConfirmation { - ebitenutil.DrawRect(screen, 0.0, 0.0, 800.0, 600.0, color.RGBA{0, 0, 0, 128}) + screen.DrawRect(800, 600, color.RGBA{0, 0, 0, 128}) v.okCancelBox.RenderSegmented(screen, 2, 1, 0) v.deleteCharConfirmLabel.Render(screen) } diff --git a/d2core/d2scene/credits.go b/d2core/d2scene/credits.go index 506c6c98..bb423400 100644 --- a/d2core/d2scene/credits.go +++ b/d2core/d2scene/credits.go @@ -18,8 +18,8 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common" dh "github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" - "github.com/hajimehoshi/ebiten" ) type labelItem struct { @@ -107,7 +107,7 @@ func (v *Credits) Unload() { } // Render renders the credits scene -func (v *Credits) Render(screen *ebiten.Image) { +func (v *Credits) Render(screen *d2surface.Surface) { v.creditsBackground.RenderSegmented(screen, 4, 3, 0) for _, label := range v.labels { if label.Available { diff --git a/d2core/d2scene/game.go b/d2core/d2scene/game.go index 772aa9db..b815a8cd 100644 --- a/d2core/d2scene/game.go +++ b/d2core/d2scene/game.go @@ -11,8 +11,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" - "github.com/hajimehoshi/ebiten" ) type Game struct { @@ -87,8 +87,8 @@ func (v *Game) Load() []func() { func (v *Game) Unload() { } -func (v Game) Render(screen *ebiten.Image) { - screen.Fill(color.Black) +func (v Game) Render(screen *d2surface.Surface) { + screen.Clear(color.Black) v.mapEngine.Render(screen) v.gameControls.Render(screen) } diff --git a/d2core/d2scene/main_menu.go b/d2core/d2scene/main_menu.go index d0106492..f795fc4d 100644 --- a/d2core/d2scene/main_menu.go +++ b/d2core/d2scene/main_menu.go @@ -18,9 +18,8 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" - - "github.com/hajimehoshi/ebiten" ) // MainMenu represents the main menu @@ -223,7 +222,7 @@ func (v *MainMenu) Unload() { } // Render renders the main menu -func (v *MainMenu) Render(screen *ebiten.Image) { +func (v *MainMenu) Render(screen *d2surface.Surface) { if v.ShowTrademarkScreen { v.trademarkBackground.RenderSegmented(screen, 4, 3, 0) } else { diff --git a/d2core/d2scene/map_engine_testing.go b/d2core/d2scene/map_engine_testing.go index 02086d1d..cd573e85 100644 --- a/d2core/d2scene/map_engine_testing.go +++ b/d2core/d2scene/map_engine_testing.go @@ -1,7 +1,6 @@ package d2scene import ( - "fmt" "math" "os" @@ -12,9 +11,9 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/inpututil" ) @@ -160,7 +159,7 @@ func (v *MapEngineTest) Unload() { } -func (v *MapEngineTest) Render(screen *ebiten.Image) { +func (v *MapEngineTest) Render(screen *d2surface.Surface) { v.mapEngine.Render(screen) screenX := v.uiManager.CursorX screenY := v.uiManager.CursorY @@ -173,14 +172,6 @@ func (v *MapEngineTest) Render(screen *ebiten.Image) { } tileRect := curRegion.GetTileRect() - line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)", - screenX, - screenY, - int(math.Floor(worldX))-tileRect.Left, - subtileX, - int(math.Floor(worldY))-tileRect.Top, - subtileY, - ) levelFilesToPick := make([]string, 0) fileIndex := v.fileIndex @@ -199,12 +190,20 @@ func (v *MapEngineTest) Render(screen *ebiten.Image) { v.fileIndex = fileIndex } v.filesCount = len(levelFilesToPick) - ebitenutil.DebugPrintAt(screen, line, 5, 5) - ebitenutil.DebugPrintAt(screen, "Map: "+curRegion.GetLevelType().Name, 5, 17) - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%v: %v/%v [%v, %v]", regionPath, fileIndex+1, v.filesCount, v.currentRegion, v.levelPreset), 5, 29) - ebitenutil.DebugPrintAt(screen, "N - next region, P - previous region", 5, 41) - ebitenutil.DebugPrintAt(screen, "Shift+N - next preset, Shift+P - previous preset", 5, 53) - ebitenutil.DebugPrintAt(screen, "Ctrl+N - next file, Ctrl+P - previous file", 5, 65) + + screen.PushTranslation(5, 5) + screen.DrawText("%d, %d (Tile %d.%d, %d.%d)", screenX, screenY, int(math.Floor(worldX))-tileRect.Left, subtileX, int(math.Floor(worldY))-tileRect.Top, subtileY) + screen.PushTranslation(0, 16) + screen.DrawText("Map: " + curRegion.GetLevelType().Name) + screen.PushTranslation(0, 16) + screen.DrawText("%v: %v/%v [%v, %v]", regionPath, fileIndex+1, v.filesCount, v.currentRegion, v.levelPreset) + screen.PushTranslation(0, 16) + screen.DrawText("N - next region, P - previous region") + screen.PushTranslation(0, 16) + screen.DrawText("Shift+N - next preset, Shift+P - previous preset") + screen.PushTranslation(0, 16) + screen.DrawText("Ctrl+N - next file, Ctrl+P - previous file") + screen.PopN(6) } func (v *MapEngineTest) Update(tickTime float64) { diff --git a/d2core/d2scene/select_hero_class.go b/d2core/d2scene/select_hero_class.go index 86d771d3..b928d5d6 100644 --- a/d2core/d2scene/select_hero_class.go +++ b/d2core/d2scene/select_hero_class.go @@ -16,8 +16,8 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common" dh "github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" - "github.com/hajimehoshi/ebiten" ) type HeroRenderInfo struct { @@ -444,7 +444,7 @@ func (v SelectHeroClass) onOkButtonClicked() { v.sceneProvider.SetNextScene(CreateGame(v.sceneProvider, v.uiManager, v.soundManager, gameState)) } -func (v *SelectHeroClass) Render(screen *ebiten.Image) { +func (v *SelectHeroClass) Render(screen *d2surface.Surface) { v.bgImage.RenderSegmented(screen, 4, 3, 0) v.headingLabel.Render(screen) if v.selectedHero != d2enum.HeroNone { @@ -560,7 +560,7 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero d2enum.Hero, canSelect b } -func (v *SelectHeroClass) renderHero(screen *ebiten.Image, hero d2enum.Hero) { +func (v *SelectHeroClass) renderHero(screen *d2surface.Surface, hero d2enum.Hero) { renderInfo := v.heroRenderInfo[hero] switch renderInfo.Stance { case d2enum.HeroStanceIdle: @@ -647,7 +647,7 @@ func setSpriteToFirstFrame(sprite *d2render.Sprite) { } } -func drawSprite(sprite *d2render.Sprite, target *ebiten.Image) { +func drawSprite(sprite *d2render.Sprite, target *d2surface.Surface) { if sprite != nil { sprite.Render(target) } diff --git a/d2core/engine.go b/d2core/engine.go index 335a327e..bbd5c6ef 100644 --- a/d2core/engine.go +++ b/d2core/engine.go @@ -28,7 +28,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/inpututil" ) @@ -176,30 +175,41 @@ func (v *Engine) Update() { } // Draw draws the game -func (v Engine) Draw(screen *ebiten.Image) { +func (v Engine) Draw(target *d2surface.Surface) { if v.loadingProgress < 1.0 { v.LoadingSprite.SetCurrentFrame(int(d2helper.Max(0, d2helper.Min(uint32(v.LoadingSprite.GetFrameCount()-1), uint32(float64(v.LoadingSprite.GetFrameCount()-1)*v.loadingProgress))))) - v.LoadingSprite.Render(screen) + v.LoadingSprite.Render(target) } else { if v.CurrentScene == nil { log.Fatal("no scene loaded") } - v.CurrentScene.Render(screen) - v.UIManager.Render(screen) + v.CurrentScene.Render(target) + v.UIManager.Render(target) } if v.showFPS { - ebitenutil.DebugPrintAt(screen, "vsync:"+strconv.FormatBool(ebiten.IsVsyncEnabled())+"\nFPS:"+strconv.Itoa(int(ebiten.CurrentFPS())), 5, 565) + target.PushTranslation(5, 565) + target.DrawText("vsync:" + strconv.FormatBool(ebiten.IsVsyncEnabled()) + "\nFPS:" + strconv.Itoa(int(ebiten.CurrentFPS()))) + target.Pop() + + cx, cy := ebiten.CursorPosition() + var m runtime.MemStats runtime.ReadMemStats(&m) - ebitenutil.DebugPrintAt(screen, "Alloc "+strconv.FormatInt(int64(m.Alloc)/1024/1024, 10), 680, 0) - ebitenutil.DebugPrintAt(screen, "Pause "+strconv.FormatInt(int64(m.PauseTotalNs/1024/1024), 10), 680, 10) - ebitenutil.DebugPrintAt(screen, "HeapSys "+strconv.FormatInt(int64(m.HeapSys/1024/1024), 10), 680, 20) - ebitenutil.DebugPrintAt(screen, "NumGC "+strconv.FormatInt(int64(m.NumGC), 10), 680, 30) - cx, cy := ebiten.CursorPosition() - ebitenutil.DebugPrintAt(screen, "Coords "+strconv.FormatInt(int64(cx), 10)+","+strconv.FormatInt(int64(cy), 10), 680, 40) + + target.PushTranslation(680, 0) + target.DrawText("Alloc " + strconv.FormatInt(int64(m.Alloc)/1024/1024, 10)) + target.PushTranslation(0, 16) + target.DrawText("Pause " + strconv.FormatInt(int64(m.PauseTotalNs/1024/1024), 10)) + target.PushTranslation(0, 16) + target.DrawText("HeapSys " + strconv.FormatInt(int64(m.HeapSys/1024/1024), 10)) + target.PushTranslation(0, 16) + target.DrawText("NumGC " + strconv.FormatInt(int64(m.NumGC), 10)) + target.PushTranslation(0, 16) + target.DrawText("Coords " + strconv.FormatInt(int64(cx), 10) + "," + strconv.FormatInt(int64(cy), 10)) + target.PopN(5) } - d2term.Render(d2surface.CreateSurface(screen)) + d2term.Render(target) } // SetNextScene tells the engine what scene to load on the next update cycle diff --git a/d2core/hero.go b/d2core/hero.go index 3c9e45f1..732df49e 100644 --- a/d2core/hero.go +++ b/d2core/hero.go @@ -5,7 +5,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/D2Shared/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type Hero struct { @@ -52,8 +52,8 @@ func (v *Hero) Advance(tickTime float64) { v.AnimatedEntity.Advance(tickTime) } -func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) { - v.AnimatedEntity.Render(target, offsetX, offsetY) +func (v *Hero) Render(target *d2surface.Surface) { + v.AnimatedEntity.Render(target) } func (v *Hero) GetPosition() (float64, float64) { diff --git a/d2core/npc.go b/d2core/npc.go index c077d57c..0a7a6caa 100644 --- a/d2core/npc.go +++ b/d2core/npc.go @@ -5,7 +5,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/D2Shared/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type NPC struct { @@ -44,8 +44,8 @@ func (v *NPC) SetPaths(paths []d2common.Path) { v.HasPaths = len(paths) > 0 } -func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) { - v.AnimatedEntity.Render(target, offsetX, offsetY) +func (v *NPC) Render(target *d2surface.Surface) { + v.AnimatedEntity.Render(target) } func (v *NPC) GetPosition() (float64, float64) { diff --git a/d2corecommon/d2coreinterface/drawable.go b/d2corecommon/d2coreinterface/drawable.go index 32e2acbb..9078d01a 100644 --- a/d2corecommon/d2coreinterface/drawable.go +++ b/d2corecommon/d2coreinterface/drawable.go @@ -1,10 +1,10 @@ package d2coreinterface -import "github.com/hajimehoshi/ebiten" +import "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" // Drawable represents an instance that can be drawn type Drawable interface { - Render(target *ebiten.Image) + Render(target *d2surface.Surface) GetSize() (width, height int) SetPosition(x, y int) GetPosition() (x, y int) diff --git a/d2corecommon/d2coreinterface/scene.go b/d2corecommon/d2coreinterface/scene.go index d305bcba..1f646190 100644 --- a/d2corecommon/d2coreinterface/scene.go +++ b/d2corecommon/d2coreinterface/scene.go @@ -1,13 +1,11 @@ package d2coreinterface -import ( - "github.com/hajimehoshi/ebiten" -) +import "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" // Scene defines the function necessary for scene management type Scene interface { Load() []func() Unload() - Render(screen *ebiten.Image) + Render(target *d2surface.Surface) Update(tickTime float64) } diff --git a/d2player/game_controls.go b/d2player/game_controls.go index a73d14c2..331df9e4 100644 --- a/d2player/game_controls.go +++ b/d2player/game_controls.go @@ -5,6 +5,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core" "github.com/OpenDiablo2/OpenDiablo2/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -67,8 +68,8 @@ func (g *GameControls) Load() { } // TODO: consider caching the panels to single image that is reused. -func (g *GameControls) Render(target *ebiten.Image) { - width, height := target.Size() +func (g *GameControls) Render(target *d2surface.Surface) { + width, height := target.GetSize() offset := int(0) // Left globe holder diff --git a/d2render/animated_entity.go b/d2render/animated_entity.go index dcc05ef2..8c6f9cd6 100644 --- a/d2render/animated_entity.go +++ b/d2render/animated_entity.go @@ -8,7 +8,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2data/d2datadict" "github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) // AnimatedEntity represents an entity on the map that can be animated @@ -70,14 +70,13 @@ func (v AnimatedEntity) Wait() bool { } // Render draws this animated entity onto the target -func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) { - localX := (v.subcellX - v.subcellY) * 16 - localY := ((v.subcellX + v.subcellY) * 8) - 5 - v.composite.Render( - target, - int(v.offsetX)+offsetX+int(localX), - int(v.offsetY)+offsetY+int(localY), +func (v *AnimatedEntity) Render(target *d2surface.Surface) { + target.PushTranslation( + int(v.offsetX)+int((v.subcellX-v.subcellY)*16), + int(v.offsetY)+int(((v.subcellX+v.subcellY)*8)-5), ) + defer target.Pop() + v.composite.Render(target) } func (v AnimatedEntity) GetDirection() int { diff --git a/d2render/d2mapengine/engine.go b/d2render/d2mapengine/engine.go index b646d30e..e9e7c683 100644 --- a/d2render/d2mapengine/engine.go +++ b/d2render/d2mapengine/engine.go @@ -3,17 +3,16 @@ package d2mapengine import ( "strings" - "github.com/hajimehoshi/ebiten" - "github.com/OpenDiablo2/D2Shared/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2core" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type MapEntity interface { - Render(target *ebiten.Image, screenX, screenY int) - GetPosition() (float64, float64) + Render(target *d2surface.Surface) Advance(tickTime float64) + GetPosition() (float64, float64) } type MapEngine struct { @@ -134,7 +133,7 @@ func (me *MapEngine) Advance(tickTime float64) { } } -func (me *MapEngine) Render(target *ebiten.Image) { +func (me *MapEngine) Render(target *d2surface.Surface) { for _, region := range me.regions { if region.isVisbile(me.viewport) { region.renderPass1(me.viewport, target) diff --git a/d2render/d2mapengine/region.go b/d2render/d2mapengine/region.go index ddbbb57a..45b91373 100644 --- a/d2render/d2mapengine/region.go +++ b/d2render/d2mapengine/region.go @@ -1,7 +1,6 @@ package d2mapengine import ( - "fmt" "image/color" "log" "math" @@ -9,7 +8,6 @@ import ( "strconv" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/OpenDiablo2/D2Shared/d2common" "github.com/OpenDiablo2/D2Shared/d2common/d2enum" @@ -20,8 +18,8 @@ import ( "github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type MapRegion struct { @@ -234,7 +232,7 @@ func (mr *MapRegion) getTileWorldPosition(tileX, tileY int) (float64, float64) { return float64(tileX + mr.tileRect.Left), float64(tileY + mr.tileRect.Top) } -func (mr *MapRegion) renderPass1(viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderPass1(viewport *Viewport, target *d2surface.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -247,7 +245,7 @@ func (mr *MapRegion) renderPass1(viewport *Viewport, target *ebiten.Image) { } } -func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, target *d2surface.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -258,8 +256,9 @@ func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, targe for _, entity := range entities { entWorldX, entWorldY := entity.GetPosition() if entWorldX == worldX && entWorldY == worldY { - screenX, screenY := viewport.GetTranslationScreen() - entity.Render(target, screenX, screenY) + target.PushTranslation(viewport.GetTranslationScreen()) + entity.Render(target) + target.Pop() } } @@ -269,7 +268,7 @@ func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, targe } } -func (mr *MapRegion) renderPass3(viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderPass3(viewport *Viewport, target *d2surface.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -282,7 +281,7 @@ func (mr *MapRegion) renderPass3(viewport *Viewport, target *ebiten.Image) { } } -func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { for _, wall := range tile.Walls { if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() { mr.renderWall(wall, viewport, target) @@ -302,7 +301,7 @@ func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { for _, wall := range tile.Walls { if !wall.Hidden && wall.Type.UpperWall() { mr.renderWall(wall, viewport, target) @@ -310,7 +309,7 @@ func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { for _, wall := range tile.Walls { if wall.Type == d2enum.Roof { mr.renderWall(wall, viewport, target) @@ -318,7 +317,7 @@ func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *d2surface.Surface) { var img *ebiten.Image if !tile.Animated { img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) @@ -331,14 +330,15 @@ func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewpor } viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)) - screenX, screenY := viewport.GetTranslationScreen() - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(screenX), float64(screenY)) - target.DrawImage(img, opts) - viewport.PopTranslation() + defer viewport.PopTranslation() + + target.PushTranslation(viewport.GetTranslationScreen()) + defer target.Pop() + + target.Render(img) } -func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *d2surface.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type) @@ -346,14 +346,15 @@ func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, targe } viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)) - screenX, screenY := viewport.GetTranslationScreen() - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(screenX), float64(screenY)) - target.DrawImage(img, opts) - viewport.PopTranslation() + defer viewport.PopTranslation() + + target.PushTranslation(viewport.GetTranslationScreen()) + defer target.Pop() + + target.Render(img) } -func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *d2surface.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) @@ -361,15 +362,16 @@ func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewpo } viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)) - screenX, screenY := viewport.GetTranslationScreen() - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(screenX), float64(screenY)) - opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{255, 255, 255, 160}) - target.DrawImage(img, opts) - viewport.PopTranslation() + defer viewport.PopTranslation() + + target.PushTranslation(viewport.GetTranslationScreen()) + target.PushColor(color.RGBA{255, 255, 255, 160}) + defer target.PopN(2) + + target.Render(img) } -func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target *d2surface.Surface) { for tileY := range mr.ds1.Tiles { for tileX := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -380,7 +382,7 @@ func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target * } } -func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *Viewport, target *ebiten.Image) { +func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *Viewport, target *d2surface.Surface) { if debugVisLevel > 0 { subtileColor := color.RGBA{80, 80, 255, 100} tileColor := color.RGBA{255, 255, 255, 255} @@ -389,22 +391,34 @@ func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *View screenX2, screenY2 := viewport.WorldToScreen(float64(x+1), float64(y)) screenX3, screenY3 := viewport.WorldToScreen(float64(x), float64(y+1)) - ebitenutil.DrawLine(target, float64(screenX1), float64(screenY1), float64(screenX2), float64(screenY2), tileColor) - ebitenutil.DrawLine(target, float64(screenX1), float64(screenY1), float64(screenX3), float64(screenY3), tileColor) - ebitenutil.DebugPrintAt(target, fmt.Sprintf("%v,%v", x, y), screenX1-10, screenY1+10) + target.PushTranslation(screenX1, screenY1) + defer target.Pop() + + target.DrawLine(screenX2-screenX1, screenY2-screenY1, tileColor) + target.DrawLine(screenX3-screenX1, screenY3-screenY1, tileColor) + target.PushTranslation(-10, 10) + target.DrawText("%v, %v", x, y) + target.Pop() if debugVisLevel > 1 { for i := 1; i <= 4; i++ { x := i * 16 y := i * 8 - ebitenutil.DrawLine(target, float64(screenX1-x), float64(screenY1+y), float64(screenX1-x+80), float64(screenY1+y+40), subtileColor) - ebitenutil.DrawLine(target, float64(screenX1+x), float64(screenY1+y), float64(screenX1+x-80), float64(screenY1+y+40), subtileColor) + target.PushTranslation(-x, y) + target.DrawLine(80, 40, subtileColor) + target.Pop() + + target.PushTranslation(x, y) + target.DrawLine(-80, 40, subtileColor) + target.Pop() } tile := mr.ds1.Tiles[y][x] - for i := range tile.Floors { - ebitenutil.DebugPrintAt(target, fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence), screenX1-20, screenY1+10+(i+1)*14) + for i, floor := range tile.Floors { + target.PushTranslation(-20, 10+(i+1)*14) + target.DrawText("f: %v-%v", floor.Style, floor.Sequence) + target.Pop() } } } diff --git a/d2render/d2surface/surface.go b/d2render/d2surface/surface.go index cc4418b2..11055b7c 100644 --- a/d2render/d2surface/surface.go +++ b/d2render/d2surface/surface.go @@ -1,16 +1,20 @@ package d2surface import ( + "fmt" "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" ) type surfaceState struct { - x int - y int - mode ebiten.CompositeMode + x int + y int + mode ebiten.CompositeMode + filter ebiten.Filter + color color.Color } type Surface struct { @@ -23,7 +27,8 @@ func CreateSurface(image *ebiten.Image) *Surface { return &Surface{ image: image, stateCurrent: surfaceState{ - mode: ebiten.CompositeModeSourceOver, + filter: ebiten.FilterNearest, + mode: ebiten.CompositeModeSourceOver, }, } } @@ -39,6 +44,16 @@ func (s *Surface) PushCompositeMode(mode ebiten.CompositeMode) { s.stateCurrent.mode = mode } +func (s *Surface) PushFilter(filter ebiten.Filter) { + s.stateStack = append(s.stateStack, s.stateCurrent) + s.stateCurrent.filter = filter +} + +func (s *Surface) PushColor(color color.Color) { + s.stateStack = append(s.stateStack, s.stateCurrent) + s.stateCurrent.color = color +} + func (s *Surface) Pop() { count := len(s.stateStack) if count == 0 { @@ -49,14 +64,36 @@ func (s *Surface) Pop() { s.stateStack = s.stateStack[:count-1] } +func (s *Surface) PopN(n int) { + for i := 0; i < n; i++ { + s.Pop() + } +} + func (s *Surface) Render(image *ebiten.Image) error { opts := &ebiten.DrawImageOptions{CompositeMode: s.stateCurrent.mode} opts.GeoM.Translate(float64(s.stateCurrent.x), float64(s.stateCurrent.y)) + opts.Filter = s.stateCurrent.filter + if s.stateCurrent.color != nil { + opts.ColorM = d2corehelper.ColorToColorM(s.stateCurrent.color) + } + return s.image.DrawImage(image, opts) } -func (s *Surface) DrawText(text string) { - ebitenutil.DebugPrintAt(s.image, text, s.stateCurrent.x, s.stateCurrent.y) +func (s *Surface) DrawText(format string, params ...interface{}) { + ebitenutil.DebugPrintAt(s.image, fmt.Sprintf(format, params...), s.stateCurrent.x, s.stateCurrent.y) +} + +func (s *Surface) DrawLine(x, y int, color color.Color) { + ebitenutil.DrawLine( + s.image, + float64(s.stateCurrent.x), + float64(s.stateCurrent.y), + float64(s.stateCurrent.x+x), + float64(s.stateCurrent.y+y), + color, + ) } func (s *Surface) DrawRect(width, height int, color color.Color) { @@ -70,6 +107,14 @@ func (s *Surface) DrawRect(width, height int, color color.Color) { ) } +func (s *Surface) Clear(color color.Color) error { + return s.image.Fill(color) +} + func (s *Surface) GetSize() (int, int) { return s.image.Size() } + +func (s *Surface) GetDepth() int { + return len(s.stateStack) +} diff --git a/d2render/d2ui/Scrollbar.go b/d2render/d2ui/Scrollbar.go index 484aa674..9af44e11 100644 --- a/d2render/d2ui/Scrollbar.go +++ b/d2render/d2ui/Scrollbar.go @@ -3,6 +3,7 @@ package d2ui import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -72,7 +73,7 @@ func (v Scrollbar) GetLastDirChange() int { return v.lastDirChange } -func (v *Scrollbar) Render(target *ebiten.Image) { +func (v *Scrollbar) Render(target *d2surface.Surface) { if !v.visible || v.maxOffset == 0 { return } diff --git a/d2render/d2ui/button.go b/d2render/d2ui/button.go index 8f5221bc..4bd61825 100644 --- a/d2render/d2ui/button.go +++ b/d2render/d2ui/button.go @@ -6,8 +6,8 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -127,33 +127,38 @@ func CreateButton(buttonType ButtonType, text string) Button { } result.normalImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) + normalSurface := d2surface.CreateSurface(result.normalImage) _, fontHeight := font.GetTextMetrics(text) textY := int((result.height/2)-(int(fontHeight)/2)) + buttonLayout.TextOffset buttonSprite.SetPosition(0, 0) buttonSprite.SetBlend(true) - buttonSprite.RenderSegmented(result.normalImage, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.normalImage) + buttonSprite.RenderSegmented(normalSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, normalSurface) if buttonLayout.AllowFrameChange { if totalButtonTypes > 1 { result.pressedImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - buttonSprite.RenderSegmented(result.pressedImage, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+1) - font.Render(-2, textY+2, text, color.RGBA{100, 100, 100, 255}, result.pressedImage) + pressedSurface := d2surface.CreateSurface(result.pressedImage) + buttonSprite.RenderSegmented(pressedSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+1) + font.Render(-2, textY+2, text, color.RGBA{100, 100, 100, 255}, pressedSurface) } if totalButtonTypes > 2 { result.toggledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - buttonSprite.RenderSegmented(result.toggledImage, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+2) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.toggledImage) + toggledSurface := d2surface.CreateSurface(result.toggledImage) + buttonSprite.RenderSegmented(toggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+2) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, toggledSurface) } if totalButtonTypes > 3 { result.pressedToggledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - buttonSprite.RenderSegmented(result.pressedToggledImage, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+3) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.pressedToggledImage) + pressedToggledSurface := d2surface.CreateSurface(result.pressedToggledImage) + buttonSprite.RenderSegmented(pressedToggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+3) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, pressedToggledSurface) } if buttonLayout.DisabledFrame != -1 { result.disabledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - buttonSprite.RenderSegmented(result.disabledImage, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.DisabledFrame) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.disabledImage) + disabledSurface := d2surface.CreateSurface(result.disabledImage) + buttonSprite.RenderSegmented(disabledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.DisabledFrame) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, disabledSurface) } } return result @@ -173,25 +178,24 @@ func (v *Button) Activate() { } // Render renders the button -func (v Button) Render(target *ebiten.Image) { - opts := &ebiten.DrawImageOptions{ - CompositeMode: ebiten.CompositeModeSourceAtop, - Filter: ebiten.FilterNearest, - } - opts.GeoM.Translate(float64(v.x), float64(v.y)) +func (v Button) Render(target *d2surface.Surface) { + target.PushCompositeMode(ebiten.CompositeModeSourceAtop) + target.PushFilter(ebiten.FilterNearest) + target.PushTranslation(v.x, v.y) + defer target.PopN(3) if !v.enabled { - //opts.CompositeMode = ebiten.CompositeModeLighter - opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{128, 128, 128, 195}) - target.DrawImage(v.disabledImage, opts) + target.PushColor(color.RGBA{128, 128, 128, 195}) + defer target.Pop() + target.Render(v.disabledImage) } else if v.toggled && v.pressed { - target.DrawImage(v.pressedToggledImage, opts) + target.Render(v.pressedToggledImage) } else if v.pressed { - target.DrawImage(v.pressedImage, opts) + target.Render(v.pressedImage) } else if v.toggled { - target.DrawImage(v.toggledImage, opts) + target.Render(v.toggledImage) } else { - target.DrawImage(v.normalImage, opts) + target.Render(v.normalImage) } } diff --git a/d2render/d2ui/checkbox.go b/d2render/d2ui/checkbox.go index e17474a3..718e4539 100644 --- a/d2render/d2ui/checkbox.go +++ b/d2render/d2ui/checkbox.go @@ -3,6 +3,7 @@ package d2ui import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -30,23 +31,25 @@ func CreateCheckbox(checkState bool) Checkbox { checkboxSprite.SetPosition(0, 0) result.Image, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - checkboxSprite.RenderSegmented(result.Image, 1, 1, 0) + surface := d2surface.CreateSurface(result.Image) + checkboxSprite.RenderSegmented(surface, 1, 1, 0) result.checkedImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - checkboxSprite.RenderSegmented(result.checkedImage, 1, 1, 1) + checkedSurface := d2surface.CreateSurface(result.checkedImage) + checkboxSprite.RenderSegmented(checkedSurface, 1, 1, 1) return result } -func (v Checkbox) Render(target *ebiten.Image) { - opts := &ebiten.DrawImageOptions{ - CompositeMode: ebiten.CompositeModeSourceAtop, - Filter: ebiten.FilterNearest, - } - opts.GeoM.Translate(float64(v.x), float64(v.y)) - if v.checkState == false { - target.DrawImage(v.Image, opts) +func (v Checkbox) Render(target *d2surface.Surface) { + target.PushCompositeMode(ebiten.CompositeModeSourceAtop) + target.PushTranslation(v.x, v.y) + target.PushFilter(ebiten.FilterNearest) + defer target.PopN(3) + + if v.checkState { + target.Render(v.checkedImage) } else { - target.DrawImage(v.checkedImage, opts) + target.Render(v.Image) } } func (v Checkbox) GetEnabled() bool { diff --git a/d2render/d2ui/font.go b/d2render/d2ui/font.go index cc4c640b..9388330b 100644 --- a/d2render/d2ui/font.go +++ b/d2render/d2ui/font.go @@ -8,8 +8,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2render" - - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "encoding/binary" @@ -106,7 +105,7 @@ func (v *Font) GetTextMetrics(text string) (width, height int) { } // Render draws the font on the target surface -func (v *Font) Render(x, y int, text string, color color.Color, target *ebiten.Image) { +func (v *Font) Render(x, y int, text string, color color.Color, target *d2surface.Surface) { v.fontSprite.SetColorMod(color) v.fontSprite.SetBlend(false) @@ -115,7 +114,7 @@ func (v *Font) Render(x, y int, text string, color color.Color, target *ebiten.I maxCharHeight = d2helper.Max(maxCharHeight, uint32(m.Height)) } - targetWidth, _ := target.Size() + targetWidth, _ := target.GetSize() lines := strings.Split(text, "\n") for lineIdx, line := range lines { lineWidth, _ := v.GetTextMetrics(line) diff --git a/d2render/d2ui/label.go b/d2render/d2ui/label.go index c1ecd73e..34fe81fd 100644 --- a/d2render/d2ui/label.go +++ b/d2render/d2ui/label.go @@ -3,6 +3,7 @@ package d2ui import ( "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -42,23 +43,25 @@ func CreateLabel(fontPath, palettePath string) Label { } // Render draws the label on the screen -func (v *Label) Render(target *ebiten.Image) { +func (v *Label) Render(target *d2surface.Surface) { if len(v.text) == 0 { return } v.cacheImage() - opts := &ebiten.DrawImageOptions{} + x, y := v.X, v.Y if v.Alignment == LabelAlignCenter { - opts.GeoM.Translate(float64(v.X-int(v.Width/2)), float64(v.Y)) + x, y = v.X-int(v.Width/2), v.Y } else if v.Alignment == LabelAlignRight { - opts.GeoM.Translate(float64(v.X-int(v.Width)), float64(v.Y)) - } else { - opts.GeoM.Translate(float64(v.X), float64(v.Y)) + x, y = v.X-int(v.Width), v.Y } - opts.CompositeMode = ebiten.CompositeModeSourceAtop - opts.Filter = ebiten.FilterNearest - target.DrawImage(v.imageData, opts) + + target.PushFilter(ebiten.FilterNearest) + target.PushCompositeMode(ebiten.CompositeModeSourceAtop) + target.PushTranslation(x, y) + defer target.PopN(3) + + target.Render(v.imageData) } // SetPosition moves the label to the specified location @@ -79,7 +82,8 @@ func (v *Label) cacheImage() { v.Width = width v.Height = height v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest) - v.font.Render(0, 0, v.text, v.Color, v.imageData) + surface := d2surface.CreateSurface(v.imageData) + v.font.Render(0, 0, v.text, v.Color, surface) } // SetText sets the label's text diff --git a/d2render/d2ui/manager.go b/d2render/d2ui/manager.go index 0e0f31fe..9efee1e5 100644 --- a/d2render/d2ui/manager.go +++ b/d2render/d2ui/manager.go @@ -4,6 +4,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -58,17 +59,16 @@ func (v *Manager) WaitForMouseRelease() { } // Render renders all of the UI elements -func (v *Manager) Render(screen *ebiten.Image) { +func (v *Manager) Render(target *d2surface.Surface) { for _, widget := range v.widgets { - if !widget.GetVisible() { - continue + if widget.GetVisible() { + widget.Render(target) } - widget.Render(screen) } cx, cy := ebiten.CursorPosition() v.cursorSprite.SetPosition(cx, cy) - v.cursorSprite.Render(screen) + v.cursorSprite.Render(target) } // Update updates all of the UI elements diff --git a/d2render/d2ui/textbox.go b/d2render/d2ui/textbox.go index 14906cae..144cb0fb 100644 --- a/d2render/d2ui/textbox.go +++ b/d2render/d2ui/textbox.go @@ -8,6 +8,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/hajimehoshi/ebiten" ) @@ -51,7 +52,7 @@ func repeatingKeyPressed(key ebiten.Key) bool { return false } -func (v TextBox) Render(target *ebiten.Image) { +func (v TextBox) Render(target *d2surface.Surface) { if !v.visible { return } diff --git a/d2render/sprite.go b/d2render/sprite.go index 169c6994..994ac160 100644 --- a/d2render/sprite.go +++ b/d2render/sprite.go @@ -5,8 +5,7 @@ import ( "github.com/OpenDiablo2/D2Shared/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2asset" - - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type Sprite struct { @@ -34,21 +33,19 @@ func MustLoadSprite(animationPath, palettePath string) *Sprite { return sprite } -func (s *Sprite) Render(target *ebiten.Image) error { +func (s *Sprite) Render(target *d2surface.Surface) error { if err := s.advance(); err != nil { return err } _, frameHeight := s.animation.GetCurrentFrameSize() - if err := s.animation.Render(target, s.x, s.y-frameHeight); err != nil { - return err - } - - return nil + target.PushTranslation(s.x, s.y-frameHeight) + defer target.Pop() + return s.animation.Render(target) } -func (s *Sprite) RenderSegmented(target *ebiten.Image, segmentsX, segmentsY, frameOffset int) error { +func (s *Sprite) RenderSegmented(target *d2surface.Surface, segmentsX, segmentsY, frameOffset int) error { if err := s.advance(); err != nil { return err } @@ -62,7 +59,10 @@ func (s *Sprite) RenderSegmented(target *ebiten.Image, segmentsX, segmentsY, fra return err } - if err := s.animation.Render(target, s.x+currentX, s.y+currentY); err != nil { + target.PushTranslation(s.x+currentX, s.y+currentY) + err := s.animation.Render(target) + target.Pop() + if err != nil { return err } diff --git a/main.go b/main.go index 74759507..c1054624 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "log" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scene" + "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "github.com/OpenDiablo2/OpenDiablo2/d2term" "github.com/hajimehoshi/ebiten/ebitenutil" @@ -67,9 +68,13 @@ func main() { func update(screen *ebiten.Image) error { d2Engine.Update() - if ebiten.IsDrawingSkipped() { - return nil + if !ebiten.IsDrawingSkipped() { + surface := d2surface.CreateSurface(screen) + d2Engine.Draw(surface) + if surface.GetDepth() > 0 { + panic("detected surface stack leak") + } } - d2Engine.Draw(screen) + return nil }