Map vertex outputs to fragment inputs

This commit is contained in:
Kovid Goyal 2026-07-07 08:05:55 +05:30
parent 498818cff1
commit bf6281f8f4
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C

View file

@ -510,12 +510,14 @@ class GLSLMetadata:
uniform_structs: dict[str, dict[str, str]]
input_locations: dict[str, int]
uniform_struct_names: dict[str, str]
fragment_inputs: dict[int, str]
def __init__(self) -> None:
self.loose_uniforms = {}
self.uniform_structs = {}
self.input_locations = {}
self.uniform_struct_names = {}
self.fragment_inputs = {}
def merge(self, other: 'GLSLMetadata') -> None:
self.loose_uniforms.update(other.loose_uniforms)
@ -528,7 +530,8 @@ class GLSLMetadata:
'loose_uniforms': self.loose_uniforms,
'uniform_structs': self.uniform_structs,
'input_locations': self.input_locations,
'uniform_struct_names': self.uniform_struct_names
'uniform_struct_names': self.uniform_struct_names,
'fragment_inputs': self.fragment_inputs,
}
@classmethod
@ -538,11 +541,13 @@ class GLSLMetadata:
ans.uniform_structs = d['uniform_structs']
ans.input_locations = d['input_locations']
ans.uniform_struct_names = d['uniform_struct_names']
ans.fragment_inputs = d['fragment_inputs']
return ans
def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
is_fragment_shader = 'frag' in os.path.basename(path).split('.')
def fixup_opengl_code(glsl_code: str, shader_name: str, existing_metadata: GLSLMetadata | None) -> tuple[str, GLSLMetadata]:
is_fragment_shader = existing_metadata is None
shader_name += '.frag.glsl' if is_fragment_shader else '.vert.glsl'
lines: list[str] = []
in_uniform_block = False
in_uniform_block_contents = False
@ -556,12 +561,32 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
uniform_struct_names = {}
input_locations = {}
named_interface_blocks = set()
pipeline_io_vars: dict[int, str] = {}
replacements = {
'gl_VertexIndex': 'gl_VertexID',
'gl_BaseVertex': '0',
'gl_InstanceIndex': 'gl_InstanceID',
'gl_BaseInstance': '0',
}
fragment_inputs = {} if existing_metadata is None else existing_metadata.fragment_inputs.copy()
def register_pipeline_boundary_io(line: str, next_line: str) -> None:
m = re.search(r'location = (\d+)', line)
assert m is not None
name = next_line.split()[-1].rstrip(';')
location = int(m.group(1))
if existing_metadata is None:
if not next_line.startswith('out '):
pipeline_io_vars[location] = name
else:
with suppress(KeyError):
replacements[name] = fragment_inputs.pop(location)
def add_uniform_name(name: str, uniform_names: dict[str, str] = uniform_names) -> str:
name = name.rstrip(';')
uniform_name = name.rpartition('_')[0]
if uniform_name in uniform_names:
raise KeyError(f'The uniform name {uniform_name} is used with multiple suffixes in {path}')
raise KeyError(f'The uniform name {uniform_name} is used with multiple suffixes in {shader_name}')
if '[' in name:
name = name.partition('[')[0] + '[0]'
uniform_names[uniform_name] = name
@ -604,9 +629,11 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
line = '// ' + line
elif line.startswith('layout(binding ='):
line = '// ' + line
elif line.startswith('layout(location =') and (is_fragment_shader or next_line.startswith('out ')):
elif line.startswith('layout(location =') and (is_fragment_shader or next_line.startswith('out ')): # ))
register_pipeline_boundary_io(line, next_line)
line = '// ' + line
elif line.startswith('flat layout(location ='):
register_pipeline_boundary_io(line[len('flat '):], next_line)
line = 'flat'
elif line: # ))))
words = line.split()
@ -628,46 +655,57 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
name = words[-1].rstrip(';')
input_locations[name.rpartition('_')[0]] = int(lines[-1].split()[-1].rstrip(')'))
lines.append(line)
if fragment_inputs:
raise ValueError(
f'Could not match vertex outputs to fragment inputs for shader: {shader_name}. Leftover fragment inputs: {", ".join(fragment_inputs.values())}')
ans = '\n'.join(lines)
for block_name, names in uniform_blocks.items():
for u in names:
u = u.partition('[')[0] # ]
ans = ans.replace(f'{block_name}.{u}', u)
ans = ans.replace('gl_VertexIndex', 'gl_VertexID')
ans = ans.replace('gl_BaseVertex', '0')
ans = ans.replace('gl_InstanceIndex', 'gl_InstanceID')
ans = ans.replace('gl_BaseInstance', '0')
replacements[f'{block_name}.{u}'] = u
for x in named_interface_blocks:
ans = re.sub(rf'\b{x}\.', '', ans)
replacements[f'{x}.'] = ''
def sub(m: re.Match[str]) -> str:
return replacements[m.group(1)]
ans = re.sub(r'\b(' + '|'.join(re.escape(word) for word in replacements) + r')\b', sub, ans)
m = GLSLMetadata()
m.loose_uniforms = uniform_names
m.uniform_structs = uniform_structs
m.input_locations = input_locations
m.uniform_struct_names = uniform_struct_names
if is_fragment_shader:
m.fragment_inputs = pipeline_io_vars
return ans, m
def shader_name_from_path(path: str) -> str:
parts = os.path.basename(path).split('.')
if parts[1] in ('vert', 'frag', 'glsl'):
return parts[0]
return '.'.join(parts[:2])
def fixup_opengl_files(paths: Iterable[str]) -> None:
' Convert the GLSL output of slangc to something that will work with OpenGL 3.1 '
metadata_map: dict[str, GLSLMetadata] = {}
dest_dir = ''
for path in paths:
for path in sorted(paths):
dest_dir = os.path.dirname(path)
with open(path, 'r') as f:
glsl_code = f.read()
shader_name = shader_name_from_path(path)
try:
fixed, metadata = fixup_opengl_code(glsl_code, path)
fixed, metadata = fixup_opengl_code(glsl_code, shader_name, metadata_map.get(shader_name))
except Exception:
os.unlink(path)
raise
parts = os.path.basename(path).split('.')
write_if_changed(path, fixed)
if len(parts) == 3:
name = parts[0]
if name in metadata_map:
metadata_map[name].merge(metadata)
else:
metadata_map[name] = metadata
if shader_name in metadata_map:
metadata_map[shader_name].merge(metadata)
else:
metadata_map[shader_name] = metadata
for name, gm in metadata_map.items():
with open(os.path.join(dest_dir, f'{name}.glsl.json'), 'w') as f:
f.write(json.dumps(gm.asdict()))
@ -685,14 +723,16 @@ def write_if_changed(dest: str, text: str) -> None:
def write_glsl_header(dest_dir: str, dest: str = 'kitty/glsl-uniforms.h') -> None:
metadata_map = {}
for x in glob.glob(os.path.join(dest_dir, '*.glsl.json')):
name = os.path.basename(x).split('.')[0]
shader_name = shader_name_from_path(x)
with open(x) as f:
d = json.load(f)
metadata_map[name] = GLSLMetadata.fromdict(d)
metadata_map[shader_name] = GLSLMetadata.fromdict(d)
lines = ['// generated by slang.py DO NOT EDIT', '#include "gl.h"', '']
a = lines.append
for name in sorted(metadata_map):
if '.' in name:
continue
m = metadata_map[name]
struct_name = name.capitalize() + 'Uniforms'
a('')