zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

glsl.go (18532B)


      1 // Copyright 2020 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package glsl
     16 
     17 import (
     18 	"fmt"
     19 	"go/constant"
     20 	"go/token"
     21 	"regexp"
     22 	"sort"
     23 	"strings"
     24 
     25 	"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
     26 )
     27 
     28 type GLSLVersion int
     29 
     30 const (
     31 	GLSLVersionDefault GLSLVersion = iota
     32 	GLSLVersionES100
     33 	GLSLVersionES300
     34 )
     35 
     36 func VertexPrelude(version GLSLVersion) string {
     37 	if version == GLSLVersionES300 {
     38 		return `#version 300 es`
     39 	}
     40 	return ""
     41 }
     42 
     43 func FragmentPrelude(version GLSLVersion) string {
     44 	var prefix string
     45 	switch version {
     46 	case GLSLVersionES100:
     47 		prefix = `#extension GL_OES_standard_derivatives : enable` + "\n\n"
     48 	case GLSLVersionES300:
     49 		prefix = `#version 300 es` + "\n\n"
     50 	}
     51 	return prefix + `#if defined(GL_ES)
     52 precision highp float;
     53 #else
     54 #define lowp
     55 #define mediump
     56 #define highp
     57 #endif`
     58 }
     59 
     60 type compileContext struct {
     61 	version     GLSLVersion
     62 	structNames map[string]string
     63 	structTypes []shaderir.Type
     64 }
     65 
     66 func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) string {
     67 	if t.Main != shaderir.Struct {
     68 		panic("glsl: the given type at structName must be a struct")
     69 	}
     70 	s := t.String()
     71 	if n, ok := c.structNames[s]; ok {
     72 		return n
     73 	}
     74 	n := fmt.Sprintf("S%d", len(c.structNames))
     75 	c.structNames[s] = n
     76 	c.structTypes = append(c.structTypes, *t)
     77 	return n
     78 }
     79 
     80 func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) {
     81 	c := &compileContext{
     82 		version:     version,
     83 		structNames: map[string]string{},
     84 	}
     85 
     86 	indexToFunc := map[int]*shaderir.Func{}
     87 	for _, f := range p.Funcs {
     88 		f := f
     89 		indexToFunc[f.Index] = &f
     90 	}
     91 
     92 	// Vertex func
     93 	var vslines []string
     94 	{
     95 		vslines = append(vslines, strings.Split(VertexPrelude(version), "\n")...)
     96 		vslines = append(vslines, "{{.Structs}}")
     97 		if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Attributes) > 0 || len(p.Varyings) > 0 {
     98 			vslines = append(vslines, "")
     99 			for i, t := range p.Uniforms {
    100 				vslines = append(vslines, fmt.Sprintf("uniform %s;", c.glslVarDecl(p, &t, fmt.Sprintf("U%d", i))))
    101 			}
    102 			for i := 0; i < p.TextureNum; i++ {
    103 				vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
    104 			}
    105 			for i, t := range p.Attributes {
    106 				keyword := "attribute"
    107 				if version == GLSLVersionES300 {
    108 					keyword = "in"
    109 				}
    110 				vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("A%d", i))))
    111 			}
    112 			for i, t := range p.Varyings {
    113 				keyword := "varying"
    114 				if version == GLSLVersionES300 {
    115 					keyword = "out"
    116 				}
    117 				vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
    118 			}
    119 		}
    120 
    121 		var funcs []*shaderir.Func
    122 		if p.VertexFunc.Block != nil {
    123 			indices := p.ReferredFuncIndicesInVertexShader()
    124 			sort.Ints(indices)
    125 			funcs = make([]*shaderir.Func, 0, len(indices))
    126 			for _, idx := range indices {
    127 				funcs = append(funcs, indexToFunc[idx])
    128 			}
    129 		} else {
    130 			// When a vertex entry point is not defined, allow to put all the functions. This is useful for testing.
    131 			funcs = make([]*shaderir.Func, 0, len(p.Funcs))
    132 			for _, f := range p.Funcs {
    133 				f := f
    134 				funcs = append(funcs, &f)
    135 			}
    136 		}
    137 		if len(funcs) > 0 {
    138 			vslines = append(vslines, "")
    139 			for _, f := range funcs {
    140 				vslines = append(vslines, c.glslFunc(p, f, true)...)
    141 			}
    142 			for _, f := range funcs {
    143 				if len(vslines) > 0 && vslines[len(vslines)-1] != "" {
    144 					vslines = append(vslines, "")
    145 				}
    146 				vslines = append(vslines, c.glslFunc(p, f, false)...)
    147 			}
    148 		}
    149 
    150 		// Add a dummy function to just touch uniform array variable's elements (#1754).
    151 		// Without this, the first elements of a uniform array might not be initialized correctly on some environments.
    152 		var touchedUniforms []string
    153 		for i, t := range p.Uniforms {
    154 			if t.Main != shaderir.Array {
    155 				continue
    156 			}
    157 			if t.Length <= 1 {
    158 				continue
    159 			}
    160 			str := fmt.Sprintf("U%d[%d]", i, t.Length-1)
    161 			switch t.Sub[0].Main {
    162 			case shaderir.Vec2, shaderir.Vec3, shaderir.Vec4:
    163 				str += ".x"
    164 			case shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
    165 				str += "[0][0]"
    166 			}
    167 			str = "float(" + str + ")"
    168 			touchedUniforms = append(touchedUniforms, str)
    169 		}
    170 
    171 		var touchUniformsFunc []string
    172 		if len(touchedUniforms) > 0 {
    173 			touchUniformsFunc = append(touchUniformsFunc, "float touchUniforms() {")
    174 			touchUniformsFunc = append(touchUniformsFunc, fmt.Sprintf("\treturn %s;", strings.Join(touchedUniforms, " + ")))
    175 			touchUniformsFunc = append(touchUniformsFunc, "}")
    176 
    177 		}
    178 
    179 		if p.VertexFunc.Block != nil && len(p.VertexFunc.Block.Stmts) > 0 {
    180 			if len(touchUniformsFunc) > 0 {
    181 				vslines = append(vslines, "")
    182 				vslines = append(vslines, touchUniformsFunc...)
    183 			}
    184 			vslines = append(vslines, "")
    185 			vslines = append(vslines, "void main(void) {")
    186 			if len(touchUniformsFunc) > 0 {
    187 				vslines = append(vslines, "\ttouchUniforms();")
    188 			}
    189 			vslines = append(vslines, c.glslBlock(p, p.VertexFunc.Block, p.VertexFunc.Block, 0)...)
    190 			vslines = append(vslines, "}")
    191 		}
    192 	}
    193 
    194 	// Fragment func
    195 	var fslines []string
    196 	{
    197 		fslines = append(fslines, strings.Split(FragmentPrelude(version), "\n")...)
    198 		fslines = append(fslines, "", "{{.Structs}}")
    199 		if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Varyings) > 0 {
    200 			fslines = append(fslines, "")
    201 			for i, t := range p.Uniforms {
    202 				fslines = append(fslines, fmt.Sprintf("uniform %s;", c.glslVarDecl(p, &t, fmt.Sprintf("U%d", i))))
    203 			}
    204 			for i := 0; i < p.TextureNum; i++ {
    205 				fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
    206 			}
    207 			for i, t := range p.Varyings {
    208 				keyword := "varying"
    209 				if version == GLSLVersionES300 {
    210 					keyword = "in"
    211 				}
    212 				fslines = append(fslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
    213 			}
    214 		}
    215 		if version == GLSLVersionES300 {
    216 			fslines = append(fslines, "out vec4 fragColor;")
    217 		}
    218 
    219 		var funcs []*shaderir.Func
    220 		if p.VertexFunc.Block != nil {
    221 			indices := p.ReferredFuncIndicesInFragmentShader()
    222 			sort.Ints(indices)
    223 			funcs = make([]*shaderir.Func, 0, len(indices))
    224 			for _, idx := range indices {
    225 				funcs = append(funcs, indexToFunc[idx])
    226 			}
    227 		} else {
    228 			// When a fragment entry point is not defined, allow to put all the functions. This is useful for testing.
    229 			funcs = make([]*shaderir.Func, 0, len(p.Funcs))
    230 			for _, f := range p.Funcs {
    231 				f := f
    232 				funcs = append(funcs, &f)
    233 			}
    234 		}
    235 		if len(funcs) > 0 {
    236 			fslines = append(fslines, "")
    237 			for _, f := range funcs {
    238 				fslines = append(fslines, c.glslFunc(p, f, true)...)
    239 			}
    240 			for _, f := range funcs {
    241 				if len(fslines) > 0 && fslines[len(fslines)-1] != "" {
    242 					fslines = append(fslines, "")
    243 				}
    244 				fslines = append(fslines, c.glslFunc(p, f, false)...)
    245 			}
    246 		}
    247 
    248 		if p.FragmentFunc.Block != nil && len(p.FragmentFunc.Block.Stmts) > 0 {
    249 			fslines = append(fslines, "")
    250 			fslines = append(fslines, "void main(void) {")
    251 			fslines = append(fslines, c.glslBlock(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
    252 			fslines = append(fslines, "}")
    253 		}
    254 	}
    255 
    256 	vs := strings.Join(vslines, "\n")
    257 	fs := strings.Join(fslines, "\n")
    258 
    259 	// Struct types are determined after converting the program.
    260 	if len(c.structTypes) > 0 {
    261 		var stlines []string
    262 		for i, t := range c.structTypes {
    263 			stlines = append(stlines, fmt.Sprintf("struct S%d {", i))
    264 			for j, st := range t.Sub {
    265 				stlines = append(stlines, fmt.Sprintf("\t%s;", c.glslVarDecl(p, &st, fmt.Sprintf("M%d", j))))
    266 			}
    267 			stlines = append(stlines, "};")
    268 		}
    269 		st := strings.Join(stlines, "\n")
    270 		vs = strings.ReplaceAll(vs, "{{.Structs}}", st)
    271 		fs = strings.ReplaceAll(fs, "{{.Structs}}", st)
    272 	} else {
    273 		vs = strings.ReplaceAll(vs, "{{.Structs}}", "")
    274 		fs = strings.ReplaceAll(fs, "{{.Structs}}", "")
    275 	}
    276 
    277 	nls := regexp.MustCompile(`\n\n+`)
    278 	vs = nls.ReplaceAllString(vs, "\n\n")
    279 	fs = nls.ReplaceAllString(fs, "\n\n")
    280 
    281 	vs = strings.TrimSpace(vs) + "\n"
    282 	fs = strings.TrimSpace(fs) + "\n"
    283 
    284 	return vs, fs
    285 }
    286 
    287 func (c *compileContext) glslType(p *shaderir.Program, t *shaderir.Type) (string, string) {
    288 	switch t.Main {
    289 	case shaderir.None:
    290 		return "void", ""
    291 	case shaderir.Struct:
    292 		return c.structName(p, t), ""
    293 	default:
    294 		return typeString(t)
    295 	}
    296 }
    297 
    298 func (c *compileContext) glslVarDecl(p *shaderir.Program, t *shaderir.Type, varname string) string {
    299 	switch t.Main {
    300 	case shaderir.None:
    301 		return "?(none)"
    302 	case shaderir.Struct:
    303 		return fmt.Sprintf("%s %s", c.structName(p, t), varname)
    304 	default:
    305 		t0, t1 := typeString(t)
    306 		return fmt.Sprintf("%s %s%s", t0, varname, t1)
    307 	}
    308 }
    309 
    310 func (c *compileContext) glslVarInit(p *shaderir.Program, t *shaderir.Type) string {
    311 	switch t.Main {
    312 	case shaderir.None:
    313 		return "?(none)"
    314 	case shaderir.Array:
    315 		init := c.glslVarInit(p, &t.Sub[0])
    316 		es := make([]string, 0, t.Length)
    317 		for i := 0; i < t.Length; i++ {
    318 			es = append(es, init)
    319 		}
    320 		t0, t1 := typeString(t)
    321 		return fmt.Sprintf("%s%s(%s)", t0, t1, strings.Join(es, ", "))
    322 	case shaderir.Struct:
    323 		panic("not implemented")
    324 	case shaderir.Bool:
    325 		return "false"
    326 	case shaderir.Int:
    327 		return "0"
    328 	case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4:
    329 		return fmt.Sprintf("%s(0)", basicTypeString(t.Main))
    330 	default:
    331 		t0, t1 := c.glslType(p, t)
    332 		panic(fmt.Sprintf("?(unexpected type: %s%s)", t0, t1))
    333 	}
    334 }
    335 
    336 func (c *compileContext) glslFunc(p *shaderir.Program, f *shaderir.Func, prototype bool) []string {
    337 	var args []string
    338 	var idx int
    339 	for _, t := range f.InParams {
    340 		args = append(args, "in "+c.glslVarDecl(p, &t, fmt.Sprintf("l%d", idx)))
    341 		idx++
    342 	}
    343 	for _, t := range f.OutParams {
    344 		args = append(args, "out "+c.glslVarDecl(p, &t, fmt.Sprintf("l%d", idx)))
    345 		idx++
    346 	}
    347 	argsstr := "void"
    348 	if len(args) > 0 {
    349 		argsstr = strings.Join(args, ", ")
    350 	}
    351 
    352 	t0, t1 := c.glslType(p, &f.Return)
    353 	sig := fmt.Sprintf("%s%s F%d(%s)", t0, t1, f.Index, argsstr)
    354 
    355 	var lines []string
    356 	if prototype {
    357 		lines = append(lines, fmt.Sprintf("%s;", sig))
    358 		return lines
    359 	}
    360 	lines = append(lines, fmt.Sprintf("%s {", sig))
    361 	lines = append(lines, c.glslBlock(p, f.Block, f.Block, 0)...)
    362 	lines = append(lines, "}")
    363 
    364 	return lines
    365 }
    366 
    367 func constantToNumberLiteral(t shaderir.ConstType, v constant.Value) string {
    368 	switch t {
    369 	case shaderir.ConstTypeNone:
    370 		if v.Kind() == constant.Bool {
    371 			if constant.BoolVal(v) {
    372 				return "true"
    373 			}
    374 			return "false"
    375 		}
    376 		fallthrough
    377 	case shaderir.ConstTypeFloat:
    378 		if i := constant.ToInt(v); i.Kind() == constant.Int {
    379 			x, _ := constant.Int64Val(i)
    380 			return fmt.Sprintf("%d.0", x)
    381 		}
    382 		if i := constant.ToFloat(v); i.Kind() == constant.Float {
    383 			x, _ := constant.Float64Val(i)
    384 			return fmt.Sprintf("%.10e", x)
    385 		}
    386 	case shaderir.ConstTypeInt:
    387 		if i := constant.ToInt(v); i.Kind() == constant.Int {
    388 			x, _ := constant.Int64Val(i)
    389 			return fmt.Sprintf("%d", x)
    390 		}
    391 	}
    392 	return fmt.Sprintf("?(unexpected literal: %s)", v)
    393 }
    394 
    395 func (c *compileContext) localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx int) string {
    396 	switch topBlock {
    397 	case p.VertexFunc.Block:
    398 		na := len(p.Attributes)
    399 		nv := len(p.Varyings)
    400 		switch {
    401 		case idx < na:
    402 			return fmt.Sprintf("A%d", idx)
    403 		case idx == na:
    404 			return "gl_Position"
    405 		case idx < na+nv+1:
    406 			return fmt.Sprintf("V%d", idx-na-1)
    407 		default:
    408 			return fmt.Sprintf("l%d", idx-(na+nv+1))
    409 		}
    410 	case p.FragmentFunc.Block:
    411 		nv := len(p.Varyings)
    412 		switch {
    413 		case idx == 0:
    414 			return "gl_FragCoord"
    415 		case idx < nv+1:
    416 			return fmt.Sprintf("V%d", idx-1)
    417 		case idx == nv+1:
    418 			if c.version == GLSLVersionES300 {
    419 				return "fragColor"
    420 			}
    421 			return "gl_FragColor"
    422 		default:
    423 			return fmt.Sprintf("l%d", idx-(nv+2))
    424 		}
    425 	default:
    426 		return fmt.Sprintf("l%d", idx)
    427 	}
    428 }
    429 
    430 func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
    431 	idt := strings.Repeat("\t", level+1)
    432 	name := c.localVariableName(p, topBlock, block, index)
    433 	t := p.LocalVariableType(topBlock, block, index)
    434 
    435 	var lines []string
    436 	switch t.Main {
    437 	case shaderir.Array:
    438 		if decl {
    439 			lines = append(lines, fmt.Sprintf("%s%s;", idt, c.glslVarDecl(p, &t, name)))
    440 		}
    441 		init := c.glslVarInit(p, &t.Sub[0])
    442 		for i := 0; i < t.Length; i++ {
    443 			lines = append(lines, fmt.Sprintf("%s%s[%d] = %s;", idt, name, i, init))
    444 		}
    445 	case shaderir.None:
    446 		// The type is None e.g., when the variable is a for-loop counter.
    447 	default:
    448 		if decl {
    449 			lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, c.glslVarDecl(p, &t, name), c.glslVarInit(p, &t)))
    450 		} else {
    451 			lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, name, c.glslVarInit(p, &t)))
    452 		}
    453 	}
    454 	return lines
    455 }
    456 
    457 func (c *compileContext) glslBlock(p *shaderir.Program, topBlock, block *shaderir.Block, level int) []string {
    458 	if block == nil {
    459 		return nil
    460 	}
    461 
    462 	var lines []string
    463 	for i := range block.LocalVars {
    464 		lines = append(lines, c.initVariable(p, topBlock, block, block.LocalVarIndexOffset+i, true, level)...)
    465 	}
    466 
    467 	var glslExpr func(e *shaderir.Expr) string
    468 	glslExpr = func(e *shaderir.Expr) string {
    469 		switch e.Type {
    470 		case shaderir.NumberExpr:
    471 			return constantToNumberLiteral(e.ConstType, e.Const)
    472 		case shaderir.UniformVariable:
    473 			return fmt.Sprintf("U%d", e.Index)
    474 		case shaderir.TextureVariable:
    475 			return fmt.Sprintf("T%d", e.Index)
    476 		case shaderir.LocalVariable:
    477 			return c.localVariableName(p, topBlock, block, e.Index)
    478 		case shaderir.StructMember:
    479 			return fmt.Sprintf("M%d", e.Index)
    480 		case shaderir.BuiltinFuncExpr:
    481 			return c.builtinFuncString(e.BuiltinFunc)
    482 		case shaderir.SwizzlingExpr:
    483 			if !shaderir.IsValidSwizzling(e.Swizzling) {
    484 				return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
    485 			}
    486 			return e.Swizzling
    487 		case shaderir.FunctionExpr:
    488 			return fmt.Sprintf("F%d", e.Index)
    489 		case shaderir.Unary:
    490 			var op string
    491 			switch e.Op {
    492 			case shaderir.Add, shaderir.Sub, shaderir.NotOp:
    493 				op = string(e.Op)
    494 			default:
    495 				op = fmt.Sprintf("?(unexpected op: %s)", string(e.Op))
    496 			}
    497 			return fmt.Sprintf("%s(%s)", op, glslExpr(&e.Exprs[0]))
    498 		case shaderir.Binary:
    499 			return fmt.Sprintf("(%s) %s (%s)", glslExpr(&e.Exprs[0]), e.Op, glslExpr(&e.Exprs[1]))
    500 		case shaderir.Selection:
    501 			return fmt.Sprintf("(%s) ? (%s) : (%s)", glslExpr(&e.Exprs[0]), glslExpr(&e.Exprs[1]), glslExpr(&e.Exprs[2]))
    502 		case shaderir.Call:
    503 			var args []string
    504 			for _, exp := range e.Exprs[1:] {
    505 				args = append(args, glslExpr(&exp))
    506 			}
    507 			// Using parentheses at the callee is illegal.
    508 			return fmt.Sprintf("%s(%s)", glslExpr(&e.Exprs[0]), strings.Join(args, ", "))
    509 		case shaderir.FieldSelector:
    510 			return fmt.Sprintf("(%s).%s", glslExpr(&e.Exprs[0]), glslExpr(&e.Exprs[1]))
    511 		case shaderir.Index:
    512 			return fmt.Sprintf("(%s)[%s]", glslExpr(&e.Exprs[0]), glslExpr(&e.Exprs[1]))
    513 		default:
    514 			return fmt.Sprintf("?(unexpected expr: %d)", e.Type)
    515 		}
    516 	}
    517 
    518 	idt := strings.Repeat("\t", level+1)
    519 	for _, s := range block.Stmts {
    520 		switch s.Type {
    521 		case shaderir.ExprStmt:
    522 			lines = append(lines, fmt.Sprintf("%s%s;", idt, glslExpr(&s.Exprs[0])))
    523 		case shaderir.BlockStmt:
    524 			lines = append(lines, idt+"{")
    525 			lines = append(lines, c.glslBlock(p, topBlock, s.Blocks[0], level+1)...)
    526 			lines = append(lines, idt+"}")
    527 		case shaderir.Assign:
    528 			lhs := s.Exprs[0]
    529 			rhs := s.Exprs[1]
    530 			if lhs.Type == shaderir.LocalVariable {
    531 				if t := p.LocalVariableType(topBlock, block, lhs.Index); t.Main == shaderir.Array {
    532 					for i := 0; i < t.Length; i++ {
    533 						lines = append(lines, fmt.Sprintf("%[1]s%[2]s[%[3]d] = %[4]s[%[3]d];", idt, glslExpr(&lhs), i, glslExpr(&rhs)))
    534 					}
    535 					continue
    536 				}
    537 			}
    538 			lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, glslExpr(&lhs), glslExpr(&rhs)))
    539 		case shaderir.Init:
    540 			lines = append(lines, c.initVariable(p, topBlock, block, s.InitIndex, false, level)...)
    541 		case shaderir.If:
    542 			lines = append(lines, fmt.Sprintf("%sif (%s) {", idt, glslExpr(&s.Exprs[0])))
    543 			lines = append(lines, c.glslBlock(p, topBlock, s.Blocks[0], level+1)...)
    544 			if len(s.Blocks) > 1 {
    545 				lines = append(lines, fmt.Sprintf("%s} else {", idt))
    546 				lines = append(lines, c.glslBlock(p, topBlock, s.Blocks[1], level+1)...)
    547 			}
    548 			lines = append(lines, fmt.Sprintf("%s}", idt))
    549 		case shaderir.For:
    550 			var ct shaderir.ConstType
    551 			switch s.ForVarType.Main {
    552 			case shaderir.Int:
    553 				ct = shaderir.ConstTypeInt
    554 			case shaderir.Float:
    555 				ct = shaderir.ConstTypeFloat
    556 			}
    557 
    558 			v := c.localVariableName(p, topBlock, block, s.ForVarIndex)
    559 			var delta string
    560 			switch val, _ := constant.Float64Val(s.ForDelta); val {
    561 			case 0:
    562 				delta = fmt.Sprintf("?(unexpected delta: %v)", s.ForDelta)
    563 			case 1:
    564 				delta = fmt.Sprintf("%s++", v)
    565 			case -1:
    566 				delta = fmt.Sprintf("%s--", v)
    567 			default:
    568 				d := s.ForDelta
    569 				if val > 0 {
    570 					delta = fmt.Sprintf("%s += %s", v, constantToNumberLiteral(ct, d))
    571 				} else {
    572 					d = constant.UnaryOp(token.SUB, d, 0)
    573 					delta = fmt.Sprintf("%s -= %s", v, constantToNumberLiteral(ct, d))
    574 				}
    575 			}
    576 			var op string
    577 			switch s.ForOp {
    578 			case shaderir.LessThanOp, shaderir.LessThanEqualOp, shaderir.GreaterThanOp, shaderir.GreaterThanEqualOp, shaderir.EqualOp, shaderir.NotEqualOp:
    579 				op = string(s.ForOp)
    580 			default:
    581 				op = fmt.Sprintf("?(unexpected op: %s)", string(s.ForOp))
    582 			}
    583 
    584 			t := s.ForVarType
    585 			init := constantToNumberLiteral(ct, s.ForInit)
    586 			end := constantToNumberLiteral(ct, s.ForEnd)
    587 			t0, t1 := typeString(&t)
    588 			lines = append(lines, fmt.Sprintf("%sfor (%s %s%s = %s; %s %s %s; %s) {", idt, t0, v, t1, init, v, op, end, delta))
    589 			lines = append(lines, c.glslBlock(p, topBlock, s.Blocks[0], level+1)...)
    590 			lines = append(lines, fmt.Sprintf("%s}", idt))
    591 		case shaderir.Continue:
    592 			lines = append(lines, idt+"continue;")
    593 		case shaderir.Break:
    594 			lines = append(lines, idt+"break;")
    595 		case shaderir.Return:
    596 			if len(s.Exprs) == 0 {
    597 				lines = append(lines, idt+"return;")
    598 			} else {
    599 				lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, glslExpr(&s.Exprs[0])))
    600 			}
    601 		case shaderir.Discard:
    602 			lines = append(lines, idt+"discard;")
    603 		default:
    604 			lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
    605 		}
    606 	}
    607 
    608 	return lines
    609 }