☰
Stride 引擎 SDSL 着色器语言的解析与编译:从语法设计到源码实现
2026/9/28 6:17:36 网站建设 项目流程
  • 游戏开发
  • 图形学
  • VR

【免费下载链接】stride

Stride (formerly Xenko), a free and open-source cross-platform C# game engine.

项目地址:https://gitcode.com/gh_mirrors/st/stride
点击查看免费下载

Stride(前身 Xenko)是一款免费开源、跨平台的 C# 游戏引擎。本文围绕 sources/shaders/README.md 介绍 SDSL(Stride Shading Language)——引擎内置的着色器语言,它是 HLSL 的超集,新增了面向对象式的可扩展性、模块化与复用机制。文章将深入解析 SDSL 的语法要素、在 sources/shaders 目录中"解析(Parser)—语义分析(Analysis)—SPIR-V 构建—合并(Mixin)"的完整编译流水线,并给出可直接参考的语法示例与测试用例,帮助你理解 Stride 如何把高级着色器代码最终编译为可运行字节码。

SDSL 是什么:为游戏引擎而生的着色语言

SDSL(Stride Shading Language)是 Stride 游戏引擎专门设计的着色器语言,核心定位是HLSL 的超集:它完整继承了 HLSL 的语法与能力,同时引入面向对象编程(OOP)理念,让着色器代码获得三方面的提升:

  • 可扩展性(extensibility):通过类(shader)、继承(mixin)、组合(compose)等概念,着色器可以被轻松扩展,不必复制粘贴整段代码;
  • 模块化(modularity):每个着色器只聚焦一种渲染技术(例如光照、阴影、后处理),整体更易管理与组合;
  • 可复用性(reusability):最大化着色器之间的代码复用,公共逻辑沉淀为基类或工具着色器,被多个效果引用。

这套实现位于 sources/shaders,由多个项目协作完成:Stride.Shaders.Parsers(词法/语法解析)、Stride.Shaders.Compilers(语义分析、SPIR-V 构建与合并)、Stride.Shaders.Spirv(SPIR-V 数据结构)、Stride.Shaders.Effects(效果编译器与缓存)、Stride.Shaders.Generators与Stride.Shaders.Generators.Internal(代码生成/内置函数生成)等。

本文所有结论均来自当前仓库实际内容。仓库为只读,以下均用于查看、运行与配置说明。

从一份 SDSL 文件看语言骨架

仓库 sources/shaders/assets/SDSL 提供了一批可直接阅读的示例。以Test.sdsl为例:

namespace Machin; shader Test { stream float4 Position : SV_POSITION; stream float4 Color : SV_TARGET; bool machin = false; void PSMain() { streams.Position.w = 1.0; } void VSMain() { int a = 0; a = 3; if(machin) streams.Color *= 2; if(a < 2) a *= 3; streams.Color = float4(1.0,0.0,1.0,1f32); } }

这份代码揭示了 SDSL 的几个关键概念:

  • namespace:SDSL 支持命名空间组织着色器(支持namespace a.b.c;或namespace a { ... }两种写法)。在 ShaderFileParsers.cs 中,NamespaceParsers会循环读取点分标识符,并允许在命名空间内声明 shader、effect 或 params;
  • shader:声明一个着色器类,是 SDSL 的基本组织单元;
  • stream:声明流变量(顶点/像素阶段间传递的插值数据),后面的语义(如SV_POSITION、SV_TARGET)决定其在管线中的含义;
  • streams.xxx:在方法体内通过streams.前缀访问流变量——这是 SDSL 相对 HLSL 在写法上的显著差异(HLSL 通常通过结构体形参传递);
  • 字面量后缀:1f32表示 32 位浮点、3f16表示 16 位浮点,也支持1.(带尾点)等 HLSL 语法变体,测试用例 ParsingTests.cs 专门验证了这些边界情况。

再看Simple.sdsl中更"激进"的语法形态:

namespace machin { namespace hello; namespace other.thing { internal partial effect class.heloo { } shader MyCLass<int a, Link machin> : Hello<1,2,3.0f32,"Hello"> { patchstream float a = 5; float3x3 b; float Foo(int a, int b) { elif(a == b) { float a = Comm(); } const int a = 3; machin = 5; streams.a.machin.hello[1] = 9; } } shader other; } }

这里出现了更完整的语法要素:partial分部类、带泛型参数(<int a, Link machin>)与泛型实参(Hello<1,2,3.0f32,"Hello">)的类声明、patchstream流变量、elif语句等,说明 SDSL 的语法面比传统 HLSL 宽得多。

类、继承与组合:SDSL 面向对象三要素

