def onUse(self, args) : if not self.current or not self.current.getTag() in ['block', 'vs', 'fs'] : util.fmtError("@use must come after @block, @vs or @fs!") if len(args) < 1: util.fmtError("@use must have at least one arg!") for arg in args : self.current.dependencies.append(Reference(arg, self.fileName, self.lineNumber))
def parseOutput(output, lines) : ''' Parse error output lines from the GLSL reference compiler, map them to the original source code location and output an error message compatible with Xcode or VStudio ''' outLines = output.splitlines() for outLine in outLines : if outLine.startswith('ERROR: ') : # extract generated shader source column, line and message colStartIndex = 7 colEndIndex = outLine.find(':', colStartIndex) if colEndIndex == -1 : continue lineStartIndex = colEndIndex + 1 lineEndIndex = outLine.find(':', lineStartIndex) if lineEndIndex == -1 : continue msgStartIndex = lineEndIndex + 1 colNr = int(outLine[colStartIndex:colEndIndex]) lineNr = int(outLine[lineStartIndex:lineEndIndex]) msg = outLine[msgStartIndex:] # map to original location lineIndex = lineNr - 1 if lineIndex >= len(lines) : lineIndex = len(lines) - 1 srcPath = lines[lineIndex].path srcLineNr = lines[lineIndex].lineNumber # and output... util.setErrorLocation(srcPath, srcLineNr) util.fmtError(msg)
def onProgram(self, args): if not self.current or self.current.getTag() != 'bundle': util.fmtError("@program must come after @bundle!") if len(args) != 2: util.fmtError("@program must have 2 args (vs fs)") vs = args[0] fs = args[1] self.current.programs.append(Program(vs, fs))
def onEnd(self, args): if not self.current or not self.current.getTag() in [ 'block', 'vs', 'fs', 'bundle' ]: util.fmtError("@end must come after @block, @vs, @fs or @bundle!") if len(args) != 0: util.fmtError("@end must not have arguments") self.current = None
def onProgram(self, args) : if not self.current or self.current.getTag() != 'bundle' : util.fmtError("@program must come after @bundle!") if len(args) != 2: util.fmtError("@program must have 2 args (vs fs)") vs = args[0] fs = args[1] self.current.programs.append(Program(vs, fs, self.fileName, self.lineNumber))
def parseTags(self, line): # first check if the line contains a tag, the tag must be # alone on the line tagStartIndex = line.find('@') if tagStartIndex != -1: if tagStartIndex > 0: util.fmtError("only whitespace allowed in front of tag") if line.find(';') != -1: util.fmtError("no semicolons allowed in tag lines") tagAndArgs = line[tagStartIndex + 1:].split() tag = tagAndArgs[0] args = tagAndArgs[1:] if tag == 'block': self.onBlock(args) elif tag == 'vs': self.onVertexShader(args) elif tag == 'fs': self.onFragmentShader(args) elif tag == 'bundle': self.onBundle(args) elif tag == 'use': self.onUse(args) elif tag == 'in': self.onIn(args) elif tag == 'out': self.onOut(args) elif tag == 'uniform': self.onUniform(args) elif tag == 'highp': self.onPrecision(args) elif tag == 'program': self.onProgram(args) elif tag == 'end': self.onEnd(args) else: util.fmtError("unrecognized @ tag '{}'".format(tag)) return '' # handle any $ macros for macro in macroKeywords: startIndex = line.find(macro) if startIndex != -1: if self.current is not None: line = line.replace(macro, macroKeywords[macro]) if macroKeywords[macro] not in self.current.macros: self.current.macros.append(macroKeywords[macro]) else: util.fmtError( '$ tag must come after @block, @vs, @fs and before @end' ) # if there are still $ characters in the line, it must be an # unrecognized macro keyword (typo?) if '$' in line: util.fmtError('unrecognized $ keyword!') return line
def parseTags(self, line) : # first check if the line contains a tag, the tag must be # alone on the line tagStartIndex = line.find('@') if tagStartIndex != -1 : if tagStartIndex > 0 : util.fmtError("only whitespace allowed in front of tag") if line.find(';') != -1 : util.fmtError("no semicolons allowed in tag lines") tagAndArgs = line[tagStartIndex+1 :].split() tag = tagAndArgs[0] args = tagAndArgs[1:] if tag == 'block': self.onBlock(args) elif tag == 'vs': self.onVertexShader(args) elif tag == 'fs': self.onFragmentShader(args) elif tag == 'bundle': self.onBundle(args) elif tag == 'use': self.onUse(args) elif tag == 'in': self.onIn(args) elif tag == 'out': self.onOut(args) elif tag == 'uniform': self.onUniform(args) elif tag == 'highp' : self.onPrecision(args) elif tag == 'program': self.onProgram(args) elif tag == 'end': self.onEnd(args) else : util.fmtError("unrecognized @ tag '{}'".format(tag)) return '' # handle any $ macros for macro in macroKeywords : startIndex = line.find(macro) if startIndex != -1 : if self.current is not None: line = line.replace(macro, macroKeywords[macro]) if macroKeywords[macro] not in self.current.macros : self.current.macros.append(macroKeywords[macro]) else : util.fmtError('$ tag must come after @block, @vs, @fs and before @end') # if there are still $ characters in the line, it must be an # unrecognized macro keyword (typo?) if '$' in line : util.fmtError('unrecognized $ keyword!') return line
def resolveDeps(self, shd, dep): ''' Recursively resolve dependencies for a shader. ''' # just add new dependencies at the end of resolvedDeps, # and remove dups in a second pass after recursion if not dep.name in self.blocks: util.setErrorLocation(dep.path, dep.lineNumber) util.fmtError("unknown block dependency '{}'".format(dep.name)) shd.resolvedDeps.append(dep.name) for depdep in self.blocks[dep.name].dependencies: self.resolveDeps(shd, depdep)
def resolveDeps(self, shd, dep) : ''' Recursively resolve dependencies for a shader. ''' # just add new dependencies at the end of resolvedDeps, # and remove dups in a second pass after recursion if not dep.name in self.blocks : util.setErrorLocation(dep.path, dep.lineNumber) util.fmtError("unknown block dependency '{}'".format(dep.name)) shd.resolvedDeps.append(dep.name) for depdep in self.blocks[dep.name].dependencies : self.resolveDeps(shd, depdep)
def checkAddUniform(self, uniform, list): ''' Check if uniform already exists in list, if yes check if type and binding matches, if not write error. If uniform doesn't exist yet in list, add it. ''' listUniform = findByName(uniform.name, list) if listUniform is not None: # redundant uniform, check if type and binding name match if listUniform.type != uniform.type: util.setErrorLocation(uniform.filePath, uniform.lineNumber) util.fmtError( "uniform type mismatch '{}' vs '{}'".format( uniform.type, listUniform.type), False) util.setErrorLocation(listUniform.filePath, listUniform.lineNumber) util.fmtError("uniform type mismatch '{}' vs '{}'".format( listUniform.type, uniform.type)) if listUniform.bind != uniform.bind: util.setErrorLocation(uniform.filePath, uniform.lineNumber) util.fmtError( "uniform bind name mismatch '{}' vs '{}'".format( uniform.bind, listUniform.bind), False) util.setErrorLocation(listUniform.filePath, listUniform.lineNumber) util.fmtError("uniform bind name mismatch '{}' vs '{}'".format( listUniform.bind, uniform.bind)) else: # new uniform from block, add to list list.append(uniform)
def resolveBundleUniforms(self, bundle) : ''' Gathers all uniforms from all shaders in the bundle. ''' for program in bundle.programs : if program.vs not in self.vertexShaders : util.setErrorLocation(program.filePath, program.lineNumber) util.fmtError("unknown vertex shader '{}'".format(program.vs)) for uniform in self.vertexShaders[program.vs].uniforms : self.checkAddUniform(uniform, bundle.uniforms) if program.fs not in self.fragmentShaders : util.setErrorLocation(program.filePath, program.lineNumber) util.fmtError("unknown fragment shader '{}'".format(program.fs)) for uniform in self.fragmentShaders[program.fs].uniforms : self.checkAddUniform(uniform, bundle.uniforms)
def parseSource(self, fileName) : ''' Parse a single file and populate shader lib ''' print '=> parsing {}'.format(fileName) f = open(fileName, 'r') self.fileName = fileName self.lineNumber = 0 for line in f : util.setErrorLocation(self.fileName, self.lineNumber) self.parseLine(line) self.lineNumber += 1 f.close() # all blocks must be closed if self.current is not None : util.fmtError('missing @end at end of file')
def stripComments(self, line): ''' Remove comments from a single line, can carry over to next or from previous line. ''' done = False while not done: # if currently in comment, look for end-of-comment if self.inComment: endIndex = line.find('*/') if endIndex == -1: # entire line is comment if '/*' in line or '//' in line: util.fmtError('comment in comment!') else: return '' else: comment = line[:endIndex + 2] if '/*' in comment or '//' in comment: util.fmtError('comment in comment!') else: line = line[endIndex + 2:] self.inComment = False # clip off winged comment (if exists) wingedIndex = line.find('//') if wingedIndex != -1: line = line[:wingedIndex] # look for start of comment startIndex = line.find('/*') if startIndex != -1: # ...and for the matching end... endIndex = line.find('*/', startIndex) if endIndex != -1: line = line[:startIndex] + line[endIndex + 2:] else: # comment carries over to next line self.inComment = True line = line[:startIndex] done = True else: # no comment until end of line, done done = True line = line.strip(' \t\n\r') return line
def stripComments(self, line) : ''' Remove comments from a single line, can carry over to next or from previous line. ''' done = False while not done : # if currently in comment, look for end-of-comment if self.inComment : endIndex = line.find('*/') if endIndex == -1 : # entire line is comment if '/*' in line or '//' in line : util.fmtError('comment in comment!') else : return '' else : comment = line[:endIndex+2] if '/*' in comment or '//' in comment : util.fmtError('comment in comment!') else : line = line[endIndex+2:] self.inComment = False # clip off winged comment (if exists) wingedIndex = line.find('//') if wingedIndex != -1 : line = line[:wingedIndex] # look for start of comment startIndex = line.find('/*') if startIndex != -1 : # ...and for the matching end... endIndex = line.find('*/', startIndex) if endIndex != -1 : line = line[:startIndex] + line[endIndex+2:] else : # comment carries over to next line self.inComment = True line = line[:startIndex] done = True else : # no comment until end of line, done done = True; line = line.strip(' \t\n\r') return line
def parseSource(self, fileName): ''' Parse a single file and populate shader lib ''' print '=> parsing {}'.format(fileName) f = open(fileName, 'r') self.fileName = fileName self.lineNumber = 0 for line in f: util.setErrorLocation(self.fileName, self.lineNumber) self.parseLine(line) self.lineNumber += 1 f.close() # all blocks must be closed if self.current is not None: util.fmtError('missing @end at end of file')
def onPrecision(self, args) : if not self.current or not self.current.getTag() in ['vs', 'fs'] : util.fmtError("@highp must come after @vs or @fs!") if len(args) != 1: util.fmtError("@highp must have 1 arg (type)") type = args[0] if checkListDup(type, self.current.highPrecision) : util.fmtError("@highp type '{}' already defined in '{}'!".format(type, self.current.name)) self.current.highPrecision.append(type)
def onOut(self, args) : if not self.current or not self.current.getTag() in ['vs'] : util.fmtError("@out must come after @vs!") if len(args) != 2: util.fmtError("@out must have 2 args (type name)") type = args[0] name = args[1] if checkListDup(name, self.current.outputs) : util.fmtError("@out '{}' already defined in '{}'!".format(name, self.current.name)) self.current.outputs.append(Attr(type, name, self.fileName, self.lineNumber))
def onBlock(self, args) : if len(args) != 1 : util.fmtError("@block must have 1 arg (name)") if self.current is not None : util.fmtError("cannot nest @block (missing @end in '{}'?)".format(self.current.name)) name = args[0] if name in self.shaderLib.blocks : util.fmtError("@block '{}' already defined".format(name)) block = Block(name) self.shaderLib.blocks[name] = block self.current = block
def onBundle(self, args) : if len(args) != 1: util.fmtError("@bundle must have 1 arg (name)") if self.current is not None : util.fmtError("cannot nest @bundle (missing @end tag in '{}'?)".format(self.current.name)) name = args[0] if name in self.shaderLib.bundles : util.fmtError("@bundle '{}' already defined!".format(name)) bundle = Bundle(name) self.shaderLib.bundles[name] = bundle self.current = bundle
def onFragmentShader(self, args) : if len(args) != 1: util.fmtError("@fs must have 1 arg (name)") if self.current is not None : util.fmtError("cannot nest @fs (missing @end in '{}'?)".format(self.current.name)) name = args[0] if name in self.shaderLib.fragmentShaders : util.fmtError("@fs '{}' already defined!".format(name)) fs = FragmentShader(name) self.shaderLib.fragmentShaders[name] = fs self.current = fs
def onUniform(self, args) : if not self.current or not self.current.getTag() in ['block', 'vs', 'fs'] : util.fmtError("@uniform must come after @block, @vs or @fs tag!") if len(args) != 3: util.fmtError("@uniform must have 3 args (type name binding)") type = args[0] name = args[1] bind = args[2] if checkListDup(name, self.current.uniforms) : util.fmtError("@uniform '{}' already defined in '{}'!".format(name, self.current.name)) self.current.uniforms.append(Uniform(type, name, bind, self.fileName, self.lineNumber))
def onVertexShader(self, args) : if len(args) != 1: util.fmtError("@vs must have 1 arg (name)") if self.current is not None : util.fmtError("cannot nest @vs (missing @end in '{}'?)".format(self.current.name)) name = args[0] if name in self.shaderLib.vertexShaders : util.fmtError("@vs '{}' already defined".format(name)) vs = VertexShader(name) self.shaderLib.vertexShaders[name] = vs self.current = vs
def checkAddUniform(self, uniform, list) : ''' Check if uniform already exists in list, if yes check if type and binding matches, if not write error. If uniform doesn't exist yet in list, add it. ''' listUniform = findByName(uniform.name, list) if listUniform is not None: # redundant uniform, check if type and binding name match if listUniform.type != uniform.type : util.setErrorLocation(uniform.filePath, uniform.lineNumber) util.fmtError("uniform type mismatch '{}' vs '{}'".format(uniform.type, listUniform.type), False) util.setErrorLocation(listUniform.filePath, listUniform.lineNumber) util.fmtError("uniform type mismatch '{}' vs '{}'".format(listUniform.type, uniform.type)) if listUniform.bind != uniform.bind : util.setErrorLocation(uniform.filePath, uniform.lineNumber) util.fmtError("uniform bind name mismatch '{}' vs '{}'".format(uniform.bind, listUniform.bind), False) util.setErrorLocation(listUniform.filePath, listUniform.lineNumber) util.fmtError("uniform bind name mismatch '{}' vs '{}'".format(listUniform.bind, uniform.bind)) else : # new uniform from block, add to list list.append(uniform)
def onEnd(self, args) : if not self.current or not self.current.getTag() in ['block', 'vs', 'fs', 'bundle'] : util.fmtError("@end must come after @block, @vs, @fs or @bundle!") if len(args) != 0: util.fmtError("@end must not have arguments") self.current = None