ebiten: bug fix: add an indirect function call for a fragment shader

Closes #2245
Closes #2247
This commit is contained in:
Hajime Hoshi 2022-08-17 01:01:23 +09:00
parent 652cd169b2
commit cf92158e33
8 changed files with 240 additions and 31 deletions

View File

@ -3,27 +3,34 @@ uniform float U1;
uniform float U2; uniform float U2;
int F0(in int l0); int F0(in int l0);
void F1(in vec4 l0, out vec4 l1);
int F0(in int l0) { int F0(in int l0) {
return l0; return l0;
} }
void main(void) { void F1(in vec4 l0, out vec4 l1) {
int l0 = 0;
int l2 = 0; int l2 = 0;
l0 = 0; int l4 = 0;
for (int l1 = 0; l1 < 10; l1++) { l2 = 0;
int l2 = 0; for (int l3 = 0; l3 < 10; l3++) {
l2 = F0(l1); int l4 = 0;
l0 = (l0) + (l2); l4 = F0(l3);
for (int l3 = 0; l3 < 10; l3++) { l2 = (l2) + (l4);
int l4 = 0; for (int l5 = 0; l5 < 10; l5++) {
l4 = F0(l3); int l6 = 0;
l0 = (l0) + (l4); l6 = F0(l5);
l2 = (l2) + (l6);
} }
} }
l2 = 0; l4 = 0;
l0 = (l0) + (l2); l2 = (l2) + (l4);
gl_FragColor = vec4(l0); l1 = vec4(l2);
return; return;
} }
void main(void) {
vec4 l0 = vec4(0);
F1(gl_FragCoord, l0);
gl_FragColor = l0;
}

View File

@ -1,8 +1,16 @@
void main(void) { void F0(in vec4 l0, out vec4 l1);
void F0(in vec4 l0, out vec4 l1) {
if (true) { if (true) {
gl_FragColor = gl_FragCoord; l1 = l0;
return; return;
} }
gl_FragColor = gl_FragCoord; l1 = l0;
return; return;
} }
void main(void) {
vec4 l0 = vec4(0);
F0(gl_FragCoord, l0);
gl_FragColor = l0;
}

View File

@ -1,8 +1,16 @@
void main(void) { void F0(in vec4 l0, out vec4 l1);
vec4 l0 = vec4(0);
for (float l1 = 0.0; l1 < 4.0; l1++) { void F0(in vec4 l0, out vec4 l1) {
(l0).x = ((l0).x) + ((l1) * (1.0000000000e-02)); vec4 l2 = vec4(0);
for (float l3 = 0.0; l3 < 4.0; l3++) {
(l2).x = ((l2).x) + ((l3) * (1.0000000000e-02));
} }
gl_FragColor = l0; l1 = l2;
return; return;
} }
void main(void) {
vec4 l0 = vec4(0);
F0(gl_FragCoord, l0);
gl_FragColor = l0;
}

View File

@ -1,5 +1,6 @@
void F2(void); void F2(void);
void F3(void); void F3(void);
void F5(in vec4 l0, out vec4 l1);
void F2(void) { void F2(void) {
} }
@ -8,8 +9,14 @@ void F3(void) {
F2(); F2();
} }
void main(void) { void F5(in vec4 l0, out vec4 l1) {
F3(); F3();
gl_FragColor = vec4(0.0); l1 = vec4(0.0);
return; return;
} }
void main(void) {
vec4 l0 = vec4(0);
F5(gl_FragCoord, l0);
gl_FragColor = l0;
}

View File

@ -2,7 +2,15 @@ uniform vec2 U0;
varying vec2 V0; varying vec2 V0;
varying vec4 V1; varying vec4 V1;
void main(void) { void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3);
gl_FragColor = vec4((gl_FragCoord).x, (V0).y, (V1).z, 1.0);
void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3) {
l3 = vec4((l0).x, (l1).y, (l2).z, 1.0);
return; return;
} }
void main(void) {
vec4 l0 = vec4(0);
F0(gl_FragCoord, V0, V1, l0);
gl_FragColor = l0;
}

View File

