summaryrefslogtreecommitdiff
path: root/shaderbake
blob: 96a8d07742faf140ae46f94e06994e5b0270eb26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python3
# vim: set syntax=python ts=4 sw=4 sts=4 smarttab expandtab autoindent

DESCRIPTION = \
"Compile shader source code to a baked SPIR-V binary file."

EPILOG = \
"Applying a number of quirks and fixes to deal with drivers bugging out on certain" \
"SPIR-V instructions. Generate GLSL from spirv-opt-ified binary and recompile with" \
"that in order to have consistency between Nvidia and everyone else."

args = None
import subprocess

def glslc(src, src_is_a_filepath = True, emit_assembly = False):
    incdirs = []
    for i in args.incdirs:
        incdirs.append('-I')
        incdirs.append(i)

    rr = subprocess.run([
            "glslc",
            f"--target-env={args.env}",
            f"-std={args.std}",
            f"-fshader-stage={args.stage}"] + incdirs +
            ["-g", "-O", "-o", "-",] + (["-S"] if emit_assembly else []) +
            [(src if src_is_a_filepath else '-')],
        stdout=subprocess.PIPE,
        input = None if src_is_a_filepath else src)
    return rr.returncode, rr.stdout

def spirv_opt(src, strip_debug = True):
    rr = subprocess.run([
            "spirv-opt",
            '--preserve-bindings', '--preserve-spec-constants',
            '--strip-nonsemantic', '--strip-reflect'
            ] + (['--strip-debug'] if strip_debug else []) + [
            '--amd-ext-to-khr', '--workaround-1209',
            f"--target-env={args.env}",
            "-O", "-o", "-", "-" ],
        stdout=subprocess.PIPE, input = src )
    return rr.returncode, rr.stdout

def jfmt_glsl(src):
    srclines = [b'']
    for sl in [l for l in src.split(b'\n') if 0 < len(l)]:
        if sl.startswith(b'#'):
            srclines[-1] = sl
            srclines.append(b'')
        else:
            srclines[-1] += b' ' + sl
    srclines = [b' '.join(sl.split()) for sl in srclines]
    return b'\n'.join(srclines)

def filter_spirv(spirv, filename='<stdin>'):
    return b'\n'.join(
            [l for l in spirv.split(b'\n') if not
                (b'OpLine' in l or b'OpNoLine' in l)
            ]).replace(b'<stdin>', filename.encode())

def spirv_cross(src):
    rr = subprocess.run([
            "spirv-cross", '--no-es', '-'],
        stdout=subprocess.PIPE, input = src )
    return rr.returncode, rr.stdout

def spirv_dis(src):
    rr = subprocess.run(["spirv-dis", "-o", "-", "-"],
        stdout=subprocess.PIPE, input = src )
    return rr.returncode, rr.stdout

def spirv_as(src, dstpath = None):
    rr = subprocess.run(["spirv-as",
            "--target-env", args.env,
            "-o", ("-" if not dstpath else dstpath), "-"],
            input = src )
    return rr.returncode

def exit_on_error(ec):
    import sys
    if 0 != ec:
        sys.exit(ec)

if __name__ == '__main__':
    from argparse import ArgumentParser
    ap = ArgumentParser(
            prog = "shaderbake",
            description = DESCRIPTION,
            epilog = EPILOG )
    ap.add_argument('input', default='-', nargs='?')
    ap.add_argument('-I', '--add-include-directory', dest='incdirs', action='append', default=[])
    ap.add_argument('-o', '--output', dest='output', default=None)
    ap.add_argument('-s', '--std', dest='std', default='450core')
    ap.add_argument('-e', '--environment', dest='env', default='opengl4.5')
    ap.add_argument('-t', '--stage', dest='stage', default=None)

    import sys
    args = ap.parse_args(sys.argv[1:])

    args.stripped_input = args.input[:-5] if args.input.lower().endswith('.glsl') else args.input
    if not args.stage:
        if args.stripped_input.lower().endswith('.cs'):
            args.stage = 'comp'
        if args.stripped_input.lower().endswith('.vs'):
            args.stage = 'vert'
        if args.stripped_input.lower().endswith('.tc'):
            args.stage = 'tesc'
        if args.stripped_input.lower().endswith('.gs'):
            args.stage = 'geom'
        if args.stripped_input.lower().endswith('.te'):
            args.stage = 'tese'
        if args.stripped_input.lower().endswith('.fs'):
            args.stage = 'frag'

    ec,sc = glslc(args.input)
    exit_on_error(ec)

    if True:
        ec,sc = spirv_opt(sc, strip_debug=False)
        exit_on_error(ec)

    if True:
        ec,sc = spirv_cross(sc)
        exit_on_error(ec)
        ec,sc = glslc(jfmt_glsl(sc), src_is_a_filepath=False, emit_assembly=False)
        exit_on_error(ec)
        ec,sc = spirv_opt(sc, strip_debug=False)
        exit_on_error(ec)

    if True:
        ec,sc = spirv_dis(sc)
        exit_on_error(ec)
        ec = spirv_as(
                filter_spirv(sc, args.stripped_input),
                args.output)
        exit_on_error(ec)
    else:
        with open(args.output, 'wb') if args.output else sys.stdout as output:
            output.write(sc)