mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-07-01 21:36:09 +00:00
Work on generating build tree for slang files
This commit is contained in:
parent
5b4e3a12a1
commit
135ba45c7e
5 changed files with 160 additions and 18 deletions
20
.github/workflows/ci.py
vendored
20
.github/workflows/ci.py
vendored
|
|
@ -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'])
|
||||
|
|
|
|||
|
|
@ -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
121
kitty/shaders/slang.py
Normal 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'])
|
||||
|
|
@ -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
33
kitty_tests/slang.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue