1bf215546Sopenharmony_ci#! /usr/bin/env python3 2bf215546Sopenharmony_ci# Taken from Crucible and modified to parse declarations 3bf215546Sopenharmony_ci 4bf215546Sopenharmony_ciimport argparse 5bf215546Sopenharmony_ciimport io 6bf215546Sopenharmony_ciimport os 7bf215546Sopenharmony_ciimport re 8bf215546Sopenharmony_ciimport shutil 9bf215546Sopenharmony_ciimport struct 10bf215546Sopenharmony_ciimport subprocess 11bf215546Sopenharmony_ciimport sys 12bf215546Sopenharmony_ciimport tempfile 13bf215546Sopenharmony_cifrom textwrap import dedent 14bf215546Sopenharmony_ci 15bf215546Sopenharmony_ciclass ShaderCompileError(RuntimeError): 16bf215546Sopenharmony_ci def __init__(self, *args): 17bf215546Sopenharmony_ci super(ShaderCompileError, self).__init__(*args) 18bf215546Sopenharmony_ci 19bf215546Sopenharmony_citarget_env_re = re.compile(r'QO_TARGET_ENV\s+(\S+)') 20bf215546Sopenharmony_ci 21bf215546Sopenharmony_cistage_to_glslang_stage = { 22bf215546Sopenharmony_ci 'VERTEX': 'vert', 23bf215546Sopenharmony_ci 'TESS_CONTROL': 'tesc', 24bf215546Sopenharmony_ci 'TESS_EVALUATION': 'tese', 25bf215546Sopenharmony_ci 'GEOMETRY': 'geom', 26bf215546Sopenharmony_ci 'FRAGMENT': 'frag', 27bf215546Sopenharmony_ci 'COMPUTE': 'comp', 28bf215546Sopenharmony_ci} 29bf215546Sopenharmony_ci 30bf215546Sopenharmony_cibase_layout_qualifier_id_re = r'({0}\s*=\s*(?P<{0}>\d+))' 31bf215546Sopenharmony_ciid_re = '(?P<name_%d>[^(gl_)]\S+)' 32bf215546Sopenharmony_citype_re = '(?P<dtype_%d>\S+)' 33bf215546Sopenharmony_cilocation_re = base_layout_qualifier_id_re.format('location') 34bf215546Sopenharmony_cicomponent_re = base_layout_qualifier_id_re.format('component') 35bf215546Sopenharmony_cibinding_re = base_layout_qualifier_id_re.format('binding') 36bf215546Sopenharmony_ciset_re = base_layout_qualifier_id_re.format('set') 37bf215546Sopenharmony_ciunk_re = r'\S+(=\d+)?' 38bf215546Sopenharmony_cilayout_qualifier_re = r'layout\W*\((%s)+\)' % '|'.join([location_re, binding_re, set_re, unk_re, '[, ]+']) 39bf215546Sopenharmony_ciubo_decl_re = 'uniform\W+%s(\W*{)?(?P<type_ubo>)' % (id_re%0) 40bf215546Sopenharmony_cissbo_decl_re = 'buffer\W+%s(\W*{)?(?P<type_ssbo>)' % (id_re%1) 41bf215546Sopenharmony_ciimage_buffer_decl_re = r'uniform\W+imageBuffer\w+%s;(?P<type_img_buf>)' % (id_re%2) 42bf215546Sopenharmony_ciimage_decl_re = r'uniform\W+image\S+\W+%s;(?P<type_img>)' % (id_re%3) 43bf215546Sopenharmony_citexture_buffer_decl_re = r'uniform\W+textureBuffer\w+%s;(?P<type_tex_buf>)' % (id_re%4) 44bf215546Sopenharmony_cicombined_texture_sampler_decl_re = r'uniform\W+sampler\S+\W+%s;(?P<type_combined>)' % (id_re%5) 45bf215546Sopenharmony_citexture_decl_re = r'uniform\W+texture\S+\W+%s;(?P<type_tex>)' % (id_re%6) 46bf215546Sopenharmony_cisampler_decl_re = r'uniform\W+sampler\w+%s;(?P<type_samp>)' % (id_re%7) 47bf215546Sopenharmony_ciinput_re = r'in\W+%s\W+%s;(?P<type_in>)' % (type_re%0, id_re%8) 48bf215546Sopenharmony_cioutput_re = r'out\W+%s\W+%s;(?P<type_out>)' % (type_re%1, id_re%9) 49bf215546Sopenharmony_cimatch_decl_re = re.compile(layout_qualifier_re + r'\W*((' + r')|('.join([ubo_decl_re, ssbo_decl_re, image_buffer_decl_re, image_decl_re, texture_buffer_decl_re, combined_texture_sampler_decl_re, texture_decl_re, sampler_decl_re, input_re, output_re]) + r'))$') 50bf215546Sopenharmony_ci 51bf215546Sopenharmony_ciclass Shader: 52bf215546Sopenharmony_ci def __init__(self, stage): 53bf215546Sopenharmony_ci self.glsl = None 54bf215546Sopenharmony_ci self.stream = io.StringIO() 55bf215546Sopenharmony_ci self.stage = stage 56bf215546Sopenharmony_ci self.dwords = None 57bf215546Sopenharmony_ci self.target_env = "" 58bf215546Sopenharmony_ci self.declarations = [] 59bf215546Sopenharmony_ci 60bf215546Sopenharmony_ci def add_text(self, s): 61bf215546Sopenharmony_ci self.stream.write(s) 62bf215546Sopenharmony_ci 63bf215546Sopenharmony_ci def finish_text(self, start_line, end_line): 64bf215546Sopenharmony_ci self.glsl = self.stream.getvalue() 65bf215546Sopenharmony_ci self.stream = None 66bf215546Sopenharmony_ci 67bf215546Sopenharmony_ci # Handle the QO_EXTENSION macro 68bf215546Sopenharmony_ci self.glsl = self.glsl.replace('QO_EXTENSION', '#extension') 69bf215546Sopenharmony_ci 70bf215546Sopenharmony_ci # Handle the QO_DEFINE macro 71bf215546Sopenharmony_ci self.glsl = self.glsl.replace('QO_DEFINE', '#define') 72bf215546Sopenharmony_ci 73bf215546Sopenharmony_ci m = target_env_re.search(self.glsl) 74bf215546Sopenharmony_ci if m: 75bf215546Sopenharmony_ci self.target_env = m.group(1) 76bf215546Sopenharmony_ci self.glsl = self.glsl.replace('QO_TARGET_ENV', '// --target-env') 77bf215546Sopenharmony_ci 78bf215546Sopenharmony_ci self.start_line = start_line 79bf215546Sopenharmony_ci self.end_line = end_line 80bf215546Sopenharmony_ci 81bf215546Sopenharmony_ci def __run_glslang(self, extra_args=[]): 82bf215546Sopenharmony_ci stage = stage_to_glslang_stage[self.stage] 83bf215546Sopenharmony_ci stage_flags = ['-S', stage] 84bf215546Sopenharmony_ci 85bf215546Sopenharmony_ci in_file = tempfile.NamedTemporaryFile(suffix='.'+stage) 86bf215546Sopenharmony_ci src = ('#version 450\n' + self.glsl).encode('utf-8') 87bf215546Sopenharmony_ci in_file.write(src) 88bf215546Sopenharmony_ci in_file.flush() 89bf215546Sopenharmony_ci out_file = tempfile.NamedTemporaryFile(suffix='.spirv') 90bf215546Sopenharmony_ci args = [glslang, '-H'] + extra_args + stage_flags 91bf215546Sopenharmony_ci if self.target_env: 92bf215546Sopenharmony_ci args += ['--target-env', self.target_env] 93bf215546Sopenharmony_ci args += ['-o', out_file.name, in_file.name] 94bf215546Sopenharmony_ci with subprocess.Popen(args, 95bf215546Sopenharmony_ci stdout = subprocess.PIPE, 96bf215546Sopenharmony_ci stderr = subprocess.PIPE, 97bf215546Sopenharmony_ci stdin = subprocess.PIPE) as proc: 98bf215546Sopenharmony_ci 99bf215546Sopenharmony_ci out, err = proc.communicate(timeout=30) 100bf215546Sopenharmony_ci in_file.close() 101bf215546Sopenharmony_ci 102bf215546Sopenharmony_ci if proc.returncode != 0: 103bf215546Sopenharmony_ci # Unfortunately, glslang dumps errors to standard out. 104bf215546Sopenharmony_ci # However, since we don't really want to count on that, 105bf215546Sopenharmony_ci # we'll grab the output of both 106bf215546Sopenharmony_ci message = out.decode('utf-8') + '\n' + err.decode('utf-8') 107bf215546Sopenharmony_ci raise ShaderCompileError(message.strip()) 108bf215546Sopenharmony_ci 109bf215546Sopenharmony_ci out_file.seek(0) 110bf215546Sopenharmony_ci spirv = out_file.read() 111bf215546Sopenharmony_ci out_file.close() 112bf215546Sopenharmony_ci return (spirv, out) 113bf215546Sopenharmony_ci 114bf215546Sopenharmony_ci def _parse_declarations(self): 115bf215546Sopenharmony_ci for line in self.glsl.splitlines(): 116bf215546Sopenharmony_ci res = re.match(match_decl_re, line.lstrip().rstrip()) 117bf215546Sopenharmony_ci if res == None: 118bf215546Sopenharmony_ci continue 119bf215546Sopenharmony_ci res = {k:v for k, v in res.groupdict().items() if v != None} 120bf215546Sopenharmony_ci name = [v for k, v in res.items() if k.startswith('name_')][0] 121bf215546Sopenharmony_ci data_type = ([v for k, v in res.items() if k.startswith('dtype_')] + [''])[0] 122bf215546Sopenharmony_ci decl_type = [k for k, v in res.items() if k.startswith('type_')][0][5:] 123bf215546Sopenharmony_ci location = int(res.get('location', 0)) 124bf215546Sopenharmony_ci component = int(res.get('component', 0)) 125bf215546Sopenharmony_ci binding = int(res.get('binding', 0)) 126bf215546Sopenharmony_ci desc_set = int(res.get('set', 0)) 127bf215546Sopenharmony_ci self.declarations.append('{"%s", "%s", QoShaderDeclType_%s, %d, %d, %d, %d}' % 128bf215546Sopenharmony_ci (name, data_type, decl_type, location, component, binding, desc_set)) 129bf215546Sopenharmony_ci 130bf215546Sopenharmony_ci def compile(self): 131bf215546Sopenharmony_ci def dwords(f): 132bf215546Sopenharmony_ci while True: 133bf215546Sopenharmony_ci dword_str = f.read(4) 134bf215546Sopenharmony_ci if not dword_str: 135bf215546Sopenharmony_ci return 136bf215546Sopenharmony_ci assert len(dword_str) == 4 137bf215546Sopenharmony_ci yield struct.unpack('I', dword_str)[0] 138bf215546Sopenharmony_ci 139bf215546Sopenharmony_ci (spirv, assembly) = self.__run_glslang() 140bf215546Sopenharmony_ci self.dwords = list(dwords(io.BytesIO(spirv))) 141bf215546Sopenharmony_ci self.assembly = str(assembly, 'utf-8') 142bf215546Sopenharmony_ci 143bf215546Sopenharmony_ci self._parse_declarations() 144bf215546Sopenharmony_ci 145bf215546Sopenharmony_ci def _dump_glsl_code(self, f): 146bf215546Sopenharmony_ci # Dump GLSL code for reference. Use // instead of /* */ 147bf215546Sopenharmony_ci # comments so we don't need to escape the GLSL code. 148bf215546Sopenharmony_ci f.write('// GLSL code:\n') 149bf215546Sopenharmony_ci f.write('//') 150bf215546Sopenharmony_ci for line in self.glsl.splitlines(): 151bf215546Sopenharmony_ci f.write('\n// {0}'.format(line)) 152bf215546Sopenharmony_ci f.write('\n\n') 153bf215546Sopenharmony_ci 154bf215546Sopenharmony_ci def _dump_spirv_code(self, f, var_name): 155bf215546Sopenharmony_ci f.write('/* SPIR-V Assembly:\n') 156bf215546Sopenharmony_ci f.write(' *\n') 157bf215546Sopenharmony_ci for line in self.assembly.splitlines(): 158bf215546Sopenharmony_ci f.write(' * ' + line + '\n') 159bf215546Sopenharmony_ci f.write(' */\n') 160bf215546Sopenharmony_ci 161bf215546Sopenharmony_ci f.write('static const uint32_t {0}[] = {{'.format(var_name)) 162bf215546Sopenharmony_ci line_start = 0 163bf215546Sopenharmony_ci while line_start < len(self.dwords): 164bf215546Sopenharmony_ci f.write('\n ') 165bf215546Sopenharmony_ci for i in range(line_start, min(line_start + 6, len(self.dwords))): 166bf215546Sopenharmony_ci f.write(' 0x{:08x},'.format(self.dwords[i])) 167bf215546Sopenharmony_ci line_start += 6 168bf215546Sopenharmony_ci f.write('\n};\n') 169bf215546Sopenharmony_ci 170bf215546Sopenharmony_ci def dump_c_code(self, f): 171bf215546Sopenharmony_ci f.write('\n\n') 172bf215546Sopenharmony_ci var_prefix = '__qonos_shader{0}'.format(self.end_line) 173bf215546Sopenharmony_ci 174bf215546Sopenharmony_ci self._dump_glsl_code(f) 175bf215546Sopenharmony_ci self._dump_spirv_code(f, var_prefix + '_spir_v_src') 176bf215546Sopenharmony_ci f.write('static const QoShaderDecl {0}_decls[] = {{{1}}};\n'.format(var_prefix, ', '.join(self.declarations))) 177bf215546Sopenharmony_ci 178bf215546Sopenharmony_ci f.write(dedent("""\ 179bf215546Sopenharmony_ci static const QoShaderModuleCreateInfo {0}_info = {{ 180bf215546Sopenharmony_ci .spirvSize = sizeof({0}_spir_v_src), 181bf215546Sopenharmony_ci .pSpirv = {0}_spir_v_src, 182bf215546Sopenharmony_ci .declarationCount = sizeof({0}_decls) / sizeof({0}_decls[0]), 183bf215546Sopenharmony_ci .pDeclarations = {0}_decls, 184bf215546Sopenharmony_ci """.format(var_prefix))) 185bf215546Sopenharmony_ci 186bf215546Sopenharmony_ci f.write(" .stage = VK_SHADER_STAGE_{0}_BIT,\n".format(self.stage)) 187bf215546Sopenharmony_ci 188bf215546Sopenharmony_ci f.write('};\n') 189bf215546Sopenharmony_ci 190bf215546Sopenharmony_ci f.write('#define __qonos_shader{0}_info __qonos_shader{1}_info\n'\ 191bf215546Sopenharmony_ci .format(self.start_line, self.end_line)) 192bf215546Sopenharmony_ci 193bf215546Sopenharmony_citoken_exp = re.compile(r'(qoShaderModuleCreateInfoGLSL|qoCreateShaderModuleGLSL|\(|\)|,)') 194bf215546Sopenharmony_ci 195bf215546Sopenharmony_ciclass Parser: 196bf215546Sopenharmony_ci def __init__(self, f): 197bf215546Sopenharmony_ci self.infile = f 198bf215546Sopenharmony_ci self.paren_depth = 0 199bf215546Sopenharmony_ci self.shader = None 200bf215546Sopenharmony_ci self.line_number = 1 201bf215546Sopenharmony_ci self.shaders = [] 202bf215546Sopenharmony_ci 203bf215546Sopenharmony_ci def tokenize(f): 204bf215546Sopenharmony_ci leftover = '' 205bf215546Sopenharmony_ci for line in f: 206bf215546Sopenharmony_ci pos = 0 207bf215546Sopenharmony_ci while True: 208bf215546Sopenharmony_ci m = token_exp.search(line, pos) 209bf215546Sopenharmony_ci if m: 210bf215546Sopenharmony_ci if m.start() > pos: 211bf215546Sopenharmony_ci leftover += line[pos:m.start()] 212bf215546Sopenharmony_ci pos = m.end() 213bf215546Sopenharmony_ci 214bf215546Sopenharmony_ci if leftover: 215bf215546Sopenharmony_ci yield leftover 216bf215546Sopenharmony_ci leftover = '' 217bf215546Sopenharmony_ci 218bf215546Sopenharmony_ci yield m.group(0) 219bf215546Sopenharmony_ci 220bf215546Sopenharmony_ci else: 221bf215546Sopenharmony_ci leftover += line[pos:] 222bf215546Sopenharmony_ci break 223bf215546Sopenharmony_ci 224bf215546Sopenharmony_ci self.line_number += 1 225bf215546Sopenharmony_ci 226bf215546Sopenharmony_ci if leftover: 227bf215546Sopenharmony_ci yield leftover 228bf215546Sopenharmony_ci 229bf215546Sopenharmony_ci self.token_iter = tokenize(self.infile) 230bf215546Sopenharmony_ci 231bf215546Sopenharmony_ci def handle_shader_src(self): 232bf215546Sopenharmony_ci paren_depth = 1 233bf215546Sopenharmony_ci for t in self.token_iter: 234bf215546Sopenharmony_ci if t == '(': 235bf215546Sopenharmony_ci paren_depth += 1 236bf215546Sopenharmony_ci elif t == ')': 237bf215546Sopenharmony_ci paren_depth -= 1 238bf215546Sopenharmony_ci if paren_depth == 0: 239bf215546Sopenharmony_ci return 240bf215546Sopenharmony_ci 241bf215546Sopenharmony_ci self.current_shader.add_text(t) 242bf215546Sopenharmony_ci 243bf215546Sopenharmony_ci def handle_macro(self, macro): 244bf215546Sopenharmony_ci t = next(self.token_iter) 245bf215546Sopenharmony_ci assert t == '(' 246bf215546Sopenharmony_ci 247bf215546Sopenharmony_ci start_line = self.line_number 248bf215546Sopenharmony_ci 249bf215546Sopenharmony_ci if macro == 'qoCreateShaderModuleGLSL': 250bf215546Sopenharmony_ci # Throw away the device parameter 251bf215546Sopenharmony_ci t = next(self.token_iter) 252bf215546Sopenharmony_ci t = next(self.token_iter) 253bf215546Sopenharmony_ci assert t == ',' 254bf215546Sopenharmony_ci 255bf215546Sopenharmony_ci stage = next(self.token_iter).strip() 256bf215546Sopenharmony_ci 257bf215546Sopenharmony_ci t = next(self.token_iter) 258bf215546Sopenharmony_ci assert t == ',' 259bf215546Sopenharmony_ci 260bf215546Sopenharmony_ci self.current_shader = Shader(stage) 261bf215546Sopenharmony_ci self.handle_shader_src() 262bf215546Sopenharmony_ci self.current_shader.finish_text(start_line, self.line_number) 263bf215546Sopenharmony_ci 264bf215546Sopenharmony_ci self.shaders.append(self.current_shader) 265bf215546Sopenharmony_ci self.current_shader = None 266bf215546Sopenharmony_ci 267bf215546Sopenharmony_ci def run(self): 268bf215546Sopenharmony_ci for t in self.token_iter: 269bf215546Sopenharmony_ci if t in ('qoShaderModuleCreateInfoGLSL', 'qoCreateShaderModuleGLSL'): 270bf215546Sopenharmony_ci self.handle_macro(t) 271bf215546Sopenharmony_ci 272bf215546Sopenharmony_cidef open_file(name, mode): 273bf215546Sopenharmony_ci if name == '-': 274bf215546Sopenharmony_ci if mode == 'w': 275bf215546Sopenharmony_ci return sys.stdout 276bf215546Sopenharmony_ci elif mode == 'r': 277bf215546Sopenharmony_ci return sys.stdin 278bf215546Sopenharmony_ci else: 279bf215546Sopenharmony_ci assert False 280bf215546Sopenharmony_ci else: 281bf215546Sopenharmony_ci return open(name, mode) 282bf215546Sopenharmony_ci 283bf215546Sopenharmony_cidef parse_args(): 284bf215546Sopenharmony_ci description = dedent("""\ 285bf215546Sopenharmony_ci This program scrapes a C file for any instance of the 286bf215546Sopenharmony_ci qoShaderModuleCreateInfoGLSL and qoCreateShaderModuleGLSL macaros, 287bf215546Sopenharmony_ci grabs the GLSL source code, compiles it to SPIR-V. The resulting 288bf215546Sopenharmony_ci SPIR-V code is written to another C file as an array of 32-bit 289bf215546Sopenharmony_ci words. 290bf215546Sopenharmony_ci 291bf215546Sopenharmony_ci If '-' is passed as the input file or output file, stdin or stdout 292bf215546Sopenharmony_ci will be used instead of a file on disc.""") 293bf215546Sopenharmony_ci 294bf215546Sopenharmony_ci p = argparse.ArgumentParser( 295bf215546Sopenharmony_ci description=description, 296bf215546Sopenharmony_ci formatter_class=argparse.RawDescriptionHelpFormatter) 297bf215546Sopenharmony_ci p.add_argument('-o', '--outfile', default='-', 298bf215546Sopenharmony_ci help='Output to the given file (default: stdout).') 299bf215546Sopenharmony_ci p.add_argument('--with-glslang', metavar='PATH', 300bf215546Sopenharmony_ci default='glslangValidator', 301bf215546Sopenharmony_ci dest='glslang', 302bf215546Sopenharmony_ci help='Full path to the glslangValidator shader compiler.') 303bf215546Sopenharmony_ci p.add_argument('infile', metavar='INFILE') 304bf215546Sopenharmony_ci 305bf215546Sopenharmony_ci return p.parse_args() 306bf215546Sopenharmony_ci 307bf215546Sopenharmony_ci 308bf215546Sopenharmony_ciargs = parse_args() 309bf215546Sopenharmony_ciinfname = args.infile 310bf215546Sopenharmony_cioutfname = args.outfile 311bf215546Sopenharmony_ciglslang = args.glslang 312bf215546Sopenharmony_ci 313bf215546Sopenharmony_ciwith open_file(infname, 'r') as infile: 314bf215546Sopenharmony_ci parser = Parser(infile) 315bf215546Sopenharmony_ci parser.run() 316bf215546Sopenharmony_ci 317bf215546Sopenharmony_cifor shader in parser.shaders: 318bf215546Sopenharmony_ci shader.compile() 319bf215546Sopenharmony_ci 320bf215546Sopenharmony_ciwith open_file(outfname, 'w') as outfile: 321bf215546Sopenharmony_ci outfile.write(dedent("""\ 322bf215546Sopenharmony_ci /* ========================== DO NOT EDIT! ========================== 323bf215546Sopenharmony_ci * This file is autogenerated by glsl_scraper.py. 324bf215546Sopenharmony_ci */ 325bf215546Sopenharmony_ci 326bf215546Sopenharmony_ci #include <stdint.h> 327bf215546Sopenharmony_ci 328bf215546Sopenharmony_ci #define __QO_SHADER_INFO_VAR2(_line) __qonos_shader ## _line ## _info 329bf215546Sopenharmony_ci #define __QO_SHADER_INFO_VAR(_line) __QO_SHADER_INFO_VAR2(_line) 330bf215546Sopenharmony_ci 331bf215546Sopenharmony_ci #define qoShaderModuleCreateInfoGLSL(stage, ...) \\ 332bf215546Sopenharmony_ci __QO_SHADER_INFO_VAR(__LINE__) 333bf215546Sopenharmony_ci 334bf215546Sopenharmony_ci #define qoCreateShaderModuleGLSL(dev, stage, ...) \\ 335bf215546Sopenharmony_ci __qoCreateShaderModule((dev), &__QO_SHADER_INFO_VAR(__LINE__)) 336bf215546Sopenharmony_ci """)) 337bf215546Sopenharmony_ci 338bf215546Sopenharmony_ci for shader in parser.shaders: 339bf215546Sopenharmony_ci shader.dump_c_code(outfile) 340