2020-05-08 17:46:01 +02:00
// 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 shader
import (
"fmt"
"go/ast"
2021-04-08 16:55:35 +02:00
gconstant "go/constant"
2020-05-08 17:46:01 +02:00
"go/token"
"strings"
2020-10-03 19:35:13 +02:00
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
2020-05-08 17:46:01 +02:00
)
2020-05-08 21:21:45 +02:00
type variable struct {
2020-07-11 14:08:19 +02:00
name string
typ shaderir . Type
forLoopCounter bool
2020-05-09 17:39:26 +02:00
}
type constant struct {
2021-04-08 16:55:35 +02:00
name string
typ shaderir . Type
2021-04-08 17:32:15 +02:00
ctyp shaderir . ConstType
2021-04-08 16:55:35 +02:00
value gconstant . Value
2020-05-08 17:46:01 +02:00
}
2020-05-09 11:05:30 +02:00
type function struct {
2020-05-30 19:23:51 +02:00
name string
block * block
2020-05-31 11:01:12 +02:00
ir shaderir . Func
2020-05-09 11:05:30 +02:00
}
2020-05-30 10:03:43 +02:00
type compileState struct {
2020-05-09 09:47:07 +02:00
fs * token . FileSet
2020-06-04 18:11:39 +02:00
vertexEntry string
fragmentEntry string
2020-05-30 18:56:07 +02:00
ir shaderir . Program
2020-05-30 10:03:43 +02:00
2020-06-06 17:49:28 +02:00
funcs [ ] function
2020-05-09 17:59:18 +02:00
global block
2020-05-09 11:05:30 +02:00
2020-06-03 16:56:08 +02:00
varyingParsed bool
2020-05-08 17:46:01 +02:00
errs [ ] string
}
2020-06-06 17:49:28 +02:00
func ( cs * compileState ) findFunction ( name string ) ( int , bool ) {
for i , f := range cs . funcs {
if f . name == name {
return i , true
}
}
return 0 , false
}
2020-06-02 15:45:44 +02:00
func ( cs * compileState ) findUniformVariable ( name string ) ( int , bool ) {
2020-09-05 20:41:13 +02:00
for i , u := range cs . ir . UniformNames {
2020-06-02 15:45:44 +02:00
if u == name {
return i , true
}
}
return 0 , false
}
2020-06-07 12:36:38 +02:00
type typ struct {
name string
ir shaderir . Type
}
2020-05-30 19:23:51 +02:00
type block struct {
2020-09-13 13:13:30 +02:00
types [ ] typ
vars [ ] variable
unusedVars map [ int ] token . Pos
consts [ ] constant
pos token . Pos
outer * block
2020-05-31 11:01:12 +02:00
2020-08-09 08:55:59 +02:00
ir * shaderir . Block
2020-05-30 19:23:51 +02:00
}
2022-07-12 18:50:19 +02:00
func ( b * block ) totalLocalVariableCount ( ) int {
2020-08-09 16:08:24 +02:00
c := len ( b . vars )
if b . outer != nil {
2022-07-12 18:50:19 +02:00
c += b . outer . totalLocalVariableCount ( )
2020-08-09 16:08:24 +02:00
}
return c
}
2020-09-13 13:13:30 +02:00
func ( b * block ) addNamedLocalVariable ( name string , typ shaderir . Type , pos token . Pos ) {
b . vars = append ( b . vars , variable {
name : name ,
typ : typ ,
} )
if name == "_" {
return
}
idx := len ( b . vars ) - 1
if b . unusedVars == nil {
b . unusedVars = map [ int ] token . Pos { }
}
b . unusedVars [ idx ] = pos
}
func ( b * block ) findLocalVariable ( name string , markLocalVariableUsed bool ) ( int , shaderir . Type , bool ) {
2020-09-02 18:15:37 +02:00
if name == "" || name == "_" {
panic ( "shader: variable name must be non-empty and non-underscore" )
}
2020-05-31 16:57:03 +02:00
idx := 0
for outer := b . outer ; outer != nil ; outer = outer . outer {
idx += len ( outer . vars )
}
for i , v := range b . vars {
if v . name == name {
2020-09-13 13:13:30 +02:00
if markLocalVariableUsed {
delete ( b . unusedVars , i )
}
2020-06-18 19:37:03 +02:00
return idx + i , v . typ , true
2020-05-31 16:57:03 +02:00
}
}
if b . outer != nil {
2020-09-13 13:13:30 +02:00
return b . outer . findLocalVariable ( name , markLocalVariableUsed )
2020-05-31 16:57:03 +02:00
}
2020-06-18 19:37:03 +02:00
return 0 , shaderir . Type { } , false
2020-05-31 16:57:03 +02:00
}
2020-07-12 11:40:01 +02:00
func ( b * block ) findLocalVariableByIndex ( idx int ) ( shaderir . Type , bool ) {
bs := [ ] * block { b }
for outer := b . outer ; outer != nil ; outer = outer . outer {
bs = append ( bs , outer )
}
for i := len ( bs ) - 1 ; i >= 0 ; i -- {
if len ( bs [ i ] . vars ) <= idx {
idx -= len ( bs [ i ] . vars )
continue
}
return bs [ i ] . vars [ idx ] . typ , true
}
return shaderir . Type { } , false
}
2021-04-08 16:55:35 +02:00
func ( b * block ) findConstant ( name string ) ( constant , bool ) {
if name == "" || name == "_" {
panic ( "shader: constant name must be non-empty and non-underscore" )
}
for _ , c := range b . consts {
if c . name == name {
return c , true
}
}
if b . outer != nil {
return b . outer . findConstant ( name )
}
return constant { } , false
}
2020-05-08 17:46:01 +02:00
type ParseError struct {
errs [ ] string
}
func ( p * ParseError ) Error ( ) string {
return strings . Join ( p . errs , "\n" )
}
2020-07-05 20:36:15 +02:00
func Compile ( fs * token . FileSet , f * ast . File , vertexEntry , fragmentEntry string , textureNum int ) ( * shaderir . Program , error ) {
2020-05-30 10:03:43 +02:00
s := & compileState {
2020-06-04 18:11:39 +02:00
fs : fs ,
vertexEntry : vertexEntry ,
fragmentEntry : fragmentEntry ,
2020-05-09 09:47:07 +02:00
}
2020-08-09 08:55:59 +02:00
s . global . ir = & shaderir . Block { }
2020-05-08 17:46:01 +02:00
s . parse ( f )
if len ( s . errs ) > 0 {
return nil , & ParseError { s . errs }
}
2020-05-09 16:38:52 +02:00
// TODO: Resolve identifiers?
// TODO: Resolve constants
2020-05-08 17:46:01 +02:00
// TODO: Make a call graph and reorder the elements.
2020-06-23 18:41:27 +02:00
2020-07-05 20:36:15 +02:00
s . ir . TextureNum = textureNum
2020-05-30 18:56:07 +02:00
return & s . ir , nil
2020-05-08 17:46:01 +02:00
}
2020-05-30 10:03:43 +02:00
func ( s * compileState ) addError ( pos token . Pos , str string ) {
2020-05-09 09:47:07 +02:00
p := s . fs . Position ( pos )
s . errs = append ( s . errs , fmt . Sprintf ( "%s: %s" , p , str ) )
2020-05-08 20:38:37 +02:00
}
2020-05-30 10:03:43 +02:00
func ( cs * compileState ) parse ( f * ast . File ) {
2020-06-03 15:30:34 +02:00
// Parse GenDecl for global variables, and then parse functions.
2020-05-09 10:32:10 +02:00
for _ , d := range f . Decls {
2020-06-03 15:30:34 +02:00
if _ , ok := d . ( * ast . FuncDecl ) ; ! ok {
2020-07-12 08:51:06 +02:00
ss , ok := cs . parseDecl ( & cs . global , d )
if ! ok {
2020-06-20 19:18:21 +02:00
return
}
2020-07-12 08:51:06 +02:00
cs . global . ir . Stmts = append ( cs . global . ir . Stmts , ss ... )
2020-06-03 15:30:34 +02:00
}
}
2020-06-04 20:00:43 +02:00
// Sort the uniform variable so that special variable starting with __ should come first.
var unames [ ] string
var utypes [ ] shaderir . Type
2020-09-05 20:41:13 +02:00
for i , u := range cs . ir . UniformNames {
2020-06-04 20:00:43 +02:00
if strings . HasPrefix ( u , "__" ) {
unames = append ( unames , u )
utypes = append ( utypes , cs . ir . Uniforms [ i ] )
}
}
2020-07-18 12:56:22 +02:00
// TODO: Check len(unames) == graphics.PreservedUniformVariablesNum. Unfortunately this is not true on tests.
2020-09-05 20:41:13 +02:00
for i , u := range cs . ir . UniformNames {
2020-06-04 20:00:43 +02:00
if ! strings . HasPrefix ( u , "__" ) {
unames = append ( unames , u )
utypes = append ( utypes , cs . ir . Uniforms [ i ] )
}
}
2020-09-05 20:41:13 +02:00
cs . ir . UniformNames = unames
2020-06-04 20:00:43 +02:00
cs . ir . Uniforms = utypes
2020-06-06 17:49:28 +02:00
// Parse function names so that any other function call the others.
// The function data is provisional and will be updated soon.
for _ , d := range f . Decls {
fd , ok := d . ( * ast . FuncDecl )
if ! ok {
continue
}
n := fd . Name . Name
if n == cs . vertexEntry {
continue
}
if n == cs . fragmentEntry {
continue
}
2021-02-07 14:24:20 +01:00
for _ , f := range cs . funcs {
if f . name == n {
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "redeclared function: %s" , n ) )
return
}
}
2022-07-10 08:45:28 +02:00
inParams , outParams , ret := cs . parseFuncParams ( & cs . global , fd )
2020-06-06 17:49:28 +02:00
var inT , outT [ ] shaderir . Type
for _ , v := range inParams {
inT = append ( inT , v . typ )
}
for _ , v := range outParams {
outT = append ( outT , v . typ )
}
cs . funcs = append ( cs . funcs , function {
name : n ,
ir : shaderir . Func {
Index : len ( cs . funcs ) ,
InParams : inT ,
OutParams : outT ,
2022-07-10 08:45:28 +02:00
Return : ret ,
2020-08-09 08:55:59 +02:00
Block : & shaderir . Block { } ,
2020-06-06 17:49:28 +02:00
} ,
} )
}
2020-06-04 20:00:43 +02:00
// Parse functions.
2020-06-03 15:30:34 +02:00
for _ , d := range f . Decls {
if _ , ok := d . ( * ast . FuncDecl ) ; ok {
2020-07-12 08:51:06 +02:00
ss , ok := cs . parseDecl ( & cs . global , d )
if ! ok {
2020-06-20 19:18:21 +02:00
return
}
2020-07-12 08:51:06 +02:00
cs . global . ir . Stmts = append ( cs . global . ir . Stmts , ss ... )
2020-06-03 15:30:34 +02:00
}
2020-05-09 17:59:18 +02:00
}
2020-05-31 11:01:12 +02:00
if len ( cs . errs ) > 0 {
return
}
2020-06-06 17:49:28 +02:00
for _ , f := range cs . funcs {
2020-05-31 11:01:12 +02:00
cs . ir . Funcs = append ( cs . ir . Funcs , f . ir )
}
2020-05-09 17:59:18 +02:00
}
2020-07-12 08:51:06 +02:00
func ( cs * compileState ) parseDecl ( b * block , d ast . Decl ) ( [ ] shaderir . Stmt , bool ) {
var stmts [ ] shaderir . Stmt
2020-05-09 17:59:18 +02:00
switch d := d . ( type ) {
case * ast . GenDecl :
switch d . Tok {
case token . TYPE :
2020-05-10 16:19:39 +02:00
// TODO: Parse other types
2020-05-09 17:59:18 +02:00
for _ , s := range d . Specs {
s := s . ( * ast . TypeSpec )
2020-07-29 05:02:07 +02:00
t , ok := cs . parseType ( b , s . Type )
if ! ok {
return nil , false
}
2020-06-07 12:36:38 +02:00
b . types = append ( b . types , typ {
name : s . Name . Name ,
ir : t ,
} )
2020-05-08 20:38:37 +02:00
}
2020-05-09 17:59:18 +02:00
case token . CONST :
for _ , s := range d . Specs {
s := s . ( * ast . ValueSpec )
2021-04-08 16:55:35 +02:00
cs , ok := cs . parseConstant ( b , s )
if ! ok {
return nil , false
}
2020-05-09 17:59:18 +02:00
b . consts = append ( b . consts , cs ... )
}
case token . VAR :
for _ , s := range d . Specs {
s := s . ( * ast . ValueSpec )
2020-07-12 08:51:06 +02:00
vs , inits , ss , ok := cs . parseVariable ( b , s )
2020-06-20 19:18:21 +02:00
if ! ok {
2020-07-12 08:51:06 +02:00
return nil , false
2020-06-20 19:18:21 +02:00
}
2022-04-08 15:44:37 +02:00
2020-07-12 08:51:06 +02:00
stmts = append ( stmts , ss ... )
2020-05-31 16:57:03 +02:00
if b == & cs . global {
2020-06-07 09:21:02 +02:00
// TODO: Should rhs be ignored?
2020-05-31 16:57:03 +02:00
for i , v := range vs {
2020-06-04 20:00:43 +02:00
if ! strings . HasPrefix ( v . name , "__" ) {
if v . name [ 0 ] < 'A' || 'Z' < v . name [ 0 ] {
cs . addError ( s . Names [ i ] . Pos ( ) , fmt . Sprintf ( "global variables must be exposed: %s" , v . name ) )
}
2020-05-31 16:57:03 +02:00
}
2020-09-05 20:41:13 +02:00
cs . ir . UniformNames = append ( cs . ir . UniformNames , v . name )
2020-06-07 12:36:38 +02:00
cs . ir . Uniforms = append ( cs . ir . Uniforms , v . typ )
2020-05-31 16:57:03 +02:00
}
2020-05-10 14:57:12 +02:00
continue
}
2020-06-07 16:23:58 +02:00
2020-09-17 10:29:31 +02:00
// base must be obtained before adding the variables.
2022-07-12 18:50:19 +02:00
base := b . totalLocalVariableCount ( )
2020-09-17 10:29:31 +02:00
for _ , v := range vs {
b . addNamedLocalVariable ( v . name , v . typ , d . Pos ( ) )
}
2020-09-17 10:32:07 +02:00
2020-06-07 16:23:58 +02:00
if len ( inits ) > 0 {
for i := range vs {
2020-07-12 08:51:06 +02:00
stmts = append ( stmts , shaderir . Stmt {
2020-06-07 09:21:02 +02:00
Type : shaderir . Assign ,
Exprs : [ ] shaderir . Expr {
{
Type : shaderir . LocalVariable ,
2020-06-07 16:23:58 +02:00
Index : base + i ,
2020-06-07 09:21:02 +02:00
} ,
2020-06-07 16:23:58 +02:00
inits [ i ] ,
2020-06-07 09:21:02 +02:00
} ,
} )
}
2020-05-10 14:57:12 +02:00
}
2020-05-09 17:59:18 +02:00
}
case token . IMPORT :
2020-05-30 10:03:43 +02:00
cs . addError ( d . Pos ( ) , "import is forbidden" )
2020-05-09 10:32:10 +02:00
default :
2020-05-30 10:03:43 +02:00
cs . addError ( d . Pos ( ) , "unexpected token" )
2020-05-08 17:46:01 +02:00
}
2020-05-09 17:59:18 +02:00
case * ast . FuncDecl :
2020-06-20 19:18:21 +02:00
f , ok := cs . parseFunc ( b , d )
if ! ok {
2020-07-12 08:51:06 +02:00
return nil , false
2020-06-20 19:18:21 +02:00
}
2020-06-06 17:49:28 +02:00
if b != & cs . global {
cs . addError ( d . Pos ( ) , "non-global function is not implemented" )
2020-07-12 08:51:06 +02:00
return nil , false
2020-06-06 17:49:28 +02:00
}
switch d . Name . Name {
case cs . vertexEntry :
cs . ir . VertexFunc . Block = f . ir . Block
case cs . fragmentEntry :
cs . ir . FragmentFunc . Block = f . ir . Block
default :
// The function is already registered for their names.
for i := range cs . funcs {
if cs . funcs [ i ] . name == d . Name . Name {
// Index is already determined by the provisional parsing.
f . ir . Index = cs . funcs [ i ] . ir . Index
cs . funcs [ i ] = f
break
}
2020-06-03 16:56:08 +02:00
}
2020-06-02 15:45:44 +02:00
}
2020-05-09 17:59:18 +02:00
default :
2020-05-30 10:03:43 +02:00
cs . addError ( d . Pos ( ) , "unexpected decl" )
2020-07-12 08:51:06 +02:00
return nil , false
2020-05-08 17:46:01 +02:00
}
2020-06-20 19:18:21 +02:00
2020-07-12 08:51:06 +02:00
return stmts , true
2020-05-08 17:46:01 +02:00
}
2020-06-19 17:20:17 +02:00
// functionReturnTypes returns the original returning value types, if the given expression is call.
//
// Note that parseExpr returns the returning types for IR, not the original function.
func ( cs * compileState ) functionReturnTypes ( block * block , expr ast . Expr ) ( [ ] shaderir . Type , bool ) {
call , ok := expr . ( * ast . CallExpr )
if ! ok {
return nil , false
}
ident , ok := call . Fun . ( * ast . Ident )
if ! ok {
return nil , false
}
for _ , f := range cs . funcs {
if f . name == ident . Name {
// TODO: Is it correct to combine out-params and return param?
ts := f . ir . OutParams
if f . ir . Return . Main != shaderir . None {
ts = append ( ts , f . ir . Return )
}
return ts , true
}
}
return nil , false
}
2020-06-20 19:18:21 +02:00
func ( s * compileState ) parseVariable ( block * block , vs * ast . ValueSpec ) ( [ ] variable , [ ] shaderir . Expr , [ ] shaderir . Stmt , bool ) {
2020-06-07 16:23:58 +02:00
if len ( vs . Names ) != len ( vs . Values ) && len ( vs . Values ) != 1 && len ( vs . Values ) != 0 {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "the numbers of lhs and rhs don't match" ) )
2020-06-20 19:18:21 +02:00
return nil , nil , nil , false
2020-06-07 16:23:58 +02:00
}
2020-06-19 17:20:17 +02:00
var declt shaderir . Type
2020-05-09 16:38:52 +02:00
if vs . Type != nil {
2020-07-29 05:02:07 +02:00
var ok bool
declt , ok = s . parseType ( block , vs . Type )
if ! ok {
return nil , nil , nil , false
}
2020-05-08 20:38:37 +02:00
}
2020-05-09 16:38:52 +02:00
2020-06-07 16:23:58 +02:00
var (
vars [ ] variable
inits [ ] shaderir . Expr
stmts [ ] shaderir . Stmt
)
2020-06-21 09:25:28 +02:00
// These variables are used only in multiple-value context.
var inittypes [ ] shaderir . Type
var initexprs [ ] shaderir . Expr
2020-06-19 17:20:17 +02:00
2020-06-21 09:25:28 +02:00
for i , n := range vs . Names {
2020-06-19 17:20:17 +02:00
t := declt
2020-06-21 09:25:28 +02:00
switch {
case len ( vs . Values ) == 0 :
// No initialization
case len ( vs . Names ) == len ( vs . Values ) :
// Single-value context
init := vs . Values [ i ]
2022-04-08 15:44:37 +02:00
es , rts , ss , ok := s . parseExpr ( block , init , true )
2020-06-21 09:25:28 +02:00
if ! ok {
return nil , nil , nil , false
}
2020-06-07 16:23:58 +02:00
if t . Main == shaderir . None {
2020-06-19 17:20:17 +02:00
ts , ok := s . functionReturnTypes ( block , init )
if ! ok {
2022-04-08 15:44:37 +02:00
ts = rts
2020-06-19 17:20:17 +02:00
}
2020-06-21 09:25:28 +02:00
if len ( ts ) > 1 {
2020-06-07 16:23:58 +02:00
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "the numbers of lhs and rhs don't match" ) )
}
2020-06-21 09:25:28 +02:00
t = ts [ 0 ]
2020-06-07 16:23:58 +02:00
}
2020-06-21 09:25:28 +02:00
2020-06-21 13:25:46 +02:00
if es [ 0 ] . Type == shaderir . NumberExpr {
switch t . Main {
case shaderir . Int :
es [ 0 ] . ConstType = shaderir . ConstTypeInt
case shaderir . Float :
es [ 0 ] . ConstType = shaderir . ConstTypeFloat
}
}
2022-04-08 15:44:37 +02:00
for i , rt := range rts {
if ! canAssign ( & es [ i ] , & t , & rt ) {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "cannot use type %s as type %s in variable declaration" , rt . String ( ) , t . String ( ) ) )
}
}
2020-06-21 13:25:46 +02:00
inits = append ( inits , es ... )
stmts = append ( stmts , ss ... )
2020-06-07 16:23:58 +02:00
default :
2020-06-21 09:25:28 +02:00
// Multiple-value context
if i == 0 {
init := vs . Values [ 0 ]
var ss [ ] shaderir . Stmt
var ok bool
2020-09-13 13:13:30 +02:00
initexprs , inittypes , ss , ok = s . parseExpr ( block , init , true )
2020-06-19 17:20:17 +02:00
if ! ok {
2020-06-21 09:25:28 +02:00
return nil , nil , nil , false
2020-06-19 17:20:17 +02:00
}
2020-06-21 09:25:28 +02:00
stmts = append ( stmts , ss ... )
if t . Main == shaderir . None {
ts , ok := s . functionReturnTypes ( block , init )
if ok {
inittypes = ts
}
if len ( ts ) != len ( vs . Names ) {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "the numbers of lhs and rhs don't match" ) )
continue
}
2020-06-07 16:32:50 +02:00
}
2020-05-10 12:32:40 +02:00
}
2022-04-08 15:44:37 +02:00
if t . Main == shaderir . None && len ( inittypes ) > 0 {
2020-06-21 09:25:28 +02:00
t = inittypes [ i ]
}
2022-04-08 15:44:37 +02:00
if ! canAssign ( & initexprs [ i ] , & t , & inittypes [ i ] ) {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "cannot use type %s as type %s in variable declaration" , inittypes [ i ] . String ( ) , t . String ( ) ) )
}
2020-06-21 09:25:28 +02:00
// Add the same initexprs for each variable.
inits = append ( inits , initexprs ... )
2020-05-09 19:01:28 +02:00
}
2020-06-07 16:23:58 +02:00
2020-05-09 10:32:10 +02:00
name := n . Name
2020-08-30 14:11:27 +02:00
for _ , v := range append ( block . vars , vars ... ) {
if v . name == name {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "duplicated local variable name: %s" , name ) )
return nil , nil , nil , false
}
}
2021-04-08 17:12:14 +02:00
for _ , c := range block . consts {
if c . name == name {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "duplicated local constant/variable name: %s" , name ) )
return nil , nil , nil , false
}
}
2020-05-09 16:38:52 +02:00
vars = append ( vars , variable {
2020-05-10 17:13:08 +02:00
name : name ,
typ : t ,
2020-05-09 16:38:52 +02:00
} )
2020-06-07 16:23:58 +02:00
}
2020-06-20 19:18:21 +02:00
return vars , inits , stmts , true
2020-05-08 21:21:45 +02:00
}
2021-04-08 16:55:35 +02:00
func ( s * compileState ) parseConstant ( block * block , vs * ast . ValueSpec ) ( [ ] constant , bool ) {
2020-06-07 12:36:38 +02:00
var t shaderir . Type
2020-05-09 17:39:26 +02:00
if vs . Type != nil {
2020-07-29 05:02:07 +02:00
var ok bool
t , ok = s . parseType ( block , vs . Type )
if ! ok {
2021-04-08 16:55:35 +02:00
return nil , false
2020-07-29 05:02:07 +02:00
}
2020-05-08 21:21:45 +02:00
}
2020-05-09 17:39:26 +02:00
var cs [ ] constant
2020-05-09 10:32:10 +02:00
for i , n := range vs . Names {
2021-04-08 17:12:14 +02:00
name := n . Name
for _ , c := range block . consts {
if c . name == name {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "duplicated local constant name: %s" , name ) )
return nil , false
}
}
for _ , v := range block . vars {
if v . name == name {
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "duplicated local constant/variable name: %s" , name ) )
return nil , false
}
}
2021-04-08 17:32:15 +02:00
es , ts , ss , ok := s . parseExpr ( block , vs . Values [ i ] , false )
2021-04-08 16:55:35 +02:00
if ! ok {
return nil , false
}
if len ( ss ) > 0 {
2021-04-08 17:12:14 +02:00
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "invalid constant expression: %s" , name ) )
2021-04-08 16:55:35 +02:00
return nil , false
}
2021-04-08 17:32:15 +02:00
if len ( ts ) != 1 || len ( es ) != 1 {
2021-04-08 16:55:35 +02:00
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "invalid constant expression: %s" , n ) )
return nil , false
}
if es [ 0 ] . Type != shaderir . NumberExpr {
2022-03-09 18:54:10 +01:00
s . addError ( vs . Pos ( ) , fmt . Sprintf ( "constant expression must be a number but not: %s" , n ) )
2021-04-08 16:55:35 +02:00
return nil , false
}
2020-05-09 17:39:26 +02:00
cs = append ( cs , constant {
2021-04-08 17:12:14 +02:00
name : name ,
2021-04-08 16:55:35 +02:00
typ : t ,
2021-04-08 17:32:15 +02:00
ctyp : es [ 0 ] . ConstType ,
2021-04-08 16:55:35 +02:00
value : es [ 0 ] . Const ,
2020-05-09 17:39:26 +02:00
} )
2020-05-08 20:38:37 +02:00
}
2021-04-08 16:55:35 +02:00
return cs , true
2020-05-08 17:46:01 +02:00
}
2022-07-10 08:45:28 +02:00
func ( cs * compileState ) parseFuncParams ( block * block , d * ast . FuncDecl ) ( in , out [ ] variable , ret shaderir . Type ) {
2020-05-09 12:21:01 +02:00
for _ , f := range d . Type . Params . List {
2020-07-29 05:02:07 +02:00
t , ok := cs . parseType ( block , f . Type )
if ! ok {
return
}
2020-05-09 12:21:01 +02:00
for _ , n := range f . Names {
2020-06-06 17:49:28 +02:00
in = append ( in , variable {
2020-05-31 11:11:55 +02:00
name : n . Name ,
typ : t ,
} )
2020-05-09 12:21:01 +02:00
}
}
2020-06-06 17:49:28 +02:00
if d . Type . Results == nil {
return
}
2020-06-02 15:45:44 +02:00
2020-06-06 17:49:28 +02:00
for _ , f := range d . Type . Results . List {
2020-07-29 05:02:07 +02:00
t , ok := cs . parseType ( block , f . Type )
if ! ok {
return
}
2020-06-06 17:49:28 +02:00
if len ( f . Names ) == 0 {
out = append ( out , variable {
name : "" ,
typ : t ,
} )
} else {
for _ , n := range f . Names {
out = append ( out , variable {
name : n . Name ,
2020-05-31 11:11:55 +02:00
typ : t ,
} )
2020-05-09 12:21:01 +02:00
}
}
}
2022-07-10 08:45:28 +02:00
if len ( out ) == 1 && out [ 0 ] . name == "" {
ret = out [ 0 ] . typ
out = nil
}
2020-06-06 17:49:28 +02:00
return
}
2020-06-20 19:18:21 +02:00
func ( cs * compileState ) parseFunc ( block * block , d * ast . FuncDecl ) ( function , bool ) {
2020-06-06 17:49:28 +02:00
if d . Name == nil {
cs . addError ( d . Pos ( ) , "function must have a name" )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-06 17:49:28 +02:00
}
2020-09-12 12:50:20 +02:00
if d . Name . Name == "init" {
cs . addError ( d . Pos ( ) , "init function is not implemented" )
return function { } , false
}
2020-06-06 17:49:28 +02:00
if d . Body == nil {
cs . addError ( d . Pos ( ) , "function must have a body" )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-06 17:49:28 +02:00
}
2020-05-09 12:21:01 +02:00
2022-07-10 08:45:28 +02:00
inParams , outParams , returnType := cs . parseFuncParams ( block , d )
2020-06-06 17:49:28 +02:00
checkVaryings := func ( vs [ ] variable ) {
if len ( cs . ir . Varyings ) != len ( vs ) {
2022-08-03 15:40:39 +02:00
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "the number of vertex entry point's returning values and the number of fragment entry point's params must be the same" ) )
2020-06-03 16:56:08 +02:00
return
2020-06-02 15:45:44 +02:00
}
2020-06-03 16:56:08 +02:00
for i , t := range cs . ir . Varyings {
2020-06-06 17:49:28 +02:00
if t . Main != vs [ i ] . typ . Main {
2022-08-03 15:40:39 +02:00
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "vertex entry point's returning value types and fragment entry point's param types must match" ) )
2020-06-03 16:56:08 +02:00
}
2020-06-02 15:45:44 +02:00
}
2020-06-03 16:56:08 +02:00
}
2020-06-02 15:45:44 +02:00
2020-06-03 16:56:08 +02:00
if block == & cs . global {
switch d . Name . Name {
2020-06-04 18:11:39 +02:00
case cs . vertexEntry :
2020-06-06 17:49:28 +02:00
for _ , v := range inParams {
cs . ir . Attributes = append ( cs . ir . Attributes , v . typ )
2020-06-03 16:56:08 +02:00
}
2020-06-02 15:45:44 +02:00
2022-07-10 08:45:28 +02:00
// For the vertex entry, a parameter (variable) is used as a returning value.
// For example, GLSL doesn't treat gl_Position as a returning value.
2020-06-03 16:56:08 +02:00
if len ( outParams ) == 0 {
2022-07-10 08:45:28 +02:00
outParams = append ( outParams , variable {
typ : shaderir . Type { Main : shaderir . Vec4 } ,
} )
2020-06-03 16:56:08 +02:00
}
2022-07-10 08:45:28 +02:00
// The first out-param is treated as gl_Position in GLSL.
2020-06-06 17:49:28 +02:00
if outParams [ 0 ] . typ . Main != shaderir . Vec4 {
2020-06-03 16:56:08 +02:00
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "vertex entry point must have at least one returning vec4 value for a position" ) )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-03 16:56:08 +02:00
}
if cs . varyingParsed {
2020-06-06 17:49:28 +02:00
checkVaryings ( outParams [ 1 : ] )
2020-06-03 16:56:08 +02:00
} else {
2020-06-06 17:49:28 +02:00
for _ , v := range outParams [ 1 : ] {
2020-06-03 16:56:08 +02:00
// TODO: Check that these params are not arrays or structs
2020-06-06 17:49:28 +02:00
cs . ir . Varyings = append ( cs . ir . Varyings , v . typ )
2020-06-03 16:56:08 +02:00
}
}
cs . varyingParsed = true
2020-06-04 18:11:39 +02:00
case cs . fragmentEntry :
2020-06-03 16:56:08 +02:00
if len ( inParams ) == 0 {
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "fragment entry point must have at least one vec4 parameter for a position" ) )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-03 16:56:08 +02:00
}
2020-06-06 17:49:28 +02:00
if inParams [ 0 ] . typ . Main != shaderir . Vec4 {
2020-06-03 16:56:08 +02:00
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "fragment entry point must have at least one vec4 parameter for a position" ) )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-03 16:56:08 +02:00
}
2022-07-10 08:45:28 +02:00
// For the fragment entry, a parameter (variable) is used as a returning value.
// For example, GLSL doesn't treat gl_FragColor as a returning value.
if len ( outParams ) == 0 {
outParams = append ( outParams , variable {
typ : shaderir . Type { Main : shaderir . Vec4 } ,
} )
2020-06-03 16:56:08 +02:00
}
2022-07-10 08:45:28 +02:00
if len ( outParams ) != 1 || outParams [ 0 ] . typ . Main != shaderir . Vec4 {
2020-06-03 16:57:45 +02:00
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "fragment entry point must have one returning vec4 value for a color" ) )
2020-06-20 19:18:21 +02:00
return function { } , false
2020-06-03 16:56:08 +02:00
}
if cs . varyingParsed {
2020-06-06 17:49:28 +02:00
checkVaryings ( inParams [ 1 : ] )
2020-06-03 16:56:08 +02:00
} else {
2020-06-06 17:49:28 +02:00
for _ , v := range inParams [ 1 : ] {
cs . ir . Varyings = append ( cs . ir . Varyings , v . typ )
2020-06-03 16:56:08 +02:00
}
}
cs . varyingParsed = true
2020-06-02 15:45:44 +02:00
}
}
2022-07-10 08:45:28 +02:00
b , ok := cs . parseBlock ( block , d . Name . Name , d . Body . List , inParams , outParams , returnType , true )
2020-06-20 19:18:21 +02:00
if ! ok {
return function { } , false
}
2020-05-31 09:20:36 +02:00
2022-07-10 08:45:28 +02:00
if len ( outParams ) > 0 || returnType . Main != shaderir . None {
2020-09-06 15:33:19 +02:00
var hasReturn func ( stmts [ ] shaderir . Stmt ) bool
hasReturn = func ( stmts [ ] shaderir . Stmt ) bool {
for _ , stmt := range stmts {
if stmt . Type == shaderir . Return {
return true
}
for _ , b := range stmt . Blocks {
if hasReturn ( b . Stmts ) {
return true
}
}
}
return false
}
if ! hasReturn ( b . ir . Stmts ) {
cs . addError ( d . Pos ( ) , fmt . Sprintf ( "function %s must have a return statement but not" , d . Name ) )
return function { } , false
}
}
2020-06-06 17:49:28 +02:00
var inT , outT [ ] shaderir . Type
for _ , v := range inParams {
inT = append ( inT , v . typ )
}
for _ , v := range outParams {
outT = append ( outT , v . typ )
}
2020-05-09 17:59:18 +02:00
return function {
2020-05-31 11:01:12 +02:00
name : d . Name . Name ,
block : b ,
ir : shaderir . Func {
InParams : inT ,
OutParams : outT ,
2022-07-10 08:45:28 +02:00
Return : returnType ,
2020-05-31 11:01:12 +02:00
Block : b . ir ,
} ,
2020-06-20 19:18:21 +02:00
} , true
2020-05-09 11:05:30 +02:00
}
2022-07-10 08:45:28 +02:00
func ( cs * compileState ) parseBlock ( outer * block , fname string , stmts [ ] ast . Stmt , inParams , outParams [ ] variable , returnType shaderir . Type , checkLocalVariableUsage bool ) ( * block , bool ) {
2020-07-04 21:30:14 +02:00
var vars [ ] variable
if outer == & cs . global {
vars = make ( [ ] variable , 0 , len ( inParams ) + len ( outParams ) )
vars = append ( vars , inParams ... )
vars = append ( vars , outParams ... )
}
2020-08-09 16:40:41 +02:00
var offset int
2020-09-07 19:42:31 +02:00
for b := outer ; b != nil ; b = b . outer {
offset += len ( b . vars )
}
if outer == & cs . global {
offset += len ( inParams ) + len ( outParams )
2020-08-09 16:40:41 +02:00
}
2020-05-10 12:32:40 +02:00
block := & block {
2020-05-31 12:20:53 +02:00
vars : vars ,
2020-05-10 12:32:40 +02:00
outer : outer ,
2020-08-09 16:40:41 +02:00
ir : & shaderir . Block {
LocalVarIndexOffset : offset ,
} ,
2020-05-10 12:32:40 +02:00
}
2020-08-09 08:55:59 +02:00
2020-06-07 16:50:53 +02:00
defer func ( ) {
2020-07-04 21:30:14 +02:00
var offset int
if outer == & cs . global {
offset = len ( inParams ) + len ( outParams )
}
for _ , v := range block . vars [ offset : ] {
2020-07-11 14:08:19 +02:00
if v . forLoopCounter {
block . ir . LocalVars = append ( block . ir . LocalVars , shaderir . Type { } )
continue
}
2020-06-07 16:50:53 +02:00
block . ir . LocalVars = append ( block . ir . LocalVars , v . typ )
}
} ( )
2020-05-09 16:38:52 +02:00
2020-09-06 17:13:46 +02:00
if outer . outer == nil && len ( outParams ) > 0 && outParams [ 0 ] . name != "" {
for i := range outParams {
block . ir . Stmts = append ( block . ir . Stmts , shaderir . Stmt {
Type : shaderir . Init ,
InitIndex : len ( inParams ) + i ,
} )
}
}
2020-07-04 15:49:29 +02:00
for _ , stmt := range stmts {
2022-07-10 08:45:28 +02:00
ss , ok := cs . parseStmt ( block , fname , stmt , inParams , outParams , returnType )
2020-07-04 17:03:21 +02:00
if ! ok {
2020-06-20 19:18:21 +02:00
return nil , false
2020-05-09 16:38:52 +02:00
}
2020-07-04 15:49:29 +02:00
block . ir . Stmts = append ( block . ir . Stmts , ss ... )
2020-05-09 16:38:52 +02:00
}
2020-09-13 13:13:30 +02:00
if checkLocalVariableUsage && len ( block . unusedVars ) > 0 {
for idx , pos := range block . unusedVars {
cs . addError ( pos , fmt . Sprintf ( "local variable %s is not used" , block . vars [ idx ] . name ) )
}
return nil , false
}
2020-06-20 19:18:21 +02:00
return block , true
2020-05-09 16:38:52 +02:00
}