vendor dependencies, make some changes to how input is done

This commit is contained in:
2026-06-15 18:54:00 +02:00
parent 535130933c
commit 4800eb28d9
759 changed files with 360941 additions and 30 deletions
+284
View File
@@ -0,0 +1,284 @@
// Copyright 2023 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shaderir
import (
"go/constant"
)
func ResolveUntypedConstsForBinaryOp(op Op, lhs, rhs constant.Value, lhst, rhst Type) (newLhs, newRhs constant.Value, ok bool) {
if lhst.Main == None && rhst.Main == None {
if op == LeftShift || op == RightShift {
newLhs = constant.ToInt(lhs)
newRhs = constant.ToInt(rhs)
if newLhs.Kind() == constant.Unknown {
return nil, nil, false
}
if newRhs.Kind() == constant.Unknown {
return nil, nil, false
}
return newLhs, newRhs, true
}
if lhs.Kind() == rhs.Kind() {
return lhs, rhs, true
}
if lhs.Kind() == constant.Float && constant.ToFloat(rhs).Kind() != constant.Unknown {
return lhs, constant.ToFloat(rhs), true
}
if rhs.Kind() == constant.Float && constant.ToFloat(lhs).Kind() != constant.Unknown {
return constant.ToFloat(lhs), rhs, true
}
if lhs.Kind() == constant.Int && constant.ToInt(rhs).Kind() != constant.Unknown {
return lhs, constant.ToInt(rhs), true
}
if rhs.Kind() == constant.Int && constant.ToInt(lhs).Kind() != constant.Unknown {
return constant.ToInt(lhs), rhs, true
}
return nil, nil, false
}
if lhst.Main == None {
if (rhst.Main == Float || rhst.IsFloatVector() || rhst.IsMatrix()) && constant.ToFloat(lhs).Kind() != constant.Unknown {
return constant.ToFloat(lhs), rhs, true
}
if (rhst.Main == Int || rhst.IsIntVector()) && constant.ToInt(lhs).Kind() != constant.Unknown {
return constant.ToInt(lhs), rhs, true
}
if rhst.Main == Bool && lhs.Kind() == constant.Bool {
return lhs, rhs, true
}
return nil, nil, false
}
if rhst.Main == None {
if (lhst.Main == Float || lhst.IsFloatVector() || lhst.IsMatrix()) && constant.ToFloat(rhs).Kind() != constant.Unknown {
return lhs, constant.ToFloat(rhs), true
}
if (lhst.Main == Int || lhst.IsIntVector()) && constant.ToInt(rhs).Kind() != constant.Unknown {
return lhs, constant.ToInt(rhs), true
}
if lhst.Main == Bool && rhs.Kind() == constant.Bool {
return lhs, rhs, true
}
return nil, nil, false
}
// lhst and rhst might not match, but this has nothing to do with resolving untyped consts.
return lhs, rhs, true
}
func TypeFromBinaryOp(op Op, lhst, rhst Type, lhsConst, rhsConst constant.Value) (Type, bool) {
// If both are untyped consts, compare the constants and try to truncate them if necessary.
if lhst.Main == None && rhst.Main == None {
// Assume that the constant types are already adjusted.
if lhsConst.Kind() != rhsConst.Kind() {
panic("shaderir: const types for a binary op must be adjusted")
}
if op == AndAnd || op == OrOr {
if lhsConst.Kind() == constant.Bool && rhsConst.Kind() == constant.Bool {
return Type{Main: Bool}, true
}
return Type{}, false
}
// For %, both operands must be integers if both are constants. Truncatable to an integer is not enough.
if op == ModOp {
if lhsConst.Kind() == constant.Int && rhsConst.Kind() == constant.Int {
return Type{Main: Int}, true
}
return Type{}, false
}
if op == And || op == Or || op == Xor {
if lhsConst.Kind() == constant.Int && rhsConst.Kind() == constant.Int {
return Type{Main: Int}, true
}
return Type{}, false
}
if op == LeftShift || op == RightShift {
if lhsConst.Kind() == constant.Int && rhsConst.Kind() == constant.Int {
return Type{Main: Int}, true
}
return Type{}, false
}
if op == EqualOp || op == NotEqualOp || op == LessThanOp || op == LessThanEqualOp || op == GreaterThanOp || op == GreaterThanEqualOp {
return Type{Main: Bool}, true
}
if lhst.Main == Float || rhst.Main == Float {
return Type{Main: Float}, true
}
if lhst.Main == Int || rhst.Main == Int {
return Type{Main: Int}, true
}
if lhst.Main == Bool || rhst.Main == Bool {
return Type{Main: Bool}, true
}
// If both operands are untyped, keep untyped.
return Type{}, true
}
// Both types must not be untyped.
if lhst.Main == None || rhst.Main == None {
panic("shaderir: cannot resolve untyped values")
}
if op == AndAnd || op == OrOr {
if lhst.Main == Bool && rhst.Main == Bool {
return Type{Main: Bool}, true
}
return Type{}, false
}
if op == VectorEqualOp || op == VectorNotEqualOp {
if (lhst.IsFloatVector() || lhst.IsIntVector()) && (rhst.IsFloatVector() || lhst.IsIntVector()) && lhst.Equal(&rhst) {
return Type{Main: Bool}, true
}
return Type{}, false
}
if op == LessThanOp || op == LessThanEqualOp || op == GreaterThanOp || op == GreaterThanEqualOp {
if (lhst.Main == Int && rhst.Main == Int) || (lhst.Main == Float && rhst.Main == Float) {
return Type{Main: Bool}, true
}
return Type{}, false
}
// Comparing matrices are forbidden (#2187).
if op == EqualOp || op == NotEqualOp {
if lhst.IsMatrix() || rhst.IsMatrix() {
return Type{}, false
}
if lhst.Equal(&rhst) {
return Type{Main: Bool}, true
}
return Type{}, false
}
if op == Div && rhst.IsMatrix() {
return Type{}, false
}
if op == ModOp {
if lhst.Main == IVec2 && rhst.Main == IVec2 {
return Type{Main: IVec2}, true
}
if lhst.Main == IVec3 && rhst.Main == IVec3 {
return Type{Main: IVec3}, true
}
if lhst.Main == IVec4 && rhst.Main == IVec4 {
return Type{Main: IVec4}, true
}
if (lhst.Main == Int || lhst.IsIntVector()) && rhst.Main == Int {
return lhst, true
}
return Type{}, false
}
if op == And || op == Or || op == Xor {
if lhst.Main == Int && rhst.Main == Int {
return Type{Main: Int}, true
}
if lhst.Main == IVec2 && rhst.Main == IVec2 {
return Type{Main: IVec2}, true
}
if lhst.Main == IVec3 && rhst.Main == IVec3 {
return Type{Main: IVec3}, true
}
if lhst.Main == IVec4 && rhst.Main == IVec4 {
return Type{Main: IVec4}, true
}
if lhst.IsIntVector() && rhst.Main == Int {
return lhst, true
}
if lhst.Main == Int && rhst.IsIntVector() {
return rhst, true
}
return Type{}, false
}
if op == LeftShift || op == RightShift {
if (lhst.Main == Int || lhst.IsIntVector()) && rhst.Main == Int {
return lhst, true
}
if lhst.IsIntVector() && rhst.IsIntVector() && lhst.VectorElementCount() == rhst.VectorElementCount() {
return lhst, true
}
return Type{}, false
}
if lhst.Equal(&rhst) {
if lhst.Main == None {
return rhst, true
}
return lhst, true
}
if op == MatrixMul {
if lhst.IsMatrix() && rhst.Main == Float {
return lhst, true
}
if lhst.Main == Mat2 && rhst.Main == Vec2 {
return rhst, true
}
if lhst.Main == Mat3 && rhst.Main == Vec3 {
return rhst, true
}
if lhst.Main == Mat4 && rhst.Main == Vec4 {
return rhst, true
}
if lhst.Main == Float && rhst.IsMatrix() {
return rhst, true
}
if lhst.Main == Vec2 && rhst.Main == Mat2 {
return lhst, true
}
if lhst.Main == Vec3 && rhst.Main == Mat3 {
return lhst, true
}
if lhst.Main == Vec4 && rhst.Main == Mat4 {
return lhst, true
}
return Type{}, false
}
if op == Div {
if lhst.IsMatrix() && rhst.Main == Float {
return lhst, true
}
// fallback
}
if lhst.IsFloatVector() && rhst.Main == Float {
return lhst, true
}
if lhst.Main == Float && rhst.IsFloatVector() {
return rhst, true
}
if lhst.IsIntVector() && rhst.Main == Int {
return lhst, true
}
if lhst.Main == Int && rhst.IsIntVector() {
return rhst, true
}
return Type{}, false
}
+696
View File
@@ -0,0 +1,696 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package glsl
import (
"fmt"
"go/constant"
"go/token"
"math"
"regexp"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
type GLSLVersion int
const (
GLSLVersionDefault GLSLVersion = iota
GLSLVersionES300
)
// 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 {
switch version {
case GLSLVersionDefault:
return `#version 150` + "\n\n" + utilFunctions
case GLSLVersionES300:
return `#version 300 es`
}
return ""
}
func FragmentPrelude(version GLSLVersion) string {
var prefix string
switch version {
case GLSLVersionDefault:
prefix = `#version 150` + "\n\n"
case GLSLVersionES300:
prefix = `#version 300 es` + "\n\n"
}
prelude := prefix + `#if defined(GL_ES)
precision highp float;
precision highp int;
#else
#define lowp
#define mediump
#define highp
#endif
out vec4 fragColor;`
if version == GLSLVersionDefault {
prelude += "\n\n" + utilFunctions
}
return prelude
}
type compileContext struct {
version GLSLVersion
structNames map[string]string
structTypes []shaderir.Type
unit shaderir.Unit
}
func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) string {
if t.Main != shaderir.Struct {
panic("glsl: the given type at structName must be a struct")
}
s := t.String()
if n, ok := c.structNames[s]; ok {
return n
}
n := fmt.Sprintf("S%d", len(c.structNames))
c.structNames[s] = n
c.structTypes = append(c.structTypes, *t)
return n
}
func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) {
p = adjustProgram(p)
c := &compileContext{
version: version,
structNames: map[string]string{},
unit: p.Unit,
}
// Vertex func
var vslines []string
{
vslines = append(vslines, strings.Split(VertexPrelude(version), "\n")...)
vslines = append(vslines, "", "{{.Structs}}")
if len(p.Uniforms) > 0 || p.TextureCount > 0 || len(p.Attributes) > 0 || len(p.Varyings) > 0 {
vslines = append(vslines, "")
for i, t := range p.Uniforms {
vslines = append(vslines, fmt.Sprintf("uniform %s;", c.varDecl(p, &t, fmt.Sprintf("U%d", i))))
}
for i := 0; i < p.TextureCount; i++ {
vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
}
for i, t := range p.Attributes {
vslines = append(vslines, fmt.Sprintf("in %s;", c.varDecl(p, &t, fmt.Sprintf("A%d", i))))
}
for i, t := range p.Varyings {
vslines = append(vslines, fmt.Sprintf("out %s;", c.varDecl(p, &t, fmt.Sprintf("V%d", i))))
}
}
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
funcs = p.ReachableFuncsFromBlock(p.VertexFunc.Block)
} 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.function(p, f, true)...)
}
for _, f := range funcs {
if len(vslines) > 0 && vslines[len(vslines)-1] != "" {
vslines = append(vslines, "")
}
vslines = append(vslines, c.function(p, f, false)...)
}
}
// Add a dummy function to just touch uniform array variable's elements (#1754).
// Without this, the first elements of a uniform array might not be initialized correctly on some environments.
var touchedUniforms []string
for i, t := range p.Uniforms {
if t.Main != shaderir.Array {
continue
}
if t.Length <= 1 {
continue
}
str := fmt.Sprintf("U%d[%d]", i, t.Length-1)
switch t.Sub[0].Main {
case shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.IVec2, shaderir.IVec3, shaderir.IVec4:
str += ".x"
case shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
str += "[0][0]"
}
str = "float(" + str + ")"
touchedUniforms = append(touchedUniforms, str)
}
var touchUniformsFunc []string
if len(touchedUniforms) > 0 {
touchUniformsFunc = append(touchUniformsFunc, "float touchUniforms() {")
touchUniformsFunc = append(touchUniformsFunc, fmt.Sprintf("\treturn %s;", strings.Join(touchedUniforms, " + ")))
touchUniformsFunc = append(touchUniformsFunc, "}")
}
if p.VertexFunc.Block != nil && len(p.VertexFunc.Block.Stmts) > 0 {
if len(touchUniformsFunc) > 0 {
vslines = append(vslines, "")
vslines = append(vslines, touchUniformsFunc...)
}
vslines = append(vslines, "")
vslines = append(vslines, "void main(void) {")
if len(touchUniformsFunc) > 0 {
vslines = append(vslines, "\ttouchUniforms();")
}
vslines = append(vslines, c.block(p, p.VertexFunc.Block, p.VertexFunc.Block, 0)...)
vslines = append(vslines, "}")
}
}
// Fragment func
var fslines []string
{
fslines = append(fslines, strings.Split(FragmentPrelude(version), "\n")...)
fslines = append(fslines, "", "{{.Structs}}")
if len(p.Uniforms) > 0 || p.TextureCount > 0 || len(p.Varyings) > 0 {
fslines = append(fslines, "")
for i, t := range p.Uniforms {
fslines = append(fslines, fmt.Sprintf("uniform %s;", c.varDecl(p, &t, fmt.Sprintf("U%d", i))))
}
for i := 0; i < p.TextureCount; i++ {
fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
}
for i, t := range p.Varyings {
fslines = append(fslines, fmt.Sprintf("in %s;", c.varDecl(p, &t, fmt.Sprintf("V%d", i))))
}
}
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
funcs = p.ReachableFuncsFromBlock(p.FragmentFunc.Block)
} 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.function(p, f, true)...)
}
for _, f := range funcs {
if len(fslines) > 0 && fslines[len(fslines)-1] != "" {
fslines = append(fslines, "")
}
fslines = append(fslines, c.function(p, f, false)...)
}
}
if p.FragmentFunc.Block != nil && len(p.FragmentFunc.Block.Stmts) > 0 {
fslines = append(fslines, "")
fslines = append(fslines, "void main(void) {")
fslines = append(fslines, c.block(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
fslines = append(fslines, "}")
}
}
vs := strings.Join(vslines, "\n")
fs := strings.Join(fslines, "\n")
// Struct types are determined after converting the program.
if len(c.structTypes) > 0 {
var stlines []string
for i, t := range c.structTypes {
stlines = append(stlines, fmt.Sprintf("struct S%d {", i))
for j, st := range t.Sub {
stlines = append(stlines, fmt.Sprintf("\t%s;", c.varDecl(p, &st, fmt.Sprintf("M%d", j))))
}
stlines = append(stlines, "};")
}
st := strings.Join(stlines, "\n")
vs = strings.ReplaceAll(vs, "{{.Structs}}", st)
fs = strings.ReplaceAll(fs, "{{.Structs}}", st)
} else {
vs = strings.ReplaceAll(vs, "{{.Structs}}", "")
fs = strings.ReplaceAll(fs, "{{.Structs}}", "")
}
nls := regexp.MustCompile(`\n\n+`)
vs = nls.ReplaceAllString(vs, "\n\n")
fs = nls.ReplaceAllString(fs, "\n\n")
vs = strings.TrimSpace(vs) + "\n"
fs = strings.TrimSpace(fs) + "\n"
return vs, fs
}
func (c *compileContext) typ(p *shaderir.Program, t *shaderir.Type) (string, string) {
switch t.Main {
case shaderir.None:
return "void", ""
case shaderir.Struct:
return c.structName(p, t), ""
default:
return typeString(t)
}
}
func (c *compileContext) varDecl(p *shaderir.Program, t *shaderir.Type, varname string) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Struct:
return fmt.Sprintf("%s %s", c.structName(p, t), varname)
default:
t0, t1 := typeString(t)
return fmt.Sprintf("%s %s%s", t0, varname, t1)
}
}
func (c *compileContext) varInit(p *shaderir.Program, t *shaderir.Type) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Array:
init := c.varInit(p, &t.Sub[0])
es := make([]string, 0, t.Length)
for i := 0; i < t.Length; i++ {
es = append(es, init)
}
t0, t1 := typeString(t)
return fmt.Sprintf("%s%s(%s)", t0, t1, strings.Join(es, ", "))
case shaderir.Struct:
panic("not implemented")
case shaderir.Bool:
return "false"
case shaderir.Int:
return "0"
case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4,
shaderir.IVec2, shaderir.IVec3, shaderir.IVec4,
shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
return fmt.Sprintf("%s(0)", basicTypeString(t.Main))
default:
t0, t1 := c.typ(p, t)
panic(fmt.Sprintf("?(unexpected type: %s%s)", t0, t1))
}
}
func (c *compileContext) function(p *shaderir.Program, f *shaderir.Func, prototype bool) []string {
var args []string
var idx int
for _, t := range f.InParams {
args = append(args, "in "+c.varDecl(p, &t, fmt.Sprintf("l%d", idx)))
idx++
}
for _, t := range f.OutParams {
args = append(args, "out "+c.varDecl(p, &t, fmt.Sprintf("l%d", idx)))
idx++
}
argsstr := "void"
if len(args) > 0 {
argsstr = strings.Join(args, ", ")
}
t0, t1 := c.typ(p, &f.Return)
sig := fmt.Sprintf("%s%s F%d(%s)", t0, t1, f.Index, argsstr)
var lines []string
if prototype {
lines = append(lines, fmt.Sprintf("%s;", sig))
return lines
}
lines = append(lines, fmt.Sprintf("%s {", sig))
lines = append(lines, c.block(p, f.Block, f.Block, 0)...)
lines = append(lines, "}")
return lines
}
func constantToNumberLiteral(v constant.Value) string {
switch v.Kind() {
case constant.Bool:
if constant.BoolVal(v) {
return "true"
}
return "false"
case constant.Int:
x, _ := constant.Int64Val(v)
return fmt.Sprintf("%d", x)
case constant.Float:
x, _ := constant.Float64Val(v)
if i := math.Floor(x); i == x {
return fmt.Sprintf("%d.0", int64(i))
}
return fmt.Sprintf("%.10e", x)
}
return fmt.Sprintf("?(unexpected literal: %s)", v)
}
func (c *compileContext) localVariableName(p *shaderir.Program, topBlock *shaderir.Block, idx int) string {
switch topBlock {
case p.VertexFunc.Block:
na := len(p.Attributes)
nv := len(p.Varyings)
switch {
case idx < na:
return fmt.Sprintf("A%d", idx)
case idx == na:
return "gl_Position"
case idx < na+nv+1:
return fmt.Sprintf("V%d", idx-na-1)
default:
return fmt.Sprintf("l%d", idx-(na+nv+1))
}
case p.FragmentFunc.Block:
nv := len(p.Varyings)
switch {
case idx == 0:
return "gl_FragCoord"
case idx < nv+1:
return fmt.Sprintf("V%d", idx-1)
default:
return fmt.Sprintf("l%d", idx-(nv+1))
}
default:
return fmt.Sprintf("l%d", idx)
}
}
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
idt := strings.Repeat("\t", level+1)
name := c.localVariableName(p, topBlock, index)
t := p.LocalVariableType(topBlock, block, index)
var lines []string
switch t.Main {
case shaderir.Array:
if decl {
lines = append(lines, fmt.Sprintf("%s%s;", idt, c.varDecl(p, &t, name)))
}
init := c.varInit(p, &t.Sub[0])
for i := 0; i < t.Length; i++ {
lines = append(lines, fmt.Sprintf("%s%s[%d] = %s;", idt, name, i, init))
}
case shaderir.None:
// The type is None e.g., when the variable is a for-loop counter.
default:
if decl {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, c.varDecl(p, &t, name), c.varInit(p, &t)))
} else {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, name, c.varInit(p, &t)))
}
}
return lines
}
func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Block, level int) []string {
if block == nil {
return nil
}
var lines []string
for i := range block.LocalVars {
lines = append(lines, c.initVariable(p, topBlock, block, block.LocalVarIndexOffset+i, true, level)...)
}
var expr func(e *shaderir.Expr) string
expr = func(e *shaderir.Expr) string {
switch e.Type {
case shaderir.NumberExpr:
return constantToNumberLiteral(e.Const)
case shaderir.UniformVariable:
return fmt.Sprintf("U%d", e.Index)
case shaderir.TextureVariable:
return fmt.Sprintf("T%d", e.Index)
case shaderir.LocalVariable:
return c.localVariableName(p, topBlock, e.Index)
case shaderir.StructMember:
return fmt.Sprintf("M%d", e.Index)
case shaderir.BuiltinFuncExpr:
return c.builtinFuncString(e.BuiltinFunc)
case shaderir.SwizzlingExpr:
if !shaderir.IsValidSwizzling(e.Swizzling) {
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
}
return e.Swizzling
case shaderir.FunctionExpr:
return fmt.Sprintf("F%d", e.Index)
case shaderir.Unary:
var op string
switch e.Op {
case shaderir.Add, shaderir.Sub, shaderir.NotOp:
op = opString(e.Op)
default:
op = fmt.Sprintf("?(unexpected op: %d)", e.Op)
}
return fmt.Sprintf("%s(%s)", op, expr(&e.Exprs[0]))
case shaderir.Binary:
if e.Op == shaderir.ModOp && c.version == GLSLVersionDefault {
// '%' is not defined.
return fmt.Sprintf("modInt((%s), (%s))", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
}
return fmt.Sprintf("(%s) %s (%s)", expr(&e.Exprs[0]), opString(e.Op), expr(&e.Exprs[1]))
case shaderir.Selection:
return fmt.Sprintf("(%s) ? (%s) : (%s)", expr(&e.Exprs[0]), expr(&e.Exprs[1]), expr(&e.Exprs[2]))
case shaderir.Call:
var args []string
for _, exp := range e.Exprs[1:] {
args = append(args, expr(&exp))
}
f := expr(&e.Exprs[0])
if f == "texelFetch" {
return fmt.Sprintf("%s(%s, ivec2(%s), 0)", f, args[0], args[1])
}
// Using parentheses at the callee is illegal.
return fmt.Sprintf("%s(%s)", f, strings.Join(args, ", "))
case shaderir.FieldSelector:
return fmt.Sprintf("(%s).%s", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.Index:
return fmt.Sprintf("(%s)[%s]", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
default:
return fmt.Sprintf("?(unexpected expr: %d)", e.Type)
}
}
idt := strings.Repeat("\t", level+1)
for _, s := range block.Stmts {
switch s.Type {
case shaderir.ExprStmt:
lines = append(lines, fmt.Sprintf("%s%s;", idt, expr(&s.Exprs[0])))
case shaderir.BlockStmt:
lines = append(lines, idt+"{")
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, idt+"}")
case shaderir.Assign:
lhs := s.Exprs[0]
rhs := s.Exprs[1]
if lhs.Type == shaderir.LocalVariable {
if t := p.LocalVariableType(topBlock, block, lhs.Index); t.Main == shaderir.Array {
for i := 0; i < t.Length; i++ {
lines = append(lines, fmt.Sprintf("%[1]s%[2]s[%[3]d] = %[4]s[%[3]d];", idt, expr(&lhs), i, expr(&rhs)))
}
continue
}
}
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, expr(&lhs), expr(&rhs)))
case shaderir.Init:
lines = append(lines, c.initVariable(p, topBlock, block, s.InitIndex, false, level)...)
case shaderir.If:
lines = append(lines, fmt.Sprintf("%sif (%s) {", idt, expr(&s.Exprs[0])))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
if len(s.Blocks) > 1 {
lines = append(lines, fmt.Sprintf("%s} else {", idt))
lines = append(lines, c.block(p, topBlock, s.Blocks[1], level+1)...)
}
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.For:
v := c.localVariableName(p, topBlock, s.ForVarIndex)
var delta string
switch val, _ := constant.Float64Val(s.ForDelta); val {
case 0:
delta = fmt.Sprintf("?(unexpected delta: %v)", s.ForDelta)
case 1:
delta = fmt.Sprintf("%s++", v)
case -1:
delta = fmt.Sprintf("%s--", v)
default:
d := s.ForDelta
if val > 0 {
delta = fmt.Sprintf("%s += %s", v, constantToNumberLiteral(d))
} else {
d = constant.UnaryOp(token.SUB, d, 0)
delta = fmt.Sprintf("%s -= %s", v, constantToNumberLiteral(d))
}
}
var op string
switch s.ForOp {
case shaderir.LessThanOp, shaderir.LessThanEqualOp, shaderir.GreaterThanOp, shaderir.GreaterThanEqualOp, shaderir.EqualOp, shaderir.NotEqualOp:
op = opString(s.ForOp)
default:
op = fmt.Sprintf("?(unexpected op: %d)", s.ForOp)
}
t := s.ForVarType
init := constantToNumberLiteral(s.ForInit)
end := constantToNumberLiteral(s.ForEnd)
t0, t1 := typeString(&t)
lines = append(lines, fmt.Sprintf("%sfor (%s %s%s = %s; %s %s %s; %s) {", idt, t0, v, t1, init, v, op, end, delta))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.Continue:
lines = append(lines, idt+"continue;")
case shaderir.Break:
lines = append(lines, idt+"break;")
case shaderir.Return:
switch {
case topBlock == p.FragmentFunc.Block:
lines = append(lines, fmt.Sprintf("%sfragColor = %s;", idt, expr(&s.Exprs[0])))
// The 'return' statement is not required so far, as the fragment entrypoint has only one sentence so far. See adjustProgram implementation.
case len(s.Exprs) == 0:
lines = append(lines, idt+"return;")
default:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, expr(&s.Exprs[0])))
}
case shaderir.Discard:
// 'discard' is invoked only in the fragment shader entry point.
lines = append(lines, idt+"discard;", idt+"return vec4(0.0);")
default:
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
}
}
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)
newP.Funcs = append(newP.Funcs, shaderir.Func{
Index: funcIdx,
InParams: inParams,
OutParams: nil,
Return: shaderir.Type{
Main: shaderir.Vec4,
},
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,
})
}
// Replace the entry point with just calling the new function.
stmts := []shaderir.Stmt{
{
// Return: This will be replaced with assignment to gl_FragColor.
Type: shaderir.Return,
Exprs: []shaderir.Expr{
// The function call
{
Type: shaderir.Call,
Exprs: call,
},
},
},
}
newP.FragmentFunc = shaderir.FragmentFunc{
Block: &shaderir.Block{
LocalVars: nil,
LocalVarIndexOffset: 1 + len(newP.Varyings) + 1,
Stmts: stmts,
},
}
return &newP
}
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package glsl
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
func opString(op shaderir.Op) string {
switch op {
case shaderir.Add:
return "+"
case shaderir.Sub:
return "-"
case shaderir.NotOp:
return "!"
case shaderir.ComponentWiseMul, shaderir.MatrixMul:
return "*"
case shaderir.Div:
return "/"
case shaderir.ModOp:
return "%"
case shaderir.LeftShift:
return "<<"
case shaderir.RightShift:
return ">>"
case shaderir.LessThanOp:
return "<"
case shaderir.LessThanEqualOp:
return "<="
case shaderir.GreaterThanOp:
return ">"
case shaderir.GreaterThanEqualOp:
return ">="
case shaderir.EqualOp, shaderir.VectorEqualOp:
return "=="
case shaderir.NotEqualOp, shaderir.VectorNotEqualOp:
return "!="
case shaderir.And:
return "&"
case shaderir.Xor:
return "^"
case shaderir.Or:
return "|"
case shaderir.AndAnd:
return "&&"
case shaderir.OrOr:
return "||"
}
return fmt.Sprintf("?(unexpected operator: %d)", op)
}
func typeString(t *shaderir.Type) (string, string) {
switch t.Main {
case shaderir.Array:
t0, t1 := typeString(&t.Sub[0])
return t0 + t1, fmt.Sprintf("[%d]", t.Length)
case shaderir.Struct:
panic("glsl: a struct is not implemented")
default:
return basicTypeString(t.Main), ""
}
}
func basicTypeString(t shaderir.BasicType) string {
switch t {
case shaderir.None:
return "?(none)"
case shaderir.Bool:
return "bool"
case shaderir.Int:
return "int"
case shaderir.Float:
return "float"
case shaderir.Vec2:
return "vec2"
case shaderir.Vec3:
return "vec3"
case shaderir.Vec4:
return "vec4"
case shaderir.IVec2:
return "ivec2"
case shaderir.IVec3:
return "ivec3"
case shaderir.IVec4:
return "ivec4"
case shaderir.Mat2:
return "mat2"
case shaderir.Mat3:
return "mat3"
case shaderir.Mat4:
return "mat4"
case shaderir.Array:
return "?(array)"
case shaderir.Struct:
return "?(struct)"
default:
return fmt.Sprintf("?(unknown type: %d)", t)
}
}
func (c *compileContext) builtinFuncString(f shaderir.BuiltinFunc) string {
switch f {
case shaderir.Atan2:
return "atan"
case shaderir.Dfdx:
return "dFdx"
case shaderir.Dfdy:
return "dFdy"
case shaderir.TexelAt:
if c.unit == shaderir.Pixels {
return "texelFetch"
}
return "texture"
default:
return string(f)
}
}
+580
View File
@@ -0,0 +1,580 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hlsl
import (
"fmt"
"go/constant"
"go/token"
"math"
"regexp"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
const (
vsOut = "varyings"
)
type compileContext struct {
structNames map[string]string
structTypes []shaderir.Type
unit shaderir.Unit
}
func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) string {
if t.Main != shaderir.Struct {
panic("hlsl: the given type at structName must be a struct")
}
s := t.String()
if n, ok := c.structNames[s]; ok {
return n
}
n := fmt.Sprintf("S%d", len(c.structNames))
if c.structNames == nil {
c.structNames = map[string]string{}
}
c.structNames[s] = n
c.structTypes = append(c.structTypes, *t)
return n
}
const Prelude = `struct Varyings {
float4 Position : SV_POSITION;
float2 M0 : TEXCOORD0;
float4 M1 : COLOR;
};
float mod(float x, float y) {
return x - y * floor(x/y);
}
float2 mod(float2 x, float2 y) {
return x - y * floor(x/y);
}
float3 mod(float3 x, float3 y) {
return x - y * floor(x/y);
}
float4 mod(float4 x, float4 y) {
return x - y * floor(x/y);
}
float2x2 float2x2FromScalar(float x) {
return float2x2(x, 0, 0, x);
}
float3x3 float3x3FromScalar(float x) {
return float3x3(x, 0, 0, 0, x, 0, 0, 0, x);
}
float4x4 float4x4FromScalar(float x) {
return float4x4(x, 0, 0, 0, 0, x, 0, 0, 0, 0, x, 0, 0, 0, 0, x);
}`
func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []int) {
offsets = calculateMemoryOffsets(p.Uniforms)
c := &compileContext{
unit: p.Unit,
}
var lines []string
lines = append(lines, strings.Split(Prelude, "\n")...)
lines = append(lines, "", "{{.Structs}}")
if len(p.Uniforms) > 0 {
lines = append(lines, "")
lines = append(lines, "cbuffer Uniforms : register(b0) {")
for i, t := range p.Uniforms {
// packingoffset is not mandatory, but this is useful to ensure the correct offset is used.
offset := fmt.Sprintf("c%d", offsets[i]/boundaryInBytes)
switch offsets[i] % boundaryInBytes {
case 4:
offset += ".y"
case 8:
offset += ".z"
case 12:
offset += ".w"
}
lines = append(lines, fmt.Sprintf("\t%s : packoffset(%s);", c.varDecl(p, &t, fmt.Sprintf("U%d", i)), offset))
}
lines = append(lines, "}")
}
if p.TextureCount > 0 {
lines = append(lines, "")
for i := 0; i < p.TextureCount; i++ {
lines = append(lines, fmt.Sprintf("Texture2D T%[1]d : register(t%[1]d);", i))
}
if c.unit == shaderir.Texels {
lines = append(lines, "SamplerState samp : register(s0);")
}
}
vslines := make([]string, len(lines))
copy(vslines, lines)
pslines := make([]string, len(lines))
copy(pslines, lines)
var vsfuncs []*shaderir.Func
if p.VertexFunc.Block != nil {
vsfuncs = p.ReachableFuncsFromBlock(p.VertexFunc.Block)
} else {
// Use all the functions for testing.
vsfuncs = make([]*shaderir.Func, 0, len(p.Funcs))
for _, f := range p.Funcs {
f := f
vsfuncs = append(vsfuncs, &f)
}
}
if len(vsfuncs) > 0 {
vslines = append(vslines, "")
for _, f := range vsfuncs {
vslines = append(vslines, c.function(p, f, true)...)
}
for _, f := range vsfuncs {
if len(vslines) > 0 && vslines[len(vslines)-1] != "" {
vslines = append(vslines, "")
}
vslines = append(vslines, c.function(p, f, false)...)
}
}
if p.VertexFunc.Block != nil && len(p.VertexFunc.Block.Stmts) > 0 {
vslines = append(vslines, "")
vslines = append(vslines, "Varyings VSMain(float2 A0 : POSITION, float2 A1 : TEXCOORD, float4 A2 : COLOR) {")
vslines = append(vslines, fmt.Sprintf("\tVaryings %s;", vsOut))
vslines = append(vslines, c.block(p, p.VertexFunc.Block, p.VertexFunc.Block, 0)...)
if last := fmt.Sprintf("\treturn %s;", vsOut); vslines[len(vslines)-1] != last {
vslines = append(vslines, last)
}
vslines = append(vslines, "}")
}
var psfuncs []*shaderir.Func
if p.FragmentFunc.Block != nil {
psfuncs = p.ReachableFuncsFromBlock(p.FragmentFunc.Block)
} else {
// Use all the functions for testing.
psfuncs = make([]*shaderir.Func, 0, len(p.Funcs))
for _, f := range p.Funcs {
f := f
psfuncs = append(psfuncs, &f)
}
}
if len(psfuncs) > 0 {
pslines = append(pslines, "")
for _, f := range psfuncs {
pslines = append(pslines, c.function(p, f, true)...)
}
for _, f := range psfuncs {
if len(pslines) > 0 && pslines[len(pslines)-1] != "" {
pslines = append(pslines, "")
}
pslines = append(pslines, c.function(p, f, false)...)
}
}
if p.FragmentFunc.Block != nil && len(p.FragmentFunc.Block.Stmts) > 0 {
pslines = append(pslines, "")
pslines = append(pslines, fmt.Sprintf("float4 PSMain(Varyings %s) : SV_TARGET {", vsOut))
pslines = append(pslines, c.block(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
pslines = append(pslines, "}")
}
vertexShader = strings.Join(vslines, "\n")
pixelShader = strings.Join(pslines, "\n")
// Struct types are determined after converting the program.
shaders := []string{vertexShader, pixelShader}
for i, shader := range shaders {
if len(c.structTypes) > 0 {
var stlines []string
for i, t := range c.structTypes {
stlines = append(stlines, fmt.Sprintf("struct S%d {", i))
for j, st := range t.Sub {
stlines = append(stlines, fmt.Sprintf("\t%s;", c.varDecl(p, &st, fmt.Sprintf("M%d", j))))
}
stlines = append(stlines, "};")
}
st := strings.Join(stlines, "\n")
shader = strings.ReplaceAll(shader, "{{.Structs}}", st)
} else {
shader = strings.ReplaceAll(shader, "{{.Structs}}", "")
}
nls := regexp.MustCompile(`\n\n+`)
shader = nls.ReplaceAllString(shader, "\n\n")
shader = strings.TrimSpace(shader) + "\n"
shaders[i] = shader
}
vertexShader = shaders[0]
pixelShader = shaders[1]
return
}
func (c *compileContext) typ(p *shaderir.Program, t *shaderir.Type) (string, string) {
switch t.Main {
case shaderir.None:
return "void", ""
case shaderir.Struct:
return c.structName(p, t), ""
default:
return typeString(t)
}
}
func (c *compileContext) varDecl(p *shaderir.Program, t *shaderir.Type, varname string) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Struct:
return fmt.Sprintf("%s %s", c.structName(p, t), varname)
default:
t0, t1 := typeString(t)
return fmt.Sprintf("%s %s%s", t0, varname, t1)
}
}
func (c *compileContext) varInit(p *shaderir.Program, t *shaderir.Type) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Array:
init := c.varInit(p, &t.Sub[0])
es := make([]string, 0, t.Length)
for i := 0; i < t.Length; i++ {
es = append(es, init)
}
t0, t1 := typeString(t)
return fmt.Sprintf("%s%s(%s)", t0, t1, strings.Join(es, ", "))
case shaderir.Struct:
panic("not implemented")
case shaderir.Bool:
return "false"
case shaderir.Int, shaderir.IVec2, shaderir.IVec3, shaderir.IVec4:
return "0"
case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
return "0.0"
default:
t0, t1 := c.typ(p, t)
panic(fmt.Sprintf("?(unexpected type: %s%s)", t0, t1))
}
}
func (c *compileContext) function(p *shaderir.Program, f *shaderir.Func, prototype bool) []string {
var args []string
var idx int
for _, t := range f.InParams {
args = append(args, "in "+c.varDecl(p, &t, fmt.Sprintf("l%d", idx)))
idx++
}
for _, t := range f.OutParams {
args = append(args, "out "+c.varDecl(p, &t, fmt.Sprintf("l%d", idx)))
idx++
}
argsstr := "void"
if len(args) > 0 {
argsstr = strings.Join(args, ", ")
}
t0, t1 := c.typ(p, &f.Return)
sig := fmt.Sprintf("%s%s F%d(%s)", t0, t1, f.Index, argsstr)
var lines []string
if prototype {
lines = append(lines, fmt.Sprintf("%s;", sig))
return lines
}
lines = append(lines, fmt.Sprintf("%s {", sig))
lines = append(lines, c.block(p, f.Block, f.Block, 0)...)
lines = append(lines, "}")
return lines
}
func constantToNumberLiteral(v constant.Value) string {
switch v.Kind() {
case constant.Bool:
if constant.BoolVal(v) {
return "true"
}
return "false"
case constant.Int:
x, _ := constant.Int64Val(v)
return fmt.Sprintf("%d", x)
case constant.Float:
x, _ := constant.Float64Val(v)
if i := math.Floor(x); i == x {
return fmt.Sprintf("%d.0", int64(i))
}
return fmt.Sprintf("%.10e", x)
}
return fmt.Sprintf("?(unexpected literal: %s)", v)
}
func (c *compileContext) localVariableName(p *shaderir.Program, topBlock *shaderir.Block, idx int) string {
switch topBlock {
case p.VertexFunc.Block:
na := len(p.Attributes)
nv := len(p.Varyings)
switch {
case idx < na:
return fmt.Sprintf("A%d", idx)
case idx == na:
return fmt.Sprintf("%s.Position", vsOut)
case idx < na+nv+1:
return fmt.Sprintf("%s.M%d", vsOut, idx-na-1)
default:
return fmt.Sprintf("l%d", idx-(na+nv+1))
}
case p.FragmentFunc.Block:
nv := len(p.Varyings)
switch {
case idx == 0:
return fmt.Sprintf("%s.Position", vsOut)
case idx < nv+1:
return fmt.Sprintf("%s.M%d", vsOut, idx-1)
default:
return fmt.Sprintf("l%d", idx-(nv+1))
}
default:
return fmt.Sprintf("l%d", idx)
}
}
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
idt := strings.Repeat("\t", level+1)
name := c.localVariableName(p, topBlock, index)
t := p.LocalVariableType(topBlock, block, index)
var lines []string
switch t.Main {
case shaderir.Array:
if decl {
lines = append(lines, fmt.Sprintf("%s%s;", idt, c.varDecl(p, &t, name)))
}
init := c.varInit(p, &t.Sub[0])
for i := 0; i < t.Length; i++ {
lines = append(lines, fmt.Sprintf("%s%s[%d] = %s;", idt, name, i, init))
}
case shaderir.None:
// The type is None e.g., when the variable is a for-loop counter.
default:
if decl {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, c.varDecl(p, &t, name), c.varInit(p, &t)))
} else {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, name, c.varInit(p, &t)))
}
}
return lines
}
func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Block, level int) []string {
if block == nil {
return nil
}
var lines []string
for i := range block.LocalVars {
lines = append(lines, c.initVariable(p, topBlock, block, block.LocalVarIndexOffset+i, true, level)...)
}
var expr func(e *shaderir.Expr) string
expr = func(e *shaderir.Expr) string {
switch e.Type {
case shaderir.NumberExpr:
return constantToNumberLiteral(e.Const)
case shaderir.UniformVariable:
return fmt.Sprintf("U%d", e.Index)
case shaderir.TextureVariable:
return fmt.Sprintf("T%d", e.Index)
case shaderir.LocalVariable:
return c.localVariableName(p, topBlock, e.Index)
case shaderir.StructMember:
return fmt.Sprintf("M%d", e.Index)
case shaderir.BuiltinFuncExpr:
return c.builtinFuncString(e.BuiltinFunc)
case shaderir.SwizzlingExpr:
if !shaderir.IsValidSwizzling(e.Swizzling) {
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
}
return e.Swizzling
case shaderir.FunctionExpr:
return fmt.Sprintf("F%d", e.Index)
case shaderir.Unary:
var op string
switch e.Op {
case shaderir.Add, shaderir.Sub, shaderir.NotOp:
op = opString(e.Op)
default:
op = fmt.Sprintf("?(unexpected op: %d)", e.Op)
}
return fmt.Sprintf("%s(%s)", op, expr(&e.Exprs[0]))
case shaderir.Binary:
switch e.Op {
case shaderir.VectorEqualOp:
return fmt.Sprintf("all((%s) == (%s))", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.VectorNotEqualOp:
return fmt.Sprintf("!all((%s) == (%s))", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.MatrixMul:
// If either is a matrix, use the mul function.
// Swap the order of the lhs and the rhs since matrices are row-major in HLSL.
return fmt.Sprintf("mul(%s, %s)", expr(&e.Exprs[1]), expr(&e.Exprs[0]))
}
return fmt.Sprintf("(%s) %s (%s)", expr(&e.Exprs[0]), opString(e.Op), expr(&e.Exprs[1]))
case shaderir.Selection:
return fmt.Sprintf("(%s) ? (%s) : (%s)", expr(&e.Exprs[0]), expr(&e.Exprs[1]), expr(&e.Exprs[2]))
case shaderir.Call:
callee := e.Exprs[0]
var args []string
for _, exp := range e.Exprs[1:] {
args = append(args, expr(&exp))
}
if callee.Type == shaderir.BuiltinFuncExpr {
switch callee.BuiltinFunc {
case shaderir.Vec2F, shaderir.Vec3F, shaderir.Vec4F, shaderir.IVec2F, shaderir.IVec3F, shaderir.IVec4F:
if len(args) == 1 {
// Use casting. For example, `float4(1)` doesn't work.
return fmt.Sprintf("(%s)(%s)", expr(&e.Exprs[0]), args[0])
}
case shaderir.Mat2F:
if len(args) == 1 {
// In HSLS, casting a scalar to a matrix initializes all the components.
// There seems no easy way to have an identity matrix.
return fmt.Sprintf("float2x2FromScalar(%s)", args[0])
}
case shaderir.Mat3F:
if len(args) == 1 {
return fmt.Sprintf("float3x3FromScalar(%s)", args[0])
}
case shaderir.Mat4F:
if len(args) == 1 {
return fmt.Sprintf("float4x4FromScalar(%s)", args[0])
}
case shaderir.TexelAt:
switch c.unit {
case shaderir.Pixels:
return fmt.Sprintf("%s.Load(int3(%s, 0))", args[0], strings.Join(args[1:], ", "))
case shaderir.Texels:
return fmt.Sprintf("%s.Sample(samp, %s)", args[0], strings.Join(args[1:], ", "))
default:
panic(fmt.Sprintf("hlsl: unexpected unit: %d", p.Unit))
}
}
}
return fmt.Sprintf("%s(%s)", expr(&e.Exprs[0]), strings.Join(args, ", "))
case shaderir.FieldSelector:
return fmt.Sprintf("(%s).%s", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.Index:
return fmt.Sprintf("(%s)[%s]", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
default:
return fmt.Sprintf("?(unexpected expr: %d)", e.Type)
}
}
idt := strings.Repeat("\t", level+1)
for _, s := range block.Stmts {
switch s.Type {
case shaderir.ExprStmt:
lines = append(lines, fmt.Sprintf("%s%s;", idt, expr(&s.Exprs[0])))
case shaderir.BlockStmt:
lines = append(lines, idt+"{")
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, idt+"}")
case shaderir.Assign:
lhs := s.Exprs[0]
rhs := s.Exprs[1]
if lhs.Type == shaderir.LocalVariable {
if t := p.LocalVariableType(topBlock, block, lhs.Index); t.Main == shaderir.Array {
for i := 0; i < t.Length; i++ {
lines = append(lines, fmt.Sprintf("%[1]s%[2]s[%[3]d] = %[4]s[%[3]d];", idt, expr(&lhs), i, expr(&rhs)))
}
continue
}
}
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, expr(&lhs), expr(&rhs)))
case shaderir.Init:
lines = append(lines, c.initVariable(p, topBlock, block, s.InitIndex, false, level)...)
case shaderir.If:
lines = append(lines, fmt.Sprintf("%sif (%s) {", idt, expr(&s.Exprs[0])))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
if len(s.Blocks) > 1 {
lines = append(lines, fmt.Sprintf("%s} else {", idt))
lines = append(lines, c.block(p, topBlock, s.Blocks[1], level+1)...)
}
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.For:
v := c.localVariableName(p, topBlock, s.ForVarIndex)
var delta string
switch val, _ := constant.Float64Val(s.ForDelta); val {
case 0:
delta = fmt.Sprintf("?(unexpected delta: %v)", s.ForDelta)
case 1:
delta = fmt.Sprintf("%s++", v)
case -1:
delta = fmt.Sprintf("%s--", v)
default:
d := s.ForDelta
if val > 0 {
delta = fmt.Sprintf("%s += %s", v, constantToNumberLiteral(d))
} else {
d = constant.UnaryOp(token.SUB, d, 0)
delta = fmt.Sprintf("%s -= %s", v, constantToNumberLiteral(d))
}
}
var op string
switch s.ForOp {
case shaderir.LessThanOp, shaderir.LessThanEqualOp, shaderir.GreaterThanOp, shaderir.GreaterThanEqualOp, shaderir.EqualOp, shaderir.NotEqualOp:
op = opString(s.ForOp)
default:
op = fmt.Sprintf("?(unexpected op: %d)", s.ForOp)
}
t := s.ForVarType
init := constantToNumberLiteral(s.ForInit)
end := constantToNumberLiteral(s.ForEnd)
t0, t1 := typeString(&t)
lines = append(lines, fmt.Sprintf("%sfor (%s %s%s = %s; %s %s %s; %s) {", idt, t0, v, t1, init, v, op, end, delta))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.Continue:
lines = append(lines, idt+"continue;")
case shaderir.Break:
lines = append(lines, idt+"break;")
case shaderir.Return:
switch {
case topBlock == p.VertexFunc.Block:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, vsOut))
case len(s.Exprs) == 0:
lines = append(lines, idt+"return;")
default:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, expr(&s.Exprs[0])))
}
case shaderir.Discard:
// 'discard' is invoked only in the fragment shader entry point.
lines = append(lines, idt+"discard;", idt+"return float4(0.0, 0.0, 0.0, 0.0);")
default:
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
}
}
return lines
}
@@ -0,0 +1,106 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hlsl
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
const boundaryInBytes = 16
func calculateMemoryOffsets(uniforms []shaderir.Type) []int {
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules
// https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing
var offsets []int
var head int
align := func(x int) int {
if x == 0 {
return 0
}
return ((x-1)/boundaryInBytes + 1) * boundaryInBytes
}
// TODO: Reorder the variables with packoffset.
// See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-packoffset
for _, u := range uniforms {
switch u.Main {
case shaderir.Float:
offsets = append(offsets, head)
head += 4
case shaderir.Int:
offsets = append(offsets, head)
head += 4
case shaderir.Vec2, shaderir.IVec2:
if head%boundaryInBytes >= 4*3 {
head = align(head)
}
offsets = append(offsets, head)
head += 4 * 2
case shaderir.Vec3, shaderir.IVec3:
if head%boundaryInBytes >= 4*2 {
head = align(head)
}
offsets = append(offsets, head)
head += 4 * 3
case shaderir.Vec4, shaderir.IVec4:
if head%boundaryInBytes >= 4*1 {
head = align(head)
}
offsets = append(offsets, head)
head += 4 * 4
case shaderir.Mat2:
// For matrices, each column is aligned to the boundary.
head = align(head)
offsets = append(offsets, head)
head += 4 * 6
case shaderir.Mat3:
head = align(head)
offsets = append(offsets, head)
head += 4 * 11
case shaderir.Mat4:
head = align(head)
offsets = append(offsets, head)
head += 4 * 16
case shaderir.Array:
// Each array is 16-byte aligned.
// TODO: What if the array has 2 or more dimensions?
head = align(head)
offsets = append(offsets, head)
n := u.Sub[0].Uint32Count()
switch u.Sub[0].Main {
case shaderir.Mat2:
n = 6
case shaderir.Mat3:
n = 11
case shaderir.Mat4:
n = 16
}
head += (u.Length - 1) * align(4*n)
// The last element is not with a padding.
head += 4 * n
case shaderir.Struct:
// TODO: Implement this
panic("hlsl: offset for a struct is not implemented yet")
default:
panic(fmt.Sprintf("hlsl: unexpected type: %s", u.String()))
}
}
return offsets
}
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hlsl
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
func opString(op shaderir.Op) string {
switch op {
case shaderir.Add:
return "+"
case shaderir.Sub:
return "-"
case shaderir.NotOp:
return "!"
case shaderir.ComponentWiseMul:
return "*"
case shaderir.Div:
return "/"
case shaderir.ModOp:
return "%"
case shaderir.LeftShift:
return "<<"
case shaderir.RightShift:
return ">>"
case shaderir.LessThanOp:
return "<"
case shaderir.LessThanEqualOp:
return "<="
case shaderir.GreaterThanOp:
return ">"
case shaderir.GreaterThanEqualOp:
return ">="
case shaderir.EqualOp:
return "=="
case shaderir.NotEqualOp:
return "!="
case shaderir.And:
return "&"
case shaderir.Xor:
return "^"
case shaderir.Or:
return "|"
case shaderir.AndAnd:
return "&&"
case shaderir.OrOr:
return "||"
}
return fmt.Sprintf("?(unexpected operator: %d)", op)
}
func typeString(t *shaderir.Type) (string, string) {
switch t.Main {
case shaderir.Array:
t0, t1 := typeString(&t.Sub[0])
return t0 + t1, fmt.Sprintf("[%d]", t.Length)
case shaderir.Struct:
panic("hlsl: a struct is not implemented")
default:
return basicTypeString(t.Main), ""
}
}
func basicTypeString(t shaderir.BasicType) string {
switch t {
case shaderir.None:
return "?(none)"
case shaderir.Bool:
return "bool"
case shaderir.Int:
return "int"
case shaderir.Float:
return "float"
case shaderir.Vec2:
return "float2"
case shaderir.Vec3:
return "float3"
case shaderir.Vec4:
return "float4"
case shaderir.IVec2:
return "int2"
case shaderir.IVec3:
return "int3"
case shaderir.IVec4:
return "int4"
case shaderir.Mat2:
return "float2x2"
case shaderir.Mat3:
return "float3x3"
case shaderir.Mat4:
return "float4x4"
case shaderir.Array:
return "?(array)"
case shaderir.Struct:
return "?(struct)"
default:
return fmt.Sprintf("?(unknown type: %d)", t)
}
}
func (c *compileContext) builtinFuncString(f shaderir.BuiltinFunc) string {
switch f {
case shaderir.Vec2F:
return "float2"
case shaderir.Vec3F:
return "float3"
case shaderir.Vec4F:
return "float4"
case shaderir.IVec2F:
return "int2"
case shaderir.IVec3F:
return "int3"
case shaderir.IVec4F:
return "int4"
case shaderir.Mat2F:
return "float2x2"
case shaderir.Mat3F:
return "float3x3"
case shaderir.Mat4F:
return "float4x4"
case shaderir.Inversesqrt:
return "rsqrt"
case shaderir.Fract:
return "frac"
case shaderir.Mix:
return "lerp"
case shaderir.Dfdx:
return "ddx"
case shaderir.Dfdy:
return "ddy"
case shaderir.TexelAt:
return "?(__texelAt)"
default:
return string(f)
}
}
+510
View File
@@ -0,0 +1,510 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msl
import (
"fmt"
"go/constant"
"go/token"
"math"
"regexp"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
const (
vertexOut = "varyings"
)
type compileContext struct {
structNames map[string]string
structTypes []shaderir.Type
}
func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) string {
if t.Main != shaderir.Struct {
panic("msl: the given type at structName must be a struct")
}
s := t.String()
if n, ok := c.structNames[s]; ok {
return n
}
n := fmt.Sprintf("S%d", len(c.structNames))
c.structNames[s] = n
c.structTypes = append(c.structTypes, *t)
return n
}
func Prelude(unit shaderir.Unit) string {
str := `#include <metal_stdlib>
using namespace metal;
template<typename T, typename U>
T mod(T x, U y) {
return x - y * floor(x/y);
}`
if unit == shaderir.Texels {
str += `
constexpr sampler texture_sampler{filter::nearest};`
}
return str
}
const (
VertexName = "Vertex"
FragmentName = "Fragment"
)
func Compile(p *shaderir.Program) (shader string) {
c := &compileContext{
structNames: map[string]string{},
}
var lines []string
lines = append(lines, strings.Split(Prelude(p.Unit), "\n")...)
lines = append(lines, "", "{{.Structs}}")
if len(p.Attributes) > 0 {
lines = append(lines, "")
lines = append(lines, "struct Attributes {")
for i, a := range p.Attributes {
lines = append(lines, fmt.Sprintf("\t%s;", c.varDecl(p, &a, fmt.Sprintf("M%d", i), false)))
}
lines = append(lines, "};")
}
if len(p.Varyings) > 0 {
lines = append(lines, "")
lines = append(lines, "struct Varyings {")
lines = append(lines, "\tfloat4 Position [[position]];")
for i, v := range p.Varyings {
lines = append(lines, fmt.Sprintf("\t%s;", c.varDecl(p, &v, fmt.Sprintf("M%d", i), false)))
}
lines = append(lines, "};")
}
if len(p.Funcs) > 0 {
lines = append(lines, "")
for _, f := range p.Funcs {
lines = append(lines, c.function(p, &f, true)...)
}
for _, f := range p.Funcs {
if len(lines) > 0 && lines[len(lines)-1] != "" {
lines = append(lines, "")
}
lines = append(lines, c.function(p, &f, false)...)
}
}
if p.VertexFunc.Block != nil && len(p.VertexFunc.Block.Stmts) > 0 {
lines = append(lines, "")
lines = append(lines,
fmt.Sprintf("vertex Varyings %s(", VertexName),
"\tuint vid [[vertex_id]],",
"\tconst device Attributes* attributes [[buffer(0)]]")
for i, u := range p.Uniforms {
lines[len(lines)-1] += ","
lines = append(lines, fmt.Sprintf("\tconstant %s [[buffer(%d)]]", c.varDecl(p, &u, fmt.Sprintf("U%d", i), true), i+1))
}
for i := 0; i < p.TextureCount; i++ {
lines[len(lines)-1] += ","
lines = append(lines, fmt.Sprintf("\ttexture2d<float> T%[1]d [[texture(%[1]d)]]", i))
}
lines[len(lines)-1] += ") {"
lines = append(lines, fmt.Sprintf("\tVaryings %s = {};", vertexOut))
lines = append(lines, c.block(p, p.VertexFunc.Block, p.VertexFunc.Block, 0)...)
if last := fmt.Sprintf("\treturn %s;", vertexOut); lines[len(lines)-1] != last {
lines = append(lines, last)
}
lines = append(lines, "}")
}
if p.FragmentFunc.Block != nil && len(p.FragmentFunc.Block.Stmts) > 0 {
lines = append(lines, "")
lines = append(lines,
fmt.Sprintf("fragment float4 %s(", FragmentName),
"\tVaryings varyings [[stage_in]]")
for i, u := range p.Uniforms {
lines[len(lines)-1] += ","
lines = append(lines, fmt.Sprintf("\tconstant %s [[buffer(%d)]]", c.varDecl(p, &u, fmt.Sprintf("U%d", i), true), i+1))
}
for i := 0; i < p.TextureCount; i++ {
lines[len(lines)-1] += ","
lines = append(lines, fmt.Sprintf("\ttexture2d<float> T%[1]d [[texture(%[1]d)]]", i))
}
lines[len(lines)-1] += ") {"
lines = append(lines, c.block(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
lines = append(lines, "}")
}
ls := strings.Join(lines, "\n")
// Struct types are determined after converting the program.
if len(c.structTypes) > 0 {
var stlines []string
for i, t := range c.structTypes {
stlines = append(stlines, fmt.Sprintf("struct S%d {", i))
for j, st := range t.Sub {
stlines = append(stlines, fmt.Sprintf("\t%s;", c.varDecl(p, &st, fmt.Sprintf("M%d", j), false)))
}
stlines = append(stlines, "};")
}
ls = strings.ReplaceAll(ls, "{{.Structs}}", strings.Join(stlines, "\n"))
} else {
ls = strings.ReplaceAll(ls, "{{.Structs}}", "")
}
nls := regexp.MustCompile(`\n\n+`)
ls = nls.ReplaceAllString(ls, "\n\n")
ls = strings.TrimSpace(ls) + "\n"
return ls
}
func (c *compileContext) typ(p *shaderir.Program, t *shaderir.Type) string {
switch t.Main {
case shaderir.None:
return "void"
case shaderir.Struct:
return c.structName(p, t)
default:
return typeString(t, false)
}
}
func (c *compileContext) varDecl(p *shaderir.Program, t *shaderir.Type, varname string, ref bool) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Struct:
s := c.structName(p, t)
if ref {
s += "&"
}
return fmt.Sprintf("%s %s", s, varname)
default:
t := typeString(t, ref)
return fmt.Sprintf("%s %s", t, varname)
}
}
func (c *compileContext) varInit(p *shaderir.Program, t *shaderir.Type) string {
switch t.Main {
case shaderir.None:
return "?(none)"
case shaderir.Array:
return "{}"
case shaderir.Struct:
return "{}"
case shaderir.Bool:
return "false"
case shaderir.Int:
return "0"
case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4,
shaderir.IVec2, shaderir.IVec3, shaderir.IVec4,
shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
return fmt.Sprintf("%s(0)", basicTypeString(t.Main))
default:
t := c.typ(p, t)
panic(fmt.Sprintf("?(unexpected type: %s)", t))
}
}
func (c *compileContext) function(p *shaderir.Program, f *shaderir.Func, prototype bool) []string {
var args []string
// Uniform variables and texture variables. In Metal, non-const global variables are not available.
for i, u := range p.Uniforms {
args = append(args, "constant "+c.varDecl(p, &u, fmt.Sprintf("U%d", i), true))
}
for i := 0; i < p.TextureCount; i++ {
args = append(args, fmt.Sprintf("texture2d<float> T%d", i))
}
var idx int
for _, t := range f.InParams {
args = append(args, c.varDecl(p, &t, fmt.Sprintf("l%d", idx), false))
idx++
}
for _, t := range f.OutParams {
args = append(args, "thread "+c.varDecl(p, &t, fmt.Sprintf("l%d", idx), true))
idx++
}
argsstr := "void"
if len(args) > 0 {
argsstr = strings.Join(args, ", ")
}
t := c.typ(p, &f.Return)
sig := fmt.Sprintf("%s F%d(%s)", t, f.Index, argsstr)
var lines []string
if prototype {
lines = append(lines, fmt.Sprintf("%s;", sig))
return lines
}
lines = append(lines, fmt.Sprintf("%s {", sig))
lines = append(lines, c.block(p, f.Block, f.Block, 0)...)
lines = append(lines, "}")
return lines
}
func constantToNumberLiteral(v constant.Value) string {
switch v.Kind() {
case constant.Bool:
if constant.BoolVal(v) {
return "true"
}
return "false"
case constant.Int:
x, _ := constant.Int64Val(v)
return fmt.Sprintf("%d", x)
case constant.Float:
x, _ := constant.Float64Val(v)
if i := math.Floor(x); i == x {
return fmt.Sprintf("%d.0", int64(i))
}
return fmt.Sprintf("%.10e", x)
}
return fmt.Sprintf("?(unexpected literal: %s)", v)
}
func localVariableName(p *shaderir.Program, topBlock *shaderir.Block, idx int) string {
switch topBlock {
case p.VertexFunc.Block:
na := len(p.Attributes)
nv := len(p.Varyings)
switch {
case idx < na:
return fmt.Sprintf("attributes[vid].M%d", idx)
case idx == na:
return fmt.Sprintf("%s.Position", vertexOut)
case idx < na+nv+1:
return fmt.Sprintf("%s.M%d", vertexOut, idx-na-1)
default:
return fmt.Sprintf("l%d", idx-(na+nv+1))
}
case p.FragmentFunc.Block:
nv := len(p.Varyings)
switch {
case idx == 0:
return fmt.Sprintf("%s.Position", vertexOut)
case idx < nv+1:
return fmt.Sprintf("%s.M%d", vertexOut, idx-1)
default:
return fmt.Sprintf("l%d", idx-(nv+1))
}
default:
return fmt.Sprintf("l%d", idx)
}
}
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
idt := strings.Repeat("\t", level+1)
name := localVariableName(p, topBlock, index)
t := p.LocalVariableType(topBlock, block, index)
var lines []string
if decl {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, c.varDecl(p, &t, name, false), c.varInit(p, &t)))
} else {
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, name, c.varInit(p, &t)))
}
return lines
}
func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Block, level int) []string {
if block == nil {
return nil
}
idt := strings.Repeat("\t", level+1)
var lines []string
for i, t := range block.LocalVars {
// The type is None e.g., when the variable is a for-loop counter.
if t.Main != shaderir.None {
lines = append(lines, c.initVariable(p, topBlock, block, block.LocalVarIndexOffset+i, true, level)...)
}
}
var expr func(e *shaderir.Expr) string
expr = func(e *shaderir.Expr) string {
switch e.Type {
case shaderir.NumberExpr:
return constantToNumberLiteral(e.Const)
case shaderir.UniformVariable:
return fmt.Sprintf("U%d", e.Index)
case shaderir.TextureVariable:
return fmt.Sprintf("T%d", e.Index)
case shaderir.LocalVariable:
return localVariableName(p, topBlock, e.Index)
case shaderir.StructMember:
return fmt.Sprintf("M%d", e.Index)
case shaderir.BuiltinFuncExpr:
return builtinFuncString(e.BuiltinFunc)
case shaderir.SwizzlingExpr:
if !shaderir.IsValidSwizzling(e.Swizzling) {
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
}
return e.Swizzling
case shaderir.FunctionExpr:
return fmt.Sprintf("F%d", e.Index)
case shaderir.Unary:
var op string
switch e.Op {
case shaderir.Add, shaderir.Sub, shaderir.NotOp:
op = opString(e.Op)
default:
op = fmt.Sprintf("?(unexpected op: %d)", e.Op)
}
return fmt.Sprintf("%s(%s)", op, expr(&e.Exprs[0]))
case shaderir.Binary:
switch e.Op {
case shaderir.VectorEqualOp:
return fmt.Sprintf("all((%s) == (%s))", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.VectorNotEqualOp:
return fmt.Sprintf("!all((%s) == (%s))", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
}
return fmt.Sprintf("(%s) %s (%s)", expr(&e.Exprs[0]), opString(e.Op), expr(&e.Exprs[1]))
case shaderir.Selection:
return fmt.Sprintf("(%s) ? (%s) : (%s)", expr(&e.Exprs[0]), expr(&e.Exprs[1]), expr(&e.Exprs[2]))
case shaderir.Call:
callee := e.Exprs[0]
var args []string
if callee.Type != shaderir.BuiltinFuncExpr {
for i := range p.Uniforms {
args = append(args, fmt.Sprintf("U%d", i))
}
for i := 0; i < p.TextureCount; i++ {
args = append(args, fmt.Sprintf("T%d", i))
}
}
for _, exp := range e.Exprs[1:] {
args = append(args, expr(&exp))
}
if callee.Type == shaderir.BuiltinFuncExpr && callee.BuiltinFunc == shaderir.TexelAt {
switch p.Unit {
case shaderir.Texels:
return fmt.Sprintf("%s.sample(texture_sampler, %s)", args[0], strings.Join(args[1:], ", "))
case shaderir.Pixels:
return fmt.Sprintf("%s.read(static_cast<uint2>(%s))", args[0], strings.Join(args[1:], ", "))
default:
panic(fmt.Sprintf("msl: unexpected unit: %d", p.Unit))
}
}
return fmt.Sprintf("%s(%s)", expr(&callee), strings.Join(args, ", "))
case shaderir.FieldSelector:
return fmt.Sprintf("(%s).%s", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
case shaderir.Index:
return fmt.Sprintf("(%s)[%s]", expr(&e.Exprs[0]), expr(&e.Exprs[1]))
default:
return fmt.Sprintf("?(unexpected expr: %d)", e.Type)
}
}
for _, s := range block.Stmts {
switch s.Type {
case shaderir.ExprStmt:
lines = append(lines, fmt.Sprintf("%s%s;", idt, expr(&s.Exprs[0])))
case shaderir.BlockStmt:
lines = append(lines, idt+"{")
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, idt+"}")
case shaderir.Assign:
lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, expr(&s.Exprs[0]), expr(&s.Exprs[1])))
case shaderir.Init:
init := true
if topBlock == p.VertexFunc.Block {
// In the vertex function, varying values are the output parameters.
// These values are represented as a struct and not needed to be initialized.
na := len(p.Attributes)
nv := len(p.Varyings)
if s.InitIndex < na+nv+1 {
init = false
}
}
if init {
lines = append(lines, c.initVariable(p, topBlock, block, s.InitIndex, false, level)...)
}
case shaderir.If:
lines = append(lines, fmt.Sprintf("%sif (%s) {", idt, expr(&s.Exprs[0])))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
if len(s.Blocks) > 1 {
lines = append(lines, fmt.Sprintf("%s} else {", idt))
lines = append(lines, c.block(p, topBlock, s.Blocks[1], level+1)...)
}
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.For:
v := localVariableName(p, topBlock, s.ForVarIndex)
var delta string
switch val, _ := constant.Float64Val(s.ForDelta); val {
case 0:
delta = fmt.Sprintf("?(unexpected delta: %v)", s.ForDelta)
case 1:
delta = fmt.Sprintf("%s++", v)
case -1:
delta = fmt.Sprintf("%s--", v)
default:
d := s.ForDelta
if val > 0 {
delta = fmt.Sprintf("%s += %s", v, constantToNumberLiteral(d))
} else {
d = constant.UnaryOp(token.SUB, d, 0)
delta = fmt.Sprintf("%s -= %s", v, constantToNumberLiteral(d))
}
}
var op string
switch s.ForOp {
case shaderir.LessThanOp, shaderir.LessThanEqualOp, shaderir.GreaterThanOp, shaderir.GreaterThanEqualOp, shaderir.EqualOp, shaderir.NotEqualOp:
op = opString(s.ForOp)
default:
op = fmt.Sprintf("?(unexpected op: %d)", s.ForOp)
}
t := s.ForVarType
init := constantToNumberLiteral(s.ForInit)
end := constantToNumberLiteral(s.ForEnd)
ts := typeString(&t, false)
lines = append(lines, fmt.Sprintf("%sfor (%s %s = %s; %s %s %s; %s) {", idt, ts, v, init, v, op, end, delta))
lines = append(lines, c.block(p, topBlock, s.Blocks[0], level+1)...)
lines = append(lines, fmt.Sprintf("%s}", idt))
case shaderir.Continue:
lines = append(lines, idt+"continue;")
case shaderir.Break:
lines = append(lines, idt+"break;")
case shaderir.Return:
switch {
case topBlock == p.VertexFunc.Block:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, vertexOut))
case len(s.Exprs) == 0:
lines = append(lines, idt+"return;")
default:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, expr(&s.Exprs[0])))
}
case shaderir.Discard:
// 'discard' is invoked only in the fragment shader entry point.
lines = append(lines, idt+"discard_fragment();", idt+"return float4(0.0);")
default:
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
}
}
return lines
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msl
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
func opString(op shaderir.Op) string {
switch op {
case shaderir.Add:
return "+"
case shaderir.Sub:
return "-"
case shaderir.NotOp:
return "!"
case shaderir.ComponentWiseMul, shaderir.MatrixMul:
return "*"
case shaderir.Div:
return "/"
case shaderir.ModOp:
return "%"
case shaderir.LeftShift:
return "<<"
case shaderir.RightShift:
return ">>"
case shaderir.LessThanOp:
return "<"
case shaderir.LessThanEqualOp:
return "<="
case shaderir.GreaterThanOp:
return ">"
case shaderir.GreaterThanEqualOp:
return ">="
case shaderir.EqualOp:
return "=="
case shaderir.NotEqualOp:
return "!="
case shaderir.And:
return "&"
case shaderir.Xor:
return "^"
case shaderir.Or:
return "|"
case shaderir.AndAnd:
return "&&"
case shaderir.OrOr:
return "||"
}
return fmt.Sprintf("?(unexpected operator: %d)", op)
}
func typeString(t *shaderir.Type, ref bool) string {
switch t.Main {
case shaderir.Array:
st := typeString(&t.Sub[0], false)
t := fmt.Sprintf("array<%s, %d>", st, t.Length)
if ref {
t += "&"
}
return t
case shaderir.Struct:
panic("msl: a struct is not implemented")
default:
t := basicTypeString(t.Main)
if ref {
t += "&"
}
return t
}
}
func basicTypeString(t shaderir.BasicType) string {
switch t {
case shaderir.None:
return "?(none)"
case shaderir.Bool:
return "bool"
case shaderir.Int:
return "int"
case shaderir.Float:
return "float"
case shaderir.Vec2:
return "float2"
case shaderir.Vec3:
return "float3"
case shaderir.Vec4:
return "float4"
case shaderir.IVec2:
return "int2"
case shaderir.IVec3:
return "int3"
case shaderir.IVec4:
return "int4"
case shaderir.Mat2:
return "float2x2"
case shaderir.Mat3:
return "float3x3"
case shaderir.Mat4:
return "float4x4"
case shaderir.Array:
return "?(array)"
case shaderir.Struct:
return "?(struct)"
default:
return fmt.Sprintf("?(unknown type: %d)", t)
}
}
func builtinFuncString(f shaderir.BuiltinFunc) string {
switch f {
case shaderir.BoolF:
return "static_cast<bool>"
case shaderir.IntF:
return "static_cast<int>"
case shaderir.FloatF:
return "static_cast<float>"
case shaderir.Vec2F:
return "float2"
case shaderir.Vec3F:
return "float3"
case shaderir.Vec4F:
return "float4"
case shaderir.IVec2F:
return "int2"
case shaderir.IVec3F:
return "int3"
case shaderir.IVec4F:
return "int4"
case shaderir.Mat2F:
return "float2x2"
case shaderir.Mat3F:
return "float3x3"
case shaderir.Mat4F:
return "float4x4"
case shaderir.Inversesqrt:
return "rsqrt"
case shaderir.TexelAt:
return "?(__texelAt)"
}
return string(f)
}
+490
View File
@@ -0,0 +1,490 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shaderir offers intermediate representation for shader programs.
package shaderir
import (
"go/constant"
"go/token"
"sort"
"strings"
)
type Unit int
const (
Texels Unit = iota
Pixels
)
type Program struct {
UniformNames []string
Uniforms []Type
TextureCount int
Attributes []Type
Varyings []Type
Funcs []Func
VertexFunc VertexFunc
FragmentFunc FragmentFunc
Unit Unit
uniformFactors []uint32
}
type Func struct {
Index int
InParams []Type
OutParams []Type
Return Type
Block *Block
}
// VertexFunc takes pseudo params, and the number if len(attributes) + len(varyings) + 1.
// If 0 <= index < len(attributes), the params are in-params and represent attribute variables.
// If index == len(attributes), the param is an out-param and represents the position in vec4 (gl_Position in GLSL)
// If len(attributes) + 1 <= index < len(attributes) + len(varyings) + 1, the params are out-params and represent
// varying variables.
type VertexFunc struct {
Block *Block
}
// FragmentFunc takes pseudo params, and the number is len(varyings) + 2.
// If index == 0, the param represents the coordinate of the fragment (gl_FragCoord in GLSL).
// If 0 < index <= len(varyings), the param represents (index-1)th varying variable.
type FragmentFunc struct {
Block *Block
}
type Block struct {
LocalVars []Type
LocalVarIndexOffset int
Stmts []Stmt
}
type Stmt struct {
Type StmtType
Exprs []Expr
Blocks []*Block
ForVarType Type
ForVarIndex int
ForInit constant.Value
ForEnd constant.Value
ForOp Op
ForDelta constant.Value
InitIndex int
}
type StmtType int
const (
ExprStmt StmtType = iota
BlockStmt
Assign
Init
If
For
Continue
Break
Return
Discard
)
type Expr struct {
Type ExprType
Exprs []Expr
Const constant.Value
BuiltinFunc BuiltinFunc
Swizzling string
Index int
Op Op
}
type ExprType int
const (
Blank ExprType = iota
NumberExpr
UniformVariable
TextureVariable
LocalVariable
StructMember
BuiltinFuncExpr
SwizzlingExpr
FunctionExpr
Unary
Binary
Selection
Call
FieldSelector
Index
)
type Op int
const (
Add Op = iota
Sub
NotOp
ComponentWiseMul
MatrixMul
Div
ModOp
LeftShift
RightShift
LessThanOp
LessThanEqualOp
GreaterThanOp
GreaterThanEqualOp
EqualOp
NotEqualOp
VectorEqualOp
VectorNotEqualOp
And
Xor
Or
AndAnd
OrOr
)
func OpFromToken(t token.Token, lhs, rhs Type) (Op, bool) {
switch t {
case token.ADD:
return Add, true
case token.SUB:
return Sub, true
case token.NOT:
return NotOp, true
case token.MUL:
if lhs.IsMatrix() || rhs.IsMatrix() {
return MatrixMul, true
}
return ComponentWiseMul, true
case token.QUO:
return Div, true
case token.QUO_ASSIGN:
// QUO_ASSIGN indicates an integer division.
// https://pkg.go.dev/go/constant/#BinaryOp
return Div, true
case token.REM:
return ModOp, true
case token.SHL:
return LeftShift, true
case token.SHR:
return RightShift, true
case token.LSS:
return LessThanOp, true
case token.LEQ:
return LessThanEqualOp, true
case token.GTR:
return GreaterThanOp, true
case token.GEQ:
return GreaterThanEqualOp, true
case token.EQL:
if lhs.IsFloatVector() || lhs.IsIntVector() || rhs.IsFloatVector() || rhs.IsIntVector() {
return VectorEqualOp, true
}
return EqualOp, true
case token.NEQ:
if lhs.IsFloatVector() || lhs.IsIntVector() || rhs.IsFloatVector() || rhs.IsIntVector() {
return VectorNotEqualOp, true
}
return NotEqualOp, true
case token.AND:
return And, true
case token.XOR:
return Xor, true
case token.OR:
return Or, true
case token.LAND:
return AndAnd, true
case token.LOR:
return OrOr, true
}
return 0, false
}
type BuiltinFunc string
const (
Len BuiltinFunc = "len"
Cap BuiltinFunc = "cap"
BoolF BuiltinFunc = "bool"
IntF BuiltinFunc = "int"
FloatF BuiltinFunc = "float"
Vec2F BuiltinFunc = "vec2"
Vec3F BuiltinFunc = "vec3"
Vec4F BuiltinFunc = "vec4"
IVec2F BuiltinFunc = "ivec2"
IVec3F BuiltinFunc = "ivec3"
IVec4F BuiltinFunc = "ivec4"
Mat2F BuiltinFunc = "mat2"
Mat3F BuiltinFunc = "mat3"
Mat4F BuiltinFunc = "mat4"
Radians BuiltinFunc = "radians" // This function is not used yet (#2253)
Degrees BuiltinFunc = "degrees" // This function is not used yet (#2253)
Sin BuiltinFunc = "sin"
Cos BuiltinFunc = "cos"
Tan BuiltinFunc = "tan"
Asin BuiltinFunc = "asin"
Acos BuiltinFunc = "acos"
Atan BuiltinFunc = "atan"
Atan2 BuiltinFunc = "atan2"
Pow BuiltinFunc = "pow"
Exp BuiltinFunc = "exp"
Log BuiltinFunc = "log"
Exp2 BuiltinFunc = "exp2"
Log2 BuiltinFunc = "log2"
Sqrt BuiltinFunc = "sqrt"
Inversesqrt BuiltinFunc = "inversesqrt"
Abs BuiltinFunc = "abs"
Sign BuiltinFunc = "sign"
Floor BuiltinFunc = "floor"
Ceil BuiltinFunc = "ceil"
Fract BuiltinFunc = "fract"
Mod BuiltinFunc = "mod"
Min BuiltinFunc = "min"
Max BuiltinFunc = "max"
Clamp BuiltinFunc = "clamp"
Mix BuiltinFunc = "mix"
Step BuiltinFunc = "step"
Smoothstep BuiltinFunc = "smoothstep"
Length BuiltinFunc = "length"
Distance BuiltinFunc = "distance"
Dot BuiltinFunc = "dot"
Cross BuiltinFunc = "cross"
Normalize BuiltinFunc = "normalize"
Faceforward BuiltinFunc = "faceforward"
Reflect BuiltinFunc = "reflect"
Refract BuiltinFunc = "refract"
Transpose BuiltinFunc = "transpose"
Dfdx BuiltinFunc = "dfdx"
Dfdy BuiltinFunc = "dfdy"
Fwidth BuiltinFunc = "fwidth"
DiscardF BuiltinFunc = "discard"
TexelAt BuiltinFunc = "__texelAt"
)
func ParseBuiltinFunc(str string) (BuiltinFunc, bool) {
switch BuiltinFunc(str) {
case Len,
Cap,
BoolF,
IntF,
FloatF,
Vec2F,
Vec3F,
Vec4F,
IVec2F,
IVec3F,
IVec4F,
Mat2F,
Mat3F,
Mat4F,
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Atan2,
Pow,
Exp,
Log,
Exp2,
Log2,
Sqrt,
Inversesqrt,
Abs,
Sign,
Floor,
Ceil,
Fract,
Mod,
Min,
Max,
Clamp,
Mix,
Step,
Smoothstep,
Length,
Distance,
Dot,
Cross,
Normalize,
Faceforward,
Reflect,
Refract,
Transpose,
Dfdx,
Dfdy,
Fwidth,
DiscardF,
TexelAt:
return BuiltinFunc(str), true
}
return "", false
}
func IsValidSwizzling(s string) bool {
if len(s) < 1 || 4 < len(s) {
return false
}
const (
xyzw = "xyzw"
rgba = "rgba"
strq = "strq"
)
switch {
case strings.IndexByte(xyzw, s[0]) >= 0:
for _, c := range s {
if strings.IndexRune(xyzw, c) == -1 {
return false
}
}
return true
case strings.IndexByte(rgba, s[0]) >= 0:
for _, c := range s {
if strings.IndexRune(rgba, c) == -1 {
return false
}
}
return true
case strings.IndexByte(strq, s[0]) >= 0:
for _, c := range s {
if strings.IndexRune(strq, c) == -1 {
return false
}
}
return true
}
return false
}
func (p *Program) ReachableFuncsFromBlock(block *Block) []*Func {
indexToFunc := map[int]*Func{}
for _, f := range p.Funcs {
f := f
indexToFunc[f.Index] = &f
}
visited := map[int]struct{}{}
var indices []int
var f func(expr *Expr)
f = func(expr *Expr) {
if expr.Type != FunctionExpr {
return
}
if _, ok := visited[expr.Index]; ok {
return
}
indices = append(indices, expr.Index)
visited[expr.Index] = struct{}{}
walkExprs(f, indexToFunc[expr.Index].Block)
}
walkExprs(f, block)
sort.Ints(indices)
funcs := make([]*Func, 0, len(indices))
for _, i := range indices {
funcs = append(funcs, indexToFunc[i])
}
return funcs
}
func walkExprs(f func(expr *Expr), block *Block) {
if block == nil {
return
}
for _, s := range block.Stmts {
for _, e := range s.Exprs {
walkExprsInExpr(f, &e)
}
for _, b := range s.Blocks {
walkExprs(f, b)
}
}
}
func walkExprsInExpr(f func(expr *Expr), expr *Expr) {
if expr == nil {
return
}
f(expr)
for _, e := range expr.Exprs {
walkExprsInExpr(f, &e)
}
}
func (p *Program) appendReachableUniformVariablesFromBlock(indices []int, block *Block) []int {
indexToFunc := map[int]*Func{}
for _, f := range p.Funcs {
f := f
indexToFunc[f.Index] = &f
}
visitedFuncs := map[int]struct{}{}
indicesSet := map[int]struct{}{}
var f func(expr *Expr)
f = func(expr *Expr) {
switch expr.Type {
case UniformVariable:
if _, ok := indicesSet[expr.Index]; ok {
return
}
indicesSet[expr.Index] = struct{}{}
indices = append(indices, expr.Index)
case FunctionExpr:
if _, ok := visitedFuncs[expr.Index]; ok {
return
}
visitedFuncs[expr.Index] = struct{}{}
walkExprs(f, indexToFunc[expr.Index].Block)
}
}
walkExprs(f, block)
return indices
}
// FilterUniformVariables replaces uniform variables with 0 when they are not used.
// By minimizing uniform variables, more commands can be merged in the graphicscommand package.
func (p *Program) FilterUniformVariables(uniforms []uint32) {
if p.uniformFactors == nil {
indices := p.appendReachableUniformVariablesFromBlock(nil, p.VertexFunc.Block)
indices = p.appendReachableUniformVariablesFromBlock(indices, p.FragmentFunc.Block)
reachableUniforms := make([]bool, len(p.Uniforms))
for _, idx := range indices {
reachableUniforms[idx] = true
}
p.uniformFactors = make([]uint32, len(uniforms))
var idx int
for i, typ := range p.Uniforms {
c := typ.Uint32Count()
if reachableUniforms[i] {
for i := idx; i < idx+c; i++ {
p.uniformFactors[i] = 1
}
}
idx += c
}
}
for i, factor := range p.uniformFactors {
uniforms[i] *= factor
}
}
+247
View File
@@ -0,0 +1,247 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shaderir
import (
"fmt"
"strings"
)
type Type struct {
Main BasicType
Sub []Type
Length int
}
func (t *Type) Equal(rhs *Type) bool {
if t.Main != rhs.Main {
return false
}
if t.Length != rhs.Length {
return false
}
if len(t.Sub) != len(rhs.Sub) {
return false
}
for i, s := range t.Sub {
if !s.Equal(&rhs.Sub[i]) {
return false
}
}
return true
}
func (t *Type) String() string {
switch t.Main {
case None:
return "none"
case Bool:
return "bool"
case Int:
return "int"
case Float:
return "float"
case Vec2:
return "vec2"
case Vec3:
return "vec3"
case Vec4:
return "vec4"
case IVec2:
return "ivec2"
case IVec3:
return "ivec3"
case IVec4:
return "ivec4"
case Mat2:
return "mat2"
case Mat3:
return "mat3"
case Mat4:
return "mat4"
case Array:
return fmt.Sprintf("[%d]%s", t.Length, t.Sub[0].String())
case Struct:
str := "struct{"
sub := make([]string, 0, len(t.Sub))
for _, st := range t.Sub {
sub = append(sub, st.String())
}
str += strings.Join(sub, ",")
str += "}"
return str
default:
return fmt.Sprintf("?(unknown type: %d)", t.Main)
}
}
func (t *Type) Uint32Count() int {
switch t.Main {
case Int:
return 1
case Float:
return 1
case Vec2:
return 2
case Vec3:
return 3
case Vec4:
return 4
case IVec2:
return 2
case IVec3:
return 3
case IVec4:
return 4
case Mat2:
return 4
case Mat3:
return 9
case Mat4:
return 16
case Array:
return t.Length * t.Sub[0].Uint32Count()
default: // TODO: Parse a struct correctly
return -1
}
}
func (t *Type) IsFloatVector() bool {
switch t.Main {
case Vec2, Vec3, Vec4:
return true
}
return false
}
func (t *Type) IsIntVector() bool {
switch t.Main {
case IVec2, IVec3, IVec4:
return true
}
return false
}
func (t *Type) VectorElementCount() int {
switch t.Main {
case Vec2:
return 2
case Vec3:
return 3
case Vec4:
return 4
case IVec2:
return 2
case IVec3:
return 3
case IVec4:
return 4
default:
return -1
}
}
func (t *Type) IsMatrix() bool {
switch t.Main {
case Mat2, Mat3, Mat4:
return true
}
return false
}
type BasicType int
const (
None BasicType = iota
Bool
Int
Float
Vec2
Vec3
Vec4
IVec2
IVec3
IVec4
Mat2
Mat3
Mat4
Texture
Array
Struct
)
func descendantLocalVars(block, target *Block) ([]Type, bool) {
if block == target {
return block.LocalVars, true
}
var ts []Type
for _, s := range block.Stmts {
for _, b := range s.Blocks {
if ts2, found := descendantLocalVars(b, target); found {
n := b.LocalVarIndexOffset - block.LocalVarIndexOffset
ts = append(ts, block.LocalVars[:n]...)
ts = append(ts, ts2...)
return ts, true
}
}
}
return nil, false
}
func localVariableType(p *Program, topBlock, block *Block, absidx int) Type {
// TODO: Rename this function (truly-local variable?)
var ts []Type
for _, f := range p.Funcs {
if f.Block == topBlock {
ts = append(f.InParams, f.OutParams...)
break
}
}
ts2, _ := descendantLocalVars(topBlock, block)
ts = append(ts, ts2...)
return ts[absidx]
}
func (p *Program) LocalVariableType(topBlock, block *Block, idx int) Type {
switch topBlock {
case p.VertexFunc.Block:
na := len(p.Attributes)
nv := len(p.Varyings)
switch {
case idx < na:
return p.Attributes[idx]
case idx == na:
return Type{Main: Vec4}
case idx < na+nv+1:
return p.Varyings[idx-na-1]
default:
return localVariableType(p, topBlock, block, idx-(na+nv+1))
}
case p.FragmentFunc.Block:
nv := len(p.Varyings)
switch {
case idx == 0:
return Type{Main: Vec4}
case idx < nv+1:
return p.Varyings[idx-1]
default:
return localVariableType(p, topBlock, block, idx-(nv+1))
}
default:
return localVariableType(p, topBlock, block, idx)
}
}