Work on generating build tree for slang files

This commit is contained in:
Kovid Goyal 2026-06-26 09:40:00 +05:30
parent 5b4e3a12a1
commit 135ba45c7e
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 160 additions and 18 deletions

View file

@ -188,27 +188,14 @@ def build_kitty() -> None:
run(cmd)
def add_to_path(path: str, prepend: bool = False) -> None:
if existing := os.environ.get('PATH') or '':
parts = existing.split(os.pathsep)
parts.insert(0 if prepend else len(parts), path)
seen = set()
ans = []
for x in parts:
if x not in seen:
seen.add(x)
ans.append(x)
path = os.pathsep.join(ans)
os.environ['PATH'] = path
def test_kitty() -> None:
if is_macos:
run('ulimit -c unlimited')
run('sudo chmod -R 777 /cores')
if running_under_sanitizer:
os.environ['MallocNanoZone'] = '0'
add_to_path(os.path.join(SW if is_bundle else SLANG_INSTALL_DIR, 'bin'))
slangc = os.path.join(SW if is_bundle else SLANG_INSTALL_DIR, 'bin', 'slangc')
os.environ['SLANGC'] = slangc
run('./test.py', print_crash_reports=True)
@ -230,7 +217,8 @@ def replace_in_file(path: str, src: str, dest: str) -> None:
def setup_bundle_env() -> None:
global SW
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(
os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig')
if is_macos:
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])

View file

@ -35,7 +35,7 @@ kitty_run_data: dict[str, Any] = getattr(sys, 'kitty_run_data', {})
launched_by_launch_services = kitty_run_data.get('launched_by_launch_services', False)
is_quick_access_terminal_app = kitty_run_data.get('is_quick_access_terminal_app', False)
unserialize_launch_flag = 'kitty-unserialize-data='
slangc = ['slang']
slangc = [os.environ.get('SLANGC', 'slangc')]
if getattr(sys, 'frozen', False):

121
kitty/shaders/slang.py Normal file
View file

@ -0,0 +1,121 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
# This file is also run as a standalone module from setup.py to compile shaders
# so no top level kitty imports are allowed
import os
import re
import shutil
from enum import Enum
from functools import lru_cache
from typing import NamedTuple
class Stage(Enum):
vertex = 'vertex'
fragment = 'fragment'
class EntryPoint(NamedTuple):
stage: Stage
name: str
class SlangFile(NamedTuple):
path: str
text: str
imports: frozenset[str]
entry_points: frozenset[EntryPoint]
module: str
def parse_slang_text(text: str, path: str = '') -> SlangFile:
text = re.sub(r'/\*[\s\S]*?\*/', '', text)
entry_points, imports = [], set()
module = ''
found_entry_point = ''
for line in text.splitlines():
line = line.strip()
if not line or line.startswith('//'):
continue
words = line.split()
if found_entry_point:
if words[0].startswith('['): # ]
continue
for q in words:
if '(' in q:
name = q.partition('(')[0] # ))
match found_entry_point:
case 'vertex':
entry_points.append(EntryPoint(Stage.vertex, name))
case 'fragment' | 'pixel':
entry_points.append(EntryPoint(Stage.fragment, name))
break
found_entry_point = ''
else:
match words[0]:
case 'module':
module = words[1]
case 'import':
imports.add(words[1])
case _:
if words[0].startswith('[shader('): # ])
text = words[0].partition('(')[2].partition(')')[0].strip()
found_entry_point = text[1:-1]
return SlangFile(path, text, frozenset(imports), frozenset(entry_points), module)
@lru_cache(4096)
def parse_slang_file(path: str) -> SlangFile:
with open(path) as f:
text = f.read()
return parse_slang_text(text, path)
def build_import_graph(dirpath: str) -> dict[str, SlangFile]:
graph: dict[str, SlangFile] = {}
for root, _, files in os.walk(os.path.abspath(dirpath)):
for file in files:
if file.endswith('.slang'):
full_path = os.path.abspath(os.path.join(root, file))
relpath = os.path.relpath(full_path, root)
modname = os.path.splitext(relpath.replace(os.sep, '.'))[0]
graph[modname] = parse_slang_file(full_path)
return graph
def topological_sort(graph: dict[str, SlangFile]) -> list[str]:
visited = set()
order = []
def visit(node: str) -> None:
if node in visited or node not in graph:
return
for dep in graph[node].imports:
visit(dep)
visited.add(node)
order.append(node)
for node in graph:
visit(node)
return order
def get_ordered_sources_in_tree(dirpath: str) -> dict[str, SlangFile]:
ans = build_import_graph(dirpath)
topological_sort(ans)
return ans
@lru_cache(2)
def slangc() -> tuple[str, ...]:
try:
from kitty.constants import slangc
except ImportError:
ans = shutil.which('slangc')
if not ans:
raise SystemExit('Could not find the slangc shader compiler on PATH')
slangc = [ans]
return tuple(slangc + ['-std', '2026'])

View file

@ -28,7 +28,7 @@ class TestBuild(BaseTest):
self.assertTrue(os.access(exe, os.X_OK))
self.assertTrue(os.path.isfile(exe))
self.assertIn(str_version, subprocess.check_output([exe, '--version']).decode())
self.assertTrue(shutil.which(slangc[0]), f'slang compiler not found on PATH: {slangc[0]}')
self.assertTrue(shutil.which(slangc[0]), f'slang compiler: {slangc[0]} not found on PATH: {os.environ["PATH"]}')
def test_loading_extensions(self) -> None:
import kitty.fast_data_types as fdt

33
kitty_tests/slang.py Normal file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.shaders.slang import EntryPoint, SlangFile, Stage, parse_slang_text
from . import BaseTest
class TestSlang(BaseTest):
def test_slang_parser(self):
for src, expected in {
'''
[shader("vertex")]
void drawTriangle(float4 pos : POSITION) {
// vertex code
}
[shader("fragment")]
[numthreads(1, 1, 1)] // Handles intermediate attributes seamlessly
float4 psMain() : SV_Target {
return float4(1, 0, 0, 1);
}
''': SlangFile(
'', '', frozenset(), frozenset({EntryPoint(Stage.vertex, 'drawTriangle'), EntryPoint(Stage.fragment, 'psMain')}), ''),
}.items():
actual = parse_slang_text(src)
actual = actual._replace(text='')
self.assertEqual(expected, actual)
def test_slang_ordering(self):
pass # TODO: Test get_ordered_sources_in_tree()