From 83cb3835232bbabb1f7f96c473bcca30ae83c46c Mon Sep 17 00:00:00 2001 From: Inkeliz Date: Mon, 12 Sep 2022 01:29:35 +0100 Subject: [PATCH] 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 --- app/gl_js.go | 18 ++++++++++++++---- app/os_js.go | 39 ++++++++++++++++++++++++++++++++++++++- internal/gl/gl_js.go | 11 ++++++++++- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/app/gl_js.go b/app/gl_js.go index e3aee8d7..2693d73f 100644 --- a/app/gl_js.go +++ b/app/gl_js.go @@ -13,6 +13,7 @@ import ( type glContext struct { ctx js.Value cnv js.Value + w *window } func newContext(w *window) (*glContext, error) { @@ -32,11 +33,15 @@ func newContext(w *window) (*glContext, error) { c := &glContext{ ctx: ctx, cnv: w.cnv, + w: w, } return c, nil } func (c *glContext) RenderTarget() (gpu.RenderTarget, error) { + if c.w.contextStatus != contextStatusOkay { + return nil, gpu.ErrDeviceLost + } return gpu.OpenGLRenderTarget{}, nil } @@ -48,9 +53,6 @@ func (c *glContext) Release() { } func (c *glContext) Present() error { - if c.ctx.Call("isContextLost").Bool() { - return errors.New("context lost") - } return nil } @@ -61,7 +63,15 @@ func (c *glContext) Lock() error { func (c *glContext) Unlock() {} 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) { diff --git a/app/os_js.go b/app/os_js.go index 644ff2ff..6f0df82c 100644 --- a/app/os_js.go +++ b/app/os_js.go @@ -24,6 +24,14 @@ import ( type ViewEvent struct{} +type contextStatus int + +const ( + contextStatusOkay contextStatus = iota + contextStatusLost + contextStatusRestored +) + type window struct { window js.Value document js.Value @@ -54,6 +62,8 @@ type window struct { // is pending. animRequested bool wakeups chan struct{} + + contextStatus contextStatus } func newWindow(win *callbacks, options []Option) error { @@ -162,9 +172,25 @@ func (w *window) cleanup() { } 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.resize() - w.chanRedraw <- struct{}{} + w.requestRedraw() return nil }) 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) { + if w.contextStatus == contextStatusLost { + return + } size, insets, metric := w.getConfig() if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 { return } + w.w.Event(frameEvent{ FrameEvent: system.FrameEvent{ 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})) } +func (w *window) requestRedraw() { + select { + case w.chanRedraw <- struct{}{}: + default: + } +} + func osMain() { select {} } diff --git a/internal/gl/gl_js.go b/internal/gl/gl_js.go index c2571fc9..17a641e7 100644 --- a/internal/gl/gl_js.go +++ b/internal/gl/gl_js.go @@ -299,7 +299,12 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) { f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src)) } 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) { f._clear.Invoke(int(mask)) @@ -633,6 +638,10 @@ func paramVal(v js.Value) int { } case js.TypeNumber: return v.Int() + case js.TypeUndefined: + return 0 + case js.TypeNull: + return 0 default: panic("unknown parameter type") }