shader: Separate uniform variables and texture variabls

Textures cannot be treated as a regular variable, then they should
be treated differently from other uniform variables.

Add a new function texture0At replacing texture2D.

Updates #1239
This commit is contained in:
Hajime Hoshi 2020-07-06 03:36:15 +09:00
parent 98ae0826c7
commit 4021c24534
14 changed files with 76 additions and 66 deletions

View File

@ -18,7 +18,6 @@ package main
var Time float
var Cursor vec2
var Image texture2d
func Vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2) {
return mat4(
@ -30,6 +29,5 @@ func Vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2) {
}
func Fragment(position vec4, texCoord vec2) vec4 {
// TODO: Instead of using texture2D directly, define and use special functions for Ebiten images.
return texture2D(Image, texCoord)
return texture0At(texCoord)
}

View File

@ -3,4 +3,4 @@
package main
var image_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\nvar Image texture2d\n\nfunc Vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2) {\n\treturn mat4(\n\t\t2/viewportSize().x, 0, 0, 0,\n\t\t0, 2/viewportSize().y, 0, 0,\n\t\t0, 0, 1, 0,\n\t\t-1, -1, 0, 1,\n\t) * vec4(position, 0, 1), texCoord\n}\n\nfunc Fragment(position vec4, texCoord vec2) vec4 {\n\t// TODO: Instead of using texture2D directly, define and use special functions for Ebiten images.\n\treturn texture2D(Image, texCoord)\n}\n")
var image_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\n\nfunc Vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2) {\n\treturn mat4(\n\t\t2/viewportSize().x, 0, 0, 0,\n\t\t0, 2/viewportSize().y, 0, 0,\n\t\t0, 0, 1, 0,\n\t\t-1, -1, 0, 1,\n\t) * vec4(position, 0, 1), texCoord\n}\n\nfunc Fragment(position vec4, texCoord vec2) vec4 {\n\treturn texture0At(texCoord)\n}\n")

View File