@ -92,6 +92,8 @@ func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) strin
} }
func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) { func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) {
p = adjustProgram(p)
c := &compileContext{ c := &compileContext{
version: version, version: version,
structNames: map[string]string{}, structNames: map[string]string{},
@ -625,3 +627,109 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
return lines return lines
} }
func adjustProgram(p *shaderir.Program) *shaderir.Program {
if p.FragmentFunc.Block == nil {
return p
}
// Shallow-clone the program in order not to modify p itself.
newP := *p
// Create a new slice not to affect the original p.
newP.Funcs = make([]shaderir.Func, len(p.Funcs))
copy(newP.Funcs, p.Funcs)
// Create a new function whose body is the same is the fragment shader's entry point.
// The entry point will call this.
// This indirect call is needed for these issues:
// - Assignment to gl_FragColor doesn't work (#2245)
// - There are some odd compilers that don't work with early returns and gl_FragColor (#2247)
// Determine a unique index of the new function.
var funcIdx int
for _, f := range newP.Funcs {
if funcIdx <= f.Index {
funcIdx = f.Index + 1
}
}
// For parameters of a fragment func, see the comment in internal/shaderir/program.go.
inParams := make([]shaderir.Type, 1+len(newP.Varyings))
inParams[0] = shaderir.Type{
Main: shaderir.Vec4, // gl_FragCoord
}
copy(inParams[1:], newP.Varyings)
outParams := make([]shaderir.Type, 1)
outParams[0] = shaderir.Type{
Main: shaderir.Vec4, // gl_FragColor
}
newP.Funcs = append(newP.Funcs, shaderir.Func{
Index: funcIdx,
InParams: inParams,
OutParams: outParams,
Return: shaderir.Type{},
Block: newP.FragmentFunc.Block,
})
// Create an AST to call the new function.
call := []shaderir.Expr{
{
Type: shaderir.FunctionExpr,
Index: funcIdx,
},
}
for i := 0; i < 1+len(newP.Varyings); i++ {
call = append(call, shaderir.Expr{
Type: shaderir.LocalVariable,
Index: i,
})
}
call = append(call, shaderir.Expr{
Type: shaderir.LocalVariable,
Index: 1 + len(newP.Varyings) + 1, // l0 as an out parameter.
})
// Replace the entry point with just calling the new function.
stmts := []shaderir.Stmt{
{
Type: shaderir.ExprStmt,
Exprs: []shaderir.Expr{
{
Type: shaderir.Call,
Exprs: call,
},
},
},
{
Type: shaderir.Assign,
Exprs: []shaderir.Expr{
// gl_FragColor
{
Type: shaderir.LocalVariable,
Index: 1 + len(newP.Varyings),
},
// l0
{
Type: shaderir.LocalVariable,
Index: 1 + len(newP.Varyings) + 1,
},
},
},
}
newP.FragmentFunc = shaderir.FragmentFunc{
Block: &shaderir.Block{
LocalVars: []shaderir.Type{
{
Main: shaderir.Vec4,
},
},
LocalVarIndexOffset: 1 + len(newP.Varyings) + 1,
Stmts: stmts,
},
}
return &newP
}

View File

@ -1019,12 +1019,20 @@ uniform float U0;
varying float V0; varying float V0;
varying vec2 V1; varying vec2 V1;
void F0(in vec4 l0, in float l1, in vec2 l2, out vec4 l3);
void F0(in vec4 l0, in float l1, in vec2 l2, out vec4 l3) {
float l4 = float(0);
vec2 l5 = vec2(0);
l3 = l0;
l4 = l1;
l5 = l2;
}
void main(void) { void main(void) {
float l0 = float(0); vec4 l0 = vec4(0);
vec2 l1 = vec2(0); F0(gl_FragCoord, V0, V1, l0);
gl_FragColor = gl_FragCoord; gl_FragColor = l0;
l0 = V0;
l1 = V1;
}`, }`,
}, },
} }

View File

@ -1181,3 +1181,58 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
} }
} }
} }
// Issue #2245, #2247
func TestShaderDrawRect(t *testing.T) {
const (
dstW = 16
dstH = 16
srcW = 8
srcH = 8
)
dst := ebiten.NewImage(dstW, dstH)
src := ebiten.NewImage(srcW, srcH)
s, err := ebiten.NewShader([]byte(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
// Adjust texCoord into [0, 1].
origin, size := imageSrcRegionOnTexture()
texCoord -= origin
texCoord /= size
if texCoord.x >= 0.5 && texCoord.y >= 0.5 {
return vec4(1, 0, 0, 1)
}
return vec4(0, 1, 0, 1)
}
`))
if err != nil {
t.Fatal(err)
}
const (
offsetX = (dstW - srcW) / 2
offsetY = (dstH - srcH) / 2
)
op := &ebiten.DrawRectShaderOptions{}
op.GeoM.Translate(offsetX, offsetY)
op.Images[0] = src
dst.DrawRectShader(srcW, srcH, s, op)
for j := 0; j < dstH; j++ {
for i := 0; i < dstW; i++ {
got := dst.At(i, j).(color.RGBA)
var want color.RGBA
if offsetX <= i && i < offsetX+srcW && offsetY <= j && j < offsetY+srcH {
if offsetX+srcW/2 <= i && offsetY+srcH/2 <= j {
want = color.RGBA{0xff, 0, 0, 0xff}
} else {
want = color.RGBA{0, 0xff, 0, 0xff}
}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}