internal/graphicsdriver: Bug fix: getting a WebGL2 context might fail even though WebGL2RenderingContext exists

Closes #1738
This commit is contained in:
Hajime Hoshi 2021-08-01 21:50:21 +09:00
parent 4e6a5a9fa2
commit 8967384dac
6 changed files with 55 additions and 26 deletions

View File

@ -87,16 +87,31 @@ const (
dstColor = operation(gles.DST_COLOR) dstColor = operation(gles.DST_COLOR)
) )
type webGLVersion int
const (
webGLVersionUnknown webGLVersion = iota
webGLVersion1
webGLVersion2
)
var ( var (
isWebGL2Available = !forceWebGL1 && (js.Global().Get("WebGL2RenderingContext").Truthy() || js.Global().Get("go2cpp").Truthy()) webGL2MightBeAvailable = !forceWebGL1 && (js.Global().Get("WebGL2RenderingContext").Truthy() || js.Global().Get("go2cpp").Truthy())
) )
type contextImpl struct { type contextImpl struct {
gl *gl gl *gl
lastProgramID programID lastProgramID programID
webGLVersion webGLVersion
}
func (c *context) usesWebGL2() bool {
return c.webGLVersion == webGLVersion2
} }
func (c *context) initGL() { func (c *context) initGL() {
c.webGLVersion = webGLVersionUnknown
var gl js.Value var gl js.Value
// TODO: Define id? // TODO: Define id?
@ -107,22 +122,33 @@ func (c *context) initGL() {
attr.Set("premultipliedAlpha", true) attr.Set("premultipliedAlpha", true)
attr.Set("stencil", true) attr.Set("stencil", true)
if isWebGL2Available { if webGL2MightBeAvailable {
gl = canvas.Call("getContext", "webgl2", attr) gl = canvas.Call("getContext", "webgl2", attr)
} else { if gl.Truthy() {
c.webGLVersion = webGLVersion2
}
}
// Even though WebGL2RenderingContext exists, getting a webgl2 context might fail (#1738).
if !gl.Truthy() {
gl = canvas.Call("getContext", "webgl", attr) gl = canvas.Call("getContext", "webgl", attr)
if !gl.Truthy() { if !gl.Truthy() {
gl = canvas.Call("getContext", "experimental-webgl", attr) gl = canvas.Call("getContext", "experimental-webgl", attr)
if !gl.Truthy() {
panic("opengl: getContext failed")
}
} }
if gl.Truthy() {
c.webGLVersion = webGLVersion1
}
}
if !gl.Truthy() {
panic("opengl: getContext failed")
} }
} else if go2cpp := js.Global().Get("go2cpp"); go2cpp.Truthy() { } else if go2cpp := js.Global().Get("go2cpp"); go2cpp.Truthy() {
gl = go2cpp.Get("gl") gl = go2cpp.Get("gl")
c.webGLVersion = webGLVersion2
} }
c.gl = newGL(gl) c.gl = c.newGL(gl)
} }
func (c *context) reset() error { func (c *context) reset() error {
@ -145,7 +171,7 @@ func (c *context) reset() error {
f := gl.getParameter.Invoke(gles.FRAMEBUFFER_BINDING) f := gl.getParameter.Invoke(gles.FRAMEBUFFER_BINDING)
c.screenFramebuffer = framebufferNative(f) c.screenFramebuffer = framebufferNative(f)
if !isWebGL2Available { if !c.usesWebGL2() {
gl.getExtension.Invoke("OES_standard_derivatives") gl.getExtension.Invoke("OES_standard_derivatives")
} }
return nil return nil
@ -435,43 +461,43 @@ func (c *context) uniformFloats(p program, location string, v []float32, typ sha
switch base { switch base {
case shaderir.Float: case shaderir.Float:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniform1fv.Invoke(js.Value(l), arr, 0, len(v)) gl.uniform1fv.Invoke(js.Value(l), arr, 0, len(v))
} else { } else {
gl.uniform1fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) gl.uniform1fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v)))
} }
case shaderir.Vec2: case shaderir.Vec2:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniform2fv.Invoke(js.Value(l), arr, 0, len(v)) gl.uniform2fv.Invoke(js.Value(l), arr, 0, len(v))
} else { } else {
gl.uniform2fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) gl.uniform2fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v)))
} }
case shaderir.Vec3: case shaderir.Vec3:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniform3fv.Invoke(js.Value(l), arr, 0, len(v)) gl.uniform3fv.Invoke(js.Value(l), arr, 0, len(v))
} else { } else {
gl.uniform3fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) gl.uniform3fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v)))
} }
case shaderir.Vec4: case shaderir.Vec4:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniform4fv.Invoke(js.Value(l), arr, 0, len(v)) gl.uniform4fv.Invoke(js.Value(l), arr, 0, len(v))
} else { } else {
gl.uniform4fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) gl.uniform4fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v)))
} }
case shaderir.Mat2: case shaderir.Mat2:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr, 0, len(v)) gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr, 0, len(v))
} else { } else {
gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v))) gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v)))
} }
case shaderir.Mat3: case shaderir.Mat3:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr, 0, len(v)) gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr, 0, len(v))
} else { } else {
gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v))) gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v)))
} }
case shaderir.Mat4: case shaderir.Mat4:
if isWebGL2Available { if c.usesWebGL2() {
gl.uniformMatrix4fv.Invoke(js.Value(l), false, arr, 0, len(v)) gl.uniformMatrix4fv.Invoke(js.Value(l), false, arr, 0, len(v))
} else { } else {
gl.uniformMatrix4fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v))) gl.uniformMatrix4fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v)))
@ -528,7 +554,7 @@ func (c *context) arrayBufferSubData(data []float32) {
gl := c.gl gl := c.gl
l := len(data) * 4 l := len(data) * 4
arr := jsutil.TemporaryUint8Array(l, data) arr := jsutil.TemporaryUint8Array(l, data)
if isWebGL2Available { if c.usesWebGL2() {
gl.bufferSubData.Invoke(gles.ARRAY_BUFFER, 0, arr, 0, l) gl.bufferSubData.Invoke(gles.ARRAY_BUFFER, 0, arr, 0, l)
} else { } else {
gl.bufferSubData.Invoke(gles.ARRAY_BUFFER, 0, arr.Call("subarray", 0, l)) gl.bufferSubData.Invoke(gles.ARRAY_BUFFER, 0, arr.Call("subarray", 0, l))
@ -539,7 +565,7 @@ func (c *context) elementArrayBufferSubData(data []uint16) {
gl := c.gl gl := c.gl
l := len(data) * 2 l := len(data) * 2
arr := jsutil.TemporaryUint8Array(l, data) arr := jsutil.TemporaryUint8Array(l, data)
if isWebGL2Available { if c.usesWebGL2() {
gl.bufferSubData.Invoke(gles.ELEMENT_ARRAY_BUFFER, 0, arr, 0, l) gl.bufferSubData.Invoke(gles.ELEMENT_ARRAY_BUFFER, 0, arr, 0, l)
} else { } else {
gl.bufferSubData.Invoke(gles.ELEMENT_ARRAY_BUFFER, 0, arr.Call("subarray", 0, l)) gl.bufferSubData.Invoke(gles.ELEMENT_ARRAY_BUFFER, 0, arr.Call("subarray", 0, l))
@ -585,7 +611,7 @@ func (c *context) texSubImage2D(t textureNative, args []*driver.ReplacePixelsArg
gl := c.gl gl := c.gl
for _, a := range args { for _, a := range args {
arr := jsutil.TemporaryUint8Array(len(a.Pixels), a.Pixels) arr := jsutil.TemporaryUint8Array(len(a.Pixels), a.Pixels)
if isWebGL2Available { if c.usesWebGL2() {
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, // void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height, // GLsizei width, GLsizei height,
// GLenum format, GLenum type, ArrayBufferView pixels, srcOffset); // GLenum format, GLenum type, ArrayBufferView pixels, srcOffset);

View File

@ -93,7 +93,7 @@ type gl struct {
viewport js.Value viewport js.Value
} }
func newGL(v js.Value) *gl { func (c *context) newGL(v js.Value) *gl {
// Passing a Go string to the JS world is expensive. This causes conversion to UTF-16 (#1438). // Passing a Go string to the JS world is expensive. This causes conversion to UTF-16 (#1438).
// In order to reduce the cost when calling functions, create the function objects by bind and use them. // In order to reduce the cost when calling functions, create the function objects by bind and use them.
g := &gl{ g := &gl{
@ -168,7 +168,7 @@ func newGL(v js.Value) *gl {
vertexAttribPointer: v.Get("vertexAttribPointer").Call("bind", v), vertexAttribPointer: v.Get("vertexAttribPointer").Call("bind", v),
viewport: v.Get("viewport").Call("bind", v), viewport: v.Get("viewport").Call("bind", v),
} }
if isWebGL2Available { if c.usesWebGL2() {
g.getExtension = v.Get("getBufferSubData").Call("bind", v) g.getExtension = v.Get("getBufferSubData").Call("bind", v)
} else { } else {
g.getExtension = v.Get("getExtension").Call("bind", v) g.getExtension = v.Get("getExtension").Call("bind", v)

View File

@ -52,7 +52,7 @@ func (s *Shader) Dispose() {
} }
func (s *Shader) compile() error { func (s *Shader) compile() error {
vssrc, fssrc := glsl.Compile(s.ir, glslVersion()) vssrc, fssrc := glsl.Compile(s.ir, s.graphics.context.glslVersion())
vs, err := s.graphics.context.newVertexShader(vssrc) vs, err := s.graphics.context.newVertexShader(vssrc)
if err != nil { if err != nil {

View File

@ -21,6 +21,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl" "github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
) )
func glslVersion() glsl.GLSLVersion { func (c *context) glslVersion() glsl.GLSLVersion {
return glsl.GLSLVersionDefault return glsl.GLSLVersionDefault
} }

View File

@ -18,9 +18,12 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl" "github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
) )
func glslVersion() glsl.GLSLVersion { func (c *context) glslVersion() glsl.GLSLVersion {
if isWebGL2Available { switch c.webGLVersion {
case webGLVersion1:
return glsl.GLSLVersionES100
case webGLVersion2:
return glsl.GLSLVersionES300 return glsl.GLSLVersionES300
} }
return glsl.GLSLVersionES100 panic("opengl: WebGL context is not initialized yet at glslVersion")
} }

View File

@ -21,6 +21,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl" "github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
) )
func glslVersion() glsl.GLSLVersion { func (c *context) glslVersion() glsl.GLSLVersion {
return glsl.GLSLVersionES100 return glsl.GLSLVersionES100
} }