mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
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:
parent
98ae0826c7
commit
4021c24534
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)"
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user