From 7a392281f14be0b389811ddc0c8cf3a8d047f702 Mon Sep 17 00:00:00 2001 From: nanako <469449812@qq.com> Date: Thu, 5 Jun 2025 20:29:36 +0800 Subject: [PATCH] init --- .gitignore | 2 + __init__.py | 27 ++++ binding_manager.py | 285 +++++++++++++++++++++++++++++++++ code_generator.py | 266 +++++++++++++++++++++++++++++++ compiler.py | 93 +++++++++++ main.py | 58 +++++++ shader_parser.py | 383 +++++++++++++++++++++++++++++++++++++++++++++ shader_types.py | 44 ++++++ slangc_finder.py | 90 +++++++++++ 9 files changed, 1248 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 binding_manager.py create mode 100644 code_generator.py create mode 100644 compiler.py create mode 100644 main.py create mode 100644 shader_parser.py create mode 100644 shader_types.py create mode 100644 slangc_finder.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf6d7e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/__pycache__ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ad47ae1 --- /dev/null +++ b/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler Package +自动处理资源绑定点的Slang着色器编译器 +""" + +from shader_types import ShaderStage, ResourceType, TargetFormat, Resource, ShaderInfo +from compiler import SDL3GPUSlangCompiler +from slangc_finder import SlangcFinder +from shader_parser import ShaderParser +from binding_manager import BindingManager +from code_generator import CodeGenerator + +__all__ = [ + 'ShaderStage', + 'ResourceType', + 'TargetFormat', + 'Resource', + 'ShaderInfo', + 'SDL3GPUSlangCompiler', + 'SlangcFinder', + 'ShaderParser', + 'BindingManager', + 'CodeGenerator' +] + +version = '1.0.0' \ No newline at end of file diff --git a/binding_manager.py b/binding_manager.py new file mode 100644 index 0000000..ab78cc3 --- /dev/null +++ b/binding_manager.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Binding Manager +资源绑定点分配和管理 +""" + +import re +import os +from typing import Dict + +from shader_types import ShaderStage, ResourceType, TargetFormat, ShaderInfo + + +class BindingManager: + def assign_bindings_spirv(self, shader_info: ShaderInfo): + """为SPIR-V着色器分配绑定点""" + + if shader_info.stage == ShaderStage.VERTEX: + texture_set = 0 + uniform_set = 1 + elif shader_info.stage == ShaderStage.FRAGMENT: + texture_set = 2 + uniform_set = 3 + else: + raise ValueError(f"Unsupported shader stage: {shader_info.stage}") + + # 按类型分组资源 + sampled_textures = [] + storage_textures = [] + storage_buffers = [] + uniform_buffers = [] + samplers = [] + + for resource in shader_info.resources: + if resource.type == ResourceType.SAMPLED_TEXTURE: + sampled_textures.append(resource) + elif resource.type == ResourceType.STORAGE_TEXTURE: + storage_textures.append(resource) + elif resource.type == ResourceType.STORAGE_BUFFER: + storage_buffers.append(resource) + elif resource.type == ResourceType.UNIFORM_BUFFER: + uniform_buffers.append(resource) + elif resource.type == ResourceType.SAMPLER: + samplers.append(resource) + + # 分配绑定点 - Set 0/2: 采样纹理、存储纹理、存储缓冲区 + binding = 0 + for tex in sampled_textures: + tex.set = texture_set + tex.binding = binding + binding += 1 + + for tex in storage_textures: + tex.set = texture_set + tex.binding = binding + binding += 1 + + for buf in storage_buffers: + buf.set = texture_set + buf.binding = binding + binding += 1 + + # 分配绑定点 - Set 1/3: Uniform缓冲区 + binding = 0 + for buf in uniform_buffers: + buf.set = uniform_set + buf.binding = binding + binding += 1 + + # Samplers通常和sampled textures配对 + for i, sampler in enumerate(samplers): + if i < len(sampled_textures): + sampler.set = sampled_textures[i].set + sampler.binding = sampled_textures[i].binding + + + def assign_bindings_dxil(self, shader_info: ShaderInfo): + """为DXIL/DXBC着色器分配绑定点""" + if shader_info.stage == ShaderStage.VERTEX: + texture_space = 0 + uniform_space = 1 + elif shader_info.stage == ShaderStage.FRAGMENT: + texture_space = 2 + uniform_space = 3 + else: + raise ValueError(f"Unsupported shader stage: {shader_info.stage}") + + # 分组资源 + textures = [] # t registers + samplers = [] # s registers + uniforms = [] # b registers + + for resource in shader_info.resources: + if resource.type in [ResourceType.SAMPLED_TEXTURE, ResourceType.STORAGE_TEXTURE, ResourceType.STORAGE_BUFFER]: + textures.append(resource) + elif resource.type == ResourceType.SAMPLER: + samplers.append(resource) + elif resource.type == ResourceType.UNIFORM_BUFFER: + uniforms.append(resource) + + # 分配t寄存器 + for i, tex in enumerate(textures): + tex.register = f"t{i}" + tex.space = texture_space + + # 分配s寄存器 + for i, samp in enumerate(samplers): + samp.register = f"s{i}" + samp.space = texture_space + + # 分配b寄存器 + for i, buf in enumerate(uniforms): + buf.register = f"b{i}" + buf.space = uniform_space + + + def assign_bindings_msl(self, shader_info: ShaderInfo): + """为MSL着色器分配绑定点""" + texture_index = 0 + sampler_index = 0 + buffer_index = 0 + + # 按SDL3要求的顺序分配 + # 纹理:采样纹理,然后存储纹理 + for resource in shader_info.resources: + if resource.type == ResourceType.SAMPLED_TEXTURE: + resource.metal_index = texture_index + texture_index += 1 + + for resource in shader_info.resources: + if resource.type == ResourceType.STORAGE_TEXTURE: + resource.metal_index = texture_index + texture_index += 1 + + # 采样器 + for resource in shader_info.resources: + if resource.type == ResourceType.SAMPLER: + resource.metal_index = sampler_index + sampler_index += 1 + + # 缓冲区:uniform缓冲区,然后存储缓冲区 + for resource in shader_info.resources: + if resource.type == ResourceType.UNIFORM_BUFFER: + resource.metal_index = buffer_index + buffer_index += 1 + + for resource in shader_info.resources: + if resource.type == ResourceType.STORAGE_BUFFER: + resource.metal_index = buffer_index + buffer_index += 1 + + + def generate_binding_code(self, shader_info: ShaderInfo, target: TargetFormat) -> str: + """生成绑定代码""" + if target == TargetFormat.SPIRV: + return self._generate_spirv_bindings(shader_info) + elif target in [TargetFormat.DXIL, TargetFormat.DXBC]: + return self._generate_dx_bindings(shader_info) + elif target == TargetFormat.MSL: + return self._generate_msl_bindings(shader_info) + return '' + + + @staticmethod + def _generate_spirv_bindings(shader_info: ShaderInfo) -> str: + """生成SPIR-V绑定属性""" + bindings = [] + for resource in shader_info.resources: + if resource.set >= 0 and resource.binding >= 0: + bindings.append(f"[[vk::binding({resource.binding}, {resource.set})]] {resource.name}") + return "\n".join(bindings) + + + @staticmethod + def _generate_dx_bindings(shader_info: ShaderInfo) -> str: + """生成DirectX绑定属性""" + bindings = [] + for resource in shader_info.resources: + if resource.register and resource.space >= 0: + bindings.append(f"register({resource.register}, space{resource.space}) {resource.name}") + return "\n".join(bindings) + + + @staticmethod + def _generate_msl_bindings(shader_info: ShaderInfo) -> str: + """生成Metal绑定属性""" + bindings = [] + for resource in shader_info.resources: + if resource.metal_index >= 0: + if resource.type in [ResourceType.SAMPLED_TEXTURE, ResourceType.STORAGE_TEXTURE]: + bindings.append(f"[[texture({resource.metal_index})]] {resource.name}") + elif resource.type == ResourceType.SAMPLER: + bindings.append(f"[[sampler({resource.metal_index})]] {resource.name}") + elif resource.type in [ResourceType.UNIFORM_BUFFER, ResourceType.STORAGE_BUFFER]: + bindings.append(f"[[buffer({resource.metal_index})]] {resource.name}") + return "\n".join(bindings) + + + def inject_bindings(self, shader_info: ShaderInfo, target: TargetFormat) -> str: + """在着色器源码中注入绑定信息""" + source_lines = shader_info.source_code.split('\n') + modified_lines = [] + + # 创建资源名到绑定信息的映射 + resource_bindings = {} + for resource in shader_info.resources: + if target == TargetFormat.SPIRV: + if resource.set >= 0 and resource.binding >= 0: + resource_bindings[resource.name] = f"[[vk::binding({resource.binding}, {resource.set})]]" + elif target in [TargetFormat.DXIL, TargetFormat.DXBC]: + if resource.register and resource.space >= 0: + resource_bindings[resource.name] = f"register({resource.register}, space{resource.space})" + elif target == TargetFormat.MSL: + if resource.metal_index >= 0: + if resource.type in [ResourceType.SAMPLED_TEXTURE, ResourceType.STORAGE_TEXTURE]: + resource_bindings[resource.name] = f"[[texture({resource.metal_index})]]" + elif resource.type == ResourceType.SAMPLER: + resource_bindings[resource.name] = f"[[sampler({resource.metal_index})]]" + elif resource.type in [ResourceType.UNIFORM_BUFFER, ResourceType.STORAGE_BUFFER]: + resource_bindings[resource.name] = f"[[buffer({resource.metal_index})]]" + + # 简单的文本替换方式注入绑定 + # 实际应该使用AST解析,这里简化处理 + for line in source_lines: + modified_line = line + + # 查找资源声明 + for resource_name, binding in resource_bindings.items(): + # 匹配各种资源声明模式 + patterns = [ + rf'(\b\w+<.*?>\s+{resource_name}\b)', # Texture2D texName + rf'(\bSamplerState\s+{resource_name}\b)', # SamplerState sampName + rf'(\bConstantBuffer<.*?>\s+{resource_name}\b)', # ConstantBuffer bufName + rf'(\bStructuredBuffer<.*?>\s+{resource_name}\b)', # StructuredBuffer bufName + rf'(\bRWStructuredBuffer<.*?>\s+{resource_name}\b)', # RWStructuredBuffer bufName + rf'(\bParameterBlock<.*?>\s+{resource_name}\b)', # ParameterBlock bufName + ] + + for pattern in patterns: + if re.search(pattern, line): + # 如果是SPIRV和Metal,在声明前添加绑定属性 + if target == TargetFormat.SPIRV or target == TargetFormat.MSL: + modified_line = re.sub(pattern, f'{binding} \\1', line) + # 如果是DXIL/DXBC,在声明后添加绑定属性 + if target in [TargetFormat.DXIL, TargetFormat.DXBC]: + modified_line = re.sub(pattern, f'\\1 : {binding}', line) + break + + modified_lines.append(modified_line) + + return '\n'.join(modified_lines) + + + def generate_binding_info(self, shader_info: ShaderInfo, target: TargetFormat, output_path: str) -> tuple[str, dict]: + """生成绑定信息字典,供运行时使用""" + binding_info = { + 'stage': shader_info.stage.value, + 'entry_point': shader_info.entry_point, + 'resources': [] + } + + # 读取output_path中的二进制数据 + if not os.path.exists(output_path): + raise FileNotFoundError(f"Output file {output_path} does not exist after compilation.") + with open(output_path, 'rb') as f: + binding_info['blob'] = f.read() + + for resource in shader_info.resources: + res_info = { + 'name': resource.name, + 'type': resource.type.value + } + + if target == TargetFormat.SPIRV: + res_info['set'] = resource.set + res_info['binding'] = resource.binding + elif target in [TargetFormat.DXIL, TargetFormat.DXBC]: + res_info['register'] = resource.register + res_info['space'] = resource.space + elif target == TargetFormat.MSL: + res_info['index'] = resource.metal_index + + binding_info['resources'].append(res_info) + + return binding_info \ No newline at end of file diff --git a/code_generator.py b/code_generator.py new file mode 100644 index 0000000..e4c51a7 --- /dev/null +++ b/code_generator.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Code Generator +生成C/C++绑定代码 +""" +import os.path +from typing import List, Dict, TextIO, Optional +from pathlib import Path +from contextlib import contextmanager + +class IndentManager: + """RAII风格的缩进管理器""" + + def __init__(self, file: TextIO, indent_char: str = '\t'): + self.file = file + self.indent_char = indent_char + self.indent_level = 0 + + def write(self, text: str = '') -> None: + """写入带缩进的文本""" + if text: + self.file.write(self.indent_char * self.indent_level + text + '\n') + else: + self.file.write('\n') + self.file.flush() + + def write_raw(self, text: str) -> None: + """写入原始文本(无缩进)""" + self.file.write(text) + + @contextmanager + def indent(self, header: Optional[str] = None, footer: Optional[str] = None): + """缩进上下文管理器""" + if header: + self.write(header) + + self.indent_level += 1 + try: + yield self + finally: + self.indent_level -= 1 + if footer: + self.write(footer) + + @contextmanager + def block(self, header: str, footer: str = '}'): + """代码块上下文管理器""" + self.write(header + ' {') + self.indent_level += 1 + try: + yield self + finally: + self.indent_level -= 1 + self.write(footer) + +class CodeGenerator: + """C++绑定代码生成器""" + + # 资源类型映射表 + RESOURCE_TYPE_MAP = { + 'sampled_texture': 'Resource::SampledTexture', + 'storage_texture': 'Resource::StorageTexture', + 'storage_buffer': 'Resource::StorageBuffer', + 'uniform_buffer': 'Resource::UniformBuffer', + 'sampler': 'Resource::Sampler' + } + + def generate_binding_functions(self, source_file_pathname, binding_infos: List[Dict], output_path: str) -> None: + """生成C++绑定函数的入口方法""" + self._generate_cpp_bindings(source_file_pathname, binding_infos, output_path) + + def _generate_cpp_bindings(self, source_file_pathname, binding_infos: List[Dict], output_path: str) -> None: + """生成C++绑定函数""" + output_file = Path(output_path) + # 尝试创建输出目录 + output_file.parent.mkdir(parents=True, exist_ok=True) + + with open(output_file, 'w', encoding='utf-8') as file: + writer = IndentManager(file) + self._write_complete_file(writer, source_file_pathname, binding_infos) + + def _write_complete_file(self, writer: IndentManager, source_file_pathname, binding_infos: List[Dict]) -> None: + """写入完整的文件内容""" + self._write_header(writer) + + with writer.block('namespace SDL3GPU', '} // namespace SDL3GPU'): + writer.write() + self._write_shader_bindings_class(writer, source_file_pathname, binding_infos) + writer.write() + + def _write_header(self, writer: IndentManager) -> None: + """写入文件头部""" + headers = [ + '#pragma once', + '#include ', + '#include ', + '#include ', + '#include ', + ] + + for header in headers: + writer.write(header) + writer.write() + + def _write_shader_bindings_class(self, writer: IndentManager, source_file_pathname: str, binding_infos: List[Dict]) -> None: + """写入ShaderBindings类""" + # 获取源文件名(不带路径和拓展名) + source_file_name = Path(source_file_pathname).stem + + with writer.block(f'class {source_file_name}ShaderBindings','};'): + self._write_public_section(writer) + self._write_private_section(writer) + self._write_constructor(writer, binding_infos) + self._write_public_methods(writer) + + def _write_public_section(self, writer: IndentManager) -> None: + """写入public部分的结构体定义""" + writer.write('public:') + + # Resource结构体 + with writer.indent(): + with writer.block('struct Resource', '};'): + with writer.block('enum Type'): + enum_values = [ + 'SampledTexture,', + 'StorageTexture,', + 'StorageBuffer,', + 'UniformBuffer,', + 'Sampler' + ] + for value in enum_values: + writer.write(value) + writer.write() + writer.write('std::string name;') + writer.write('Type type;') + writer.write('int binding = -1;') + writer.write('int set = -1;') + writer.write('int space = -1;') + writer.write('int index = -1;') + writer.write() + + # ShaderInfo结构体 + with writer.block('struct ShaderInfo','};'): + writer.write('std::string entryPoint;') + writer.write('SDL_GPUShaderStage stage;') + writer.write('std::vector resources;') + writer.write() + + def _write_private_section(self, writer: IndentManager) -> None: + """写入private部分""" + writer.write('private:') + with writer.indent(): + writer.write('std::unordered_map m_shaderInfos;') + writer.write() + + def _write_constructor(self, writer: IndentManager, binding_infos: List[Dict]) -> None: + """写入构造函数""" + writer.write('public:') + + with writer.indent(): + with writer.block('ShaderBindings()'): + for info in binding_infos: + self._write_shader_initialization(writer, info) + + def _write_shader_initialization(self, writer: IndentManager, shader_info: Dict) -> None: + """写入单个着色器的初始化代码""" + entry_point = shader_info['entry_point'] + stage = shader_info['stage'].upper() + + writer.write(f'// {entry_point}') + + with writer.indent('{', '}'): + writer.write('ShaderInfo info;') + writer.write(f'info.entryPoint = "{entry_point}";') + writer.write(f'info.stage = SDL_GPU_SHADERSTAGE_{stage};') + + # 写入资源信息 + for resource in shader_info['resources']: + self._write_resource_initialization(writer, resource) + + writer.write(f'm_shaderInfos["{entry_point}"] = std::move(info);') + + writer.write() + + def _write_resource_initialization(self, writer: IndentManager, resource: Dict) -> None: + """写入单个资源的初始化代码""" + with writer.indent('{', '}'): + writer.write('Resource r;') + writer.write(f'r.name = "{resource["name"]}";') + + # 设置资源类型 + resource_type = self.RESOURCE_TYPE_MAP.get(resource["type"]) + if resource_type: + writer.write(f'r.type = {resource_type};') + + # 设置资源绑定信息 + if 'binding' in resource: + writer.write(f'r.binding = {resource["binding"]};') + writer.write(f'r.set = {resource["set"]};') + elif 'register' in resource: + writer.write(f'r.space = {resource["space"]};') + elif 'index' in resource: + writer.write(f'r.index = {resource["index"]};') + + writer.write('info.resources.push_back(r);') + + def _write_public_methods(self, writer: IndentManager) -> None: + """写入公共方法""" + writer.write() + self._write_bind_resources_method(writer) + writer.write() + self._write_get_shader_info_method(writer) + + def _write_bind_resources_method(self, writer: IndentManager) -> None: + """写入bindResources方法""" + method_signature = ( + 'void bindResources(\n' + f'{writer.indent_char * (writer.indent_level + 2)}SDL_GPUCommandBuffer* cmd,\n' + f'{writer.indent_char * (writer.indent_level + 2)}const std::string& shaderName,\n' + f'{writer.indent_char * (writer.indent_level + 2)}const std::unordered_map& resources)' + ) + + with writer.block(method_signature): + writer.write('auto it = m_shaderInfos.find(shaderName);') + + with writer.block('if (it == m_shaderInfos.end())'): + writer.write('return;') + + writer.write() + writer.write('const auto& info = it->second;') + writer.write() + writer.write('// 根据着色器阶段和资源类型进行绑定') + writer.write('// 这里需要根据SDL3 GPU的实际API来实现') + + with writer.block('for (const auto& res : info.resources)'): + writer.write('auto resIt = resources.find(res.name);') + + with writer.block('if (resIt == resources.end())'): + writer.write('continue;') + + writer.write() + writer.write('void* resource = resIt->second;') + writer.write() + writer.write('// 示例绑定逻辑') + + with writer.block('switch (res.type)'): + writer.write('case Resource::SampledTexture:') + with writer.indent(): + writer.write('// SDL_BindGPUVertexSamplers(cmd, ...);') + writer.write('break;') + + writer.write('case Resource::StorageBuffer:') + with writer.indent(): + writer.write('// SDL_BindGPUVertexStorageBuffers(cmd, ...);') + writer.write('break;') + + writer.write('// ... 其他资源类型') + writer.write('default:') + with writer.indent(): + writer.write('break;') + + def _write_get_shader_info_method(self, writer: IndentManager) -> None: + """写入getShaderInfo方法""" + with writer.block('const ShaderInfo* getShaderInfo(const std::string& shaderName) const'): + writer.write('auto it = m_shaderInfos.find(shaderName);') + writer.write('return it != m_shaderInfos.end() ? &it->second : nullptr;') diff --git a/compiler.py b/compiler.py new file mode 100644 index 0000000..3ada42a --- /dev/null +++ b/compiler.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Main Compiler +主编译器类,整合所有功能模块 +""" + +import os +import subprocess +import tempfile +from typing import List, Dict, Tuple +from shader_types import ShaderInfo, TargetFormat, ShaderStage +from slangc_finder import SlangcFinder +from shader_parser import ShaderParser +from binding_manager import BindingManager +from code_generator import CodeGenerator + +class SDL3GPUSlangCompiler: + def __init__(self, include_paths: List[str] = None): + self.slangc_path = SlangcFinder.find_slangc() + self.include_paths = include_paths or [] + self.parser = ShaderParser(self.slangc_path, self.include_paths) + self.binding_manager = BindingManager() + self.code_generator = CodeGenerator() + + def parse_slang_shader(self, source_path: str, include_paths: List[str] = None) -> Dict[str, ShaderInfo]: + """解析Slang着色器源码,提取资源信息""" + return self.parser.parse_slang_shader(source_path, include_paths) + + + def compile_shader(self, shader_info: ShaderInfo, target: TargetFormat) -> tuple[str, dict]: + """编译着色器并返回二进制路径和绑定信息""" + output_path = tempfile.mktemp() + # 根据目标格式分配绑定点 + if target == TargetFormat.SPIRV: + self.binding_manager.assign_bindings_spirv(shader_info) + elif target in [TargetFormat.DXIL, TargetFormat.DXBC]: + self.binding_manager.assign_bindings_dxil(shader_info) + elif target == TargetFormat.MSL: + self.binding_manager.assign_bindings_msl(shader_info) + + # 生成带绑定信息的着色器代码 + modified_source = self.binding_manager.inject_bindings(shader_info, target) + + # 写入临时文件 + with tempfile.NamedTemporaryFile(mode='w', suffix='.slang', delete=False, encoding='utf8') as tmp: + tmp.write(modified_source) + tmp_path = tmp.name + + try: + # 编译着色器 + target_flag = { + TargetFormat.SPIRV: 'spirv', + TargetFormat.DXIL: 'dxil', + TargetFormat.DXBC: 'dxbc', + TargetFormat.MSL: 'metal' + }[target] + + stage_flag = { + ShaderStage.VERTEX: 'vertex', + ShaderStage.FRAGMENT: 'fragment', + ShaderStage.COMPUTE: 'compute' + }[shader_info.stage] + + cmd = [ + self.slangc_path, + tmp_path, + '-entry', shader_info.entry_point, + '-o', output_path, + '-target', target_flag, + '-stage', stage_flag, + '-profile', 'sm_6_6', + '-I', *self.include_paths, # 添加包含路径 + ] + + print(f"Compiling shader with command: {' '.join(cmd)}") + + subprocess.run(cmd, check=True) + print(f"Shader compiled successfully") + + # 生成绑定信息 + binding_info = self.binding_manager.generate_binding_info(shader_info, target, output_path) + + return binding_info + + finally: + # 清理临时文件 + os.unlink(tmp_path) + os.unlink(output_path) + + + def generate_binding_functions(self, source_file_pathname, binding_infos: List[Dict], output_path: str): + """生成C/C++绑定函数""" + self.code_generator.generate_binding_functions(source_file_pathname, binding_infos, output_path) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..1917c6f --- /dev/null +++ b/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Command Line Interface +命令行接口 +""" + +import os +import sys +import tempfile +import json +import argparse +from compiler import SDL3GPUSlangCompiler +from shader_types import TargetFormat + +def main(): + parser = argparse.ArgumentParser(description='SDL3 GPU Slang Compiler') + + parser.add_argument('input', help='Input Slang shader file') + parser.add_argument('-t', '--target', choices=['spirv', 'dxil', 'dxbc', 'msl'], + required=True, help='Target shader format') + parser.add_argument('--binding-output-dir', required=True, help='Output path for binding code') + + args = parser.parse_args() + + # 获取编译文件的绝对路径 + input_path = os.path.abspath(args.input) + # 获取不包含拓展名的源文件名 + source_file_name = os.path.splitext(os.path.basename(input_path))[0] + # 仅保留路径部分 + input_path = os.path.dirname(input_path) + + # 创建编译器实例 + compiler = SDL3GPUSlangCompiler([input_path]) + + # 解析着色器 + print(f"**Parsing** {args.input}...") + shaders = compiler.parse_slang_shader(args.input) + + # 编译每个入口点 + target = TargetFormat(args.target) + binding_infos = [] + + for name, shader_info in shaders.items(): + print(f"**Compiling** {name}...") + + binding_info = compiler.compile_shader(shader_info, target) + binding_infos.append(binding_info) + + binding_output_file_pathname = os.path.abspath(args.binding_output_dir) + binding_output_file_pathname = os.path.join(binding_output_file_pathname, f"{source_file_name}.shader.h") + + # 生成绑定代码 + print(f"\n**Generating** binding code to {binding_output_file_pathname}...") + compiler.generate_binding_functions(os.path.abspath(args.input), binding_infos, binding_output_file_pathname) + print("**Done!**") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/shader_parser.py b/shader_parser.py new file mode 100644 index 0000000..a22e217 --- /dev/null +++ b/shader_parser.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Shader Parser +着色器源码解析功能 +""" + +import os +import json +import subprocess +import tempfile +import shutil +import re +from typing import List, Dict, Optional +from shader_types import ShaderStage, ResourceType, Resource, ShaderInfo + + +class ShaderParser: + def __init__(self, slangc_path: str, include_paths: List[str] = None): + self.slangc_path = slangc_path + self.include_paths = include_paths or [] + + def parse_slang_shader(self, source_path: str, include_paths: List[str] = None) -> Dict[str, ShaderInfo]: + """解析Slang着色器源码,提取资源信息""" + + # 合并包含路径 + all_include_paths = self.include_paths.copy() + if include_paths: + all_include_paths.extend(include_paths) + + # 添加源文件所在目录作为包含路径 + source_dir = os.path.dirname(os.path.abspath(source_path)) + if source_dir not in all_include_paths: + all_include_paths.insert(0, source_dir) + + print(f"Include paths: {all_include_paths}") + + with open(source_path, 'r', encoding='utf-8') as f: + source = f.read() + + # 首先分析源码找到所有入口点 + entry_points = self._find_entry_points(source) + print(f"Found potential entry points: {entry_points}") + + shaders = {} + + # 为每个入口点单独进行完整编译和反射 + for entry_name, stage in entry_points.items(): + print(f"\nProcessing entry point: {entry_name} (stage: {stage.value})") + + shader_info = self._compile_and_reflect_entry_point( + source_path, entry_name, stage, all_include_paths, source + ) + + if shader_info: + shaders[entry_name] = shader_info + else: + print(f"Failed to process entry point: {entry_name}") + + return shaders + + def _find_entry_points(self, source: str) -> Dict[str, ShaderStage]: + """在源码中查找入口点函数""" + entry_points = {} + + # 1. 查找带有Slang属性的函数 + attribute_patterns = [ + (r'$$shader\s*$\s*["\']vertex["\']\s*$\s*$$.*?(\w+)\s*\(', ShaderStage.VERTEX), + (r'$$shader\s*$\s*["\']fragment["\']\s*$\s*$$.*?(\w+)\s*\(', ShaderStage.FRAGMENT), + (r'$$shader\s*$\s*["\']pixel["\']\s*$\s*$$.*?(\w+)\s*\(', ShaderStage.FRAGMENT), + (r'$$shader\s*$\s*["\']compute["\']\s*$\s*$$.*?(\w+)\s*\(', ShaderStage.COMPUTE), + ] + + for pattern, stage in attribute_patterns: + matches = re.finditer(pattern, source, re.DOTALL | re.IGNORECASE) + for match in matches: + entry_points[match.group(1)] = stage + print(f"Found attributed entry point: {match.group(1)} -> {stage.value}") + + # 2. 查找常见的命名约定 + common_entry_points = { + # Vertex shaders + 'vertex_main': ShaderStage.VERTEX, + 'vertexMain': ShaderStage.VERTEX, + 'vert_main': ShaderStage.VERTEX, + 'vs_main': ShaderStage.VERTEX, + 'vertex_shader': ShaderStage.VERTEX, + 'VS': ShaderStage.VERTEX, + + # Fragment shaders + 'fragment_main': ShaderStage.FRAGMENT, + 'fragmentMain': ShaderStage.FRAGMENT, + 'frag_main': ShaderStage.FRAGMENT, + 'pixel_main': ShaderStage.FRAGMENT, + 'ps_main': ShaderStage.FRAGMENT, + 'fragment_shader': ShaderStage.FRAGMENT, + 'PS': ShaderStage.FRAGMENT, + + # Compute shaders + 'compute_main': ShaderStage.COMPUTE, + 'computeMain': ShaderStage.COMPUTE, + 'comp_main': ShaderStage.COMPUTE, + 'cs_main': ShaderStage.COMPUTE, + 'compute_shader': ShaderStage.COMPUTE, + 'CS': ShaderStage.COMPUTE, + } + + # 查找这些函数名是否在源码中存在 + for func_name, stage in common_entry_points.items(): + if func_name not in entry_points: + # 使用词边界确保完整匹配函数名 + pattern = r'\b' + re.escape(func_name) + r'\s*\(' + if re.search(pattern, source, re.IGNORECASE): + entry_points[func_name] = stage + print(f"Found common entry point: {func_name} -> {stage.value}") + + # 3. 查找所有函数声明,根据返回类型和参数推断 + function_pattern = r'(?:float4|void|float3|float2|float)\s+(\w+)\s*$[^)]*$(?:\s*:\s*\w+)?' + matches = re.finditer(function_pattern, source) + + for match in matches: + func_name = match.group(1) + if func_name not in entry_points: + # 根据函数名模式推断 + func_lower = func_name.lower() + + # 顶点着色器关键词 + if any(keyword in func_lower for keyword in ['vert', 'vs', 'vertex']): + entry_points[func_name] = ShaderStage.VERTEX + print(f"Inferred vertex shader: {func_name}") + # 片元着色器关键词 + elif any(keyword in func_lower for keyword in ['frag', 'pixel', 'ps', 'fs', 'fragment']): + entry_points[func_name] = ShaderStage.FRAGMENT + print(f"Inferred fragment shader: {func_name}") + # 计算着色器关键词 + elif any(keyword in func_lower for keyword in ['comp', 'cs', 'compute']): + entry_points[func_name] = ShaderStage.COMPUTE + print(f"Inferred compute shader: {func_name}") + + # 4. 查找带有HLSL语义的函数 + semantic_patterns = [ + (r'(\w+)\s*$[^)]*$\s*:\s*SV_Position', ShaderStage.VERTEX), + (r'(\w+)\s*$[^)]*$\s*:\s*SV_Target', ShaderStage.FRAGMENT), + (r'(\w+)\s*$[^)]*$\s*:\s*POSITION', ShaderStage.VERTEX), + ] + + for pattern, stage in semantic_patterns: + matches = re.finditer(pattern, source, re.IGNORECASE) + for match in matches: + func_name = match.group(1) + if func_name not in entry_points: + entry_points[func_name] = stage + print(f"Found semantic-based entry point: {func_name} -> {stage.value}") + + # 5. 如果没找到任何入口点,查找main函数 + if not entry_points: + if re.search(r'\bmain\s*\(', source, re.IGNORECASE): + entry_points['main'] = ShaderStage.VERTEX + print("Found main function, assuming vertex shader") + + print(f"Total entry points found: {len(entry_points)}") + return entry_points + + def _compile_and_reflect_entry_point(self, source_path: str, entry_name: str, + stage: ShaderStage, include_paths: List[str], + source: str) -> Optional[ShaderInfo]: + """为单个入口点进行完整编译和反射""" + + # 创建临时输出文件 + with tempfile.NamedTemporaryFile(suffix='.hlsl', delete=False) as tmp_output: + output_path = tmp_output.name + + with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as tmp_reflection: + reflection_path = tmp_reflection.name + + # 构建完整的slangc编译命令(类似你成功的命令) + cmd = [self.slangc_path] + + # 添加包含路径 + for include_path in include_paths: + cmd.extend(['-I', include_path]) + + # 添加编译参数 + stage_name = { + ShaderStage.VERTEX: 'vertex', + ShaderStage.FRAGMENT: 'fragment', + ShaderStage.COMPUTE: 'compute' + }[stage] + + cmd.extend([ + source_path, # 输入文件 + '-reflection-json', reflection_path, # 反射输出 + '-o', output_path, # 编译输出 + '-target', 'hlsl', # 目标格式(用于测试) + '-entry', entry_name, # 入口点 + '-stage', stage_name # 着色器阶段 + ]) + + print(f"Command: {' '.join(cmd)}") + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=60 + ) + + print(f"Return code: {result.returncode}") + if result.stdout: + print(f"Stdout: {result.stdout}") + if result.stderr: + print(f"Stderr: {result.stderr}") + + if result.returncode == 0: + # 读取反射数据 + if os.path.exists(reflection_path): + with open(reflection_path, 'r', encoding='utf-8') as f: + reflection_json = f.read() + print(f"Reflection JSON length: {len(reflection_json)}") + + if reflection_json.strip(): + try: + reflection = json.loads(reflection_json) + return self._create_shader_info_from_reflection( + reflection, entry_name, stage, source + ) + except json.JSONDecodeError as e: + print(f"JSON parsing error: {e}") + print(f"Raw JSON: {reflection_json[:500]}...") + else: + print(f"Reflection file not created: {reflection_path}") + else: + print(f"Compilation failed for entry point {entry_name}") + + # 如果反射失败,尝试手动解析 + print(f"Falling back to manual parsing for {entry_name}") + # 清理临时文件 + for temp_file in [output_path, reflection_path]: + if os.path.exists(temp_file): + os.unlink(temp_file) + + return self._create_shader_info_manual(entry_name, stage, source) + + def _create_shader_info_from_reflection(self, reflection: dict, entry_name: str, + stage: ShaderStage, source: str) -> ShaderInfo: + """从反射数据创建ShaderInfo""" + print(f"Processing reflection data for {entry_name}") + print(f"Reflection keys: {list(reflection.keys())}") + + shader_info = ShaderInfo( + stage=stage, + entry_point=entry_name, + resources=[], + source_code=source + ) + + # Slang反射数据的可能结构 + # 尝试不同的数据结构 + entry_point_data = None + + # 方法1: 直接在根级别查找 + if 'parameters' in reflection: + entry_point_data = reflection + + # 方法2: 在entryPoints数组中查找 + elif 'entryPoints' in reflection: + for ep in reflection['entryPoints']: + if ep.get('name') == entry_name: + entry_point_data = ep + break + + # 方法3: 在modules中查找 + elif 'modules' in reflection: + for module in reflection['modules']: + if 'entryPoints' in module: + for ep in module['entryPoints']: + if ep.get('name') == entry_name: + entry_point_data = ep + break + + if entry_point_data: + print(f"Found entry point data: {list(entry_point_data.keys())}") + + # 解析资源参数 + parameters = entry_point_data.get('parameters', []) + print(f"Found {len(parameters)} parameters") + + for param in parameters: + print(f"Processing parameter: {param}") + resource = self._parse_resource(param) + if resource: + shader_info.resources.append(resource) + print(f"Added resource: {resource.name} ({resource.type.value})") + else: + print("No entry point data found in reflection, using manual parsing") + # 使用手动解析作为fallback + manual_resources = self._extract_resources_from_source(source) + shader_info.resources.extend(manual_resources) + + print(f"Shader {entry_name} has {len(shader_info.resources)} resources") + return shader_info + + def _create_shader_info_manual(self, entry_name: str, stage: ShaderStage, source: str) -> ShaderInfo: + """手动创建ShaderInfo(fallback方法)""" + print(f"Creating shader info manually for {entry_name}") + shader_info = ShaderInfo( + stage=stage, + entry_point=entry_name, + resources=[], + source_code=source + ) + + # 手动解析资源 + resources = self._extract_resources_from_source(source) + shader_info.resources.extend(resources) + + print(f"Manual parsing found {len(resources)} resources") + return shader_info + + def _extract_resources_from_source(self, source: str) -> List[Resource]: + """从源码中提取资源声明""" + resources = [] + + # 资源声明的正则表达式模式 + patterns = { + # Texture resources + ResourceType.SAMPLED_TEXTURE: [ + r'Texture2D\s*(?:<[^>]*>)?\s+(\w+)', + r'Texture3D\s*(?:<[^>]*>)?\s+(\w+)', + r'TextureCube\s*(?:<[^>]*>)?\s+(\w+)', + ], + ResourceType.STORAGE_TEXTURE: [ + r'RWTexture2D\s*(?:<[^>]*>)?\s+(\w+)', + r'RWTexture3D\s*(?:<[^>]*>)?\s+(\w+)', + ], + # Buffer resources + ResourceType.STORAGE_BUFFER: [ + r'RWStructuredBuffer\s*<[^>]*>\s+(\w+)', + r'RWByteAddressBuffer\s+(\w+)', + r'StructuredBuffer\s*<[^>]*>\s+(\w+)', + r'ByteAddressBuffer\s+(\w+)', + ], + ResourceType.UNIFORM_BUFFER: [ + r'ConstantBuffer\s*<[^>]*>\s+(\w+)', + r'cbuffer\s+(\w+)', + ], + ResourceType.SAMPLER: [ + r'SamplerState\s+(\w+)', + r'SamplerComparisonState\s+(\w+)', + ] + } + + for resource_type, type_patterns in patterns.items(): + for pattern in type_patterns: + matches = re.findall(pattern, source, re.IGNORECASE) + for match in matches: + resources.append(Resource(match, resource_type)) + print(f"Found resource: {match} ({resource_type.value})") + + return resources + + def _parse_resource(self, param: dict) -> Optional[Resource]: + """解析资源参数""" + type_info = param.get('type', {}) + type_name = type_info.get('name', '') + + # 判断资源类型 + if 'Texture2D' in type_name or 'Texture3D' in type_name or 'TextureCube' in type_name: + if 'RW' in type_name: + return Resource(param['name'], ResourceType.STORAGE_TEXTURE) + else: + return Resource(param['name'], ResourceType.SAMPLED_TEXTURE) + elif 'StructuredBuffer' in type_name or 'ByteAddressBuffer' in type_name: + if 'RW' in type_name: + return Resource(param['name'], ResourceType.STORAGE_BUFFER) + else: + # SDL3 GPU似乎不支持只读storage buffer,这里可能需要特殊处理 + return Resource(param['name'], ResourceType.STORAGE_BUFFER) + elif 'ConstantBuffer' in type_name or type_info.get('kind') == 'ConstantBuffer' or type_info.get('kind') == 'parameterBlock': + return Resource(param['name'], ResourceType.UNIFORM_BUFFER) + elif 'SamplerState' in type_name or 'Sampler' in type_name: + return Resource(param['name'], ResourceType.SAMPLER) + + return None \ No newline at end of file diff --git a/shader_types.py b/shader_types.py new file mode 100644 index 0000000..2541c97 --- /dev/null +++ b/shader_types.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Type Definitions +数据类型和枚举定义 +""" + +from enum import Enum +from dataclasses import dataclass +from typing import List + +class ShaderStage(Enum): + VERTEX = "vertex" + FRAGMENT = "fragment" + COMPUTE = "compute" + +class ResourceType(Enum): + SAMPLED_TEXTURE = "sampled_texture" + STORAGE_TEXTURE = "storage_texture" + STORAGE_BUFFER = "storage_buffer" + UNIFORM_BUFFER = "uniform_buffer" + SAMPLER = "sampler" + +class TargetFormat(Enum): + SPIRV = "spirv" + DXIL = "dxil" + DXBC = "dxbc" + MSL = "msl" + +@dataclass +class Resource: + name: str + type: ResourceType + binding: int = -1 + set: int = -1 + space: int = -1 + register: str = "" + metal_index: int = -1 + +@dataclass +class ShaderInfo: + stage: ShaderStage + entry_point: str + resources: List[Resource] + source_code: str diff --git a/slangc_finder.py b/slangc_finder.py new file mode 100644 index 0000000..fae72f7 --- /dev/null +++ b/slangc_finder.py @@ -0,0 +1,90 @@ +# !/usr/bin/env python3 +""" +SDL3_GPU Slang Compiler - Slangc Finder +查找和验证Slangc编译器路径 +""" + +import os +import sys +import shutil +import subprocess + +class SlangcFinder: + @staticmethod + def find_slangc() -> str: + """查找slangc编译器路径""" + + # 方法1: 使用shutil.which (推荐) + slangc_path = shutil.which('slangc') + if slangc_path: + try: + subprocess.run([slangc_path, '-v'], + capture_output=True, check=True, timeout=10) + print(f"Found slangc at: {slangc_path}") + return slangc_path + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + pass + + # 方法2: 从环境变量获取 + slangc_from_env = os.environ.get('SLANGC_PATH') + if slangc_from_env and os.path.isfile(slangc_from_env): + try: + subprocess.run([slangc_from_env, '-v'], + capture_output=True, check=True, timeout=10) + print(f"Found slangc from SLANGC_PATH: {slangc_from_env}") + return slangc_from_env + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + pass + + # 方法3: 在常见位置搜索 + common_paths = [ + r'C:\Program Files\Slang\bin\slangc.exe', + r'C:\Program Files (x86)\Slang\bin\slangc.exe', + r'C:\tools\slang\bin\slangc.exe', + r'C:\slang\bin\slangc.exe' + ] + + # 也尝试从PATH中的每个目录查找 + path_dirs = os.environ.get('PATH', '').split(os.pathsep) + for path_dir in path_dirs: + if path_dir: # 跳过空路径 + potential_path = os.path.join(path_dir, 'slangc.exe') + common_paths.append(potential_path) + + for path in common_paths: + if os.path.isfile(path): + try: + subprocess.run([path, '-v'], + capture_output=True, check=True, timeout=10) + print(f"Found slangc at: {path}") + return path + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + continue + + # 方法4: 尝试直接调用 (有时候PATH在Python中不完整) + try: + result = subprocess.run(['slangc', '-v'], + capture_output=True, check=True, timeout=10) + print("Found slangc in PATH") + return 'slangc' + except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError): + pass + + # 如果都失败了,显示调试信息 + print("Debug information:") + print(f"Current working directory: {os.getcwd()}") + print(f"Python executable: {sys.executable}") + print(f"PATH environment variable:") + for i, path in enumerate(os.environ.get('PATH', '').split(os.pathsep)): + print(f" {i}: {path}") + + raise RuntimeError(""" +Cannot find slangc compiler. Please try one of the following: +Set SLANGC_PATH environment variable to the full path of slangc.exe +Ensure slangc.exe is in your PATH and restart your terminal +Install Slang to a standard location like C:\\Program Files\\Slang\\bin\\ +Run the script from the same terminal where 'slangc --version' works +You can also specify the path directly when creating the compiler: +compiler = SDL3GPUSlangCompiler() +compiler.slangc_path = r'C:\\path\\to\\slangc.exe' +""") \ No newline at end of file