#!/bin/python import argparse import hashlib import json import mmap import os import subprocess def hash_file(path): algorithm = hashlib.md5() with open(path, 'rb') as file: with mmap.mmap(file.fileno(), 0, prot=mmap.PROT_READ) as memory: algorithm.update(memory) return algorithm.hexdigest() def append_resources(code, name, data, key, resource_name): if not key in data: return for res in data[key]: if res['name'][-2:] == '_t': res['name'] = res['name'][:-2] data[key].sort(key=lambda res: res['binding']) code.append('static const char* g_{0}_{1}ResourceNames[] ='.format(name, resource_name)) code.append('{ ' + ', '.join(['"{0}"'.format(res['name']) for res in data[key]]) + ', };') code.append('static const uint32_t g_{0}_{1}ResourceBindings[] ='.format(name, resource_name)) code.append('{ ' + ', '.join([str(res['binding']) for res in data[key]]) + ', };') code.append('static const uint32_t g_{0}_{1}ResourceCounts[] ='.format(name, resource_name)) code.append('{ ' + ', '.join(['1' for res in data[key]]) + ', };') code.append('static const uint32_t g_{0}_{1}ResourceSets[] ='.format(name, resource_name)) code.append('{ ' + ', '.join([str(res['set']) for res in data[key]]) + ', };') code.append('') def process(file, name, args, output, optimization, reflection): spirv_path = os.path.join(output, name + '.spv') subprocess.run(['glslangValidator', '-V', *args, '-o', spirv_path, file]) if optimization: optimized_spirv_path = spirv_path + '_opt' subprocess.run(['spirv-opt', '-Os', spirv_path, '-o', optimized_spirv_path]) os.rename(optimized_spirv_path, spirv_path) spirv_hash = hash_file(spirv_path) name = '{0}_{1}'.format(name, str(spirv_hash)) hash_path = os.path.join(output, name + '.spv') if os.path.exists(hash_path): os.remove(spirv_path) return name, dict() else: os.rename(spirv_path, hash_path) spirv_path = hash_path spirv = list() with open(spirv_path, 'rb') as spirv_file: spirv = list(spirv_file.read()) data = dict() if reflection: json_path = os.path.join(output, name + '.json') subprocess.run(['spirv-cross', '-V', spirv_path, '--reflect', '--output', json_path]) with open(json_path) as json_file: data = json.load(json_file) os.remove(json_path) code = ['// {0}.h.'.format(name), '// Auto generated by FidelityFX-SC.py.', ''] append_resources(code, name, data, 'separate_samplers', 'Sampler') append_resources(code, name, data, 'combined_samplers', 'CombinedSampler') append_resources(code, name, data, 'separate_images', 'SampledImage') append_resources(code, name, data, 'images', 'StorageImage') append_resources(code, name, data, 'uniform_texel_buffers', 'UniformTexelBuffer') append_resources(code, name, data, 'storage_texel_buffers', 'StorageTexelBuffer') append_resources(code, name, data, 'ubos', 'UniformBuffer') append_resources(code, name, data, 'sbos', 'StorageBuffer') append_resources(code, name, data, 'inputs', 'InputAttachment') append_resources(code, name, data, 'acceleration_structures', 'AccelerationStructure') code.append('static const uint32_t g_{0}_size = {1};'.format(name, len(spirv))) code.append('') code.append('static const unsigned char g_{0}_data[] = '.format(name) + '{') for i in range(0, len(spirv), 16): line = ','.join(map('0x{0:02x}'.format, spirv[i:i + 16])) if i + 16 < len(spirv): line = line + ',' code.append(line) code.append('};') code_path = os.path.join(output, name + '.h') with open(code_path, 'w') as code_file: code_file.write('\n'.join(code)) return name, data def append_resources_info(code, resources_name, resource_name): code.append('\tconst uint32_t num{0}Resources;'.format(resources_name)) code.append('\tconst char** {0}ResourceNames;'.format(resource_name)) code.append('\tconst uint32_t* {0}ResourceBindings;'.format(resource_name)) code.append('\tconst uint32_t* {0}ResourceCounts;'.format(resource_name)) code.append('\tconst uint32_t* {0}ResourceSets;'.format(resource_name)) code.append('') def append_info_resource(code, name, data, key, resource_name): if key in data: code.append('\t\t{0},'.format(str(len(data[key])))) code.append('\t\tg_{0}_{1}ResourceNames,'.format(name, resource_name)) code.append('\t\tg_{0}_{1}ResourceBindings,'.format(name, resource_name)) code.append('\t\tg_{0}_{1}ResourceCounts,'.format(name, resource_name)) code.append('\t\tg_{0}_{1}ResourceSets,'.format(name, resource_name)) else: code.append('\t\t0, 0, 0, 0, 0,') def append_info(code, name, data): code.append('\t{') code.append('\t\tg_{0}_size,'.format(name)) code.append('\t\tg_{0}_data,'.format(name)) append_info_resource(code, name, data, 'separate_samplers', 'Sampler') append_info_resource(code, name, data, 'combined_samplers', 'CombinedSampler') append_info_resource(code, name, data, 'separate_images', 'SampledImage') append_info_resource(code, name, data, 'images', 'StorageImage') append_info_resource(code, name, data, 'uniform_texel_buffers', 'UniformTexelBuffer') append_info_resource(code, name, data, 'storage_texel_buffers', 'StorageTexelBuffer') append_info_resource(code, name, data, 'ubos', 'UniformBuffer') append_info_resource(code, name, data, 'ssbos', 'StorageBuffer') append_info_resource(code, name, data, 'inputs', 'InputAttachment') append_info_resource(code, name, data, 'acceleration_structures', 'AccelerationStructure') code.append('\t},') def main(args): permutations = list() permutations.append(list()) permutation_variables = list() for definition in args.definitions: name, value = definition.split('=') if value[0] == '{' and value[-1] == '}': values = value[1:-1].split(',') new_permutations = list() permutation_variables.append(name) for val in values: for permutation in permutations: new_permutation = permutation.copy() new_permutation.append("-D{0}={1}".format(name, val)) new_permutations.append(new_permutation) permutations = new_permutations else: for permutation in permutations: permutation.append("-D{0}={1}".format(name, value)) for permutation in permutations: permutation.append('-e') permutation.append(args.entry_point) permutation.append('-I{0}'.format(args.includes)) permutation.append('-S') permutation.append(args.stage) permutation.append('--target-env') permutation.append(args.target_env) if args.undefinitions is None: continue for undefinition in args.undefinitions: permutation.append("-U{0}".format(undefinition)) optimization = (args.optimization == 's') for j, file in enumerate(args.files): if len(args.files) == 1: file_name = args.name else: file_name = '{0}_{1}'.format(args.name, str(j)) if len(permutations) > 0: code_name = '{0}_permutations.h'.format(file_name) else: code_name = '{0}.h'.format(file_name) code = list() code_path = os.path.join(args.output, code_name) names = list() datas = list() for i, permutation in enumerate(permutations): name, data = process(file, file_name, permutation, args.output, optimization, args.reflection) names.append(name) datas.append(data) for name in names: spirv_path = os.path.join(args.output, name + '.spv') if os.path.exists(spirv_path): os.remove(spirv_path) infos = list() indices = [names.index(name) for name in names] indirections = list() for i, index in enumerate(indices): if i == index: code.append('#include "{0}.h"'.format(names[index])) indirections.append(len(infos)) infos.append({ 'name': names[index], 'data': datas[index] }) else: indirections.append(None) for i, index in enumerate(indices): if indirections[i] is None: indirections[i] = indirections[index] code.append('') code.append('typedef union {0}_PermutationKey '.format(file_name) + '{') code.append('\tstruct {') for variable in permutation_variables: code.append('\t\tuint32_t {0} : 1;'.format(variable)) code.append('\t};') code.append('\tuint32_t index;') code.append('}' + ' {0}_PermutationKey;'.format(file_name)) code.append('') code.append('typedef struct {0}_PermutationInfo '.format(file_name) + '{') code.append('\tconst uint32_t blobSize;') code.append('\tconst unsigned char* blobData;') code.append('') append_resources_info(code, 'Sampler', 'sampler') append_resources_info(code, 'CombinedSampler', 'combinedSampler') append_resources_info(code, 'SampledImage', 'sampledImage') append_resources_info(code, 'StorageImage', 'storageImage') append_resources_info(code, 'UniformTexelBuffer', 'uniformTexelBuffer') append_resources_info(code, 'StorageTexelBuffer', 'storageTexelBuffer') append_resources_info(code, 'UniformBuffer', 'uniformBuffer') append_resources_info(code, 'StorageBuffer', 'storageBuffer') append_resources_info(code, 'InputAttachment', 'inputAttachment') append_resources_info(code, 'RTAccelerationStructure', 'rtAccelerationStructure') code.append('}' + ' {0}_PermutationInfo;'.format(file_name)) code.append('') code.append('static const uint32_t g_{0}_IndirectionTable[] = '.format(file_name) + '{') for index in indirections: code.append('\t{0},'.format(index)) code.append('};') code.append('') code.append('static const {0}_PermutationInfo g_{0}_PermutationInfo[] = '.format(file_name) + '{') for info in infos: append_info(code, info['name'], info['data']) code.append('};') with open(code_path, 'w') as code_file: code_file.write('\n'.join(code)) with open(code_path + '.d', 'w') as code_file: code_file.write('{0}:'.format(code_name)) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('files', metavar='FILE', type=str, nargs='+') parser.add_argument('-compiler', choices=['glslang']) parser.add_argument('-deps', choices=['gcc']) parser.add_argument('-D', action='append', dest='definitions') parser.add_argument('-e', dest='entry_point') parser.add_argument('-I', dest='includes') parser.add_argument('-name') parser.add_argument('-O', choices=['d', 's'], dest='optimization') parser.add_argument('-output') parser.add_argument('-reflection', action='store_true') parser.add_argument('-S', dest='stage') parser.add_argument('--target-env', dest='target_env') parser.add_argument('-U', action='append', dest='undefinitions') main(parser.parse_args())