@ -78,7 +78,7 @@ func (g *Game) Update(screen *ebiten.Image) error {
g.shaders = map[int]*ebiten.Shader{}
}
if _, ok := g.shaders[g.idx]; !ok {
s, err := ebiten.NewShader([]byte(shaderSrcs[g.idx]))
s, err := ebiten.NewShader([]byte(shaderSrcs[g.idx]), 1)
if err != nil {
return err
}

View File

@ -139,7 +139,7 @@ precision mediump float;
{{.Definitions}}
uniform sampler2D texture;
uniform sampler2D T0;
uniform vec4 source_region;
#if defined(USE_COLOR_MATRIX)
@ -185,14 +185,14 @@ void main(void) {
#if defined(FILTER_NEAREST)
vec4 color;
# if defined(ADDRESS_UNSAFE)
color = texture2D(texture, pos);
color = texture2D(T0, pos);
# else
pos = adjustTexelByAddress(pos, source_region);
if (source_region[0] <= pos.x &&
source_region[1] <= pos.y &&
pos.x < source_region[2] &&
pos.y < source_region[3]) {
color = texture2D(texture, pos);
color = texture2D(T0, pos);
} else {
color = vec4(0, 0, 0, 0);
}
@ -213,10 +213,10 @@ void main(void) {
p1 = adjustTexelByAddress(p1, source_region);
# endif // defined(ADDRESS_UNSAFE)
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
vec4 c0 = texture2D(T0, p0);
vec4 c1 = texture2D(T0, vec2(p1.x, p0.y));
vec4 c2 = texture2D(T0, vec2(p0.x, p1.y));
vec4 c3 = texture2D(T0, p1);
# if !defined(ADDRESS_UNSAFE)
if (p0.x < source_region[0]) {
c0 = vec4(0, 0, 0, 0);
@ -247,10 +247,10 @@ void main(void) {
highp vec2 p0 = pos - half_scaled_texel_size + (texel_size / 512.0);
highp vec2 p1 = pos + half_scaled_texel_size + (texel_size / 512.0);
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
vec4 c0 = texture2D(T0, p0);
vec4 c1 = texture2D(T0, vec2(p1.x, p0.y));
vec4 c2 = texture2D(T0, vec2(p0.x, p1.y));
vec4 c3 = texture2D(T0, p1);
// Texels must be in the source rect, so it is not necessary to check that like linear filter.
vec2 rate_center = vec2(1.0, 1.0) - half_scaled_texel_size;

View File

@ -215,13 +215,7 @@ func (g *Graphics) Draw(dst, src driver.ImageID, indexLen int, indexOffset int,
})
}
uniforms = append(uniforms, uniformVariable{
name: "texture",
value: source.textureNative,
textureIndex: 0,
})
if err := g.useProgram(program, uniforms); err != nil {
if err := g.useProgram(program, uniforms, []textureNative{source.textureNative}); err != nil {
return err
}
@ -292,20 +286,21 @@ func (g *Graphics) DrawShader(dst driver.ImageID, shader driver.ShaderID, indexL
}
g.context.blendFunc(mode)
us := make([]uniformVariable, len(uniforms))
tidx := 0
// TODO: Accept texture variables at another slice than uniforms.
us := make([]uniformVariable, 0, len(uniforms))
ts := []textureNative{}
for k, v := range uniforms {
us[k].name = fmt.Sprintf("U%d", k)
switch v := v.(type) {
case driver.ImageID:
us[k].value = g.images[v].textureNative
us[k].textureIndex = tidx
tidx++
ts = append(ts, g.images[v].textureNative)
default:
us[k].value = v
us = append(us, uniformVariable{
name: fmt.Sprintf("U%d", k),
value: v,
})
}
}
if err := g.useProgram(s.p, us); err != nil {
if err := g.useProgram(s.p, us, ts); err != nil {
return err
}
g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes

View File

@ -235,13 +235,12 @@ func areSameFloat32Array(a, b []float32) bool {
}
type uniformVariable struct {
name string
value interface{}
textureIndex int
name string
value interface{}
}
// useProgram uses the program (programTexture).
func (g *Graphics) useProgram(program program, uniforms []uniformVariable) error {
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures []textureNative) error {
if !g.state.lastProgram.equal(program) {
g.context.useProgram(program)
if g.state.lastProgram.equal(zeroProgram) {
@ -273,17 +272,19 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable) error
}
g.context.uniformFloats(program, u.name, v)
g.state.lastUniforms[u.name] = v
case textureNative:
// Apparently, a texture must be bound every time. The cache is not used here.
g.context.uniformInt(program, u.name, u.textureIndex)
if g.state.lastActiveTexture != u.textureIndex {
g.context.activeTexture(u.textureIndex)
g.state.lastActiveTexture = u.textureIndex
}
g.context.bindTexture(v)
default:
return fmt.Errorf("opengl: unexpected uniform value: %v (type: %T)", u.value, u.value)
}
}
for i, t := range textures {
g.context.uniformInt(program, fmt.Sprintf("T%d", i), i)
if g.state.lastActiveTexture != i {
g.context.activeTexture(i)
g.state.lastActiveTexture = i
}
// Apparently, a texture must be bound every time. The cache is not used here.
g.context.bindTexture(t)
}
return nil
}

View File

@ -19,10 +19,14 @@ import (
"go/ast"
gconstant "go/constant"
"go/token"
"regexp"
"strconv"
"github.com/hajimehoshi/ebiten/internal/shaderir"
)
var textureAtRe = regexp.MustCompile(`\Atexture(\d+)At\z`)
func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr, []shaderir.Type, []shaderir.Stmt, bool) {
switch e := expr.(type) {
case *ast.BasicLit:
@ -216,6 +220,12 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
t = shaderir.Type{Main: shaderir.Vec3}
case shaderir.Texture2DF:
t = shaderir.Type{Main: shaderir.Vec4}
args = append([]shaderir.Expr{
{
Type: shaderir.TextureVariable,
Index: callee.Index,
},
}, args...)
default:
t = argts[0]
}
@ -349,6 +359,16 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
},
}, nil, nil, true
}
if m := textureAtRe.FindStringSubmatch(e.Name); m != nil {
i, _ := strconv.Atoi(m[1])
return []shaderir.Expr{
{
Type: shaderir.BuiltinFuncExpr,
BuiltinFunc: shaderir.Texture2DF,
Index: i, // Index is used as a texture ID later.
},
}, nil, nil, true
}
cs.addError(e.Pos(), fmt.Sprintf("unexpected identifier: %s", e.Name))
case *ast.ParenExpr:

View File

