From e9e5d27d2c8c5ebcd2cf4e0a44ab4a21958c754f Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 21 Nov 2022 00:49:34 +0900 Subject: [PATCH] internal/shader: bug fix: % operators for integer vecs didn't work Closes #1911 --- internal/shader/expr.go | 39 +++++++++++---------- internal/shaderir/glsl/glsl.go | 24 +++++++++++++ shader_test.go | 63 ++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 18 deletions(-) diff --git a/internal/shader/expr.go b/internal/shader/expr.go index c5576ae00..93642f3f0 100644 --- a/internal/shader/expr.go +++ b/internal/shader/expr.go @@ -228,7 +228,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar if lhs[0].Const != nil && rhs[0].Const != nil { op := e.Op - // https://golang.org/pkg/go/constant/#BinaryOp + // https://pkg.go.dev/go/constant/#BinaryOp // "To force integer division of Int operands, use op == token.QUO_ASSIGN instead of // token.QUO; the result is guaranteed to be Int in this case." if op == token.QUO && lhs[0].Const.Kind() == gconstant.Int && rhs[0].Const.Kind() == gconstant.Int { @@ -255,25 +255,25 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar v = gconstant.MakeBool(gconstant.Compare(lhs[0].Const, op, rhs[0].Const)) } t = shaderir.Type{Main: shaderir.Bool} - default: - if op == token.REM { - if !isModAvailableForConsts(&lhs[0], &rhs[0]) { - var wrongTypeName string - if lhs[0].Const.Kind() != gconstant.Int { - wrongTypeName = goConstantKindString(lhs[0].Const.Kind()) - } else { - wrongTypeName = goConstantKindString(rhs[0].Const.Kind()) - } - cs.addError(e.Pos(), fmt.Sprintf("invalid operation: operator %% not defined on untyped %s", wrongTypeName)) - return nil, nil, nil, false - } - if !cs.forceToInt(e, &lhs[0]) { - return nil, nil, nil, false - } - if !cs.forceToInt(e, &rhs[0]) { - return nil, nil, nil, false + case token.REM: + if !isModAvailableForConsts(&lhs[0], &rhs[0]) { + var wrongTypeName string + if lhs[0].Const.Kind() != gconstant.Int { + wrongTypeName = goConstantKindString(lhs[0].Const.Kind()) + } else { + wrongTypeName = goConstantKindString(rhs[0].Const.Kind()) } + cs.addError(e.Pos(), fmt.Sprintf("invalid operation: operator %% not defined on untyped %s", wrongTypeName)) + return nil, nil, nil, false } + if !cs.forceToInt(e, &lhs[0]) { + return nil, nil, nil, false + } + if !cs.forceToInt(e, &rhs[0]) { + return nil, nil, nil, false + } + fallthrough + default: v = gconstant.BinaryOp(lhs[0].Const, op, rhs[0].Const) if v.Kind() == gconstant.Float { t = shaderir.Type{Main: shaderir.Float} @@ -348,6 +348,9 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar cs.addError(e.Pos(), fmt.Sprintf("types don't match: %s %s %s", lhst.String(), e.Op, rhst.String())) return nil, nil, nil, false } + if !cs.forceToInt(e, &rhs[0]) { + return nil, nil, nil, false + } case shaderir.Int: if !canTruncateToInteger(rhs[0].Const) { cs.addError(e.Pos(), fmt.Sprintf("constant %s truncated to integer", rhs[0].Const.String())) diff --git a/internal/shaderir/glsl/glsl.go b/internal/shaderir/glsl/glsl.go index 8f85e8623..3a4455c3f 100644 --- a/internal/shaderir/glsl/glsl.go +++ b/internal/shaderir/glsl/glsl.go @@ -35,6 +35,30 @@ const ( // utilFunctions is GLSL utility functions for old GLSL versions. const utilFunctions = `int modInt(int x, int y) { return x - y*(x/y); +} + +ivec2 modInt(ivec2 x, int y) { + return x - y*(x/y); +} + +ivec3 modInt(ivec3 x, int y) { + return x - y*(x/y); +} + +ivec4 modInt(ivec4 x, int y) { + return x - y*(x/y); +} + +ivec2 modInt(ivec2 x, ivec2 y) { + return x - y*(x/y); +} + +ivec3 modInt(ivec3 x, ivec3 y) { + return x - y*(x/y); +} + +ivec4 modInt(ivec4 x, ivec4 y) { + return x - y*(x/y); }` func VertexPrelude(version GLSLVersion) string { diff --git a/shader_test.go b/shader_test.go index b58c35ef0..2b283452d 100644 --- a/shader_test.go +++ b/shader_test.go @@ -15,6 +15,7 @@ package ebiten_test import ( + "fmt" "image" "image/color" "math" @@ -1503,3 +1504,65 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { t.Errorf("got: %v, want: %v", got, want) } } + +func TestShaderIVecMod(t *testing.T) { + cases := []struct { + source string + want color.RGBA + }{ + { + source: `a := ivec4(0x24, 0x3f, 0x6a, 0x88) +return vec4(a)/255`, + want: color.RGBA{0x24, 0x3f, 0x6a, 0x88}, + }, + { + source: `a := ivec4(0x24, 0x3f, 0x6a, 0x88) +a %= 0x85 +return vec4(a)/255`, + want: color.RGBA{0x24, 0x3f, 0x6a, 0x03}, + }, + { + source: `a := ivec4(0x24, 0x3f, 0x6a, 0x88) +a %= ivec4(0x85, 0xa3, 0x08, 0xd3) +return vec4(a)/255`, + want: color.RGBA{0x24, 0x3f, 0x02, 0x88}, + }, + { + source: `a := ivec4(0x24, 0x3f, 0x6a, 0x88) +b := a % 0x85 +return vec4(b)/255`, + want: color.RGBA{0x24, 0x3f, 0x6a, 0x03}, + }, + { + source: `a := ivec4(0x24, 0x3f, 0x6a, 0x88) +b := a % ivec4(0x85, 0xa3, 0x08, 0xd3) +return vec4(b)/255`, + want: color.RGBA{0x24, 0x3f, 0x02, 0x88}, + }, + } + + for _, tc := range cases { + shader := fmt.Sprintf(`package main + +func Fragment(position vec4, texCoord vec2, color vec4) vec4 { + %s +} +`, tc.source) + const w, h = 1, 1 + + dst := ebiten.NewImage(w, h) + defer dst.Dispose() + + s, err := ebiten.NewShader([]byte(shader)) + if err != nil { + t.Fatal(err) + } + defer s.Dispose() + + op := &ebiten.DrawRectShaderOptions{} + dst.DrawRectShader(w, h, s, op) + if got, want := dst.At(0, 0).(color.RGBA), tc.want; !sameColors(got, want, 1) { + t.Errorf("%s: got: %v, want: %v", tc.source, got, want) + } + } +}