co-maps/libs/shaders/vulkan_shaders_preprocessor.py
2025-11-22 13:58:55 +01:00

279 lines
11 KiB
Python

import os
import re
import sys
from subprocess import Popen, PIPE
import json
VERTEX_SHADER_EXT = '.vsh.glsl'
FRAG_SHADER_EXT = '.fsh.glsl'
VERTEX_SHADER_EXT_OUT = '.vert'
FRAG_SHADER_EXT_OUT = '.frag'
SHADERS_LIB_COMMON_PATTERN = '// Common'
SHADERS_LIB_VS_PATTERN = '// VS'
SHADERS_LIB_FS_PATTERN = '// FS'
SHADERS_LIB_COMMON_INDEX = 0
SHADERS_LIB_VS_INDEX = 1
SHADERS_LIB_FS_INDEX = 2
UNIFORMS = 'uniforms'
SAMPLERS = 'samplers'
debug_output = False
# Read index file which contains program to shaders bindings.
def read_index_file(file_path):
gpu_programs = dict()
with open(file_path, 'r') as f:
index = 0
for line in f:
line_parts = line.strip().split()
if len(line_parts) != 3:
print('Incorrect GPU program definition : ' + line)
exit(1)
vertex_shader = next(f for f in line_parts if f.endswith(VERTEX_SHADER_EXT))
fragment_shader = next(f for f in line_parts if f.endswith(FRAG_SHADER_EXT))
if not vertex_shader:
print('Vertex shader not found in GPU program definition : ' + line)
exit(1)
if not fragment_shader:
print('Fragment shader not found in GPU program definition : ' + line)
exit(1)
if line_parts[0] in gpu_programs.keys():
print('More than one definition of %s gpu program' % line_parts[0])
exit(1)
gpu_programs[index] = (vertex_shader, fragment_shader, line_parts[0])
index += 1
gpu_programs_cache = dict()
for (k, v) in gpu_programs.items():
gpu_programs_cache[v[2]] = (v[0], v[1], k)
return gpu_programs_cache
# Read GLSL-file with common shader functions.
def read_shaders_lib_file(file_path):
shaders_library = ['', '', '']
with open(file_path, 'r') as f:
shaders_lib_content = f.read()
if len(shaders_lib_content) == 0:
return shaders_library
common_index = shaders_lib_content.find(SHADERS_LIB_COMMON_PATTERN)
if common_index < 0:
print('Common functions block is not found in ' + file_path)
exit(1)
vs_index = shaders_lib_content.find(SHADERS_LIB_VS_PATTERN)
if vs_index < 0:
print('Vertex shaders functions block is not found in ' + file_path)
exit(1)
fs_index = shaders_lib_content.find(SHADERS_LIB_FS_PATTERN)
if fs_index < 0:
print('Vertex shaders functions block is not found in ' + file_path)
exit(1)
if not (common_index < vs_index < fs_index):
print('Order of functions block is incorrect in ' + file_path)
exit(1)
shaders_library[SHADERS_LIB_COMMON_INDEX] = shaders_lib_content[common_index:vs_index - 1]
shaders_library[SHADERS_LIB_VS_INDEX] = shaders_lib_content[vs_index:fs_index - 1]
shaders_library[SHADERS_LIB_FS_INDEX] = shaders_lib_content[fs_index:]
return shaders_library
def get_shaders_lib_content(shader_file, shaders_library):
lib_content = shaders_library[SHADERS_LIB_COMMON_INDEX]
if shader_file.find(VERTEX_SHADER_EXT) >= 0:
lib_content += shaders_library[SHADERS_LIB_VS_INDEX]
elif shader_file.find(FRAG_SHADER_EXT) >= 0:
lib_content += shaders_library[SHADERS_LIB_FS_INDEX]
return lib_content
def get_shader_line(line, layout_counters):
if line.lstrip().startswith('//') or line == '\n' or len(line) == 0:
return None
output_line = line.rstrip()
if output_line.find('layout (binding') >= 0:
if output_line.find('sampler') >= 0:
match = re.search(r"binding\s*=\s*(\d+)", output_line)
sampler_match = re.search(r"sampler2D\s+(\w+)", output_line)
if match and sampler_match:
binding_index = int(match.group(1))
sampler_name = sampler_match.group(1)
if binding_index == 0:
print('Binding index must not be 0 for sampler in the line: ' + line)
exit(1)
layout_counters[SAMPLERS][sampler_name] = binding_index
else:
print('Sampler name or binding index is not found in the line: ' + line)
exit(1)
else:
match = re.search(r"binding\s*=\s*(\d+)", output_line)
if match:
binding_index = int(match.group(1))
if binding_index != 0:
print('Binding index must be 0 in the line: ' + line)
exit(1)
else:
print('Binding index is not found in the line: ' + line)
exit(1)
layout_counters[UNIFORMS] += 1
return output_line
def generate_spirv_compatible_glsl_shader(output_file, shader_file, shader_dir, shaders_library,
layout_counters, reflection_dict):
output_file.write('#version 310 es\n')
output_file.write('precision highp float;\n')
output_file.write('#define LOW_P lowp\n')
output_file.write('#define MEDIUM_P mediump\n')
output_file.write('#define HIGH_P highp\n')
output_file.write('#define VULKAN_MODE\n')
is_fragment_shader = shader_file.find(FRAG_SHADER_EXT) >= 0
lib_content = get_shaders_lib_content(shader_file, shaders_library)
conditional_started = False
conditional_skip = False
for line in open(os.path.join(shader_dir, shader_file)):
# Remove some useless conditional compilation.
if conditional_started and line.lstrip().startswith('#else'):
conditional_skip = True
continue
if conditional_started and line.lstrip().startswith('#endif'):
conditional_skip = False
conditional_started = False
continue
if conditional_skip:
continue
if line.lstrip().startswith('#ifdef ENABLE_VTF'):
conditional_started = True
continue
if line.lstrip().startswith('void main'):
# Write reflection for uniforms block.
uniforms_index = 'vs_uni';
if is_fragment_shader:
uniforms_index = 'fs_uni'
if layout_counters[UNIFORMS] > 0:
reflection_dict[uniforms_index] = 0
else:
reflection_dict[uniforms_index] = -1
# Write reflection for samplers.
sample_index = 'tex'
if not sample_index in reflection_dict:
reflection_dict[sample_index] = []
for (s, idx) in layout_counters[SAMPLERS].items():
sampler = {'name': s, 'idx': idx, 'frag':int(is_fragment_shader)}
reflection_dict[sample_index].append(sampler)
# Write shaders library.
for lib_line in lib_content.splitlines():
shader_line = get_shader_line(lib_line, layout_counters)
if shader_line:
output_file.write('%s\n' % shader_line)
shader_line = get_shader_line(line, layout_counters)
if shader_line:
output_file.write('%s\n' % shader_line)
layout_counters[UNIFORMS] = 0
layout_counters[SAMPLERS] = dict()
# Execute external program.
def execute_external(options):
p = Popen(options, stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
rc = p.returncode
if rc != 0:
for line in err.split(b'\n'):
print(line.decode('utf-8'))
# Generate SPIR-V shader from GLSL source.
def generate_shader(shader, shader_dir, generation_dir, shaders_library, program_name,
layout_counters, output_name, reflection_dict, glslc_path):
output_path = os.path.join(generation_dir, output_name)
with open(output_path, 'w') as file:
generate_spirv_compatible_glsl_shader(file, shader, shader_dir, shaders_library,
layout_counters, reflection_dict)
spv_path = output_path + '.spv'
try:
execute_external([glslc_path, '-c', output_path, '-o', spv_path, '-std=310es', '--target-env=vulkan'])
if debug_output:
debug_dir = os.path.join(generation_dir, 'debug', program_name)
debug_path = os.path.join(debug_dir, output_name)
if not os.path.exists(debug_dir):
os.makedirs(debug_dir)
execute_external([glslc_path, '-S', output_path, '-o', debug_path + '.spv.txt', '-std=310es','--target-env=vulkan'])
os.rename(output_path, debug_path)
else:
os.remove(output_path)
except:
print('Could not generate SPIR-V for the shader {0}. Most likely glslc from Android NDK is not found.'.format(shader))
os.remove(output_path)
exit(1)
return spv_path
def write_shader_to_pack(pack_file, shader_file_name):
offset = pack_file.tell()
with open(shader_file_name, 'rb') as shader_file:
pack_file.write(shader_file.read())
os.remove(shader_file_name)
return offset, pack_file.tell() - offset
if __name__ == '__main__':
if len(sys.argv) < 7:
print('Usage : ' + sys.argv[0] + ' <shader_dir> <index_file> <shaders_lib> <generation_dir> <glslc_path> [--debug]')
exit(1)
shader_dir = sys.argv[1]
index_file_name = sys.argv[2]
shaders_lib_file = sys.argv[3]
generation_dir = sys.argv[4]
glslc_path = sys.argv[5]
if len(sys.argv) >= 7:
debug_output = (sys.argv[6] == '--debug')
shaders = [file for file in os.listdir(shader_dir) if
os.path.isfile(os.path.join(shader_dir, file)) and (
file.endswith(VERTEX_SHADER_EXT) or file.endswith(FRAG_SHADER_EXT))]
gpu_programs_cache = read_index_file(os.path.join(shader_dir, index_file_name))
shaders_library = read_shaders_lib_file(os.path.join(shader_dir, shaders_lib_file))
reflection = []
current_offset = 0
with open(os.path.join(generation_dir, 'shaders_pack.spv'), 'wb') as pack_file:
for (k, v) in gpu_programs_cache.items():
layout_counters = {UNIFORMS: 0, SAMPLERS: dict()}
reflection_dict = {'prg': v[2], 'info': dict()}
vs_offset = write_shader_to_pack(pack_file, generate_shader(v[0], shader_dir, generation_dir,
shaders_library, k,
layout_counters, k + VERTEX_SHADER_EXT_OUT,
reflection_dict['info'], glslc_path))
reflection_dict['vs_off'] = vs_offset[0]
reflection_dict['vs_size'] = vs_offset[1]
fs_offset = write_shader_to_pack(pack_file, generate_shader(v[1], shader_dir, generation_dir,
shaders_library, k,
layout_counters, k + FRAG_SHADER_EXT_OUT,
reflection_dict['info'], glslc_path))
reflection_dict['fs_off'] = fs_offset[0]
reflection_dict['fs_size'] = fs_offset[1]
reflection.append(reflection_dict)
with open(os.path.join(generation_dir, 'reflection.json'), 'w') as reflection_file:
reflection_file.write(json.dumps(reflection, separators=(',',':')))