This commit is contained in:
2025-06-05 20:29:36 +08:00
commit 7a392281f1
9 changed files with 1248 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.idea
/__pycache__

27
__init__.py Normal file
View File

@@ -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'

285
binding_manager.py Normal file
View File

@@ -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<float4> texName
rf'(\bSamplerState\s+{resource_name}\b)', # SamplerState sampName
rf'(\bConstantBuffer<.*?>\s+{resource_name}\b)', # ConstantBuffer<T> bufName
rf'(\bStructuredBuffer<.*?>\s+{resource_name}\b)', # StructuredBuffer<T> bufName
rf'(\bRWStructuredBuffer<.*?>\s+{resource_name}\b)', # RWStructuredBuffer<T> bufName
rf'(\bParameterBlock<.*?>\s+{resource_name}\b)', # ParameterBlock<T> 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

266
code_generator.py Normal file
View File

@@ -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 <SDL3/SDL_gpu.h>',
'#include <string>',
'#include <vector>',
'#include <unordered_map>',
]
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<Resource> resources;')
writer.write()
def _write_private_section(self, writer: IndentManager) -> None:
"""写入private部分"""
writer.write('private:')
with writer.indent():
writer.write('std::unordered_map<std::string, ShaderInfo> 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<std::string, void*>& 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;')

93
compiler.py Normal file
View File

@@ -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)

58
main.py Normal file
View File

@@ -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()

383
shader_parser.py Normal file
View File

@@ -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:
"""手动创建ShaderInfofallback方法"""
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

44
shader_types.py Normal file
View File

@@ -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

90
slangc_finder.py Normal file
View File

@@ -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'
""")