/kitten/tools/shader.py

https://gitlab.com/depp/gamedev · Python · 127 lines · 113 code · 7 blank · 7 comment · 18 complexity · fe9daf1613711c2c8796f1afb23864ca MD5 · raw file

  1. # Copyright 2016 Dietrich Epp.
  2. #
  3. # This file is part of Kitten Teleporter. The Kitten Teleporter source
  4. # code is distributed under the terms of the MIT license.
  5. # See LICENSE.txt for details.
  6. """GLSL shader processor."""
  7. import collections
  8. import io
  9. import json
  10. import os
  11. import re
  12. import yaml
  13. from . import build
  14. Shader = collections.namedtuple('Shader', 'text attributes uniforms')
  15. DECL = re.compile(
  16. r'\s*(attribute|uniform)\s+'
  17. r'(?:.*\s+)?'
  18. r'(\w+)'
  19. r'\s*(?:\[[^]]*\]\s*)?;'
  20. );
  21. EXTS = {'.vert', '.frag'}
  22. def process_glsl(config, path):
  23. """Process a GLSL shader."""
  24. with open(path) as fp:
  25. text = fp.read()
  26. lines = []
  27. attributes = []
  28. uniforms = []
  29. for line in text.splitlines():
  30. line = line.strip()
  31. lines.append(line)
  32. m = DECL.match(line)
  33. if m:
  34. type_, name = m.groups()
  35. if type_ == 'attribute':
  36. attributes.append(name)
  37. else:
  38. uniforms.append(name)
  39. return Shader('\n'.join(lines), attributes, uniforms)
  40. def as_list(x):
  41. if isinstance(x, str):
  42. return x.split()
  43. if isinstance(x, list):
  44. return x
  45. raise TypeError('not a list or string')
  46. def process_all(config, info_path, shader_paths):
  47. path_map = {os.path.basename(path): path for path in shader_paths}
  48. with open(info_path) as fp:
  49. info = yaml.safe_load(fp)
  50. shaders = {}
  51. fp = io.StringIO()
  52. fp.write(
  53. '// This file is automatically generated.\n'
  54. "import * as shader from './shader_defs';\n")
  55. for name, value in sorted(info.items()):
  56. slist = {}
  57. attributes = set()
  58. uniforms = set()
  59. for stype in ('vert', 'frag'):
  60. pshaders = as_list(value[stype])
  61. for shader in pshaders:
  62. fname = '{}.{}'.format(shader, stype)
  63. try:
  64. sinfo = shaders[fname]
  65. except KeyError:
  66. sinfo = process_glsl(config, path_map[fname])
  67. shaders[fname] = sinfo
  68. attributes.update(sinfo.attributes)
  69. uniforms.update(sinfo.uniforms)
  70. slist[stype] = json.dumps(' '.join(sorted(pshaders)))
  71. iattr = as_list(value['attributes'])
  72. if sorted(iattr) != sorted(attributes):
  73. print('{}: warning: attribute mismatch'.format(name))
  74. print('{}: expected attributes: {}'
  75. .format(name, ' '.join(sorted(attributes))))
  76. fp.write(
  77. '\n'
  78. 'export interface {name} extends shader.Program {{\n'
  79. '{ulines}'
  80. '}}\n'
  81. 'const {name}Info: shader.ProgramInfo = {{\n'
  82. '\tname: {jname},\n'
  83. '\tvert: {vert},\n'
  84. '\tfrag: {frag},\n'
  85. '\tunif: {uniforms},\n'
  86. '\tattr: {attributes},\n'
  87. '}};\n'
  88. 'export function {fname}'
  89. '(gl: WebGLRenderingContext, vert: string, frag: string): '
  90. '{name} {{\n'
  91. '\treturn <{name}> shader.loadProgram('
  92. 'gl, Sources, {name}Info, vert, frag);\n'
  93. '}}\n'
  94. .format(
  95. name=name + 'Program',
  96. fname=name[0].lower() + name[1:] + 'Program',
  97. jname=json.dumps(name),
  98. vert=slist['vert'],
  99. frag=slist['frag'],
  100. uniforms=json.dumps(' '.join(sorted(uniforms))),
  101. attributes=json.dumps(' '.join(iattr)),
  102. ulines=''.join('\t{}: WebGLUniformLocation;\n'.format(x)
  103. for x in sorted(uniforms)),
  104. ))
  105. fp.write('\n')
  106. fp.write(
  107. 'const Sources: {{ [name: string]: string }} = {};\n'
  108. .format(json.dumps(
  109. {k: v.text for k, v in shaders.items()},
  110. sort_keys=True, indent=2)))
  111. return fp.getvalue()
  112. if __name__ == '__main__':
  113. def main():
  114. root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  115. sroot = os.path.join(root, 'shader')
  116. text = process_all({}, os.path.join(sroot, 'info.yaml'),
  117. build.all_files(sroot, exts=EXTS))
  118. import sys
  119. sys.stdout.write(text)
  120. main()