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