internal/shaderir/glsl: Bug fix: Remove uncalled functions

Some built-in functions like dFdx is not available in a vertex shader,
then a function that calls such built-in function should not be in
a vertex shader.

Closes #1701
This commit is contained in:
Hajime Hoshi 2021-07-09 18:11:56 +09:00
parent b0106e95b9
commit 853c1f2b92
6 changed files with 239 additions and 10 deletions

View File

@ -0,0 +1,15 @@
void F2(void);
void F3(void);
void F2(void) {
}
void F3(void) {
F2();
}
void main(void) {
F3();
gl_FragColor = vec4(0.0);
return;
}

View File

@ -0,0 +1,29 @@
attribute vec2 A0;
void F0(void);
void F1(void);
void F2(void);
void F3(void);
void F0(void) {
F1();
F2();
}
void F1(void) {
F2();
F3();
}
void F2(void) {
}
void F3(void) {
F2();
}
void main(void) {
F0();
gl_Position = vec4(0.0);
return;
}

35
internal/shader/testdata/issue1701.go vendored Normal file
View File

@ -0,0 +1,35 @@
package main
func Foo() {
Bar()
Baz()
}
func Bar() {
Baz()
Quux()
}
func Baz() {
}
func Quux() {
Baz()
}
func NeverCalled() {
Foo()
Bar()
Baz()
Quux()
}
func Vertex(pos vec2) vec4 {
Foo()
return vec4(0)
}
func Fragment(pos vec4) vec4 {
Quux()
return vec4(0)
}

View File

@ -19,6 +19,7 @@ import (
"go/constant"
"go/token"
"regexp"
"sort"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
@ -82,6 +83,12 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
structNames: map[string]string{},
}
indexToFunc := map[int]*shaderir.Func{}
for _, f := range p.Funcs {
f := f
indexToFunc[f.Index] = &f
}
// Vertex func
var vslines []string
{
@ -110,16 +117,33 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
}
}
if len(p.Funcs) > 0 {
vslines = append(vslines, "")
for _, f := range p.Funcs {
vslines = append(vslines, c.glslFunc(p, &f, true)...)
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
indices := p.ReferredFuncIndicesInVertexShader()
sort.Ints(indices)
funcs = make([]*shaderir.Func, 0, len(indices))
for _, idx := range indices {
funcs = append(funcs, indexToFunc[idx])
}
} else {
// When a vertex entry point is not defined, allow to put all the functions. This is useful for testing.
funcs = make([]*shaderir.Func, 0, len(p.Funcs))
for _, f := range p.Funcs {
f := f
funcs = append(funcs, &f)
}
}
if len(funcs) > 0 {
vslines = append(vslines, "")
for _, f := range funcs {
vslines = append(vslines, c.glslFunc(p, f, true)...)
}
for _, f := range funcs {
if len(vslines) > 0 && vslines[len(vslines)-1] != "" {
vslines = append(vslines, "")
}
vslines = append(vslines, c.glslFunc(p, &f, false)...)
vslines = append(vslines, c.glslFunc(p, f, false)...)
}
}
@ -156,16 +180,32 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
fslines = append(fslines, "out vec4 fragColor;")
}
if len(p.Funcs) > 0 {
fslines = append(fslines, "")
for _, f := range p.Funcs {
fslines = append(fslines, c.glslFunc(p, &f, true)...)
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
indices := p.ReferredFuncIndicesInFragmentShader()
sort.Ints(indices)
funcs = make([]*shaderir.Func, 0, len(indices))
for _, idx := range indices {
funcs = append(funcs, indexToFunc[idx])
}
} else {
// When a fragment entry point is not defined, allow to put all the functions. This is useful for testing.
funcs = make([]*shaderir.Func, 0, len(p.Funcs))
for _, f := range p.Funcs {
f := f
funcs = append(funcs, &f)
}
}
if len(funcs) > 0 {
fslines = append(fslines, "")
for _, f := range funcs {
fslines = append(fslines, c.glslFunc(p, f, true)...)
}
for _, f := range funcs {
if len(fslines) > 0 && fslines[len(fslines)-1] != "" {
fslines = append(fslines, "")
}
fslines = append(fslines, c.glslFunc(p, &f, false)...)
fslines = append(fslines, c.glslFunc(p, f, false)...)
}
}

View File

@ -347,3 +347,55 @@ func IsValidSwizzling(s string) bool {
}
return false
}
func (p *Program) ReferredFuncIndicesInVertexShader() []int {
return p.referredFuncIndicesInBlockEntryPoint(p.VertexFunc.Block)
}
func (p *Program) ReferredFuncIndicesInFragmentShader() []int {
return p.referredFuncIndicesInBlockEntryPoint(p.FragmentFunc.Block)
}
func (p *Program) referredFuncIndicesInBlockEntryPoint(b *Block) []int {
indexToFunc := map[int]*Func{}
for _, f := range p.Funcs {
f := f
indexToFunc[f.Index] = &f
}
visited := map[int]struct{}{}
return referredFuncIndicesInBlock(b, indexToFunc, visited)
}
func referredFuncIndicesInBlock(b *Block, indexToFunc map[int]*Func, visited map[int]struct{}) []int {
if b == nil {
return nil
}
var fs []int
for _, s := range b.Stmts {
for _, e := range s.Exprs {
fs = append(fs, referredFuncIndicesInExpr(&e, indexToFunc, visited)...)
}
for _, bb := range s.Blocks {
fs = append(fs, referredFuncIndicesInBlock(bb, indexToFunc, visited)...)
}
}
return fs
}
func referredFuncIndicesInExpr(e *Expr, indexToFunc map[int]*Func, visited map[int]struct{}) []int {
var fs []int
if e.Type == FunctionExpr {
if _, ok := visited[e.Index]; !ok {
fs = append(fs, e.Index)
visited[e.Index] = struct{}{}
fs = append(fs, referredFuncIndicesInBlock(indexToFunc[e.Index].Block, indexToFunc, visited)...)
}
}
for _, ee := range e.Exprs {
fs = append(fs, referredFuncIndicesInExpr(&ee, indexToFunc, visited)...)
}
return fs
}

View File

@ -1053,3 +1053,61 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
}
}
}
// Issue #1701
func TestShaderDerivatives2(t *testing.T) {
const w, h = 16, 16
s, err := NewShader([]byte(`package main
// This function uses dfdx and then should not be in GLSL's vertex shader (#1701).
func Foo(p vec4) vec4 {
return vec4(abs(dfdx(p.r)), abs(dfdy(p.g)), 0, 1)
}
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
p := imageSrc0At(texCoord)
return Foo(p)
}
`))
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)
}
}
}
}