ui: Implement restoring context lost on browsers correctly

Fixes #734
This commit is contained in:
Hajime Hoshi 2019-01-30 01:44:57 +09:00
parent a4123a479e
commit 5be567d58f
5 changed files with 48 additions and 67 deletions

View File

@ -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
}

View File

@ -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()
}

View File

@ -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")
}
}

View File

@ -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()
}

View File

@ -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
}))
}