diff --git a/examples/contextlost/main.go b/examples/contextlost/main.go index e26388356..7f60f06c8 100644 --- a/examples/contextlost/main.go +++ b/examples/contextlost/main.go @@ -23,6 +23,7 @@ import ( _ "image/jpeg" "log" "math" + "time" "github.com/gopherjs/gopherwasm/js" @@ -41,8 +42,39 @@ var ( count = 0 gophersImage *ebiten.Image extraImages []*ebiten.Image + lost = false ) +func loseAndRestoreContext(context js.Value) { + if lost { + return + } + + // Edge might not support the extension. See + // https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context + ext := context.Call("getExtension", "WEBGL_lose_context") + if ext == js.Null() { + fmt.Println("Fail to force context lost. Edge might not support the extension yet.") + return + } + + ext.Call("loseContext") + fmt.Println("Lost the context!") + fmt.Println("The context is automatically restored after 3 seconds.") + lost = true + + // If and only if the context is lost by loseContext, you need to call restoreContext. Note that in usual + // case of context lost, you cannot call restoreContext but the context should be restored automatically. + // + // After the context is lost, update will not be called. Instead, fire the goroutine to restore the context. + go func() { + time.Sleep(3 * time.Second) + ext.Call("restoreContext") + fmt.Println("Restored the context!") + lost = false + }() +} + func update(screen *ebiten.Image) error { if inpututil.IsKeyJustPressed(ebiten.KeySpace) && js.Global() != js.Null() { doc := js.Global().Get("document") @@ -51,14 +83,8 @@ func update(screen *ebiten.Image) error { if context == js.Null() { context = canvas.Call("getContext", "experimental-webgl") } - // Edge might not support the extension. See - // https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context - if ext := context.Call("getExtension", "WEBGL_lose_context"); ext != js.Null() { - ext.Call("loseContext") - fmt.Println("Context Lost!") - } else { - fmt.Println("Fail to force context lost. Edge might not support the extension yet.") - } + + loseAndRestoreContext(context) return nil } @@ -75,7 +101,7 @@ func update(screen *ebiten.Image) error { op.GeoM.Translate(screenWidth/2, screenHeight/2) screen.DrawImage(gophersImage, op) - ebitenutil.DebugPrint(screen, "Press Space to force GL context lost!\n(Browser only)") + ebitenutil.DebugPrint(screen, "Press Space to force to lose/restore the GL context!\n(Browser only)") return nil } diff --git a/graphicscontext.go b/graphicscontext.go index d95d2335d..95e22913c 100644 --- a/graphicscontext.go +++ b/graphicscontext.go @@ -23,7 +23,6 @@ import ( "github.com/hajimehoshi/ebiten/internal/hooks" "github.com/hajimehoshi/ebiten/internal/shareable" "github.com/hajimehoshi/ebiten/internal/ui" - "github.com/hajimehoshi/ebiten/internal/web" ) func newGraphicsContext(f func(*Image) error) *graphicsContext { @@ -155,9 +154,6 @@ func (c *graphicsContext) Update(afterFrameUpdate func()) error { } func (c *graphicsContext) needsRestoring() (bool, error) { - if web.IsBrowser() { - return c.invalidated, nil - } return c.offscreen.mipmap.original().IsInvalidated() } diff --git a/internal/graphicsdriver/opengl/context_js.go b/internal/graphicsdriver/opengl/context_js.go index dfe9276d0..817a8edf7 100644 --- a/internal/graphicsdriver/opengl/context_js.go +++ b/internal/graphicsdriver/opengl/context_js.go @@ -90,7 +90,6 @@ var ( type contextImpl struct { gl js.Value - loseContext js.Value lastProgramID programID } @@ -116,10 +115,6 @@ func (c *context) ensureGL() { } c.gl = gl - - // Getting an extension might fail after the context is lost, so - // it is required to get the extension here. - c.loseContext = gl.Call("getExtension", "WEBGL_lose_context") } func (c *context) reset() error { @@ -130,7 +125,11 @@ func (c *context) reset() error { c.lastViewportHeight = 0 c.lastCompositeMode = graphics.CompositeModeUnknown + c.gl = js.Value{} c.ensureGL() + if c.gl.Call("isContextLost").Bool() { + return fmt.Errorf("opengl: the context is lost") + } gl := c.gl gl.Call("enable", blend) c.blendFunc(graphics.CompositeModeSourceOver) @@ -469,15 +468,3 @@ func (c *context) flush() { gl := c.gl gl.Call("flush") } - -func (c *context) isContextLost() bool { - c.ensureGL() - gl := c.gl - return gl.Call("isContextLost").Bool() -} - -func (c *context) restoreContext() { - if c.loseContext != js.Null() { - c.loseContext.Call("restoreContext") - } -} diff --git a/internal/graphicsdriver/opengl/driver_js.go b/internal/graphicsdriver/opengl/driver_js.go deleted file mode 100644 index 91f84bb10..000000000 --- a/internal/graphicsdriver/opengl/driver_js.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 The Ebiten 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. - -// +build js - -package opengl - -func (d *Driver) IsContextLost() bool { - return d.context.isContextLost() -} - -func (d *Driver) RestoreContext() { - d.context.restoreContext() -} diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 51cb9cf9d..d690eb5ad 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -25,7 +25,6 @@ import ( "github.com/gopherjs/gopherwasm/js" "github.com/hajimehoshi/ebiten/internal/devicescale" - "github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/internal/hooks" "github.com/hajimehoshi/ebiten/internal/input" ) @@ -43,6 +42,7 @@ type userInterface struct { sizeChanged bool windowFocus bool pageVisible bool + contextLost bool lastActualScale float64 } @@ -216,15 +216,6 @@ func (u *userInterface) update(g GraphicsContext) error { } hooks.ResumeAudio() - if opengl.Get().IsContextLost() { - opengl.Get().RestoreContext() - g.Invalidate() - - // Need to return once to wait restored (#526) - // TODO: Is it necessary to handle webglcontextrestored event? - return nil - } - input.Get().UpdateGamepads() u.updateGraphicsContext(g) if err := g.Update(func() { @@ -241,6 +232,11 @@ func (u *userInterface) loop(g GraphicsContext) <-chan error { ch := make(chan error) var cf js.Callback f := func([]js.Value) { + if u.contextLost { + requestAnimationFrame.Invoke(cf) + return + } + if err := u.update(g); err != nil { ch <- err close(ch) @@ -359,11 +355,12 @@ func init() { // Do nothing. })) + // Context canvas.Call("addEventListener", "webglcontextlost", js.NewEventCallback(js.PreventDefault, func(js.Value) { - // Do nothing. + currentUI.contextLost = true })) canvas.Call("addEventListener", "webglcontextrestored", js.NewCallback(func(e []js.Value) { - // Do nothing. + currentUI.contextLost = false })) }