diff --git a/examples/skipdraw/main.go b/examples/skipdraw/main.go new file mode 100644 index 000000000..a9afaad24 --- /dev/null +++ b/examples/skipdraw/main.go @@ -0,0 +1,90 @@ +// Copyright 2022 The Ebitengine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build example +// +build example + +package main + +import ( + "bytes" + "image" + _ "image/jpeg" + "log" + "math" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/examples/resources/images" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +var ( + gophersImage *ebiten.Image +) + +type Game struct { + count int +} + +func (g *Game) Update() error { + g.count++ + if len(inpututil.AppendPressedKeys(nil)) > 0 { + return nil + } + ebiten.SkipDraw() + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + w, h := gophersImage.Size() + op := &ebiten.DrawImageOptions{} + + // Move the image's center to the screen's upper-left corner. + // This is a preparation for rotating. When geometry matrices are applied, + // the origin point is the upper-left corner. + op.GeoM.Translate(-float64(w)/2, -float64(h)/2) + + // Rotate the image. As a result, the anchor point of this rotate is + // the center of the image. + op.GeoM.Rotate(float64(g.count%360) * 2 * math.Pi / 360) + + // Move the image to the screen's center. + op.GeoM.Translate(screenWidth/2, screenHeight/2) + + screen.DrawImage(gophersImage, op) +} + +func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { + return screenWidth, screenHeight +} + +func main() { + // Decode an image from the image file's byte slice. + img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg)) + if err != nil { + log.Fatal(err) + } + gophersImage = ebiten.NewImageFromImage(img) + + ebiten.SetWindowSize(screenWidth, screenHeight) + ebiten.SetWindowTitle("Rotate (Ebitengine Demo)") + if err := ebiten.RunGame(&Game{}); err != nil { + log.Fatal(err) + } +} diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index f6e58a485..175fb4a96 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -168,7 +168,8 @@ func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bo // flush must be called the main thread. func (q *commandQueue) flush(graphicsDriver graphicsdriver.Graphics, endFrame bool) (err error) { - if len(q.commands) == 0 { + // If endFrame is true, Begin/End should be called to ensure the framebuffer is swapped. + if len(q.commands) == 0 && !endFrame { return nil } diff --git a/internal/graphicsdriver/directx/graphics_windows.go b/internal/graphicsdriver/directx/graphics_windows.go index 4a6d73c90..7be1f14cf 100644 --- a/internal/graphicsdriver/directx/graphics_windows.go +++ b/internal/graphicsdriver/directx/graphics_windows.go @@ -770,7 +770,7 @@ func (g *Graphics) End(present bool) error { g.pipelineStates.resetConstantBuffers(g.frameIndex) - if present { + if present && g.swapChain != nil { if microsoftgdk.IsXbox() { if err := g.presentXbox(); err != nil { return err diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 613c5e460..679b8e19b 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -199,11 +199,15 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint16) error { } func (g *Graphics) flushIfNeeded(present bool) { - if g.cb == (mtl.CommandBuffer{}) { + if g.cb == (mtl.CommandBuffer{}) && !present { return } g.flushRenderCommandEncoderIfNeeded() + if g.screenDrawable == (ca.MetalDrawable{}) { + g.screenDrawable = g.view.nextDrawable() + } + if !g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) { g.cb.PresentDrawable(g.screenDrawable) } diff --git a/internal/ui/context.go b/internal/ui/context.go index 624da6d95..b699c0470 100644 --- a/internal/ui/context.go +++ b/internal/ui/context.go @@ -142,8 +142,16 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update } // Draw the game. - if err := c.drawGame(graphicsDriver); err != nil { - return err + drawSkipped := theGlobalState.drawSkipped() + defer func() { + if updateCount > 0 { + theGlobalState.resetDrawSkipped() + } + }() + if !drawSkipped { + if err := c.drawGame(graphicsDriver); err != nil { + return err + } } return nil diff --git a/internal/ui/globalstate.go b/internal/ui/globalstate.go index 0b1f5c216..9a9906436 100644 --- a/internal/ui/globalstate.go +++ b/internal/ui/globalstate.go @@ -32,6 +32,7 @@ type globalState struct { fpsMode_ int32 isScreenClearedEveryFrame_ int32 graphicsLibrary_ int32 + drawSkipped_ int32 } func (g *globalState) error() error { @@ -76,6 +77,18 @@ func (g *globalState) graphicsLibrary() GraphicsLibrary { return GraphicsLibrary(atomic.LoadInt32(&g.graphicsLibrary_)) } +func (g *globalState) skipDraw() { + atomic.StoreInt32(&g.drawSkipped_, 1) +} + +func (g *globalState) resetDrawSkipped() { + atomic.StoreInt32(&g.drawSkipped_, 0) +} + +func (g *globalState) drawSkipped() bool { + return atomic.LoadInt32(&g.drawSkipped_) != 0 +} + func FPSMode() FPSModeType { return theGlobalState.fpsMode() } @@ -96,3 +109,7 @@ func SetScreenClearedEveryFrame(cleared bool) { func GetGraphicsLibrary() GraphicsLibrary { return theGlobalState.graphicsLibrary() } + +func SkipDraw() { + theGlobalState.skipDraw() +} diff --git a/run.go b/run.go index da419eee2..d96104726 100644 --- a/run.go +++ b/run.go @@ -537,3 +537,7 @@ func SetScreenTransparent(transparent bool) { func SetInitFocused(focused bool) { ui.Get().SetInitFocused(focused) } + +func SkipDraw() { + ui.SkipDraw() +}