app,internal/gl: [wasm] fix context lost

Before that change, Gio could crash when the WebGL context was lost
unexpectedly. Now, Gio will properly handle such situation and
recreate the buffers/resources when context is restored and will
wait until context is recovered.

Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
Inkeliz
2022-09-12 01:29:35 +01:00
committed by Elias Naur
parent e37deed8bb
commit 83cb383523
3 changed files with 62 additions and 6 deletions
+14 -4
View File
@@ -13,6 +13,7 @@ import (
type glContext struct { type glContext struct {
ctx js.Value ctx js.Value
cnv js.Value cnv js.Value
w *window
} }
func newContext(w *window) (*glContext, error) { func newContext(w *window) (*glContext, error) {
@@ -32,11 +33,15 @@ func newContext(w *window) (*glContext, error) {
c := &glContext{ c := &glContext{
ctx: ctx, ctx: ctx,
cnv: w.cnv, cnv: w.cnv,
w: w,
} }
return c, nil return c, nil
} }
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) { func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
if c.w.contextStatus != contextStatusOkay {
return nil, gpu.ErrDeviceLost
}
return gpu.OpenGLRenderTarget{}, nil return gpu.OpenGLRenderTarget{}, nil
} }
@@ -48,9 +53,6 @@ func (c *glContext) Release() {
} }
func (c *glContext) Present() error { func (c *glContext) Present() error {
if c.ctx.Call("isContextLost").Bool() {
return errors.New("context lost")
}
return nil return nil
} }
@@ -61,7 +63,15 @@ func (c *glContext) Lock() error {
func (c *glContext) Unlock() {} func (c *glContext) Unlock() {}
func (c *glContext) Refresh() error { func (c *glContext) Refresh() error {
return nil switch c.w.contextStatus {
case contextStatusLost:
return errOutOfDate
case contextStatusRestored:
c.w.contextStatus = contextStatusOkay
return gpu.ErrDeviceLost
default:
return nil
}
} }
func (w *window) NewContext() (context, error) { func (w *window) NewContext() (context, error) {
+38 -1
View File
@@ -24,6 +24,14 @@ import (
type ViewEvent struct{} type ViewEvent struct{}
type contextStatus int
const (
contextStatusOkay contextStatus = iota
contextStatusLost
contextStatusRestored
)
type window struct { type window struct {
window js.Value window js.Value
document js.Value document js.Value
@@ -54,6 +62,8 @@ type window struct {
// is pending. // is pending.
animRequested bool animRequested bool
wakeups chan struct{} wakeups chan struct{}
contextStatus contextStatus
} }
func newWindow(win *callbacks, options []Option) error { func newWindow(win *callbacks, options []Option) error {
@@ -162,9 +172,25 @@ func (w *window) cleanup() {
} }
func (w *window) addEventListeners() { func (w *window) addEventListeners() {
w.addEventListener(w.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
args[0].Call("preventDefault")
w.contextStatus = contextStatusLost
return nil
})
w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
args[0].Call("preventDefault")
w.contextStatus = contextStatusRestored
// Resize is required to force update the canvas content when restored.
w.cnv.Set("width", 0)
w.cnv.Set("height", 0)
w.resize()
w.requestRedraw()
return nil
})
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} { w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
w.resize() w.resize()
w.chanRedraw <- struct{}{} w.requestRedraw()
return nil return nil
}) })
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} { w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
@@ -622,10 +648,14 @@ func (w *window) resize() {
} }
func (w *window) draw(sync bool) { func (w *window) draw(sync bool) {
if w.contextStatus == contextStatusLost {
return
}
size, insets, metric := w.getConfig() size, insets, metric := w.getConfig()
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 { if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
return return
} }
w.w.Event(frameEvent{ w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{ FrameEvent: system.FrameEvent{
Now: time.Now(), Now: time.Now(),
@@ -696,6 +726,13 @@ func (w *window) navigationColor(c color.NRGBA) {
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B})) theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
} }
func (w *window) requestRedraw() {
select {
case w.chanRedraw <- struct{}{}:
default:
}
}
func osMain() { func osMain() {
select {} select {}
} }
+10 -1
View File
@@ -299,7 +299,12 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src)) f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
} }
func (f *Functions) CheckFramebufferStatus(target Enum) Enum { func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
return Enum(f._checkFramebufferStatus.Invoke(int(target)).Int()) status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
// If the context is lost, we say that everything is fine. That saves internal/opengl/opengl.go from panic.
return FRAMEBUFFER_COMPLETE
}
return status
} }
func (f *Functions) Clear(mask Enum) { func (f *Functions) Clear(mask Enum) {
f._clear.Invoke(int(mask)) f._clear.Invoke(int(mask))
@@ -633,6 +638,10 @@ func paramVal(v js.Value) int {
} }
case js.TypeNumber: case js.TypeNumber:
return v.Int() return v.Int()
case js.TypeUndefined:
return 0
case js.TypeNull:
return 0
default: default:
panic("unknown parameter type") panic("unknown parameter type")
} }