SDSL 的面向对象能力通过三种语法机制体现,它们分别对应"扩展、模块化、复用"三大设计目标。

继承 / mixin

shader之间通过冒号进行继承,子类可以覆盖父类方法、继承父类流变量与常量:

shader TestBasic : TestBase { stream float4 InputPosition : POSITION; stream float4 Position : SV_POSITION; stream float4 ExtraColor : COLOR; cbuffer Test123 { float4 CBufferValue1; } void Test() { streams.ColorTarget = streams.ExtraColor; SetColor(streams.ExtraColor); // 调用继承自 TestBase 的方法 } // ... }

其中TestBase.sdsl定义了stream float4 ColorTarget : SV_Target0;和SetColor(float4)方法,TestBasic直接继承并复用。在源码层面,Shader.cs 中ShaderClass的Mixins字段保存了所有被 mixin 的父类;继承链的处理在编译期(ShaderMixer)完成,而不是运行时动态解析。

泛型(generic)着色器

SDSL 支持带类型参数与值参数的泛型着色器,用于生成"同一模板、多种尺寸"的代码。仓库 CompilerTests/ConstUIntGeneric.sdsl 是一个完整示例:

shader ConstUIntGenericBase<int TCount> { static const uint Count = TCount; static const uint CountPlusOne = TCount + 1; } shader ConstUIntGeneric : ConstUIntGenericBase<3> { stream float4 ColorTarget : SV_Target0; cbuffer Test { uint Selector; float Values[CountPlusOne]; // 数组长度由继承的泛型常量决定 } void PSMain() { uint count = Count; float v = Selector == count ? Values[3] : 0.0; streams.ColorTarget = float4(v, v, v, v); } }

泛型实参可以是值字面量(<3>)、类型(<int a>)甚至字符串(Hello<...,"Hello">)。AST 侧由 ShaderGenericsValues.cs 的ValueTypeGenerics承载值类型泛型。这一机制配合static const常量传播,可让数组维度、循环次数等在编译期确定,类似模板元编程的效果。

组合(compose)

组合用于把一个或多个子着色器"内嵌"到当前着色器中,子着色器的方法通过引用调用:

shader ComposePlainRoot : ComposeSharedBase { RWTexture3D<float4> WriteTex; compose ComposePlainHelper helper; override void Compute() { WriteTex[streams.DispatchThreadId] = helper.Value(); // 调用组合子着色器的方法 } };

compose引入的是"对象级"复用(相对于继承的"类型级"复用):组合关系在语义分析阶段被解析,ShaderMixer会把被组合的着色器代码合并进最终产物。测试目录中还包含ComposeSharedRoot、ComposePlainHelper、ComposeNumThreadsRoot等配套文件,验证组合与继承并存时的行为(例如 ComposePlainRoot.sdsl 与共享基类的对照)。

模块化封装:cbuffer、stream、stage 与 attributes

SDSL 在类体内支持多种成员声明,均在 ShaderElements.cs 中有对应 AST 节点:

关键字含义仓库示例
stream顶点/像素阶段间传递的插值数据TestBasic.sdsl中的Position : SV_POSITION
patchstream细分阶段(hull/domain)使用的 patch 流数据Simple.sdsl中的patchstream float a = 5;
stage声明只能被特定阶段使用的方法/变量(如 compute 入口CSMain)StaticCallRoot.sdsl中的stage stream uint3 DispatchThreadId、stage void CSMain()
static const编译期常量,可参与数组维度计算ConstUIntGeneric.sdsl
cbuffer常量缓冲区声明TestBasic.sdsl中的cbuffer Test123
compose组合子着色器ComposePlainRoot.sdsl
override覆盖父类或组合对象的方法ComposePlainRoot.sdsl中的override void Compute()
abstract抽象方法,由子类/组合对象实现Parent.sdsl中的stage abstract void DoSomething();
attributes([...])附加元数据,如布局、numthreadsCommented.sdsl中的[Layout(1, 2)]

典型的 attribute 应用是 compute shader 的线程数声明:

[numthreads(8, 8, 8)] stage void CSMain() { WriteTex[streams.DispatchThreadId] = float4(...); }

示例见 CompilerTests/StaticCallRoot.sdsl。编译器测试目录还覆盖了CSNumThreadsInherit、CSNumThreadsOverride、CSNumThreadsOnNonEntry等场景,说明numthreads在继承/覆盖/非入口函数等情况下有严格校验。

预处理与宏:与 C 兼容的条件编译

SDSL 保留并兼容 C 风格的预处理指令。Macroed.sdsl展示了条件编译:

#ifdef cond shader Machin { } #endif shader Machin2 { }

预处理管线位于 Parsing/PreProcessing,由多个阶段组成(CommentPhase处理注释、CodeProcessor处理宏展开等)。解析入口 SDSLParser.cs 展示了完整调用链:

public static ParseResult Parse(string code) { var c = new CommentProcessedCode(code); return Grammar.Match<CommentProcessedCode, ShaderFileParser, ShaderFile>(c); }

即:原始代码 → 注释预处理(CommentProcessedCode)→ 文法匹配(Grammar.Match)→ 得到 ShaderFile AST。

解析器架构:Parser + Grammar + AST

解析器位于Stride.Shaders.Parsers,设计上分为三层:

  1. 扫描器(Scanner):Parsing/Scanners目录下的Scanner/ScannerGeneric等负责把源码切分成 token 流,并记录TextLocation/ErrorLocation用于精确报错;
  2. 文法(Grammar):Grammar.cs 是解析核心,Match方法创建Scanner、执行IParser<TValue>.Match,并在 token 未消耗完时追加SDSL0009错误("额外 token"类错误);
  3. AST:Parsing/SDSL/AST目录定义了ShaderFile、ShaderNamespace、ShaderClass、ShaderElement、Expression、Statement等节点层级。

解析结果用 ParseResult.cs 承载:ParseResult.AST保存成功解析的语法树,Errors保存ParseError(包含消息、位置与出错 token 的上下文预览)。ParseError.ToString()会基于出错位置自动摘取下一个 token,方便快速定位。

各语法要素按职责拆分到Parsing/SDSL/Parsers子目录:

  • Terminals/:终结符(标识符、字面量、操作符)解析;
  • ExpressionParsers/:表达式解析;
  • StatementParsers/:语句解析;
  • ShaderParsers/:着色器级解析,其中 ShaderClassParser.cs 的SimpleShaderClassParser展示了最朴素的shader Name { ... }匹配流程:匹配shader关键字 → 空格 → 标识符 →{→ 循环匹配成员元素直到};
  • DirectiveExpressions/:指令表达式解析;
  • Common/:共享辅助解析器。

语义分析与符号解析

语法解析之后,编译进入语义分析阶段,核心组件包括:

  • 符号表(SymbolTable)与符号帧(SymbolFrame):SymbolFrame.cs 实现作用域内符号的按名索引,并支持"方法组(MethodGroup)":当多个同名函数(重载)出现在同一作用域时,自动归并为FunctionGroupType符号;
  • 符号类型(SymbolTypes)与类型访问器(Node.Visitors / SymbolTypes.Visitors):负责类型推断与表达式类型检查;
  • 分析(Analysis):Parsing/Analysis 包含控制流图(CFG.cs)、SDIR.cs与符号表扩展,为后续代码生成提供静态信息;
  • 常量求值(ConstantEvaluator):Core/ConstantEvaluator.cs可在编译期求值常量表达式——这正是ConstUIntGeneric中CountPlusOne = TCount + 1能用于数组维度的原因。

从 AST 到 SPIR-V:SDSL 编译器的中间表示

语义分析完成后,代码被降级为SPIR-V(而不是直接生成 HLSL/GLSL),这一设计让"一份 SDSL 源码 → 多后端输出"成为可能。相关代码位于Stride.Shaders.Compilers/SDSL:

  • SDSLC.cs 是单文件编译入口:SDSLParser.Parse→ 逐个ShaderClass创建CompilerUnit→ 注册调试信息(OpString/OpSource/OpSourceHashSDSL,后者用于着色器缓存校验)→ 执行shader.Compile(table, compiler)生成 SPIR-V 指令;
  • ShaderMixer.cs 是合并器(mixin 编译器):创建全局SpirvContext,写入OpCapability(Shader)、OpExtension("SPV_GOOGLE_user_type")、OpMemoryModel(GLSL450)等模块头,然后递归解析 mixin/compose 关系,把多个 shader 的 SPIR-V 合并为一个可执行模块;
  • 校验:ShaderMixer.Validation.cs 负责在合并后做语义校验,例如检测"在无法计算屏幕空间导数的阶段(非 Fragment/GLCompute)使用隐式 LOD 纹理采样或ddx/ddy/fwidth",提前给出可读错误而非等后端优化器失败;
  • SPIR-V 数据结构与读写由Stride.Shaders.Spirv项目提供(OpData、SpirvBuffer、SpirvReader等)。

SDSLC.CompileOptions(SDSLC.cs)还支持两个编译选项:RegisterInCache(是否将编译结果注册进缓存)与OriginalCode(预处理前的原始源码,用于OpSource调试信息)。

后端输出:SPIR-V 翻译回 HLSL 及其他

Stride.Shaders.Compilers提供了从 SPIR-V 到具体后端代码的翻译:

  • SpirvTranslator.cs:GetEntryPoints(Backend)枚举入口点(按 ExecutionModel 区分 VS/PS/CS 等),Translate(Backend, entryPoint)把 SPIR-V 翻译为目标后端文本;
  • Backend枚举支持 HLSL 与 SPIR-V 两类目标;
  • EffectCompiler.cs 在编译效果(effect)时调用translator.Translate(Backend.Hlsl, entryPoint)生成最终 HLSL;
  • ShaderMixer.Options(ShaderMixer.cs)有两个关键开关:ResourcesRegisterSeparate(D3D11/12 下 t/b/s 寄存器各自独立编号,从 0 开始)与StripGoogleUserType(移除SPV_GOOGLE_user_type扩展与UserTypeGOOGLE装饰——Vulkan 在不支持该扩展时会拒绝)。

测试 StrideShaderTests.cs 展示了完整链路:ShaderLoader加载.sdsl→ShaderMixer.MergeSDSL产出字节码 →SpirvTranslator翻译为 HLSL → 断言数组维度[2][3][5]保持声明顺序。这类回归测试印证了"解析—合并—翻译"流水线的真实可用性。

效果文件(SDFX):组合的声明式入口

SDSL 着色器类之上还有一层SDFX(effect 文件),用声明式语法描述"如何把若干 shader mixin 组合成一个 effect"。仓库示例 assets/SDFX/BasicEffect.sdfx:

namespace Stride.Rendering.Images; shader A { int a; } shader B { float b; } params BasicParams { bool MixA; bool MixB; } effect BasicEffect { using params BasicParams; mixin compose Target1 = Test123; mixin compose Target2 += Test123; mixin macro Test = 1; mixin A; mixin (A); if(BasicParams.MixA) mixin (A); if(BasicParams.MixB) mixin (B); }

mixin声明把 shader 并入 effect;mixin compose Target1 = Test123将组合绑定到指定名称;params定义可由外部(如材质)传入的参数集合;条件mixin则允许按开关组合不同 shader。SDFX 的解析器位于 Parsing/SDFX,与 SDSL 解析器共用文法基础设施。

测试体系:验证解析与编译的正确性

仓库为 SDSL 提供了分层测试,均位于Stride.Shaders.Tests:

  • 解析测试:ParsingTests.cs 遍历assets/SDSL、assets/SDFX下所有.sdsl/.sdfx文件执行SDSLParser.Parse并断言零错误;另有大量针对"悬空后置访问器"(如a.;、a[1];、1..;)的负向用例,确保这些语法错误被正确报告而非静默吞掉;
  • 编译/翻译测试:StrideShaderTests.cs 覆盖多维数组维度顺序、字段名与内置方法名冲突、numthreads继承/覆盖、组合与继承共存等回归场景;
  • 资产语料:assets/SDSL/CompilerTests 包含 50+ 个.sdsl文件(ArrayDims3D、SwitchDuplicateLabel、HSOverride、GeometryStreamsAssignKeepsOthers等),本身就是一份极佳的语法学习与边界行为参考;ParserTests、RenderTests、ComputeTests等目录则分别覆盖解析、渲染与计算场景。

如何贡献:本地克隆与开发

如需在本地查看、运行或继续开发这套解析/编译实现(README 中说明的贡献方式):

git clone https://gitcode.com/gh_mirrors/st/stride cd ./stride

随后可打开 sources/shaders 目录下的解决方案(如Stride.Shaders.Tests测试项目)进行调试;测试项目内置了ShaderLoader(见 StrideShaderTests.cs),可指定资产目录加载.sdsl文件,是了解整个编译链路最直接的入口。

总结

SDSL 是 Stride 渲染系统的语言基石:它用shader类、继承/泛型/compose 赋予着色器代码面向对象的组织能力,用stream/stage/cbuffer/attributes 实现模块化封装,再经"预处理 → 解析 → 语义分析 → SPIR-V → 后端翻译"的流水线,最终输出 HLSL/SPIR-V 字节码供 Direct3D 或 Vulkan 使用。当前仓库的sources/shaders目录正是这套新实现(Parser + Compiler)的完整落地,从 README 出发,配合assets/SDSL的示例与Stride.Shaders.Tests的测试,开发者可以快速掌握 SDSL 的语言特性、编译原理与调试方法。

  • 游戏开发
  • 图形学
  • VR

【免费下载链接】stride

Stride (formerly Xenko), a free and open-source cross-platform C# game engine.

项目地址:https://gitcode.com/gh_mirrors/st/stride
点击查看免费下载
上一篇:【亲测免费】 ElementUI 主题生成器教程
下一篇:BitBLAS 开源项目使用教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询