zorldo

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

shader.go (19718B)


      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 shader
     16 
     17 import (
     18 	"fmt"
     19 	"go/ast"
     20 	gconstant "go/constant"
     21 	"go/token"
     22 	"strings"
     23 
     24 	"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
     25 )
     26 
     27 type variable struct {
     28 	name           string
     29 	typ            shaderir.Type
     30 	forLoopCounter bool
     31 }
     32 
     33 type constant struct {
     34 	name  string
     35 	typ   shaderir.Type
     36 	ctyp  shaderir.ConstType
     37 	value gconstant.Value
     38 }
     39 
     40 type function struct {
     41 	name  string
     42 	block *block
     43 
     44 	ir shaderir.Func
     45 }
     46 
     47 type compileState struct {
     48 	fs *token.FileSet
     49 
     50 	vertexEntry   string
     51 	fragmentEntry string
     52 
     53 	ir shaderir.Program
     54 
     55 	funcs []function
     56 
     57 	global block
     58 
     59 	varyingParsed bool
     60 
     61 	errs []string
     62 }
     63 
     64 func (cs *compileState) findFunction(name string) (int, bool) {
     65 	for i, f := range cs.funcs {
     66 		if f.name == name {
     67 			return i, true
     68 		}
     69 	}
     70 	return 0, false
     71 }
     72 
     73 func (cs *compileState) findUniformVariable(name string) (int, bool) {
     74 	for i, u := range cs.ir.UniformNames {
     75 		if u == name {
     76 			return i, true
     77 		}
     78 	}
     79 	return 0, false
     80 }
     81 
     82 type typ struct {
     83 	name string
     84 	ir   shaderir.Type
     85 }
     86 
     87 type block struct {
     88 	types      []typ
     89 	vars       []variable
     90 	unusedVars map[int]token.Pos
     91 	consts     []constant
     92 	pos        token.Pos
     93 	outer      *block
     94 
     95 	ir *shaderir.Block
     96 }
     97 
     98 func (b *block) totalLocalVariableNum() int {
     99 	c := len(b.vars)
    100 	if b.outer != nil {
    101 		c += b.outer.totalLocalVariableNum()
    102 	}
    103 	return c
    104 }
    105 
    106 func (b *block) addNamedLocalVariable(name string, typ shaderir.Type, pos token.Pos) {
    107 	b.vars = append(b.vars, variable{
    108 		name: name,
    109 		typ:  typ,
    110 	})
    111 	if name == "_" {
    112 		return
    113 	}
    114 	idx := len(b.vars) - 1
    115 	if b.unusedVars == nil {
    116 		b.unusedVars = map[int]token.Pos{}
    117 	}
    118 	b.unusedVars[idx] = pos
    119 }
    120 
    121 func (b *block) findLocalVariable(name string, markLocalVariableUsed bool) (int, shaderir.Type, bool) {
    122 	if name == "" || name == "_" {
    123 		panic("shader: variable name must be non-empty and non-underscore")
    124 	}
    125 
    126 	idx := 0
    127 	for outer := b.outer; outer != nil; outer = outer.outer {
    128 		idx += len(outer.vars)
    129 	}
    130 	for i, v := range b.vars {
    131 		if v.name == name {
    132 			if markLocalVariableUsed {
    133 				delete(b.unusedVars, i)
    134 			}
    135 			return idx + i, v.typ, true
    136 		}
    137 	}
    138 	if b.outer != nil {
    139 		return b.outer.findLocalVariable(name, markLocalVariableUsed)
    140 	}
    141 	return 0, shaderir.Type{}, false
    142 }
    143 
    144 func (b *block) findLocalVariableByIndex(idx int) (shaderir.Type, bool) {
    145 	bs := []*block{b}
    146 	for outer := b.outer; outer != nil; outer = outer.outer {
    147 		bs = append(bs, outer)
    148 	}
    149 	for i := len(bs) - 1; i >= 0; i-- {
    150 		if len(bs[i].vars) <= idx {
    151 			idx -= len(bs[i].vars)
    152 			continue
    153 		}
    154 		return bs[i].vars[idx].typ, true
    155 	}
    156 	return shaderir.Type{}, false
    157 }
    158 
    159 func (b *block) findConstant(name string) (constant, bool) {
    160 	if name == "" || name == "_" {
    161 		panic("shader: constant name must be non-empty and non-underscore")
    162 	}
    163 
    164 	for _, c := range b.consts {
    165 		if c.name == name {
    166 			return c, true
    167 		}
    168 	}
    169 	if b.outer != nil {
    170 		return b.outer.findConstant(name)
    171 	}
    172 
    173 	return constant{}, false
    174 }
    175 
    176 type ParseError struct {
    177 	errs []string
    178 }
    179 
    180 func (p *ParseError) Error() string {
    181 	return strings.Join(p.errs, "\n")
    182 }
    183 
    184 func Compile(fs *token.FileSet, f *ast.File, vertexEntry, fragmentEntry string, textureNum int) (*shaderir.Program, error) {
    185 	s := &compileState{
    186 		fs:            fs,
    187 		vertexEntry:   vertexEntry,
    188 		fragmentEntry: fragmentEntry,
    189 	}
    190 	s.global.ir = &shaderir.Block{}
    191 	s.parse(f)
    192 
    193 	if len(s.errs) > 0 {
    194 		return nil, &ParseError{s.errs}
    195 	}
    196 
    197 	// TODO: Resolve identifiers?
    198 	// TODO: Resolve constants
    199 
    200 	// TODO: Make a call graph and reorder the elements.
    201 
    202 	s.ir.TextureNum = textureNum
    203 	return &s.ir, nil
    204 }
    205 
    206 func (s *compileState) addError(pos token.Pos, str string) {
    207 	p := s.fs.Position(pos)
    208 	s.errs = append(s.errs, fmt.Sprintf("%s: %s", p, str))
    209 }
    210 
    211 func (cs *compileState) parse(f *ast.File) {
    212 	// Parse GenDecl for global variables, and then parse functions.
    213 	for _, d := range f.Decls {
    214 		if _, ok := d.(*ast.FuncDecl); !ok {
    215 			ss, ok := cs.parseDecl(&cs.global, d)
    216 			if !ok {
    217 				return
    218 			}
    219 			cs.global.ir.Stmts = append(cs.global.ir.Stmts, ss...)
    220 		}
    221 	}
    222 
    223 	// Sort the uniform variable so that special variable starting with __ should come first.
    224 	var unames []string
    225 	var utypes []shaderir.Type
    226 	for i, u := range cs.ir.UniformNames {
    227 		if strings.HasPrefix(u, "__") {
    228 			unames = append(unames, u)
    229 			utypes = append(utypes, cs.ir.Uniforms[i])
    230 		}
    231 	}
    232 	// TODO: Check len(unames) == graphics.PreservedUniformVariablesNum. Unfortunately this is not true on tests.
    233 	for i, u := range cs.ir.UniformNames {
    234 		if !strings.HasPrefix(u, "__") {
    235 			unames = append(unames, u)
    236 			utypes = append(utypes, cs.ir.Uniforms[i])
    237 		}
    238 	}
    239 	cs.ir.UniformNames = unames
    240 	cs.ir.Uniforms = utypes
    241 
    242 	// Parse function names so that any other function call the others.
    243 	// The function data is provisional and will be updated soon.
    244 	for _, d := range f.Decls {
    245 		fd, ok := d.(*ast.FuncDecl)
    246 		if !ok {
    247 			continue
    248 		}
    249 		n := fd.Name.Name
    250 		if n == cs.vertexEntry {
    251 			continue
    252 		}
    253 		if n == cs.fragmentEntry {
    254 			continue
    255 		}
    256 
    257 		for _, f := range cs.funcs {
    258 			if f.name == n {
    259 				cs.addError(d.Pos(), fmt.Sprintf("redeclared function: %s", n))
    260 				return
    261 			}
    262 		}
    263 
    264 		inParams, outParams := cs.parseFuncParams(&cs.global, fd)
    265 		var inT, outT []shaderir.Type
    266 		for _, v := range inParams {
    267 			inT = append(inT, v.typ)
    268 		}
    269 		for _, v := range outParams {
    270 			outT = append(outT, v.typ)
    271 		}
    272 
    273 		cs.funcs = append(cs.funcs, function{
    274 			name: n,
    275 			ir: shaderir.Func{
    276 				Index:     len(cs.funcs),
    277 				InParams:  inT,
    278 				OutParams: outT,
    279 				Block:     &shaderir.Block{},
    280 			},
    281 		})
    282 	}
    283 
    284 	// Parse functions.
    285 	for _, d := range f.Decls {
    286 		if _, ok := d.(*ast.FuncDecl); ok {
    287 			ss, ok := cs.parseDecl(&cs.global, d)
    288 			if !ok {
    289 				return
    290 			}
    291 			cs.global.ir.Stmts = append(cs.global.ir.Stmts, ss...)
    292 		}
    293 	}
    294 
    295 	if len(cs.errs) > 0 {
    296 		return
    297 	}
    298 
    299 	for _, f := range cs.funcs {
    300 		cs.ir.Funcs = append(cs.ir.Funcs, f.ir)
    301 	}
    302 }
    303 
    304 func (cs *compileState) parseDecl(b *block, d ast.Decl) ([]shaderir.Stmt, bool) {
    305 	var stmts []shaderir.Stmt
    306 
    307 	switch d := d.(type) {
    308 	case *ast.GenDecl:
    309 		switch d.Tok {
    310 		case token.TYPE:
    311 			// TODO: Parse other types
    312 			for _, s := range d.Specs {
    313 				s := s.(*ast.TypeSpec)
    314 				t, ok := cs.parseType(b, s.Type)
    315 				if !ok {
    316 					return nil, false
    317 				}
    318 				b.types = append(b.types, typ{
    319 					name: s.Name.Name,
    320 					ir:   t,
    321 				})
    322 			}
    323 		case token.CONST:
    324 			for _, s := range d.Specs {
    325 				s := s.(*ast.ValueSpec)
    326 				cs, ok := cs.parseConstant(b, s)
    327 				if !ok {
    328 					return nil, false
    329 				}
    330 				b.consts = append(b.consts, cs...)
    331 			}
    332 		case token.VAR:
    333 			for _, s := range d.Specs {
    334 				s := s.(*ast.ValueSpec)
    335 				vs, inits, ss, ok := cs.parseVariable(b, s)
    336 				if !ok {
    337 					return nil, false
    338 				}
    339 				stmts = append(stmts, ss...)
    340 				if b == &cs.global {
    341 					// TODO: Should rhs be ignored?
    342 					for i, v := range vs {
    343 						if !strings.HasPrefix(v.name, "__") {
    344 							if v.name[0] < 'A' || 'Z' < v.name[0] {
    345 								cs.addError(s.Names[i].Pos(), fmt.Sprintf("global variables must be exposed: %s", v.name))
    346 							}
    347 						}
    348 						cs.ir.UniformNames = append(cs.ir.UniformNames, v.name)
    349 						cs.ir.Uniforms = append(cs.ir.Uniforms, v.typ)
    350 					}
    351 					continue
    352 				}
    353 
    354 				// base must be obtained before adding the variables.
    355 				base := b.totalLocalVariableNum()
    356 				for _, v := range vs {
    357 					b.addNamedLocalVariable(v.name, v.typ, d.Pos())
    358 				}
    359 
    360 				if len(inits) > 0 {
    361 					for i := range vs {
    362 						stmts = append(stmts, shaderir.Stmt{
    363 							Type: shaderir.Assign,
    364 							Exprs: []shaderir.Expr{
    365 								{
    366 									Type:  shaderir.LocalVariable,
    367 									Index: base + i,
    368 								},
    369 								inits[i],
    370 							},
    371 						})
    372 					}
    373 				}
    374 			}
    375 		case token.IMPORT:
    376 			cs.addError(d.Pos(), "import is forbidden")
    377 		default:
    378 			cs.addError(d.Pos(), "unexpected token")
    379 		}
    380 	case *ast.FuncDecl:
    381 		f, ok := cs.parseFunc(b, d)
    382 		if !ok {
    383 			return nil, false
    384 		}
    385 		if b != &cs.global {
    386 			cs.addError(d.Pos(), "non-global function is not implemented")
    387 			return nil, false
    388 		}
    389 		switch d.Name.Name {
    390 		case cs.vertexEntry:
    391 			cs.ir.VertexFunc.Block = f.ir.Block
    392 		case cs.fragmentEntry:
    393 			cs.ir.FragmentFunc.Block = f.ir.Block
    394 		default:
    395 			// The function is already registered for their names.
    396 			for i := range cs.funcs {
    397 				if cs.funcs[i].name == d.Name.Name {
    398 					// Index is already determined by the provisional parsing.
    399 					f.ir.Index = cs.funcs[i].ir.Index
    400 					cs.funcs[i] = f
    401 					break
    402 				}
    403 			}
    404 		}
    405 	default:
    406 		cs.addError(d.Pos(), "unexpected decl")
    407 		return nil, false
    408 	}
    409 
    410 	return stmts, true
    411 }
    412 
    413 // functionReturnTypes returns the original returning value types, if the given expression is call.
    414 //
    415 // Note that parseExpr returns the returning types for IR, not the original function.
    416 func (cs *compileState) functionReturnTypes(block *block, expr ast.Expr) ([]shaderir.Type, bool) {
    417 	call, ok := expr.(*ast.CallExpr)
    418 	if !ok {
    419 		return nil, false
    420 	}
    421 
    422 	ident, ok := call.Fun.(*ast.Ident)
    423 	if !ok {
    424 		return nil, false
    425 	}
    426 
    427 	for _, f := range cs.funcs {
    428 		if f.name == ident.Name {
    429 			// TODO: Is it correct to combine out-params and return param?
    430 			ts := f.ir.OutParams
    431 			if f.ir.Return.Main != shaderir.None {
    432 				ts = append(ts, f.ir.Return)
    433 			}
    434 			return ts, true
    435 		}
    436 	}
    437 	return nil, false
    438 }
    439 
    440 func (s *compileState) parseVariable(block *block, vs *ast.ValueSpec) ([]variable, []shaderir.Expr, []shaderir.Stmt, bool) {
    441 	if len(vs.Names) != len(vs.Values) && len(vs.Values) != 1 && len(vs.Values) != 0 {
    442 		s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match"))
    443 		return nil, nil, nil, false
    444 	}
    445 
    446 	var declt shaderir.Type
    447 	if vs.Type != nil {
    448 		var ok bool
    449 		declt, ok = s.parseType(block, vs.Type)
    450 		if !ok {
    451 			return nil, nil, nil, false
    452 		}
    453 	}
    454 
    455 	var (
    456 		vars  []variable
    457 		inits []shaderir.Expr
    458 		stmts []shaderir.Stmt
    459 	)
    460 
    461 	// These variables are used only in multiple-value context.
    462 	var inittypes []shaderir.Type
    463 	var initexprs []shaderir.Expr
    464 
    465 	for i, n := range vs.Names {
    466 		t := declt
    467 		switch {
    468 		case len(vs.Values) == 0:
    469 			// No initialization
    470 
    471 		case len(vs.Names) == len(vs.Values):
    472 			// Single-value context
    473 
    474 			init := vs.Values[i]
    475 
    476 			es, origts, ss, ok := s.parseExpr(block, init, true)
    477 			if !ok {
    478 				return nil, nil, nil, false
    479 			}
    480 
    481 			if t.Main == shaderir.None {
    482 				ts, ok := s.functionReturnTypes(block, init)
    483 				if !ok {
    484 					ts = origts
    485 				}
    486 				if len(ts) > 1 {
    487 					s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match"))
    488 				}
    489 				t = ts[0]
    490 			}
    491 
    492 			if es[0].Type == shaderir.NumberExpr {
    493 				switch t.Main {
    494 				case shaderir.Int:
    495 					es[0].ConstType = shaderir.ConstTypeInt
    496 				case shaderir.Float:
    497 					es[0].ConstType = shaderir.ConstTypeFloat
    498 				}
    499 			}
    500 
    501 			inits = append(inits, es...)
    502 			stmts = append(stmts, ss...)
    503 
    504 		default:
    505 			// Multiple-value context
    506 
    507 			if i == 0 {
    508 				init := vs.Values[0]
    509 
    510 				var ss []shaderir.Stmt
    511 				var ok bool
    512 				initexprs, inittypes, ss, ok = s.parseExpr(block, init, true)
    513 				if !ok {
    514 					return nil, nil, nil, false
    515 				}
    516 				stmts = append(stmts, ss...)
    517 
    518 				if t.Main == shaderir.None {
    519 					ts, ok := s.functionReturnTypes(block, init)
    520 					if ok {
    521 						inittypes = ts
    522 					}
    523 					if len(ts) != len(vs.Names) {
    524 						s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match"))
    525 						continue
    526 					}
    527 				}
    528 			}
    529 			if len(inittypes) > 0 {
    530 				t = inittypes[i]
    531 			}
    532 
    533 			// Add the same initexprs for each variable.
    534 			inits = append(inits, initexprs...)
    535 		}
    536 
    537 		name := n.Name
    538 		for _, v := range append(block.vars, vars...) {
    539 			if v.name == name {
    540 				s.addError(vs.Pos(), fmt.Sprintf("duplicated local variable name: %s", name))
    541 				return nil, nil, nil, false
    542 			}
    543 		}
    544 		for _, c := range block.consts {
    545 			if c.name == name {
    546 				s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant/variable name: %s", name))
    547 				return nil, nil, nil, false
    548 			}
    549 		}
    550 		vars = append(vars, variable{
    551 			name: name,
    552 			typ:  t,
    553 		})
    554 	}
    555 
    556 	return vars, inits, stmts, true
    557 }
    558 
    559 func (s *compileState) parseConstant(block *block, vs *ast.ValueSpec) ([]constant, bool) {
    560 	var t shaderir.Type
    561 	if vs.Type != nil {
    562 		var ok bool
    563 		t, ok = s.parseType(block, vs.Type)
    564 		if !ok {
    565 			return nil, false
    566 		}
    567 	}
    568 
    569 	var cs []constant
    570 	for i, n := range vs.Names {
    571 		name := n.Name
    572 		for _, c := range block.consts {
    573 			if c.name == name {
    574 				s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant name: %s", name))
    575 				return nil, false
    576 			}
    577 		}
    578 		for _, v := range block.vars {
    579 			if v.name == name {
    580 				s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant/variable name: %s", name))
    581 				return nil, false
    582 			}
    583 		}
    584 
    585 		es, ts, ss, ok := s.parseExpr(block, vs.Values[i], false)
    586 		if !ok {
    587 			return nil, false
    588 		}
    589 		if len(ss) > 0 {
    590 			s.addError(vs.Pos(), fmt.Sprintf("invalid constant expression: %s", name))
    591 			return nil, false
    592 		}
    593 		if len(ts) != 1 || len(es) != 1 {
    594 			s.addError(vs.Pos(), fmt.Sprintf("invalid constant expression: %s", n))
    595 			return nil, false
    596 		}
    597 		if es[0].Type != shaderir.NumberExpr {
    598 			s.addError(vs.Pos(), fmt.Sprintf("constant expresion must be a number but not: %s", n))
    599 			return nil, false
    600 		}
    601 		cs = append(cs, constant{
    602 			name:  name,
    603 			typ:   t,
    604 			ctyp:  es[0].ConstType,
    605 			value: es[0].Const,
    606 		})
    607 	}
    608 	return cs, true
    609 }
    610 
    611 func (cs *compileState) parseFuncParams(block *block, d *ast.FuncDecl) (in, out []variable) {
    612 	for _, f := range d.Type.Params.List {
    613 		t, ok := cs.parseType(block, f.Type)
    614 		if !ok {
    615 			return
    616 		}
    617 		for _, n := range f.Names {
    618 			in = append(in, variable{
    619 				name: n.Name,
    620 				typ:  t,
    621 			})
    622 		}
    623 	}
    624 
    625 	if d.Type.Results == nil {
    626 		return
    627 	}
    628 
    629 	for _, f := range d.Type.Results.List {
    630 		t, ok := cs.parseType(block, f.Type)
    631 		if !ok {
    632 			return
    633 		}
    634 		if len(f.Names) == 0 {
    635 			out = append(out, variable{
    636 				name: "",
    637 				typ:  t,
    638 			})
    639 		} else {
    640 			for _, n := range f.Names {
    641 				out = append(out, variable{
    642 					name: n.Name,
    643 					typ:  t,
    644 				})
    645 			}
    646 		}
    647 	}
    648 	return
    649 }
    650 
    651 func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool) {
    652 	if d.Name == nil {
    653 		cs.addError(d.Pos(), "function must have a name")
    654 		return function{}, false
    655 	}
    656 	if d.Name.Name == "init" {
    657 		cs.addError(d.Pos(), "init function is not implemented")
    658 		return function{}, false
    659 	}
    660 	if d.Body == nil {
    661 		cs.addError(d.Pos(), "function must have a body")
    662 		return function{}, false
    663 	}
    664 
    665 	inParams, outParams := cs.parseFuncParams(block, d)
    666 
    667 	checkVaryings := func(vs []variable) {
    668 		if len(cs.ir.Varyings) != len(vs) {
    669 			cs.addError(d.Pos(), fmt.Sprintf("the number of vertex entry point's returning values and the number of framgent entry point's params must be the same"))
    670 			return
    671 		}
    672 		for i, t := range cs.ir.Varyings {
    673 			if t.Main != vs[i].typ.Main {
    674 				cs.addError(d.Pos(), fmt.Sprintf("vertex entry point's returning value types and framgent entry point's param types must match"))
    675 			}
    676 		}
    677 	}
    678 
    679 	if block == &cs.global {
    680 		switch d.Name.Name {
    681 		case cs.vertexEntry:
    682 			for _, v := range inParams {
    683 				cs.ir.Attributes = append(cs.ir.Attributes, v.typ)
    684 			}
    685 
    686 			// The first out-param is treated as gl_Position in GLSL.
    687 			if len(outParams) == 0 {
    688 				cs.addError(d.Pos(), fmt.Sprintf("vertex entry point must have at least one returning vec4 value for a position"))
    689 				return function{}, false
    690 			}
    691 			if outParams[0].typ.Main != shaderir.Vec4 {
    692 				cs.addError(d.Pos(), fmt.Sprintf("vertex entry point must have at least one returning vec4 value for a position"))
    693 				return function{}, false
    694 			}
    695 
    696 			if cs.varyingParsed {
    697 				checkVaryings(outParams[1:])
    698 			} else {
    699 				for _, v := range outParams[1:] {
    700 					// TODO: Check that these params are not arrays or structs
    701 					cs.ir.Varyings = append(cs.ir.Varyings, v.typ)
    702 				}
    703 			}
    704 			cs.varyingParsed = true
    705 		case cs.fragmentEntry:
    706 			if len(inParams) == 0 {
    707 				cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have at least one vec4 parameter for a position"))
    708 				return function{}, false
    709 			}
    710 			if inParams[0].typ.Main != shaderir.Vec4 {
    711 				cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have at least one vec4 parameter for a position"))
    712 				return function{}, false
    713 			}
    714 
    715 			if len(outParams) != 1 {
    716 				cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have one returning vec4 value for a color"))
    717 				return function{}, false
    718 			}
    719 			if outParams[0].typ.Main != shaderir.Vec4 {
    720 				cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have one returning vec4 value for a color"))
    721 				return function{}, false
    722 			}
    723 
    724 			if cs.varyingParsed {
    725 				checkVaryings(inParams[1:])
    726 			} else {
    727 				for _, v := range inParams[1:] {
    728 					cs.ir.Varyings = append(cs.ir.Varyings, v.typ)
    729 				}
    730 			}
    731 			cs.varyingParsed = true
    732 		}
    733 	}
    734 
    735 	b, ok := cs.parseBlock(block, d.Name.Name, d.Body.List, inParams, outParams, true)
    736 	if !ok {
    737 		return function{}, false
    738 	}
    739 
    740 	if len(outParams) > 0 {
    741 		var hasReturn func(stmts []shaderir.Stmt) bool
    742 		hasReturn = func(stmts []shaderir.Stmt) bool {
    743 			for _, stmt := range stmts {
    744 				if stmt.Type == shaderir.Return {
    745 					return true
    746 				}
    747 				for _, b := range stmt.Blocks {
    748 					if hasReturn(b.Stmts) {
    749 						return true
    750 					}
    751 				}
    752 			}
    753 			return false
    754 		}
    755 
    756 		if !hasReturn(b.ir.Stmts) {
    757 			cs.addError(d.Pos(), fmt.Sprintf("function %s must have a return statement but not", d.Name))
    758 			return function{}, false
    759 		}
    760 	}
    761 
    762 	var inT, outT []shaderir.Type
    763 	for _, v := range inParams {
    764 		inT = append(inT, v.typ)
    765 	}
    766 	for _, v := range outParams {
    767 		outT = append(outT, v.typ)
    768 	}
    769 
    770 	return function{
    771 		name:  d.Name.Name,
    772 		block: b,
    773 		ir: shaderir.Func{
    774 			InParams:  inT,
    775 			OutParams: outT,
    776 			Block:     b.ir,
    777 		},
    778 	}, true
    779 }
    780 
    781 func (cs *compileState) parseBlock(outer *block, fname string, stmts []ast.Stmt, inParams, outParams []variable, checkLocalVariableUsage bool) (*block, bool) {
    782 	var vars []variable
    783 	if outer == &cs.global {
    784 		vars = make([]variable, 0, len(inParams)+len(outParams))
    785 		vars = append(vars, inParams...)
    786 		vars = append(vars, outParams...)
    787 	}
    788 
    789 	var offset int
    790 	for b := outer; b != nil; b = b.outer {
    791 		offset += len(b.vars)
    792 	}
    793 	if outer == &cs.global {
    794 		offset += len(inParams) + len(outParams)
    795 	}
    796 
    797 	block := &block{
    798 		vars:  vars,
    799 		outer: outer,
    800 		ir: &shaderir.Block{
    801 			LocalVarIndexOffset: offset,
    802 		},
    803 	}
    804 
    805 	defer func() {
    806 		var offset int
    807 		if outer == &cs.global {
    808 			offset = len(inParams) + len(outParams)
    809 		}
    810 		for _, v := range block.vars[offset:] {
    811 			if v.forLoopCounter {
    812 				block.ir.LocalVars = append(block.ir.LocalVars, shaderir.Type{})
    813 				continue
    814 			}
    815 			block.ir.LocalVars = append(block.ir.LocalVars, v.typ)
    816 		}
    817 	}()
    818 
    819 	if outer.outer == nil && len(outParams) > 0 && outParams[0].name != "" {
    820 		for i := range outParams {
    821 			block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{
    822 				Type:      shaderir.Init,
    823 				InitIndex: len(inParams) + i,
    824 			})
    825 		}
    826 	}
    827 
    828 	for _, stmt := range stmts {
    829 		ss, ok := cs.parseStmt(block, fname, stmt, inParams, outParams)
    830 		if !ok {
    831 			return nil, false
    832 		}
    833 		block.ir.Stmts = append(block.ir.Stmts, ss...)
    834 	}
    835 
    836 	if checkLocalVariableUsage && len(block.unusedVars) > 0 {
    837 		for idx, pos := range block.unusedVars {
    838 			cs.addError(pos, fmt.Sprintf("local variable %s is not used", block.vars[idx].name))
    839 		}
    840 		return nil, false
    841 	}
    842 
    843 	return block, true
    844 }