@ -118,7 +118,7 @@ func (p *ParseError) Error() string {
return strings.Join(p.errs, "\n")
}
func Compile(fs *token.FileSet, f *ast.File, vertexEntry, fragmentEntry string) (*shaderir.Program, error) {
func Compile(fs *token.FileSet, f *ast.File, vertexEntry, fragmentEntry string, textureNum int) (*shaderir.Program, error) {
s := &compileState{
fs: fs,
vertexEntry: vertexEntry,
@ -135,6 +135,7 @@ func Compile(fs *token.FileSet, f *ast.File, vertexEntry, fragmentEntry string)
// TODO: Make a call graph and reorder the elements.
s.ir.TextureNum = textureNum
return &s.ir, nil
}

View File

@ -45,8 +45,6 @@ func (cs *compileState) parseType(expr ast.Expr) shaderir.Type {
return shaderir.Type{Main: shaderir.Mat3}
case "mat4":
return shaderir.Type{Main: shaderir.Mat4}
case "texture2d":
return shaderir.Type{Main: shaderir.Texture2D}
default:
cs.addError(t.Pos(), fmt.Sprintf("unexpected type: %s", t.Name))
return shaderir.Type{}

View File

@ -81,6 +81,9 @@ func (p *Program) Glsl() (vertexShader, fragmentShader string) {
for i, t := range p.Uniforms {
vslines = append(vslines, fmt.Sprintf("uniform %s;", p.glslVarDecl(&t, fmt.Sprintf("U%d", i))))
}
for i := 0; i < p.TextureNum; i++ {
vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
}
for i, t := range p.Attributes {
vslines = append(vslines, fmt.Sprintf("attribute %s;", p.glslVarDecl(&t, fmt.Sprintf("A%d", i))))
}
@ -110,6 +113,9 @@ func (p *Program) Glsl() (vertexShader, fragmentShader string) {
for i, t := range p.Uniforms {
fslines = append(fslines, fmt.Sprintf("uniform %s;", p.glslVarDecl(&t, fmt.Sprintf("U%d", i))))
}
for i := 0; i < p.TextureNum; i++ {
fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
}
for i, t := range p.Varyings {
fslines = append(fslines, fmt.Sprintf("varying %s;", p.glslVarDecl(&t, fmt.Sprintf("V%d", i))))
}
@ -265,6 +271,8 @@ func (p *Program) glslBlock(topBlock, block *Block, level int, localVarIndex int
return fmt.Sprintf("?(unexpected literal: %s)", e.Const)
case UniformVariable:
return fmt.Sprintf("U%d", e.Index)
case TextureVariable:
return fmt.Sprintf("T%d", e.Index)
case LocalVariable:
idx := e.Index
switch topBlock {

View File

@ -22,6 +22,7 @@ import (
type Program struct {
Uniforms []Type
TextureNum int
Attributes []Type
Varyings []Type
Funcs []Func
@ -111,6 +112,7 @@ type ExprType int
const (
NumberExpr ExprType = iota
UniformVariable
TextureVariable
LocalVariable
StructMember
BuiltinFuncExpr

View File

@ -65,8 +65,6 @@ func (t *Type) String() string {
return "mat3"
case Mat4:
return "mat4"
case Texture2D:
return "texture2D"
case Array:
return fmt.Sprintf("%s[%d]", t.Sub[0].String(), t.Length)
case Struct:
@ -89,10 +87,6 @@ func (t *Type) serialize() string {
type BasicType int
// For a texture, a name Texture2D is used instead of Sampler2D. A sampler is a combination of a texture and how to
// get its texel (a fitlter or an address), while a texture is just a 2D byte array. In Ebiten, a sampler in GLSL
// always uses the same filter (nearest) and address (clamp-to-zero), then the name Texture2D is adopted.
const (
None BasicType = iota
Bool
@ -104,7 +98,6 @@ const (
Mat2
Mat3
Mat4
Texture2D
Array
Struct
)
@ -131,8 +124,6 @@ func (t BasicType) Glsl() string {
return "mat3"
case Mat4:
return "mat4"
case Texture2D:
return "sampler2D"
case Array:
// First-class array is not available on GLSL ES 2.
return "?(array)"

View File

@ -289,7 +289,6 @@ func ShaderProgramFill(r, g, b, a byte) shaderir.Program {
// Uniform variables's indices and their values are:
//
// 0: the framebuffer size (Vec2)
// 1-: the images (Texture2D)
//
// The first image's size and region are represented in attribute variables.
//
@ -300,10 +299,7 @@ func ShaderProgramImages(imageNum int) shaderir.Program {
}
p := defaultProgram()
for i := 0; i < imageNum; i++ {
p.Uniforms = append(p.Uniforms, shaderir.Type{Main: shaderir.Texture2D})
}
p.TextureNum = imageNum
// In the fragment shader, local variables are:
//
@ -337,8 +333,8 @@ func ShaderProgramImages(imageNum int) shaderir.Program {
BuiltinFunc: shaderir.Texture2DF,
},
{
Type: shaderir.UniformVariable,
Index: 1,
Type: shaderir.TextureVariable,
Index: 0,
},
texPos,
},
@ -357,8 +353,8 @@ func ShaderProgramImages(imageNum int) shaderir.Program {
BuiltinFunc: shaderir.Texture2DF,
},
{
Type: shaderir.UniformVariable,
Index: i + 1,
Type: shaderir.TextureVariable,
Index: i,
},
texPos,
},

View File

@ -35,7 +35,7 @@ type Shader struct {
shader *buffered.Shader
}
func NewShader(src []byte) (*Shader, error) {
func NewShader(src []byte, textureNum int) (*Shader, error) {
var buf bytes.Buffer
buf.Write(src)
buf.WriteString(shaderSuffix)
@ -47,7 +47,7 @@ func NewShader(src []byte) (*Shader, error) {
}
// TODO: Create a pseudo vertex entrypoint to treat the attribute values correctly.
s, err := shader.Compile(fs, f, "Vertex", "Fragment")
s, err := shader.Compile(fs, f, "Vertex", "Fragment", textureNum)
if err != nil {
return nil, err
}