1
#-------------------------------------------------------------------------
2
# The actual shader compiler code
4
# Current code is too complex, should be split into
5
# separate 'simplify' and 'optimize' procedures.
6
# Current error checking can be misleading if the code uses include files
7
# or macros as far as the line number is concerned, since it actually
8
# relates to the preprocessed sourcecode file.
9
# Must be adapted to make use of the preprocessor #line declarations, TODO
10
# Of course if there are no include files, then it usually is ok.
11
# There is lots of ugly patch work and special case handling, as usual...
12
# Especially the handling of user-defined functions is a real mess.
13
# It should all be redone anyway, this is just a prototype after all, TODO
14
# (or not, all this is probably completely useless to blender...
15
# at least, in its current form anyway)
16
#-------------------------------------------------------------------------
18
#-------------------------------------
19
# The following AST nodes are defined:
20
#-------------------------------------
21
# 1: astProgram_t(definitions)
22
# 3: astFormals_t(formals)
23
# 4: astShaderDef_t(shadertype, id, formals, body)
24
# 5: astFunctionDef_t(returntype, id, formals, body, lexfunc)
25
# 6: astNewScope_t(stmts)
26
# 7: astNumConst_t(val)
27
# 8: astStringConst_t(val)
28
# 9: astVecConst_t(val)
29
# 10: astMtxConst_t(val)
30
# 11: astIfElse_t(rel, if, else, iscond)
31
# 12: astExprList_t(expr_list, expr)
32
# 13: astDefExpression_t(id, array_length, def_init)
33
# 15: astParameter_t(output, typespec, exprs)
34
# 16: astVariable_t(extern, typespec, exprs)
35
# 17: astTuple_t(values)
36
# 18: astStatements_t(statements)
37
# 19: astReturn_t(returnval)
38
# 20: astBreak_t(level)
39
# 21: astContinue_t(level)
40
# 22: astFor_t(init_expr, rel, inc_expr, stmt)
41
# 23: astSolar_t(axis, angle, stmt)
42
# 24: astIlluminate_t(pos, axis, angle, stmt)
43
# 25: astIlluminance_t(cat, pos, axis, angle, stmt)
44
# 26: astID_t(name, array_idx)
45
# 27: astUnaryExpr_t(typecast, expr)
46
# 28: astBinop_t(expr1, op, expr2)
47
# 29: astAssignExpr_t(lhs, asgnop, assign_expr)
48
# 30: astRelation_t(expr1, relop, expr2)
49
# 31: astProcedureCall_t(name, args)
50
# 33: astTexture_t(tex_type, texfilechan, tex_args)
51
# 34: astTexFileChan_t(id, expr, array_idx)
52
# 35: astTexArgs_t(args)
53
# 36: astFilterstep_t(edge, s1, s2, paramlist)
54
# 37: astRotate_t(arg1, arg2, arg3)
55
# 38: astFresnel_t(I, N, eta, Kr, Kt, R, T)
56
#----------------------------------------------
57
# NOTE: All the above nodes also have an extra
58
# 'lineno' argument which holds the current
59
# line number in the source code.
60
#----------------------------------------------
64
class astnode_t(object):
65
def visit(self, visitor):
66
raise Exception('visit() not defined in ' + type(self).__name__)
68
# NOTE: 'children' is solely used to pretty print the AST
69
# (not strictly necessary even for that purpose, since could just use vars() or node.__dict__ directly,
70
# but since it returns a dictionary, results are unordered)
71
# 'lineno' is the line number corresponding to the code line in the sourcecode, used for error reports.
72
class astProgram_t(astnode_t):
73
def __init__(self, definitions, lineno):
74
self.definitions = definitions
76
self.children = definitions
77
def visit(self, visitor):
78
visitor.visitProgram(self)
80
class astFormals_t(astnode_t):
81
def __init__(self, formals, lineno):
82
self.formals = formals
84
self.children = [formals]
85
def visit(self, visitor):
86
visitor.visitFormals(self)
88
class astShaderDef_t(astnode_t):
89
def __init__(self, shadertype, _id, formals, body, lineno):
90
self.shadertype = shadertype
92
self.formals = formals
95
self.children = [shadertype, _id, formals, body]
96
def visit(self, visitor):
97
visitor.visitShaderDef(self)
99
class astFunctionDef_t(astnode_t):
100
def __init__(self, returntype, _id, formals, body, lexfunc, lineno):
101
self.returntype = returntype
103
self.formals = formals
105
self.lexfunc = lexfunc
107
self.children = [returntype, _id, formals, body, lexfunc]
108
def visit(self, visitor):
109
visitor.visitFunctionDef(self)
111
# only needed to keep track of scope changes
112
class astNewScope_t(astnode_t):
113
def __init__(self, statements, lineno):
114
self.statements = statements
116
self.children = statements
117
def visit(self, visitor):
118
visitor.visitNewScope(self)
120
# used for both floats and integers, val arg must be cast to the correct type
121
class astNumConst_t(astnode_t):
122
def __init__(self, val, lineno):
126
def visit(self, visitor):
127
visitor.visitNumConst(self)
129
class astStringConst_t(astnode_t):
130
def __init__(self, val, lineno):
134
def visit(self, visitor):
135
visitor.visitStringConst(self)
137
class astVecConst_t(astnode_t):
138
def __init__(self, val, lineno):
142
def visit(self, visitor):
143
visitor.visitVecConst(self)
145
class astMtxConst_t(astnode_t):
146
def __init__(self, val, lineno):
150
def visit(self, visitor):
151
visitor.visitMtxConst(self)
153
# 'iscond' designates the conditional operator
154
class astIfElse_t(astnode_t):
155
def __init__(self, rel, _if, _else, iscond, lineno):
161
self.children = [rel, _if, _else, iscond]
162
def visit(self, visitor):
163
visitor.visitIfElse(self)
165
class astExprList_t(astnode_t):
166
def __init__(self, expr_list, expr, lineno):
167
self.expr_list = expr_list
170
self.children = [expr_list, expr]
171
def visit(self, visitor):
172
visitor.visitExprList(self)
174
class astDefExpression_t(astnode_t):
175
def __init__(self, _id, array_length, def_init, lineno):
177
if array_length == None:
178
self.array_length = 0
180
self.array_length = array_length
181
self.def_init = def_init
182
# flag that indicates that this default expression is either part of a shader or function parameter declaration
185
self.children = [_id, array_length, def_init]
186
def visit(self, visitor):
187
visitor.visitDefExpression(self)
189
class astDefExprList_t(astnode_t):
190
def __init__(self, def_exprlist, lineno):
191
self.def_exprlist = def_exprlist
193
self.children = [def_exprlist]
194
def visit(self, visitor):
195
visitor.visitDefExprList(self)
197
class astParameter_t(astnode_t):
198
def __init__(self, output, typespec, exprs, lineno):
200
self.typespec = typespec
203
self.children = [output, typespec, exprs]
204
def visit(self, visitor):
205
visitor.visitParameter(self)
207
class astVariable_t(astnode_t):
208
def __init__(self, extern, typespec, expr, lineno):
210
self.typespec = typespec
213
self.children = [extern, typespec, expr]
214
def visit(self, visitor):
215
visitor.visitVariable(self)
217
class astTuple_t(astnode_t):
218
def __init__(self, values, lineno):
221
self.children = values # already list
222
def visit(self, visitor):
223
visitor.visitTuple(self)
225
class astStatements_t(astnode_t):
226
def __init__(self, statements, lineno):
227
self.statements = statements
229
self.children = [statements]
230
def visit(self, visitor):
231
visitor.visitStatements(self)
233
class astReturn_t(astnode_t):
234
def __init__(self, returnval, lineno):
235
self.returnval = returnval
237
self.children = returnval
238
def visit(self, visitor):
239
visitor.visitReturn(self)
241
class astBreak_t(astnode_t):
242
def __init__(self, level, lineno):
245
self.children = level
246
def visit(self, visitor):
247
visitor.visitBreak(self)
249
class astContinue_t(astnode_t):
250
def __init__(self, level, lineno):
253
self.children = level
254
def visit(self, visitor):
255
visitor.visitContinue(self)
257
class astFor_t(astnode_t):
258
def __init__(self, init_expr, rel, inc_expr, stmt, lineno):
259
self.init_expr = init_expr
261
self.inc_expr = inc_expr
264
self.children = [init_expr, rel, inc_expr, stmt]
265
def visit(self, visitor):
266
visitor.visitFor(self)
268
class astSolar_t(astnode_t):
269
def __init__(self, axis, angle, stmt, lineno):
274
self.children = [axis, angle, stmt]
275
def visit(self, visitor):
276
visitor.visitSolar(self)
278
class astIlluminate_t(astnode_t):
279
def __init__(self, pos, axis, angle, stmt, lineno):
285
self.children = [pos, axis, angle, stmt]
286
def visit(self, visitor):
287
visitor.visitIlluminate(self)
289
class astIlluminance_t(astnode_t):
290
def __init__(self, cat, pos, axis, angle, stmt, lineno):
297
self.children = [cat, pos, axis, angle, stmt]
298
def visit(self, visitor):
299
visitor.visitIlluminance(self)
301
class astID_t(astnode_t):
302
def __init__(self, name, array_idx, lineno):
304
self.array_idx = array_idx
306
self.children = [name, array_idx]
307
def visit(self, visitor):
308
visitor.visitID(self)
310
class astUnaryExpr_t(astnode_t):
311
def __init__(self, typecast, expr, lineno):
312
self.typecast = typecast
315
self.children = [typecast, expr]
316
def visit(self, visitor):
317
visitor.visitUnaryExpr(self)
319
class astBinop_t(astnode_t):
320
def __init__(self, expr1, op, expr2, lineno):
325
self.children = [expr1, op, expr2]
326
def visit(self, visitor):
327
visitor.visitBinop(self)
329
class astAssignExpr_t(astnode_t):
330
def __init__(self, lhs, asgnop, assign_expr, lineno):
333
self.assign_expr = assign_expr
335
self.children = [lhs, asgnop, assign_expr]
336
def visit(self, visitor):
337
visitor.visitAssignExpr(self)
339
class astRelation_t(astnode_t):
340
def __init__(self, expr1, relop, expr2, lineno):
345
self.children = [expr1, relop, expr2]
346
def visit(self, visitor):
347
visitor.visitRelation(self)
349
class astProcedureCall_t(astnode_t):
350
def __init__(self, name, args, lineno):
354
self.children = [name, args]
355
def visit(self, visitor):
356
visitor.visitProcedureCall(self)
358
class astTexture_t(astnode_t):
359
def __init__(self, tex_type, texfilechan, tex_args, lineno):
360
self.tex_type = tex_type
361
self.texfilechan = texfilechan
362
self.tex_args = tex_args
364
self.children = [tex_type, texfilechan, tex_args]
365
def visit(self, visitor):
366
visitor.visitTexture(self)
368
class astTexFileChan_t(astnode_t):
369
def __init__(self, _id, expr, array_idx, lineno):
372
self.array_idx = array_idx
374
self.children = [_id, expr, array_idx]
375
def visit(self, visitor):
376
visitor.visitTexFileChan(self)
378
class astTexArgs_t(astnode_t):
379
def __init__(self, args, lineno):
383
def visit(self, visitor):
384
visitor.visitTexArgs(self)
386
class astFilterstep_t(astnode_t):
387
def __init__(self, edge, s1, s2, paramlist, lineno):
391
self.paramlist = paramlist
393
self.children = [edge, s1, s2, paramlist]
394
def visit(self, visitor):
395
visitor.visitFilterstep(self)
397
class astRotate_t(astnode_t):
398
def __init__(self, arg1, arg2, arg3, lineno):
404
self.children = [arg1, arg2, arg3, arg4]
405
def visit(self, visitor):
406
visitor.visitRotate(self)
408
class astFresnel_t(astnode_t):
409
def __init__(self, I, N, eta, Kr, Kt, R, T, lineno):
418
self.children = [I, N, eta, Kr, Kt, R, T]
419
def visit(self, visitor):
420
visitor.visitFresnel(self)
422
#------------------------------------------------------------
424
class visitor_t(object):
425
def visitProgram(self, node): raise Exception('visitProgram not defined in ' + type(self).__name__)
426
def visitFormals(self, node): raise Exception('visitFormals not defined in ' + type(self).__name__)
427
def visitShaderDef(self, node): raise Exception('visitShaderDef not defined in ' + type(self).__name__)
428
def visitFunctionDef(self, node): raise Exception('visitFunctionDef not defined in ' + type(self).__name__)
429
def visitNewScope(self, node): raise Exception('visitNewScope not defined in ' + type(self).__name__)
430
def visitNumConst(self, node): raise Exception('visitNumConst not defined in ' + type(self).__name__)
431
def visitStringConst(self, node): raise Exception('visitStringConst not defined in ' + type(self).__name__)
432
def visitVecConst(self, node): raise Exception('visitVecConst not defined in ' + type(self).__name__)
433
def visitMtxConst(self, node): raise Exception('visitMtxConst not defined in ' + type(self).__name__)
434
def visitIfElse(self, node): raise Exception('visitIfElse not defined in ' + type(self).__name__)
435
def visitExprList(self, node): raise Exception('visitExprList not defined in ' + type(self).__name__)
436
def visitDefExpression(self, node): raise Exception('visitDefExpression not defined in ' + type(self).__name__)
437
def visitDefExprList(self, node): raise Exception('visitDefExprList not defined in ' + type(self).__name__)
438
def visitParameter(self, node): raise Exception('visitParameter not defined in ' + type(self).__name__)
439
def visitVariable(self, node): raise Exception('visitVariable not defined in ' + type(self).__name__)
440
def visitTuple(self, node): raise Exception('visitTuple not defined in ' + type(self).__name__)
441
def visitStatements(self, node): raise Exception('visitStatements not defined in ' + type(self).__name__)
442
def visitReturn(self, node): raise Exception('visitReturn not defined in ' + type(self).__name__)
443
def visitBreak(self, node): raise Exception('visitBreak not defined in ' + type(self).__name__)
444
def visitContinue(self, node): raise Exception('visitContinue not defined in ' + type(self).__name__)
445
def visitFor(self, node): raise Exception('visitFor not defined in ' + type(self).__name__)
446
def visitSolar(self, node): raise Exception('visitSolar not defined in ' + type(self).__name__)
447
def visitIlluminate(self, node): raise Exception('visitIlluminate not defined in ' + type(self).__name__)
448
def visitIlluminance(self, node): raise Exception('visitIlluminance not defined in ' + type(self).__name__)
449
def visitID(self, node): raise Exception('visitID not defined in ' + type(self).__name__)
450
def visitUnaryExpr(self, node): raise Exception('visitUnaryExpr not defined in ' + type(self).__name__)
451
def visitBinop(self, node): raise Exception('visitBinop not defined in ' + type(self).__name__)
452
def visitAssignExpr(self, node): raise Exception('visitAssignExpr not defined in ' + type(self).__name__)
453
def visitRelation(self, node): raise Exception('visitRelation not defined in ' + type(self).__name__)
454
def visitProcedureCall(self, node): raise Exception('visitProcedureCall not defined in ' + type(self).__name__)
455
def visitTexture(self, node): raise Exception('visitTexture not defined in ' + type(self).__name__)
456
def visitTexFileChan(self, node): raise Exception('visitTexFileChan not defined in ' + type(self).__name__)
457
def visitTexArgs(self, node): raise Exception('visitTexArgs not defined in ' + type(self).__name__)
458
def visitFilterstep(self, node): raise Exception('visitFilterstep not defined in ' + type(self).__name__)
459
def visitRotate(self, node): raise Exception('visitRotate not defined in ' + type(self).__name__)
460
def visitFresnel(self, node): raise Exception('visitFresnel not defined in ' + type(self).__name__)
462
#------------------------------------------------------------
467
#------------------------------------------------------------
469
class vector_t(object):
470
def __init__(self, *args):
472
if L == 0: # defaults
473
self.v = (0.0, 0.0, 0.0)
474
elif L == 1: # single float, assign to all
475
self.v = (args[0], args[0], args[0])
479
raise Exception("vector_t: Expected 3 values got %d -> %s" % (L, args))
482
def __iter__(self): return self
488
return self.v[self.idx - 1]
489
# next math ops for direct evaluation where possible while building ast
490
def __add__(self, o):
491
return vector_t(self.v[0] + o.v[0], self.v[1] + o.v[1], self.v[2] + o.v[2])
492
def __sub__(self, o):
493
return vector_t(self.v[0] - o.v[0], self.v[1] - o.v[1], self.v[2] - o.v[2])
494
def __mul__(self, o):
495
if isinstance(o, float) or isinstance(o, int):
496
return vector_t(self.v[0] * o, self.v[1] * o, self.v[2] * o)
498
return vector_t(self.v[0] * o.v[0], self.v[1] * o.v[1], self.v[2] * o.v[2])
499
def __div__(self, o):
500
if isinstance(o, float) or isinstance(o, int):
501
return vector_t(self.v[0] / o, self.v[1] / o, self.v[2] / o)
503
return vector_t(self.v[0] / o.v[0], self.v[1] / o.v[1], self.v[2] / o.v[2])
505
if self.v[0] == o.v[0] and self.v[1] == o.v[1] and self.v[2] == o.v[2]: return True
508
if self.v[0] == o.v[0] or self.v[1] == o.v[1] or self.v[2] == o.v[2]: return False
511
return self.v[0]*o.v[0] + self.v[1]*o.v[1] + self.v[2]*o.v[2]
513
return vector_t(self.v[1]*o.v[2] - self.v[2]*o.v[1], self.v[2]*o.v[0] - self.v[0]*o.v[2], self.v[0]*o.v[1] - self.v[1]*o.v[0])
514
# the following three funcs might seem odd, but asString() is used for direct evaluation using eval()
515
# __str__() is used to print the values in a format needed for the compiler
516
# and __repr__() is just useful as general object info, like when used with print
518
return "vector_t(%g, %g, %g)" % (self.v[0], self.v[1], self.v[2])
520
return "%s %s %s" % self.v
522
return "<vector_t %g %g %g>" % self.v
524
class matrix_t(object):
525
def __init__(self, *args):
527
if L == 0: # defaults
528
self.m = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
529
elif L == 1: # single float, assign to mtx.diagonal
530
self.m = (args[0], 0.0, 0.0, 0.0, 0.0, args[0], 0.0, 0.0, 0.0, 0.0, args[0], 0.0, 0.0, 0.0, 0.0, args[0])
534
raise Exception("matrix_t: Expected 16 values got %d" % L)
537
def __iter__(self): return self
543
return self.m[self.idx - 1]
545
return "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s" % self.m
547
return "<matrix_t %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g>" % self.m
549
# function parameter flags used in the constant_t, register_t, variable_t class below
550
FF_OUTPUT = 1 # 'output' declared variable, so may be modified by function
551
FF_FPARAM = 2 # variable is a function parameter
554
# type is one char of 'fbmvs' for float, bool, matrix, vector and string respectively (vector is also used for color/point & normal)
555
# detail is either 'u' or 'v', for uniform or varying
556
# number is the register number used as its id
557
class register_t(object):
558
def __init__(self, _type, detail, number):
562
# function flags, not set directly here in ctor
563
self.flags = 0 # NOTE: mutable, do not use for comparisons or hashing!
564
def __eq__(self, other):
565
if type(self) != type(other): return False
566
if other.type == self.type and other.detail == self.detail and other.number == self.number: return True
568
def __ne__(self, other):
569
if type(self) != type(other): return True
570
if other.type != self.type and other.detail != self.detail and other.number != self.number: return True
573
return hash(self.type + self.detail) ^ self.number
574
# String representation for registers and constants are somewhat complicated, both can be created and 'deleted' at any time.
575
# This means that a simple '$number' representation will not look right, multiple registers/constants with the same name might be the result,
576
# (They are still unique since its hash value is based on all its members, only the names will look the same)
577
# Therefore here the name is created using all the important members (besides aforementioned reason, it also makes it more readable for 'visual debugging' purposes ;),
578
# optionally after the code has been built all can be converted to the simpler name if desired ('simplify_regnames' option).
580
return "$r%s%d" % (self.type + self.detail, self.number)
583
return "<register_t FUNC $r%s%d>" % (self.type + self.detail, self.number)
584
return "<register_t $r%s%d>" % (self.type + self.detail, self.number)
586
# constant value register class
587
# type is one char of 'fbmvs' for float, bool, matrix, vector and string respectively (vector is also used for color/point & normal)
588
# number is the register number used as its id
589
# val is its assigned value
590
# refc is its refrerence count (numbers can be used more than once in the program, so it can not be removed until the ref.count has reached 0)
591
# constants are always uniform, and also have global scope
592
class constant_t(object):
593
def __init__(self, _type, number, val):
597
self.refc = 1 # NOTE: mutable, do not use for comparisons or hashing!
598
self.flags = 0 # NOTE: mutable, do not use for comparisons or hashing!
599
def __eq__(self, other):
600
if type(self) != type(other): return False
601
if other.type == self.type and other.number == self.number and other.val == self.val: return True
603
def __ne__(self, other):
604
if type(self) != type(other): return True
605
if other.type != self.type and other.number != self.number and other.val != self.val: return True
608
return hash(self.type) ^ self.number ^ hash(self.val)
610
return "$c%s%d" % (self.type, self.number)
612
return "<constant_t $c%s%d>" % (self.type, self.number)
615
# type is one char of 'fmvcpns' for float, matrix, vector, color, point, normal, and string respectively (bool not used here, temp or const register only)
616
# detail is either 'u' or 'v', for uniform or varying
617
# name is the name of the variable
618
# arlen is an optional array length
619
# scope is an integer indicating the scope level of the variable
620
# flags is an integer bitmask, see above FF_ list
621
class variable_t(object):
622
def __init__(self, _type, detail, name, arlen, scope):
628
self.flags = 0 # NOTE: mutable, do not use for comparisons or hashing!
629
def __eq__(self, other):
630
if type(self) != type(other): return False
631
if other.type == self.type and other.detail == self.detail and other.name == self.name and other.arlen == self.arlen and other.scope == self.scope: return True
633
def __ne__(self, other):
634
if type(self) != type(other): return True
635
if other.type != self.type and other.detail != self.detail and other.name != self.name and other.arlen != self.arlen and other.scope != self.scope: return True
638
return hash(self.name + self.type + self.detail) ^ self.arlen ^ hash(self.scope)
640
#if self.scope: return "%s_%d" % (self.name, self.scope)
644
if self.arlen: ar = '[' + str(self.arlen) + ']'
646
if self.flags: st = "FUNC"
647
if self.scope: return "<variable_t %s '%s_%d%s'>" % (st, self.name, self.scope, ar)
648
return "<variable_t %s '%s'>" % (st, self.name + ar)
651
class array_t(object):
652
def __init__(self, isconst, values):
653
self.isconst = isconst
656
def __iter__(self): return self
658
if self.idx == len(self.values):
662
return self.values[self.idx - 1]
664
return "<array_t %s>" % self.values
666
class compile_t(visitor_t):
667
# used for conversion cpn types to v
669
# dictionary of all possible typecasts.
670
# this is more tolerant than most, since basically the typecast is based on the type size,
671
# not its actual type, so a color can be cast to a normal for instance.
672
typecast_dict = {'f': "fcpvnm", 's': 's', 'c': "cpvn", 'p': "cpvn", 'v': "cpvn", 'n': "cpvn", 'm': 'm'}
673
# probably more correct version of the above, though only difference is that a color cannot be cast to anything but itself, but not currently used
674
#typecast_dict = {'f': "fcpvnm", 's': 's', 'c': 'c', 'p': "pvn", 'v': "pvn", 'n': "pvn", 'm': 'm'}
675
# dictionary to expand type character to full type name
676
fulltype = {'f': "float", 'b': "bool", 's': "string", 'c': "color", 'p': "point", 'v': "vector", 'n': "normal", 'm': "matrix", '*': "void"}
677
# same for detail type
678
fulldetail = {'u': "uniform", 'v': "varying"}
680
relop_dict = {'<': "lt", "<=": "le", "==" : "eq", "!=": "ne", ">=": "ge", '>': "gt"}
681
# 'not' of relation operator, needed for relation expression of for(){} loops
682
notrelop_dict = {'<': "ge", "<=": "gt", "==" : "ne", "!=": "eq", ">=": "lt", '>': "le"}
683
# as above, but using the actual operator name as key, only used in visitFor()
684
w_notrelop_dict = {"lt": "ge", "le": "gt", "eq" : "ne", "ne": "eq", "ge": "lt", "gt": "le"}
686
def __init__(self, optimize = True, simplify_regnames = True):
687
# the value of all variable dicts is a string designating the type and detail,
688
# it consists of the type character (one of 'cpvnfsm') followed by the detail type, 'v' or 'u' for either 'varying' or 'uniform'
690
# set of all registers used (sets require python version >= 2.4, though could just as well use dictionary with any arbitrary value)
691
self.tempregs = set()
692
# dictionary to keep track of register usage count, if 0, can be removed, used for optimization code
694
# set of all variables defined in the shader
695
self.variables = set()
696
# this dict is used to make sure variable names are unique, since it is possible that variables have same names but different types/detail/scope
698
# the set of global variables used by the shader
699
self.globals_used = set()
700
# list of lists of variable dictionaries ordered according to scope, global first, top is current local scope
702
# dict. of constants, one where value is the key, and for the other the key is its associated register
703
self.const_byval = {}
704
self.const_byreg = {}
705
# dictionary of all parameters, value is a list containing the type and detail string and its default value if it has one
707
# initialization code
711
# dictionary of user-defined functions
713
# temporary, a list, only available while processing function parameters list to keep the parameters in order
714
self.funcparams = None
715
# dictionary of variables declared 'extern', only used when processing functions
717
# flags that are set when inside a userdefined function body and handling 'extern' var.declarations
718
self.infuncbody = False
719
self.islexfunc = False
720
self.externvar = False
721
# the intermediate stack, from which values are popped for a register based representation
722
# NOTE: only call newreg() *after* first popping all args of the stack (in reverse order!)
724
# registers are unlimited and denoted by '$number', so to keep track of all currently 'allocated' registers, only need a simple counter
726
# since constants are handled separately, they need their own counter
727
self.const_curreg = 0
730
# current type and detail of a default declaration as a two-char string
732
# current scope level
734
# flag that is set when currently evaluating the lhs of an assignment (needed because of array element assignment, lhs/rhs are handled differently)
735
self.asgn_lhs = False
736
# flag when set indicates rhs of assigment is also an assignment (eg. 'a = b = c = d = 1.23;')
737
self.chain_asgn = False
738
# the name of the shader which is also used as the name of the output file + ".sqd"
739
self.shader_name = None
740
# stack that keeps track of start & end labels in for/while loops, needed for 'continue' & 'break' statements
742
# If 'optimize' is true, try to optimize the code a bit
743
self.optimize = optimize
744
# 'simplify_regnames' when true will simplify the register names to a simple '$number' representation, see register_t and constant_t classes for more info
745
self.simplify_regnames = simplify_regnames
747
# return new register name of type t and detail d, where t is 'f', 's', 'v' or 'm' (c, p, or n are converted to vector), and d is either 'u' or 'v'
748
def newreg(self, t, d):
749
if t in self.cpn_set: t = 'v'
751
reg = register_t(t, d, self.curreg)
752
# update or add register usage count
753
if reg in self.tempregs:
754
self.reguse[reg] += 1
757
self.tempregs.add(reg)
763
assert self.curreg >= 0 # if not positive, there must be some error somewhere
765
# return a new constant register of type t and value v
766
def newconst(self, t, v):
767
if self.const_byval.has_key(v): # already defined, inc ref.count
768
creg = self.const_byval[v]
771
self.const_curreg += 1
772
creg = constant_t(t, self.const_curreg, v)
773
self.const_byval[v] = creg
774
self.const_byreg[creg] = v
777
# 'delete' a constant register, returns its value
778
def delconst(self, creg):
779
val = self.const_byreg[creg]
782
self.const_byreg.pop(creg)
783
self.const_byval.pop(val)
784
self.const_curreg -= 1
787
# returns type and detail as two char pair of given variable
788
# converts 'cpn' to 'v' if 'convert' true
789
def typeof(self, var, convert = True):
790
if isinstance(var, str) and var[0] == '"':
792
elif self.params.has_key(var):
793
tp = self.params[var][0].lower()
794
elif var in self.tempregs:
795
tp = var.type + var.detail
796
elif self.const_byreg.has_key(var):
798
elif isinstance(var, register_t):
799
tp = var.type + var.detail
800
elif isinstance(var, constant_t):
802
elif isinstance(var, variable_t):
803
tp = var.type + var.detail
805
# search all variable dictionaries starting at the current scope
806
# first match is returned
808
for scopelist in reversed(self.scopes):
809
for vardict in reversed(scopelist):
810
if vardict.has_key(var):
812
tp = tv.type + tv.detail
815
raise Exception("typeof(): Cannot determine type of: '%s' " % var)
818
if tp[0] in self.cpn_set: tp = 'v' + tp[1]
821
# push new item on stack
822
def push(self, item):
823
self.stack.append(item)
825
# pop and return item from stack
827
if len(self.stack) == 0:
828
raise Exception("Empty stack!")
829
item = self.stack.pop()
830
# if popping a register (not constant), 'delete' it, UNLESS it is currently a function parameter (must remain on stack until function exit)
831
if isinstance(item, register_t) and ((item.flags & FF_FPARAM) == 0):
835
# returns top item on stack without popping it
837
return self.stack[-1]
839
def visitProgram(self, node):
840
for d in node.definitions:
843
def visitFormals(self, node):
845
for f in node.formals:
848
def visitShaderDef(self, node):
849
self.shader_name = node.id # used for output file
850
self.code.append(node.shadertype + " " + node.id)
852
# now that the shader type is known, add the dictionary of predefined variables that are accessible to the shader,
853
# (note that for instance in the case of a surface shader, and it redefines the output variables Ci and or Oi in the main scope, the shader will be pretty much useless...)
854
if node.shadertype == "surface":
855
predefs = {"Cs": "cv", "Os": "cv", "dPdu": "vv", "dPdv": "vv", "P": "pv", "N": "nv", "Ng": "nv",
856
"u": "fv", "v": "fv", "du": "fv", "dv": "fv", "s": "fv", "t": "fv", "L": "vv",
857
"Cl": "cv", "Ol": "cv", "E": "pu", "I": "vv",
858
"ncomps": "fu", "time": "fu", "dtime": "fu", "dPdtime": "fv",
859
"Ci": "cv", "Oi": "cv"}
860
elif node.shadertype == "light":
861
predefs = {"dPdu": "vv", "dPdv": "vv", "P": "pv", "N": "nv", "Ng": "nv",
862
"u": "fv", "v": "fv", "du": "fv", "dv": "fv", "s": "fv", "t": "fv", "L": "vv",
863
"Ps": "pv", "Cl": "cv", "Ol": "cv", "E": "pu",
864
"ncomps": "fu", "time": "fu", "dtime": "fu"}
865
elif node.shadertype == "displacement":
866
predefs = {"dPdu": "vv", "dPdv": "vv", "P": "pv", "N": "nv", "Ng": "nv",
867
"u": "fv", "v": "fv", "du": "fv", "dv": "fv", "s": "fv", "t": "fv",
868
"E": "pu", "I": "vv",
869
"ncomps": "fu", "time": "fu", "dtime": "fu",
870
"Ci": "cv", "Oi": "cv"}
871
elif node.shadertype == "volume":
872
predefs = {"Cs": "cv", "Os": "cv", "dPdu": "vv", "dPdv": "vv", "P": "pv", "N": "nv", "Ng": "nv",
873
"u": "fv", "v": "fv", "du": "fv", "dv": "fv", "s": "fv", "t": "fv", "L": "vv",
874
"Ps": "pv", "Cl": "cv", "Ol": "cv", "E": "pu", "I": "vv",
875
"ncomps": "fu", "time": "fu", "dtime": "fu", "dPdtime": "fv",
876
"alpha": "fu", "Ci": "cv", "Oi": "cv"}
877
elif node.shadertype == "imager":
878
predefs = {"P": "pv", "ncomps": "fu", "time": "fu", "dtime": "fu",
879
"alpha": "fu", "Ci": "cv", "Oi": "cv"}
880
else: # never happens
881
raise Exception("Line %d: Unknown shader type?!? -> '%s'" % node.lineno, node.shadertype)
885
newvar = variable_t(td[0], td[1], name, 0, None) # note: scope set to None, indicates global var.
887
# add names to variable name list as well to make sure no duplicates are created
888
self.allnames[name] = 0 # dict is empty yet, so this is the first entry
889
self.scopes.append([d])
891
if node.formals: node.formals.visit(self)
892
# add the main code start label if initialization code was created,
893
# always 1 (or should be anyway, no jumps in initcode possible as far as I can see..)
894
if len(self.initcode):
896
self.code.append(["@%d" % self.curlabel, None])
898
self.curscope = 0 # main scope
899
self.scopes.append([{}])
900
node.body.visit(self)
903
def visitFunctionDef(self, node):
904
# Store the node into a table, later when the function is called it will be expanded inline.
905
# 'argtypes' holds the function parameter types and detail,
906
# except the first, which is only the returntype char, detail is not known in advance.
907
# Return type 'v' is reserved for vector, '*' is for 'void' (also used to indicate 'variadic' when used in func.args).
908
# Uppercase chars mean the variable is an 'output' declared var, so may be modified by the function.
909
if node.returntype == "void":
913
argtypes = [node.returntype[1][0]]
915
argtypes = [node.returntype[0]]
916
if argtypes[0] in self.cpn_set: argtypes[0] = 'v'
917
varnames = [] # variable names used in the function, only used to map to arguments
919
# declare list to temporarily store the function parameters in order
922
self.scopes.append([fdict])
923
node.formals.visit(self)
924
for vname in self.funcparams:
925
varnames.append(vname)
927
# if variable is 'output', flag argument typechar for handleUserFunc() below by making it uppercase
928
if var.flags & FF_OUTPUT:
929
td = self.typeof(var)
930
argtypes.append(td[0].upper() + td[1])
932
argtypes.append(self.typeof(var))
933
# remove the dictionary again
935
# temporary var.list can also be removed
936
self.funcparams = None
937
self.userfuncs[node.id] = [argtypes, varnames, node]
939
# used in visitProcedureCall() for userdefined functions
940
def handleUserFunc(self, funcdef):
941
argtypes, varnames, node = funcdef
943
# new temporary scope, no reference to any previous scope, except for 'extern' declared vars
945
for i in range(len(varnames)):
947
exp_tp = argtypes[i + 1]
948
var = self.stack[-(i+1)] # no pop yet, since arguments are used directly, done after all func.params processed, see below
949
var_tp = self.typeof(var)
951
# type of variable must be exected type
952
if exp_tp[0].isupper():
954
exp_tp = exp_tp[0].lower() + exp_tp[1]
956
if var_tp[0] != exp_tp[0]:
957
raise Exception("Line %d: function '%s' argument '%s' type '%s' not same as function parameter '%s' of type '%s'" % (node.lineno, node.id, repr(var), var_tp[0], vname, exp_tp[0]))
958
# if parameter detail is uniform, then argument must also be uniform, anything else ok
959
if exp_tp[1] == 'u' and var_tp[1] != 'u':
960
raise Exception("Line %d: function '%s' argument '%s' is varying but function parameter '%s' expects uniform" % (node.lineno, node.id, repr(var), vname))
962
if not isinstance(var, str):
963
newvar.flags |= FF_FPARAM
965
assert isinstance(var, constant_t) == False # constants can never be output
966
newvar.flags |= FF_OUTPUT
967
funcparams[vname] = newvar
968
# all params done, now can pop values
969
for fp in funcparams.values():
971
# prepare for the function body
973
pifb = self.infuncbody
974
self.infuncbody = True
975
pilf = self.islexfunc
976
self.islexfunc = node.lexfunc
978
self.scopes[-1].append(funcparams) # add to parent scope
980
self.scopes.append([funcparams]) # new scope
981
last_externs = self.vars_extern.copy()
982
node.body.visit(self)
984
self.vars_extern = last_externs
985
for fp in funcparams.values():
986
if not isinstance(fp, str):
987
if fp.flags & FF_FPARAM: fp.flags = 0
988
self.islexfunc = pilf
989
self.infuncbody = pifb
991
self.scopes[-1].pop()
996
def visitNewScope(self, node):
998
self.scopes[-1].append({})
999
node.statements.visit(self)
1000
# delete all variables now out of scope
1001
self.scopes[-1].pop()
1004
def visitNumConst(self, node):
1005
self.push(self.newconst('f', node.val))
1007
def visitVecConst(self, node):
1008
self.push(self.newconst('v', node.val))
1010
def visitMtxConst(self, node):
1011
self.push(self.newconst('m', node.val))
1013
def visitStringConst(self, node):
1014
self.push(self.newconst('s', node.val))
1016
# only used in visitIfElse_t() below, tries to find out type & detail from name string
1017
def typefromstring(self, s):
1026
# try variable list, have to do linear search, not indexed by name
1027
for v in self.variables:
1028
if str(v) == s: return v.type + v.detail
1030
raise Exception("Cannot determine type from string '%s'" % s)
1034
def visitIfElse(self, node):
1037
if node.iscond: # conditional operator, has both if/else
1038
node._if.visit(self)
1039
node._else.visit(self)
1040
op3, op2, op1 = self.pop(), self.pop(), self.pop()
1041
op1t, op2t, op3t = self.typeof(op1), self.typeof(op2), self.typeof(op3)
1042
# op2/op3 must be same type/detail
1044
lhs = self.newreg(op2t[0], op1t[1]) # note: lhs must be detail of boolean here, it indicates detail of the actual statement lhs
1045
self.code.append(["cmov%s" % op2t[0], "%s %s %s %s" % (lhs, op1, op2, op3)])
1047
else: # not same type/detail, have to do full if/else
1048
lhs = self.newreg(op2t[0], op2t[1])
1049
self.code.append(["cond_push", "%s" % op1])
1050
self.code.append(["mov%s" % (op2t[0] + op2t[0]), "%s %s" % (lhs, op2)])
1051
self.code.append(["cond_else", None])
1052
self.code.append(["mov%s" % (op2t[0] + op2t[0]), "%s %s" % (lhs, op3)])
1053
self.code.append(["cond_pop", None])
1056
# simple optimization, if relation expression operands are both uniform, can replace it by single conditional jump
1057
# (This might in fact not be an optimization at all, performance may actually be worse, have to test, not sure)
1058
# update: ok, tested, it is definitely faster ('uberlight' shader, about 4 sec. less)
1059
if self.optimize and nrel.relop != "&&" and nrel.relop != "||" and nrel.relop != '!': # not logic stmts
1060
last_codeline = self.code[-1]
1061
last_oprs = last_codeline[1].split()
1062
# in this case, have to determine type/detail from actual name string if register name ('$' is first char)
1063
lop1t, lop2t = self.typefromstring(last_oprs[1]), self.typefromstring(last_oprs[2])
1064
if lop1t[1] == 'u' and lop2t[1] == 'u':
1066
lab1, lab2 = self.curlabel-1, self.curlabel
1067
# want 'not' of relation here
1068
opc = self.notrelop_dict[nrel.relop]
1069
last_codeline[0], last_codeline[1] = "j%s" % (opc + lop1t[0] + lop2t[0]), "%s %s @%d" % (last_oprs[1], last_oprs[2], lab1)
1070
# the lhs of the old statement can now be deleted if not still in use
1071
last_lhs = self.pop()
1072
if self.reguse[last_lhs] == 0:
1073
self.tempregs.remove(last_lhs)
1074
node._if.visit(self)
1075
if node._else: self.code.append(["jmp", "@%d" % lab2])
1076
self.code.append(["@%d" % lab1, None])
1078
node._else.visit(self)
1079
self.code.append(["@%d" % lab2, None])
1081
# 'complex' or relation expression using varyings
1083
if self.typeof(lhs)[1] == 'u':
1084
# uniform relation, can do a conditional jump
1086
lab1, lab2 = self.curlabel-1, self.curlabel
1087
self.code.append(["cjmpnot", "%s @%d" % (lhs, lab1)])
1088
node._if.visit(self)
1089
if node._else: self.code.append(["jmp", "@%d" % lab2])
1090
self.code.append(["@%d" % lab1, None])
1092
node._else.visit(self)
1093
self.code.append(["@%d" % lab2, None])
1096
self.code.append(["cond_push", "%s" % lhs])
1097
node._if.visit(self)
1099
self.code.append(["cond_else", None])
1100
node._else.visit(self)
1101
self.code.append(["cond_pop", None])
1103
def visitExprList(self, node):
1104
if node.expr_list: node.expr_list.visit(self)
1105
node.expr.visit(self)
1107
# add a new variable to set
1108
# there can be vars with the same name, but have different type,
1109
# so this function is used to make sure names are unique
1110
def addNewVariable(self, var):
1112
if name in self.allnames:
1113
self.allnames[name] += 1
1114
var.name += "_%d" % self.allnames[name]
1116
self.allnames[name] = 0
1117
self.variables.add(var)
1119
# separate code to handle array default expressions
1120
def handleDefArray(self, node):
1124
# check redefinition
1125
if self.params.has_key(node.id):
1126
raise Exception("Line %d: Parameter '%s' already defined" % (node.lineno, node.id))
1127
self.params[node.id] = [self.typedet, arr.values]
1130
newvar = variable_t(tp[0], tp[1], node.id, node.array_length, self.curscope)
1131
if self.funcparams == None: self.addNewVariable(newvar)
1132
for i in range(node.array_length):
1133
self.code.append(["movta%s" % (tp[0] + tp[0]), "%s %s %s" % (node.id, self.newconst('f', i), self.newconst(tp[0], arr.values[i]))])
1135
# array not completely constant, initialization code needed
1136
lastcodelen = len(self.code) # used to copy newly added code to initcode segment if required
1140
defvals = [0.0]*node.array_length
1142
defvals = [vector_t() for i in range(node.array_length)]
1143
for v in defvals: v.detail = tp[1]
1145
defvals = [matrix_t() for i in range(node.array_length)]
1146
for m in defvals: m.detail = tp[1]
1148
defvals = [""]*node.array_length
1149
else: # never happens
1150
raise Exception("Line %d: DefExpression() array, unexpected type!?! -> %s" % (node.lineno, tp))
1151
for i in range(node.array_length):
1152
if isinstance(arr.values[i], astnode_t):
1153
arr.values[i].visit(self)
1154
self.code.append(["movta%s" % (tp[0] + tp[0]), "%s %s %s" % (node.id, self.newconst('f', i), self.pop())])
1156
defvals[i] = arr.values[i]
1157
# check redefinition
1158
if self.params.has_key(node.id):
1159
raise Exception("Line %d: Parameter '%s' already defined" % (node.lineno, node.id))
1160
self.params[node.id] = [tp, defvals]
1161
# move newly added codelines to initcode
1162
lc = len(self.code) - lastcodelen
1165
self.initcode.append(self.code[i + lastcodelen])
1170
newvar = variable_t(tp[0], tp[1], node.id, node.array_length, self.curscope)
1171
if self.funcparams == None: self.addNewVariable(newvar)
1172
for i in range(node.array_length):
1173
if isinstance(arr.values[i], astnode_t):
1174
arr.values[i].visit(self)
1176
self.code.append(["movta%s" % (tp[0] + tp[0]), "%s %s %s" % (node.id, self.newconst('f', i), val)])
1178
self.code.append(["movta%s" % (tp[0] + tp[0]), "%s %s %s" % (node.id, self.newconst('f', i), self.newconst(tp[0], arr.values[i]))])
1180
if not node.shparam:
1181
# check possible redefinition
1182
curscope = self.scopes[-1][-1]
1183
if node.id in curscope:
1184
raise Exception("Line %d: Variable '%s' already defined" % (node.lineno, node.id))
1185
# if 'extern' declared variable, add to externlist
1187
self.vars_extern[node.id] = self.typedet
1189
# add new variable if not processing function parameters
1190
newvar = variable_t(self.typedet[0], self.typedet[1], node.id, node.array_length, self.curscope)
1191
if self.funcparams == None: self.addNewVariable(newvar)
1192
# also add to current scope
1193
if not curscope.has_key(node.id):
1194
curscope[node.id] = newvar
1195
# add to ordered function parameter list if processing function parameters
1196
if self.funcparams != None:
1197
self.funcparams.append(node.id)
1200
def visitDefExpression(self, node):
1201
lastcodelen = len(self.code) # used to copy newly added code to initcode segment if required
1203
if isinstance(node.def_init, array_t):
1204
self.handleDefArray(node)
1206
node.def_init.visit(self)
1207
need_initcode = False
1208
isconstparam = False
1211
if isinstance(val, constant_t):
1212
# constant register, no initcode
1213
need_initcode = False
1215
elif isinstance(val, register_t):
1216
need_initcode = True
1217
elif val in self.params:
1218
# parameter ID, replace by its value
1219
val = self.params[val][1]
1220
need_initcode = False
1222
else: # should never happen
1223
raise Exception("Line " + str(node.lineno) + ": Don't know what to do with this -> " + repr(val) + " (pytype: %s)" % type(val))
1225
# since it is initialized later, use a default value for the declaration
1226
tp = self.typeof(val)
1227
# check implicit typecast
1228
if self.typedet[0] not in self.typecast_dict[tp[0]]:
1229
raise Exception("Line %d: parameter '%s', Illegal implicit typecast of %s to %s" % (node.lineno, node.id, self.fulltype[tp[0]], self.fulltype[self.typedet[0]]))
1234
defval.detail = tp[1]
1237
defval.detail = tp[1]
1240
else: # never happens
1241
raise Exception("Line " + str(node.lineno) + ": DefExpression() Unexpected type!?! -> " + tp)
1242
# check redefinition
1243
if self.params.has_key(node.id):
1244
raise Exception("Line %d: Parameter '%s' already defined" % (node.lineno, node.id))
1245
self.params[node.id] = [self.typedet, defval]
1246
# move newly added codelines to initcode
1247
lc = len(self.code) - lastcodelen
1248
if lc == 0: # nothing was added, so no initcode
1249
need_initcode = False
1252
self.initcode.append(self.code[i + lastcodelen])
1256
# if val is a constant register, replace with its actual value,
1257
# and since this is a parameter, remove the constant value from the dictionary (unless use count != 0),
1258
# and delete the value just pushed onto the stack as well
1260
tp = self.typeof(val)[0]
1261
# check implicit typecast
1262
if self.typedet[0] not in self.typecast_dict[tp[0]]:
1263
raise Exception("Line %d: parameter '%s', Illegal implicit typecast of %s to %s" % (node.lineno, node.id, self.fulltype[tp[0]], self.fulltype[self.typedet[0]]))
1265
val = self.delconst(reg)
1266
# a parameter declaration can also require an implicit typecast, eg. color c = 1
1267
# if so, expand to full v/c/p/n triple
1268
if (self.typedet[0] == 'v' or (self.typedet[0] in self.cpn_set)) and tp == 'f':
1269
val = "%g %g %g" % (val, val, val)
1270
# check redefinition
1271
if self.params.has_key(node.id):
1272
raise Exception("Line %d: Parameter '%s' already defined" % (node.lineno, node.id))
1273
self.params[node.id] = [self.typedet, val]
1274
# if this is a parameter with a constant default value, there is nothing to do after this, so can return now
1275
if isconstparam: return
1277
# 'temp' type, no default value, is assigned in code
1279
if not node.shparam:
1280
curscope = self.scopes[-1][-1]
1281
# check possible redefinition
1282
if node.id in curscope:
1283
raise Exception("Line %d: Variable '%s' already defined" % (node.lineno, node.id))
1284
# if 'extern' declared variable, add to externlist
1286
self.vars_extern[node.id] = self.typedet
1288
# add new variable set if not processing function parameters
1289
newvar = variable_t(self.typedet[0], self.typedet[1], node.id, node.array_length, self.curscope)
1290
if self.funcparams == None: self.addNewVariable(newvar)
1291
# also add to current scope
1292
if not curscope.has_key(node.id):
1293
curscope[node.id] = newvar
1294
# add to ordered function parameter list if processing function parameters
1295
if self.funcparams != None:
1296
self.funcparams.append(node.id)
1297
# optional default assignment code
1299
curcode = self.initcode
1304
# type here is the last defined type
1305
op1t, op2t = self.typedet, self.typeof(op2)
1306
# convert op1t to v if cpn
1307
if op1t[0] in self.cpn_set: op1t = 'v' + op1t[1]
1308
# simple optimization, if previous line of code is 'opcode op2 ...' and op2 is a register,
1309
# and this line will be 'mov op1 op2' then concatenate the two, modifying previous codeline to 'opcode op1 ...'
1310
# the two operands of mov must be same type, but can be different detail, but in case of mixed detail, lhs must be varying
1311
if self.optimize and isinstance(op2, register_t) and curcode[-1][1]:
1312
last_codeline = curcode[-1][1]
1313
last_ops = last_codeline.split()
1315
if last_ops[0] == op2str and op1t[0] == op2t[0] and not (op1t[1] == 'u' and op2t[1] == 'v'):
1316
if not node.shparam: # use name of newly created variable
1317
curcode[-1][1] = str(newvar) + last_codeline[len(op2str):]
1319
curcode[-1][1] = node.id + last_codeline[len(op2str):]
1320
# if op2 is only the lhs of the previous line, remove op from tempregs, result register no longer used
1322
for opr in last_ops:
1323
if opr == op2str: op2_count += 1
1325
print "OPTIM 1 rm", op2, "USE", self.reguse[op2]
1326
if self.reguse[op2] == 0:
1327
self.tempregs.remove(op2)
1329
# the actual default initialization, if this is not a parameter OR op2 is a register or constant add a mov instruction
1330
if (not node.shparam) or isinstance(op2, register_t) or isinstance(op2, constant_t):
1331
# check implicit typecast
1332
if op1t[0] not in self.typecast_dict[op2t[0]]:
1333
raise Exception("Line %d: variable '%s', Illegal implicit typecast of %s to %s" % (node.lineno, node.id, self.fulltype[op2t[0]], self.fulltype[op1t[0]]))
1334
# check detail, lhs must be varying if rhs is varying
1335
if op2t[1] == 'v' and op1t[1] != 'v':
1336
raise Exception("Line %d: variable '%s', cannot assign varying to uniform" % (node.lineno, node.id))
1337
typest = op1t[0] + op2t[0]
1339
curcode.append(["mov%s" % typest, "%s %s" % (newvar, op2)])
1341
curcode.append(["mov%s" % typest, "%s %s" % (node.id, op2)])
1343
def visitDefExprList(self, node):
1344
for e in node.expr_list:
1347
def visitParameter(self, node):
1348
# set type and detail for any possible default expressions, just use first char of each
1350
if node.typespec[1] == "void": # exception is void since 'v' already reserved for vector, use '*' instead in that case
1351
self.typedet = '*' + node.typespec[0][0]
1353
self.typedet = node.typespec[1][0] + node.typespec[0][0]
1354
for e in node.exprs:
1356
# set the 'output' flag if needed
1358
csd = self.scopes[-1][-1]
1359
for dexprs in node.exprs:
1360
if csd.has_key(dexprs.id):
1361
csd[dexprs.id].flags |= FF_OUTPUT
1362
elif self.params.has_key(dexprs.id):
1363
# shader parameter, make typechar uppercase to flag as output
1364
ptdval = self.params[dexprs.id]
1365
self.params[dexprs.id] = [ptdval[0][0].upper() + ptdval[0][1], ptdval[1]]
1366
# reset type & detail, now unknown until eg. assignment statements etc.
1369
def visitVariable(self, node):
1370
# set type and detail for any possible default expressions
1372
if node.typespec[1] == "void": # exception is void since 'v' already reserved for vector, use '*' instead in that case
1373
self.typedet = '*' + node.typespec[0][0]
1375
self.typedet = node.typespec[1][0] + node.typespec[0][0]
1376
# set extern flag for the duration of the function parameter declarations if required
1377
if self.infuncbody and node.extern:
1378
pev = self.externvar
1379
self.externvar = True
1382
if self.externvar: self.externvar = pev
1383
# reset type & detail, now unknown until eg. assignment statements etc.
1386
def visitTuple(self, node):
1387
# note: must visit all values first, then pop
1388
lhs_det = 'u' # lhs detail is varying if any value of tuple is varying
1389
for tpval in node.values:
1391
if self.typeof(self.top())[1] == 'v': lhs_det = 'v'
1393
for tpval in node.values:
1394
tup.append(self.pop())
1396
ltp = len(node.values)
1400
# code to 'cast' actual vector to register
1401
rv = self.newreg('v', v.detail)
1402
self.code.append(["movvf3", "%s %s" % (rv, v)])
1407
# cast to matrix register var
1408
rm = self.newreg('m', m.detail)
1409
self.code.append(["movmf16", "%s %s" % (rm, m)])
1411
else: # never happens
1412
raise Exception("Line %d: Expected tuple length of 3 or 16, got %d" % (node.lineno, ltp))
1414
def visitStatements(self, node):
1416
for s in node.statements:
1419
def visitReturn(self, node):
1421
node.returnval.visit(self)
1423
op1t = self.typeof(op1)
1424
lhs = self.newreg(op1t[0], op1t[1])
1425
if lhs != op1: # don't add code if move-to-self
1426
# don't have to check detail here, since both have same type/detail
1427
self.code.append(["mov%s" % (op1t[0] + op1t[0]), "%s %s" % (lhs, op1)])
1430
def visitBreak(self, node):
1431
# must do this differently, maybe add separate VM instruction for this, disabled for now
1432
raise Exception("Line %d: 'break' statement not implemented yet" % node.lineno)
1433
# jump to end of loop
1434
numlab = len(self.looplabels)
1436
self.code.append(["cond_reset", None])
1438
if node.level > numlab:
1439
raise Exception("Line %d: illegal level in 'break' statement" % node.lineno)
1440
self.code.append(["jmp", "@%d" % (self.looplabels[-node.level][1])])
1442
self.code.append(["jmp", "@%d" % (self.looplabels[-1][1])])
1444
def visitContinue(self, node):
1445
# must do this differently, maybe add separate VM instruction for this, disabled for now
1446
raise Exception("Line %d: 'continue' statement not implemented yet" % node.lineno)
1447
# jump to start of loop
1448
numlab = len(self.looplabels)
1450
self.code.append(["cond_reset", None])
1452
if node.level > numlab:
1453
raise Exception("Line %d: illegal level in 'continue' statement" % node.lineno)
1454
self.code.append(["jmp", "@%d" % (self.looplabels[-node.level][0])])
1456
self.code.append(["jmp", "@%d" % (self.looplabels[-1][0])])
1458
def visitFor(self, node):
1459
if node.init_expr: # while stmt has no init.expr
1460
node.init_expr.visit(self)
1462
lab1, lab2 = self.curlabel-1, self.curlabel
1463
self.looplabels.append([lab1, lab2])
1464
self.code.append(["@%d" % lab1, None])
1469
op1t = self.typeof(op1)
1470
# this doesn't always work for some reason, not entirely sure why yet (shadowedclouds.sl infinite loop, maybe uni/vary cond. mixup?), so disabled for now...
1472
# as in visitIfElse(), simplify code to a compare and jump if simple relation expression with uniform operands
1473
if self.optimize and op1t[1] == 'u' \
1474
and not isinstance(nrel.expr1, astRelation_t) and not isinstance(nrel.expr2, astRelation_t) \
1475
and nrel.relop != "&&" and nrel.relop != "||" and nrel.relop != '!': # not logic stmts
1476
last_codeline = self.code[-1]
1477
ops = last_codeline[1].split()
1478
last_codeline[0] = "j" + self.w_notrelop_dict[last_codeline[0][2:4]] + last_codeline[0][4:]
1479
last_codeline[1] = "%s %s @%d" % (ops[1], ops[2], lab2)
1480
# can remove lhs if not in use (not indexed by name, so have to do a search here)
1482
for reg in self.reguse.keys():
1483
if str(reg) == ops[0]:
1486
if last_lhs and self.reguse[last_lhs] == 0:
1487
print "OPTIM 6 rm", last_lhs, "USE", self.reguse[last_lhs]
1488
self.tempregs.remove(last_lhs)
1489
else: # varying, or not possible to optimize
1491
self.code.append(["cjmpnot", "%s @%d" % (op1, lab2)])
1493
self.code.append(["cond_push", str(op1)])
1495
node.stmt.visit(self)
1496
if vary: self.code.append(["cond_pop", None])
1497
if node.inc_expr: # while stmt has no incr.expr
1498
node.inc_expr.visit(self)
1499
self.code.append(["jmp", "@%d" % lab1])
1500
self.code.append(["@%d" % lab2, None])
1501
self.looplabels.pop()
1503
def visitSolar(self, node):
1505
node.axis.visit(self)
1506
node.angle.visit(self)
1507
op2, op1 = self.pop(), self.pop()
1508
self.code.append(["solar2", "%s %s" % (op1, op2)])
1510
self.code.append(["solar1", None])
1511
node.stmt.visit(self)
1512
self.code.append(["end_solar", None])
1514
def visitIlluminate(self, node):
1515
if node.pos: node.pos.visit(self)
1516
if node.axis: node.axis.visit(self)
1517
if node.angle: node.angle.visit(self)
1518
if node.pos and ((node.axis == None) and (node.angle == None)):
1519
self.code.append(["illuminate1", "%s" % self.pop()])
1521
op3, op2, op1 = self.pop(), self.pop(), self.pop()
1522
self.code.append(["illuminate2", "%s %s %s" % (op1, op2, op3)])
1523
if node.stmt: node.stmt.visit(self)
1524
self.code.append(["end_illuminate", None])
1526
def visitIlluminance(self, node):
1527
if node.cat: # category, not used yet
1528
node.cat.visit(self)
1529
category = self.pop()
1530
node.pos.visit(self)
1531
if node.axis: node.axis.visit(self)
1532
if node.angle: node.angle.visit(self)
1534
lab1, lab2 = self.curlabel-1, self.curlabel
1535
self.code.append(["@%d" % lab1, None])
1536
if node.pos and ((node.axis == None) and (node.angle == None)):
1537
self.code.append(["illuminance1", "%s @%d" % (self.pop(), lab2)])
1539
op3, op2, op1 = self.pop(), self.pop(), self.pop()
1540
self.code.append(["illuminance2", "%s %s %s @%d" % (op1, op2, op3, lab2)])
1541
node.stmt.visit(self)
1542
self.code.append(["jmp", "@%d" % lab1])
1543
self.code.append(["@%d" % lab2, None])
1545
def getID(self, node):
1546
# if currently inside a function, only search local scope, unless there are any 'extern' declared variables
1548
for vardict in reversed(self.scopes[-1]): # last first
1549
if vardict.has_key(node.name):
1550
return vardict[node.name]
1551
# not found, search for possibly extern declared vars (global scope)
1552
if self.vars_extern and self.vars_extern.has_key(node.name):
1553
# found, but declared type must be same as that of the global variable
1554
dtp, etp = self.vars_extern[node.name], self.typeof(node.name, False)
1556
raise Exception("Line %d: 'extern' declared variable '%s' is wrong type, declared as '%s', expected '%s'" % (node.lineno, node.name, self.fulltype[dtp[0]], self.fulltype[etp[0]]))
1558
# function declared inside shader or other function, can also access 'parent' scope
1559
scl = len(self.scopes[-1])
1560
for vardict in reversed(self.scopes[-1]):
1562
if vardict.has_key(node.name):
1563
var = vardict[node.name]
1564
# if the variable was found in the very first scope dictionary, then it was a global predefined shader variable
1565
if scl == 0: self.globals_used.add(node.name)
1567
# not found, try parameters
1568
if self.params.has_key(node.name):
1571
raise Exception("Line %d: Unknown 'extern' declared variable '%s'" % (node.lineno, node.name))
1573
# function outside main scope, only search global
1574
if self.scopes[0][0].has_key(node.name):
1575
# add to global list
1576
self.globals_used.add(node.name)
1577
return self.scopes[0][0][node.name]
1579
raise Exception("Line %d: Unknown 'extern' declared variable '%s'" % (node.lineno, node.name))
1581
raise Exception("Line %d: Unknown function variable '%s' (forgot 'extern' declaration?)" % (node.lineno, node.name))
1583
# search the parameter list first, since these are stored separately
1584
# if found, push its name, not the value
1585
if node.name in self.params: return node.name
1586
# search the scopelist from current to older ones for the identifier
1587
scl = len(self.scopes)
1588
for scopelist in reversed(self.scopes):
1590
for vardict in reversed(scopelist):
1591
if vardict.has_key(node.name):
1592
var = vardict[node.name]
1593
# if the variable was found in the very first scope dictionary, then it was a global predefined shader variable
1594
if scl == 0: self.globals_used.add(node.name)
1596
# not found, try parameter name match
1597
if self.params.has_key(node.name):
1600
raise Exception("Line %d: Unknown identifier '%s'" % (node.lineno, node.name))
1602
def visitID(self, node):
1604
self.push(self.getID(node))
1605
# if array index specified, add a move-from-array instruction, unless ID is on the left hand side (move-to-array handled in visitAssignExpr())
1606
if node.array_idx and not self.asgn_lhs:
1608
node.array_idx.visit(self)
1609
op2 = self.pop() # array index
1610
op1t, op2t = self.typeof(op1), self.typeof(op2)
1611
lhs = self.newreg(op2t[0], op2t[1])
1612
self.code.append(["movfa%s" % (op2t[0] + op2t[0]), "%s %s %s" % (lhs, op1, op2)])
1615
def visitUnaryExpr(self, node):
1617
if node.typecast == '-': # not typecast, but a negation
1618
node.expr.visit(self)
1620
op1t = self.typeof(op1)
1621
lhs = self.newreg(op1t[0], op1t[1])
1622
self.code.append(["neg%s" % (op1t[0]*2), "%s %s" % (lhs, op1)])
1625
else: # actual typecast
1626
vartype, spacetype = node.typecast[0], node.typecast[1]
1627
newtype = vartype[0]
1628
if newtype in self.cpn_set: newtype = 'v'
1629
# temporarily reset the current type
1631
if ptd: self.typedet = newtype + self.typedet[1]
1632
node.expr.visit(self)
1634
# nothing to do further if no transform required and expression result is already correct type
1635
if (spacetype == None) and (newtype == self.typeof(self.top())[0]): return # NOTE: do not pop! value must remain on stack in this case
1637
op2t = self.typeof(op2)
1638
# check explicit cast validity
1639
if vartype[0] not in self.typecast_dict[op2t[0]]:
1640
raise Exception("Line %d: Illegal typecast of %s to %s" % (node.lineno, self.fulltype[op2t[0]], vartype))
1641
# add code for implicit type cast if needed (eg. point p = 1)
1643
if op2t[0] == 'f' and (newtype == 'v' or newtype == 'm'):
1644
lhs = self.newreg(newtype, op2t[1])
1645
# don't have to check detail here, since both have same detail
1646
self.code.append(["mov%s" % (newtype + 'f'), "%s %s" % (lhs, op2)])
1648
op2t = self.typeof(op2)
1650
if spacetype: # transform code needed
1651
spacetype.visit(self)
1652
lhs = self.newreg(vartype[0], op2t[1])
1653
self.code.append(["%stocurr" % vartype[0], "%s %s %s" % (lhs, self.pop(), op2)])
1655
else: # only implicit type case or nothing to be done at all, push last expression result (note: have to 're-alloc' to keep regcount in sync!)
1659
self.push(self.newreg(op2t[0], op2t[1]))
1661
def visitBinop(self, node):
1662
node.expr1.visit(self)
1663
node.expr2.visit(self)
1664
# lhs type depends on operand types
1665
op2, op1 = self.stack[-1], self.stack[-2] # cannot pop yet because of possible typecast, must be done after new register request !
1666
op1t, op2t = self.typeof(op1), self.typeof(op2)
1667
# for the 'fv' or 'vf' cases need extra code to make float a vector
1668
if op1t[0] == 'f' and op2t[0] == 'v':
1670
op2, op1 = self.pop(), self.pop()
1671
# multiply, swap operands if type order 'fv', want 'vf'
1672
op1t, op2t = op2t, op1t
1674
else: # for all others need extra mov to make float a vector
1675
lhs = self.newreg('v', op1t[1])
1676
op2, op1 = self.pop(), self.pop()
1677
# no detail check needed, both same
1678
self.code.append(["movvf", "%s %s" % (lhs, op1)])
1680
op1t = 'v' + op1t[1]
1681
elif op1t[0] == 'v' and op2t[0] == 'f' and node.op != '*' and node.op != '/': # need extra mov to make float a vector (unless mul/div)
1682
lhs = self.newreg('v', op2t[1])
1683
op2, op1 = self.pop(), self.pop()
1684
# no detail check needed, both same
1685
self.code.append(["movvf", "%s %s" % (lhs, op2)])
1687
op2t = 'v' + op2t[1]
1690
op2, op1 = self.pop(), self.pop()
1691
# check implicit typecast
1692
rop1t, rop2t = self.typeof(op1, False), self.typeof(op2, False) # 'real' types
1693
if rop1t[0] not in self.typecast_dict[rop2t[0]]:
1694
raise Exception("Line %d: Illegal implicit typecast of %s to %s" % (node.lineno, self.fulltype[rop2t[0]], self.fulltype[rop1t[0]]))
1697
elif node.op == '/':
1699
elif node.op == '+':
1701
elif node.op == '-':
1703
elif node.op == '^':
1705
elif node.op == '.':
1707
else: # never happens
1708
raise Exception("Line %d: Unknown binary operator '%s'?" % (node.lineno, node.op))
1709
# lhs type, use type of first operand (should always be ok, I think...)
1710
if node.op == '.': # vdot returns float
1711
lhs_type = 'f' + op1t[1]
1713
if op1t[1] == 'v' and op2t[1] == 'v': # both varying, lhs varying
1715
elif op1t[1] == 'v' or op2t[1] == 'v': # either varying, lhs varying
1716
lhs_type = op1t[0] + 'v'
1717
else: # both uniform
1718
lhs_type = op1t[0] + 'u'
1719
lhs = self.newreg(lhs_type[0], lhs_type[1])
1720
# last three char part of instruction which consists of the argument types, but only if not vdot or vcross, which don't have any
1721
if node.op != '.' and node.op != '^':
1722
opc += lhs_type[0] + op1t[0] + op2t[0]
1723
# simple optimization, if last codeline was a multiply of the form 'mul x x y' and this line will be '(add|sub) z z x'
1724
# then concatenate the two, modifying previous to 'm(add|sub) z x y' form
1725
last_codeline = self.code[-1]
1727
if self.optimize and (node.op == '+' or node.op == '-') and last_codeline[0][:3] == "mul":
1728
last_ops = last_codeline[1].split()
1730
if last_ops[0] == last_ops[1] and last_ops[0] == op2str and lhs == op1:
1731
# if op2 is only the lhs of the previous line, remove op from tempregs, result register no longer used
1733
for opr in last_ops:
1734
if opr == op2str: op2_count += 1
1736
print "OPTIM 2 rm", op2, "USE", self.reguse[op2]
1737
if self.reguse[op2] == 0:
1738
self.tempregs.remove(op2)
1741
last_codeline[0] = "madd" + last_codeline[0][3:] # same type extension as previous code
1743
last_codeline[0] = "msub" + last_codeline[0][3:] # same type extension as previous code
1744
last_codeline[1] = str(lhs) + last_codeline[1][len(op2str):]
1747
self.code.append(["%s" % opc, "%s %s %s" % (lhs, op1, op2)])
1750
def visitAssignExpr(self, node):
1751
self.asgn_lhs = True # needs to be set here to only add a move-from-array instuction when appearing on rhs
1752
node.lhs.visit(self)
1753
self.asgn_lhs = False # and reset again
1755
op1t = self.typeof(op1)
1756
# set current type and detail to type of lhs
1759
# set flag if rh.expr is assignment as well
1760
pca = self.chain_asgn
1761
if isinstance(node.assign_expr, astAssignExpr_t):
1762
self.chain_asgn = True
1763
node.assign_expr.visit(self)
1765
self.chain_asgn = pca
1766
# reset type & detail
1769
op2t = self.typeof(op2)
1770
# for the 'fv' or 'vf' cases need extra code to make float a vector
1771
if op1t[0] == 'f' and op2t[0] == 'v':
1772
if node.asgnop == "*=":
1773
# multiply, swap operands if type order 'fv', want 'vf'
1774
op1t, op2t = op2t, op1t
1776
else: # for all others need extra mov to make float a vector
1777
lhs = self.newreg('v', op1t[1])
1778
# no detail check needed, both same
1779
self.code.append(["movvf", "%s %s" % (lhs, op1)])
1781
op1t = 'v' + op1t[1]
1782
elif op1t[0] == 'v' and op2t[0] == 'f' and node.asgnop != '*' and node.asgnop != '/': # need extra mov to make float a vector (unless mul/div)
1783
lhs = self.newreg('v', op2t[1])
1784
# no detail check needed, both same
1785
self.code.append(["movvf", "%s %s" % (lhs, op2)])
1787
op2t = 'v' + op2t[1]
1788
# check implicit typecast
1789
rop1t, rop2t = self.typeof(op1, False), self.typeof(op2, False) # 'real' types
1790
if rop1t[0] not in self.typecast_dict[rop2t[0]]:
1791
raise Exception("Line %d: Illegal implicit typecast of %s to %s" % (node.lineno, self.fulltype[rop2t[0]], self.fulltype[rop1t[0]]))
1792
# insctruction lhs type depends on operand types
1793
# use type of first operand (should always be ok, I think...)
1794
if op1t[1] == 'v' and op2t[1] == 'v': # both varying, lhs varying
1796
elif op1t[1] == 'v' or op2t[1] == 'v': # either varying, lhs varying
1797
lhs_type = op1t[0] + 'v'
1798
else: # both uniform
1799
lhs_type = op1t[0] + 'u'
1800
# last two (mov) or three char part of instruction which consists of the argument types
1801
if node.asgnop == '=':
1802
# check for invalid assigment to function parameters that are not declared 'output'
1803
if op1 in self.params:
1804
# shader parameter can be used for output too, typechar is then uppercase
1805
if not self.params[op1][0][0].upper():
1806
raise Exception("Line %d: Cannot assign to shader parameter '%s', not declared 'output'" % (node.lineno, op1))
1807
elif (op1.flags & FF_FPARAM) and ((op1.flags & FF_OUTPUT) == 0):
1808
raise Exception("Line %d: Cannot assign to function parameter '%s', not declared 'output'" % (node.lineno, op1.name))
1809
# simple optimization, if previous line of code is 'opcode op2 ...'
1810
# and this line will be 'mov op1 op2' (but not move-to-array!) then concatenate the two, modifying previous codeline to 'opcode op1 ...'
1811
# the two operands of mov must be same type, but can be different detail, but in case of mixed detail, lhs must be varying
1812
# exception here is if the previous codeline was a madd/msub instruction (eg. x += y*z, cannot replace x)
1813
last_codeline = self.code[-1]
1816
if self.optimize and not (last_codeline[0][:4] == "madd" or last_codeline[0][:4] == "msub") and not node.lhs.array_idx \
1817
and isinstance(op2, register_t) and op1t[0] == op2t[0] and not (op1t[1] == 'u' and op2t[1] == 'v') \
1818
and last_codeline[1] and last_codeline[1][:L] == op2str:
1819
# if op2 is only the lhs of the previous line, remove op from tempregs, result register no longer used
1821
for opr in last_codeline[1].split():
1822
if opr == op2str: op2_count += 1
1824
print "OPTIM 3 rm", op2, "USE", self.reguse[op2]
1825
if self.reguse[op2] == 0:
1826
self.tempregs.remove(op2)
1827
self.code[-1][1] = str(op1) + last_codeline[1][L:]
1828
# if chained assignment, push lhs on stack
1829
if self.chain_asgn: self.push(op1)
1832
# check detail, lhs must be varying if rhs is varying
1833
if op2t[1] == 'v' and op1t[1] != 'v':
1834
raise Exception("Line %d: cannot assign varying to uniform" % node.lineno)
1835
typest = op1t[0] + op2t[0]
1836
if node.lhs.array_idx: # assign to array element
1837
node.lhs.array_idx.visit(self) # !!!
1838
self.code.append(["movta%s" % typest, "%s %s %s" % (op1, self.pop(), op2)])
1840
self.code.append(["mov%s" % typest, "%s %s" % (op1, op2)])
1841
# if chained assignment, push lhs on stack
1842
if self.chain_asgn: self.push(op1)
1843
elif node.asgnop == "+=" or node.asgnop == "-=":
1844
# simple optimization, if last codeline was a multiply of the form 'mul r y z' and this line will be '(add|sub) x x r', 'r' being a register,
1845
# then concatenate the two, modifying previous to 'm(add|sub) x y z' form. 'r' can then also be removed, unless r was not only lhs of the prev.line
1846
last_codeline = self.code[-1]
1848
if self.optimize and last_codeline[0][:3] == "mul" and isinstance(op2, register_t):
1849
last_ops = last_codeline[1].split()
1851
if last_ops[0] == op2str:
1852
# if op2 is only the lhs of the previous line, remove op from tempregs, result register no longer used
1854
for opr in last_ops:
1855
if opr == op2str: op2_count += 1
1857
print "OPTIM 4 rm", op2, "USE", self.reguse[op2]
1858
if self.reguse[op2] == 0:
1859
self.tempregs.remove(op2)
1861
if node.asgnop == "+=":
1862
last_codeline[0] = "madd" + last_codeline[0][3:] # same type extension as previous code
1864
last_codeline[0] = "msub" + last_codeline[0][3:] # same type extension as previous code
1865
last_codeline[1] = str(op1) + last_codeline[1][len(op2str):]
1868
typest = lhs_type[0] + op1t[0] + op2t[0]
1869
if node.asgnop == "+=":
1870
self.code.append(["add%s" % typest, "%s %s %s" % (op1, op1, op2)])
1871
elif node.asgnop == '-=':
1872
self.code.append(["sub%s" % typest, "%s %s %s" % (op1, op1, op2)])
1874
typest = lhs_type[0] + op1t[0] + op2t[0]
1875
if node.asgnop == "*=":
1876
self.code.append(["mul%s" % typest, "%s %s %s" % (op1, op1, op2)])
1877
elif node.asgnop == '/=':
1878
self.code.append(["div%s" % typest, "%s %s %s" % (op1, op1, op2)])
1879
else: # never happens
1880
raise Exception("Line %d: Unknown assignment operator '%s'?!?" % (node.lineno, node.asgnop))
1881
# NOTE: Not entirely sure about this, but the result here might have to be pushed again on to the stack,
1882
# the grammar suggests that an expression of the form: a = (b += c) would also be valid,
1883
# currently this will not compile, TODO (Pixie does produce code, but result is incorrect, not sure about 3dl)
1885
def visitRelation(self, node):
1886
if node.expr1: node.expr1.visit(self)
1887
node.expr2.visit(self)
1889
if node.relop == '!':
1891
lhs = self.newreg('b', self.typeof(op1)[1])
1892
self.code.append(["not", "%s %s" % (lhs, op1)])
1895
elif node.relop == "&&":
1897
elif node.relop == "||":
1901
opc = self.relop_dict[node.relop]
1902
except: # never happens
1903
raise Exception("Line %d: Unknown relop '%s'?!?" % (node.lineno, node.relop))
1904
op2, op1 = self.pop(), self.pop()
1905
op1t, op2t = self.typeof(op1), self.typeof(op2)
1906
if op1t[1] == 'v' and op2t[1] == 'v': # both varying, lhs varying
1908
elif op1t[1] == 'v' or op2t[1] == 'v': # either varying, lhs varying
1910
else: # both uniform
1912
if opc == "and" or opc == "or":
1913
# simple optimization, if previous codeline was of the form 'ifxxxx b2 ...', and this line will be '(and|or) b1 b1 b2'
1914
# AND b1 b2 have the same detail, then concatenate, modifying previous line to "andxxxx b1 ...'
1915
last_codeline = self.code[-1]
1916
if self.optimize and last_codeline[0][:2] == 'if':
1917
last_ops = last_codeline[1].split()
1918
if (last_ops[0] == str(op2)) and (op1t[1] == op2t[1]):
1919
last_codeline[0] = opc + last_codeline[0][2:]
1920
last_codeline[1] = "%s %s %s" % (op1, last_ops[1], last_ops[2])
1921
# NOTE even though op1 is the 'result', cannot just directly push op1 here,
1922
# it is necesary to 're-alloc' it anyway, since otherwise reg.count will go out of sync
1923
self.push(self.newreg(lhs_type[0], lhs_type[1]))
1924
# op2 can now be removed from tempregs
1925
print "OPTIM 5 rm", op2, "USE", self.reguse[op2]
1926
if self.reguse[op2] == 0:
1927
self.tempregs.remove(op2)
1929
lhs = self.newreg(lhs_type[0], lhs_type[1])
1930
self.code.append([opc, "%s %s %s" % (lhs, op1, op2)])
1933
lhs = self.newreg('b', op1t[1])
1934
self.code.append(["if%s" % (opc + op1t[0] + op2t[0]), "%s %s %s" % (lhs, op1, op2)])
1937
# used in visitProcedureCall() below
1938
def lookup_slproc(self, slpnames, argtypes, convert=False):
1943
# convert any cpn to v
1946
if s in self.cpn_set:
1951
# if variadic, only need to match args up to that point
1952
variadic = pargs[1:].rfind('*') # '*' after first char -> 'variadic' function
1954
if pargs[:variadic] == argtypes[:variadic]:
1957
elif pargs == argtypes: # otherwise try exact match
1962
def visitProcedureCall(self, node):
1963
sts = len(self.stack)
1965
for a in reversed(node.args): # note: must be reverse iteration here
1967
argtypes, vargs = "", ""
1968
# get types of all variables pushed on stack
1969
lhs_det = 'u' # determine lhs detail, only uniform if all operands uniform
1970
for i in range(len(self.stack) - sts):
1971
t = self.typeof(self.stack[sts + i], False)
1972
if t[1] == 'v': lhs_det = 'v'
1973
argtypes = t[0] + argtypes
1974
vargs = self.typeof(self.stack[sts + i])[0] + vargs
1976
if slprocedures.slprocs.has_key(node.name):
1977
slp = slprocedures.slprocs[node.name]
1978
elif slprocedures.slcastprocs.has_key(node.name): # cast function
1979
slp = slprocedures.slcastprocs[node.name]
1981
else: # not a built-in function, maybe user-defined?
1983
slp = self.userfuncs[node.name]
1985
raise Exception("Line " + str(node.lineno) + ": Unknown procedure name -> " + node.name)
1987
# userdefined function found, expand inline
1988
self.handleUserFunc(slp)
1991
# dummy function, just pop args, skip functioncall for now, which of course might break things from here...
1992
for i in range(len(self.stack) - sts):
1995
# return type, if cast function, use declared type instead (unless void func.)
1997
tfunc = (rtp == 'T') # 'template' function
1998
if cast and rtp != '*': rtp = self.typedet[0]
1999
# if it has multiple instruction options, pick the one that matches the argument types
2001
if len(slp) == 1: # nothing to choose, only one option
2004
slproc = self.lookup_slproc(slp, argtypes)
2006
# no match found, use the argument string that has all 'cpn' converted to 'v' and try again
2008
slproc = self.lookup_slproc(slp, vargs, True)
2009
# if still nothing, give up
2011
raise Exception("Line %d: Cannot find a match for procedure '%s' and required argument types '%s' (conv.'%s')" % (node.lineno, node.name, argtypes, vargs))
2014
numoprs = len(self.stack) - sts
2015
trtp = None # for 'template' functions, use the first template argument type as the return type
2017
for i in range(numoprs):
2018
if i < len(slproc[1]):
2019
if slproc[1][i] == 'T' and trtp == None: trtp = self.typeof(self.top())
2020
oprs += " " + str(self.pop())
2022
# no operands, set lhs detail to last defined (if set)
2024
lhs_det = self.typedet[1]
2027
# if function is a 'template' function, use the first template argument type as the return type
2029
# if function has no arguments, use the declared type instead... (this means that not all code will compile properly unless adding casts)
2033
rtp = self.typedet[0]
2034
# if cast or template function, add return type char to instruction name
2036
if (cast or tfunc) and rtp != '*':
2037
if rtp in self.cpn_set:
2041
if rtp == '*': # void func, no return value
2042
self.code.append([iname, oprs[1:]])
2044
reg = self.newreg(rtp, lhs_det)
2045
self.code.append([iname, str(reg) + oprs])
2048
# doesn't always work, texture statement not implemented completely yet in VM
2049
def visitTexture(self, node):
2050
# [tex_type, texfilechan, tex_args]
2052
numopr = len(self.stack)
2053
if node.texfilechan: node.texfilechan.visit(self)
2054
if node.tex_args: node.tex_args.visit(self)
2055
numopr = len(self.stack) - numopr
2058
for i in range(numopr):
2059
opt_oprs += str(self.pop())
2060
if i != (numopr - 1): opt_oprs += " "
2061
if node.texfilechan:
2062
lhs = self.newreg(self.typedet[0], self.typedet[1])
2063
ntfc = node.texfilechan
2064
if ntfc.expr and ntfc.array_idx:
2066
if opc[:7] == "texture": opc += self.typedet[0]
2067
self.code.append([opc, "%s %s %s %d %s" % (lhs, ntfc.id, self.pop(), ntfc.array_idx, opt_oprs)])
2070
if opc[:7] == "texture": opc += self.typedet[0]
2071
self.code.append([opc, "%s %s %s %s" % (lhs, ntfc.id, self.pop(), opt_oprs)])
2073
if opc == "texture": opc += self.typedet[0]
2074
self.code.append([opc, "%s %s %s" % (lhs, ntfc.id, opt_oprs)])
2077
def visitTexFileChan(self, node):
2078
# [id, expr, array_idx]
2079
if node.expr: node.expr.visit(self)
2080
#if node.array_idx: node.array_idx.visit(self)
2082
def visitTexArgs(self, node):
2084
for a in reversed(node.args): # must do reverse iteration because of how list is built
2087
def visitFilterstep(self, node):
2088
# edge, s1, s2, paramlist
2089
node.edge.visit(self)
2091
if node.s2: node.s2.visit(self)
2092
if node.paramlist: node.visit.paramlist(self)
2093
if node.s2 and node.paramlist:
2094
raise Exception("Filterstep does not handle paramer lists yet")
2096
op3, op2, op1 = self.pop(), self.pop(), self.pop()
2097
lhs = self.newreg('f', self.typeof(op1)[1])
2098
self.code.append(["filterstep2", "%s %s %s %s" % (lhs, op1, op2, op3)])
2101
op2, op1 = self.pop(), self.pop()
2102
lhs = self.newreg('f', self.typeof(op1)[1])
2103
self.code.append(["filterstep1", "%s %s %s" % (lhs, op1, op2)])
2107
def visitRotate(self, node):
2108
raise Exception("rotate() function not implemented yet")
2110
def visitFresnel(self, node):
2113
node.eta.visit(self)
2116
op5, op4, op3, op2, op1 = self.pop(), self.pop(), self.pop(), self.pop(), self.pop()
2117
# NOTE: all outputs on lhs
2118
if node.R: # implies node.T as well
2119
assert node.T != None
2122
op7, op6 = self.pop(), self.pop()
2123
self.code.append(["fresnel2", "%s %s %s %s %s %s %s" % (op4, op5, op6, op7, op1, op2, op3)])
2125
self.code.append(["fresnel1", "%s %s %s %s %s" % (op4, op5, op1, op2, op3)])
2127
# main, starts ast traversal and builds code
2128
def visit(self, node):
2131
# simple optimization, any "cond_pop" instructions at end of code can be removed,
2132
# since by definition, no code can follow it, it's kind of pointless to execute it anyway...
2134
while self.code[-1][0] == "cond_pop":
2136
# check that the stack is now empty, if it ain't, there has to be some serious error somewhere...
2137
if len(self.stack) != 0:
2138
raise Exception("Stack is not empty! -> %s" % self.stack)
2140
assert self.shader_name != None
2141
fp = open(self.shader_name + ".sqd", 'w')
2142
fp.write("# QDSLC version 0.0.1-alpha\n") # which it probably will always remain...
2143
fp.write("%s\n" % self.code[0])
2145
for name, val in self.params.iteritems():
2146
fp.write("param %s %s " % (self.fulldetail[val[0][1]], self.fulltype[val[0][0].lower()]))
2147
if isinstance(val[1], list): # array
2151
fp.write("%s[%d] %s\n" % (name, len(val[1]), st[:-1]))
2153
fp.write("%s %s\n" % (name, str(val[1])))
2155
for v in self.variables:
2157
fp.write("temp %s %s %s[%d]\n" % (self.fulldetail[v.detail], self.fulltype[v.type], str(v), v.arlen))
2159
fp.write("temp %s %s %s\n" % (self.fulldetail[v.detail], self.fulltype[v.type], str(v)))
2161
# if 'simplify_regnames' set, first change all register names to a simpler representation, '$number'
2162
if self.simplify_regnames:
2165
for r in self.tempregs:
2166
sreg[str(r)] = "$%d" % numreg
2168
for r in self.tempregs:
2169
fp.write("temp %s %s %s\n" % (self.fulldetail[r.detail], self.fulltype[r.type], sreg[str(r)]))
2171
for r in self.tempregs:
2172
fp.write("temp %s %s %s\n" % (self.fulldetail[r.detail], self.fulltype[r.type], str(r)))
2174
# also simplify the register names if needed
2175
if self.simplify_regnames:
2176
for c in self.const_byval:
2177
td = self.const_byval[c]
2178
sreg[str(td)] = "$%d" % numreg
2180
for c in self.const_byval:
2181
td = self.const_byval[c]
2182
fp.write("const %s %s %s\n" % (self.fulltype[td.type], sreg[str(td)], str(c)))
2184
for c in self.const_byval:
2185
td = self.const_byval[c]
2186
fp.write("const %s %s %s\n" % (self.fulltype[td.type], str(td), str(c)))
2188
if len(self.globals_used):
2190
for v in self.globals_used:
2192
fp.write("%s\n" % globstr)
2194
if len(self.initcode):
2195
fp.write("codesegment @1\n") # should always be 1...
2197
fp.write("codesegment\n")
2200
if len(self.initcode):
2201
if self.simplify_regnames:
2202
for cl in self.initcode:
2204
fp.write("\t%s%s" % (cl[0], spacest[:16-len(cl[0])]))
2205
for op in cl[1].split():
2207
fp.write(" %s" % sreg[op])
2209
fp.write(" %s" % op)
2212
fp.write("\t%s\n" % cl[0])
2214
for cl in self.initcode:
2216
fp.write("\t%s%s%s\n" % (cl[0], spacest[:16-len(cl[0])], cl[1]))
2218
fp.write("\t%s\n" % cl[0])
2220
if self.simplify_regnames:
2221
for cl in self.code[1:]:
2223
fp.write("\t%s%s" % (cl[0], spacest[:16-len(cl[0])]))
2224
for op in cl[1].split():
2226
fp.write(" %s" % sreg[op])
2228
fp.write(" %s" % op)
2231
if cl[0][0] == '@': # label, no tab
2232
fp.write("%s\n" % cl[0])
2234
fp.write("\t%s\n" % cl[0])
2236
for cl in self.code[1:]:
2238
fp.write("\t%s%s%s\n" % (cl[0], spacest[:16-len(cl[0])], cl[1]))
2240
if cl[0][0] == '@': # label, no tab
2241
fp.write("%s\n" % cl[0])
2243
fp.write("\t%s\n" % cl[0])
2244
fp.write("\treturn\n")