mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
shaderir/glsl: Enable dFdx for WebGL
With WebGL1, an extension is required for dFdx. On the other hand, with WebGL2, GLSL ES 300 is required and the extension is forbidden. This change fixes shaderir/glsl to switch the output depends on the WebGL version. This change also adds a new build tag 'ebitenwebgl1' forcing WebGL 1. Updates #1404
This commit is contained in:
parent
2097312a8b
commit
b1d7a5f595
2
doc.go
2
doc.go
@ -72,6 +72,8 @@
|
||||
//
|
||||
// `ebitengl` forces to use OpenGL in any environments.
|
||||
//
|
||||
// `ebitenwebgl1` forces to use WebGL 1 on browsers.
|
||||
//
|
||||
// `ebitensinglethread` disables Ebiten's thread safety to unlock maximum performance. If you use this you will have
|
||||
// to manage threads yourself. Functions like IsKeyPressed will no longer be concurrent-safe with this build tag.
|
||||
// They must be called from the main thread or the same goroutine as the given game's callback functions like Update
|
||||
|
@ -94,7 +94,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
isWebGL2Available = js.Global().Get("WebGL2RenderingContext").Truthy()
|
||||
isWebGL2Available = !forceWebGL1 && js.Global().Get("WebGL2RenderingContext").Truthy()
|
||||
)
|
||||
|
||||
type contextImpl struct {
|
||||
@ -148,6 +148,10 @@ func (c *context) reset() error {
|
||||
c.blendFunc(driver.CompositeModeSourceOver)
|
||||
f := gl.Call("getParameter", gles.FRAMEBUFFER_BINDING)
|
||||
c.screenFramebuffer = framebufferNative(f)
|
||||
|
||||
if !isWebGL2Available {
|
||||
gl.Call("getExtension", "OES_standard_derivatives")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
19
internal/graphicsdriver/opengl/context_notwebgl1.go
Normal file
19
internal/graphicsdriver/opengl/context_notwebgl1.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2020 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 !ebitenwebgl1
|
||||
|
||||
package opengl
|
||||
|
||||
const forceWebGL1 = false
|
19
internal/graphicsdriver/opengl/context_webgl1.go
Normal file
19
internal/graphicsdriver/opengl/context_webgl1.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2020 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 ebitenwebgl1
|
||||
|
||||
package opengl
|
||||
|
||||
const forceWebGL1 = true
|
@ -52,7 +52,7 @@ func (s *Shader) Dispose() {
|
||||
}
|
||||
|
||||
func (s *Shader) compile() error {
|
||||
vssrc, fssrc := glsl.Compile(s.ir)
|
||||
vssrc, fssrc := glsl.Compile(s.ir, glslVersion())
|
||||
|
||||
vs, err := s.graphics.context.newShader(vertexShader, vssrc)
|
||||
if err != nil {
|
||||
|
26
internal/graphicsdriver/opengl/shader_js.go
Normal file
26
internal/graphicsdriver/opengl/shader_js.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
package opengl
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
|
||||
)
|
||||
|
||||
func glslVersion() glsl.GLSLVersion {
|
||||
if isWebGL2Available {
|
||||
return glsl.GLSLVersionES300
|
||||
}
|
||||
return glsl.GLSLVersionWebGL1
|
||||
}
|
25
internal/graphicsdriver/opengl/shader_notjs.go
Normal file
25
internal/graphicsdriver/opengl/shader_notjs.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2020 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
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
|
||||
)
|
||||
|
||||
func glslVersion() glsl.GLSLVersion {
|
||||
return glsl.GLSLVersionDefault
|
||||
}
|
@ -30,8 +30,9 @@ import (
|
||||
)
|
||||
|
||||
func glslNormalize(str string) string {
|
||||
if strings.HasPrefix(str, glsl.FragmentPrelude) {
|
||||
str = str[len(glsl.FragmentPrelude):]
|
||||
p := glsl.FragmentPrelude(glsl.GLSLVersionDefault)
|
||||
if strings.HasPrefix(str, p) {
|
||||
str = str[len(p):]
|
||||
}
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
@ -156,7 +157,7 @@ func TestCompile(t *testing.T) {
|
||||
}
|
||||
|
||||
// GLSL
|
||||
vs, fs := glsl.Compile(s)
|
||||
vs, fs := glsl.Compile(s, glsl.GLSLVersionDefault)
|
||||
if got, want := glslNormalize(vs), glslNormalize(string(tc.VS)); got != want {
|
||||
compare(t, "GLSL Vertex", got, want)
|
||||
}
|
||||
|
@ -24,15 +24,40 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
)
|
||||
|
||||
const FragmentPrelude = `#if defined(GL_ES)
|
||||
type GLSLVersion int
|
||||
|
||||
const (
|
||||
GLSLVersionDefault GLSLVersion = iota
|
||||
GLSLVersionWebGL1
|
||||
GLSLVersionES300
|
||||
)
|
||||
|
||||
func VertexPrelude(version GLSLVersion) string {
|
||||
if version == GLSLVersionES300 {
|
||||
return `#version 300 es`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FragmentPrelude(version GLSLVersion) string {
|
||||
var prefix string
|
||||
switch version {
|
||||
case GLSLVersionWebGL1:
|
||||
prefix = `#extension GL_OES_standard_derivatives : enable` + "\n\n"
|
||||
case GLSLVersionES300:
|
||||
prefix = `#version 300 es` + "\n\n"
|
||||
}
|
||||
return prefix + `#if defined(GL_ES)
|
||||
precision highp float;
|
||||
#else
|
||||
#define lowp
|
||||
#define mediump
|
||||
#define highp
|
||||
#endif`
|
||||
}
|
||||
|
||||
type compileContext struct {
|
||||
version GLSLVersion
|
||||
structNames map[string]string
|
||||
structTypes []shaderir.Type
|
||||
}
|
||||
@ -51,14 +76,16 @@ func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) strin
|
||||
return n
|
||||
}
|
||||
|
||||
func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
||||
func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) {
|
||||
c := &compileContext{
|
||||
version: version,
|
||||
structNames: map[string]string{},
|
||||
}
|
||||
|
||||
// Vertex func
|
||||
var vslines []string
|
||||
{
|
||||
vslines = append(vslines, strings.Split(VertexPrelude(version), "\n")...)
|
||||
vslines = append(vslines, "{{.Structs}}")
|
||||
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Attributes) > 0 || len(p.Varyings) > 0 {
|
||||
vslines = append(vslines, "")
|
||||
@ -69,10 +96,18 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
||||
vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
||||
}
|
||||
for i, t := range p.Attributes {
|
||||
vslines = append(vslines, fmt.Sprintf("attribute %s;", c.glslVarDecl(p, &t, fmt.Sprintf("A%d", i))))
|
||||
keyword := "attribute"
|
||||
if version == GLSLVersionES300 {
|
||||
keyword = "in"
|
||||
}
|
||||
vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("A%d", i))))
|
||||
}
|
||||
for i, t := range p.Varyings {
|
||||
vslines = append(vslines, fmt.Sprintf("varying %s;", c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||
keyword := "varying"
|
||||
if version == GLSLVersionES300 {
|
||||
keyword = "out"
|
||||
}
|
||||
vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||
}
|
||||
}
|
||||
if len(p.Funcs) > 0 {
|
||||
@ -99,7 +134,7 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
||||
// Fragment func
|
||||
var fslines []string
|
||||
{
|
||||
fslines = append(fslines, strings.Split(FragmentPrelude, "\n")...)
|
||||
fslines = append(fslines, strings.Split(FragmentPrelude(version), "\n")...)
|
||||
fslines = append(fslines, "", "{{.Structs}}")
|
||||
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Varyings) > 0 {
|
||||
fslines = append(fslines, "")
|
||||
@ -110,9 +145,17 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
||||
fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
||||
}
|
||||
for i, t := range p.Varyings {
|
||||
fslines = append(fslines, fmt.Sprintf("varying %s;", c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||
keyword := "varying"
|
||||
if version == GLSLVersionES300 {
|
||||
keyword = "in"
|
||||
}
|
||||
fslines = append(fslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||
}
|
||||
}
|
||||
if version == GLSLVersionES300 {
|
||||
fslines = append(fslines, "out vec4 fragColor;")
|
||||
}
|
||||
|
||||
if len(p.Funcs) > 0 {
|
||||
fslines = append(fslines, "")
|
||||
for _, f := range p.Funcs {
|
||||
@ -273,7 +316,7 @@ func constantToNumberLiteral(t shaderir.ConstType, v constant.Value) string {
|
||||
return fmt.Sprintf("?(unexpected literal: %s)", v)
|
||||
}
|
||||
|
||||
func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx int) string {
|
||||
func (c *compileContext) localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx int) string {
|
||||
switch topBlock {
|
||||
case p.VertexFunc.Block:
|
||||
na := len(p.Attributes)
|
||||
@ -296,6 +339,9 @@ func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx
|
||||
case idx < nv+1:
|
||||
return fmt.Sprintf("V%d", idx-1)
|
||||
case idx == nv+1:
|
||||
if c.version == GLSLVersionES300 {
|
||||
return "fragColor"
|
||||
}
|
||||
return "gl_FragColor"
|
||||
default:
|
||||
return fmt.Sprintf("l%d", idx-(nv+2))
|
||||
@ -307,7 +353,7 @@ func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx
|
||||
|
||||
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
|
||||
idt := strings.Repeat("\t", level+1)
|
||||
name := localVariableName(p, topBlock, block, index)
|
||||
name := c.localVariableName(p, topBlock, block, index)
|
||||
t := p.LocalVariableType(topBlock, block, index)
|
||||
|
||||
var lines []string
|
||||
@ -352,11 +398,11 @@ func (c *compileContext) glslBlock(p *shaderir.Program, topBlock, block *shaderi
|
||||
case shaderir.TextureVariable:
|
||||
return fmt.Sprintf("T%d", e.Index)
|
||||
case shaderir.LocalVariable:
|
||||
return localVariableName(p, topBlock, block, e.Index)
|
||||
return c.localVariableName(p, topBlock, block, e.Index)
|
||||
case shaderir.StructMember:
|
||||
return fmt.Sprintf("M%d", e.Index)
|
||||
case shaderir.BuiltinFuncExpr:
|
||||
return builtinFuncString(e.BuiltinFunc)
|
||||
return c.builtinFuncString(e.BuiltinFunc)
|
||||
case shaderir.SwizzlingExpr:
|
||||
if !shaderir.IsValidSwizzling(e.Swizzling) {
|
||||
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
|
||||
@ -433,7 +479,7 @@ func (c *compileContext) glslBlock(p *shaderir.Program, topBlock, block *shaderi
|
||||
ct = shaderir.ConstTypeFloat
|
||||
}
|
||||
|
||||
v := localVariableName(p, topBlock, block, s.ForVarIndex)
|
||||
v := c.localVariableName(p, topBlock, block, s.ForVarIndex)
|
||||
var delta string
|
||||
switch val, _ := constant.Float64Val(s.ForDelta); val {
|
||||
case 0:
|
||||
|
@ -63,7 +63,7 @@ func basicTypeString(t shaderir.BasicType) string {
|
||||
}
|
||||
}
|
||||
|
||||
func builtinFuncString(f shaderir.BuiltinFunc) string {
|
||||
func (c *compileContext) builtinFuncString(f shaderir.BuiltinFunc) string {
|
||||
switch f {
|
||||
case shaderir.Atan2:
|
||||
return "atan"
|
||||
@ -71,6 +71,11 @@ func builtinFuncString(f shaderir.BuiltinFunc) string {
|
||||
return "dFdx"
|
||||
case shaderir.Dfdy:
|
||||
return "dFdy"
|
||||
case shaderir.Texture2DF:
|
||||
if c.version == GLSLVersionES300 {
|
||||
return "texture"
|
||||
}
|
||||
return "texture2D"
|
||||
default:
|
||||
return string(f)
|
||||
}
|
||||
|
@ -924,3 +924,56 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
||||
testPixels("DrawTrianglesShader", dst)
|
||||
})
|
||||
}
|
||||
|
||||
// Issue #1404
|
||||
func TestShaderDerivatives(t *testing.T) {
|
||||
const w, h = 16, 16
|
||||
|
||||
s, err := NewShader([]byte(`package main
|
||||
|
||||
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
||||
p := imageSrc0At(texCoord)
|
||||
return vec4(abs(dfdx(p.r)), abs(dfdy(p.g)), 0, 1)
|
||||
}
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dst := NewImage(w, h)
|
||||
src := NewImage(w, h)
|
||||
pix := make([]byte, 4*w*h)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
if i < w/2 {
|
||||
pix[4*(j*w+i)] = 0xff
|
||||
}
|
||||
if j < h/2 {
|
||||
pix[4*(j*w+i)+1] = 0xff
|
||||
}
|
||||
pix[4*(j*w+i)+3] = 0xff
|
||||
}
|
||||
}
|
||||
src.ReplacePixels(pix)
|
||||
|
||||
op := &DrawRectShaderOptions{}
|
||||
op.Images[0] = src
|
||||
dst.DrawRectShader(w, h, s, op)
|
||||
|
||||
// The results of the edges might be unreliable. Skip the edges.
|
||||
for j := 1; j < h-1; j++ {
|
||||
for i := 1; i < w-1; i++ {
|
||||
got := dst.At(i, j).(color.RGBA)
|
||||
want := color.RGBA{0, 0, 0, 0xff}
|
||||
if i == w/2-1 || i == w/2 {
|
||||
want.R = 0xff
|
||||
}
|
||||
if j == h/2-1 || j == h/2 {
|
||||
want.G = 0xff
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user