ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Slang Shader Object 模型实战:用接口类型与 slang-gfx 简化着色器参数绑定与特化

Slang Shader Object 模型实战:用接口类型与 slang-gfx 简化着色器参数绑定与特化 编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载导读本文基于 Slang 仓库中的shader-object示例系统讲解如何在slang-gfx即slang-rhi图形抽象层之上使用Shader Object 模型来管理着色器参数绑定与着色器特化。示例通过一个ITransformer接口驱动计算着色器演示了从加载模块、组合入口点、反射查找类型到用ShaderCursor免偏移量绑定参数、以及用createShaderObject将接口参数具体化的完整链路。读完本文你将掌握一套不依赖预处理器宏、可同时支持编译期特化与运行时多态的可复用着色器参数管理模式。一、示例概览一个计算着色器变换管线shader-object示例位于 examples/shader-object/由三个文件构成shader-object.slang定义ITransformer接口、三种变换实现与一个计算入口点main.cpp宿主端全部逻辑负责加载、组合、绑定与回读CMakeLists.txt仅一行example(shader-object)复用 examples/CMakeLists.txt 中定义的example()辅助函数完成目标创建、.slang/.jpg/.obj等资源的拷贝与主函数包装。示例运行的效果是读入一个包含 4 个浮点数{0.0f, 1.0f, 2.0f, 3.0f}的缓冲经过变换函数处理后回写同一缓冲并打印结果。示例默认使用的变换是f(x) x c 10.0fAddTransformer在 shader-object.slang 中实现代码里的 10.0f使输出可辨识因此打印结果为11.0f、12.0f、13.0f、14.0f。examples/README.md 对它的定位是展示 Slang 的接口类型如何用比基于预处理器的技术更简单的逻辑实现着色器特化。这是理解本示例价值的钥匙——传统做法需要为每种变换写一套#ifdef分支并分别编译而 Shader Object 模型把变换函数变成了可在运行时构造和绑定的对象。二、着色器侧用接口参数声明多态能力着色器代码 shader-object.slang 是整个示例的语义核心。它定义了一个只含单个方法的最小接口interface ITransformer { float transform(float x); }随后给出两个直接实现与一个组合实现// f(x) x c struct AddTransformer : ITransformer { float c; float transform(float x) { return x c 10.0f; } }; // f(x) x * c struct MulTransformer : ITransformer { float c; float transform(float x) { return x * c; } }; // f(x) f0(f1(x)) struct CompositeTransformer : ITransformer { ITransformer func0; ITransformer func1; float transform(float x) { return func0.transform(func1.transform(x)); } };注意CompositeTransformer的字段本身也是ITransformer类型——接口类型可以作为结构体字段嵌套出现这为构造任意深度的变换组合如链式流水线提供了可能也直接体现接口参数的可组合性。计算入口点把变换器声明为uniform参数[shader(compute)] [numthreads(4,1,1)] void computeMain( uint3 sv_dispatchThreadID : SV_DispatchThreadID, uniform RWStructuredBufferfloat buffer, uniform ITransformer transformer) { var input buffer[sv_dispatchThreadID.x]; buffer[sv_dispatchThreadID.x] transformer.transform(input); }关键点在于uniform ITransformer transformer这一声明。它同时开启了两种能力且无需改动任何着色器代码或宿主参数设置逻辑shader-object.slang 的注释明确说明了这一点编译期特化specialization宿主在创建对象时若把具体类型如AddTransformer作为特化参数传给编译器就能生成只做加法的专用 kernel性能最优运行时多态宿主也可以在运行时把任意实现了ITransformer的对象绑定进去让同一个 kernel 处理任意变换。关于接口与泛型的语言层背景可参考 docs/user-guide/06-interfaces-generics.md接口定义类型必须提供的方法契约struct通过: ITransformer声明遵从关系编译器会检查实现是否满足接口要求缺失方法将报错。三、宿主端第一阶段加载模块并组合程序main.cpp从loadShaderProgrammain.cpp开始这一阶段完全在 Slang 编译器 APIslang.h层面工作。3.1 获取编译会话示例应用使用slang-rhi图形抽象层该层已经创建好 Slang 编译会话宿主直接取用ComPtrslang::ISession slangSession; slangSession device-getSlangSession();IDevice::getSlangSession在 include/slang-gfx.h 中声明slang-rhi各后端的Device实现如 tools/gfx/renderer-shared.h均提供该能力。这意味着你无需自行创建slang::IGlobalSession/ISession图形层已代为管理生命周期。3.2 加载模块ComPtrslang::IBlob diagnosticsBlob; Slang::String path resourceBase.resolveResource(shader-object.slang); slang::IModule* module slangSession-loadModule(path.getBuffer(), diagnosticsBlob.writeRef()); diagnoseIfNeeded(diagnosticsBlob);loadModule的行为等价于在 shader 文件中写import shader_object;编译器沿搜索路径查找同名.slang文件、编译并缓存重复加载直接复用。这里有个值得注意的细节文件名是shader-object含连字符不能直接作为 Slang 标识符因此以 import 方式使用时需把连字符替换为下划线即import shader_object;main.cpp。diagnoseIfNeeded用于把诊断信息打印到控制台。3.3 查找入口点并组合组件类型加载模块后按名字查找计算入口点char const* computeEntryPointName computeMain; ComPtrslang::IEntryPoint computeEntryPoint; SLANG_RETURN_ON_FAIL( module-findEntryPointByName(computeEntryPointName, computeEntryPoint.writeRef()));这里有一个重要的约束通过loadModule方式加载代码时入口点函数必须带[shader(...)]属性如[shader(compute)]否则编译器无法唯一确定哪些函数是入口点main.cpp。模块与入口点都属于 API 中的component type组件类型下一步把它们组合成compositeSlang::Listslang::IComponentType* componentTypes; componentTypes.add(module); componentTypes.add(computeEntryPoint); ComPtrslang::IComponentType composedProgram; SlangResult result slangSession-createCompositeComponentType( componentTypes.getBuffer(), componentTypes.getCount(), composedProgram.writeRef(), diagnosticsBlob.writeRef());createCompositeComponentType声明见 include/slang.h可能因组合非法例如重复包含同一模块而失败因此必须检查返回值与诊断输出。组合成功后composedProgram-getLayout()得到slang::ProgramLayout*反射信息后续findTypeByName依赖它示例在测试模式下还会调用testBase.printEntrypointHashes(1, 1, composedProgram)实现在 examples/example-base/test-base.cpp它通过getEntryPointHash输出入口点在指定 target 下的哈希用于回归验证 kernel 是否按预期特化——这是 CI 场景下的实用技巧。3.4 创建 IShaderProgram组合完成后把它交给图形层ShaderProgramDesc programDesc {}; programDesc.slangGlobalScope composedProgram.get(); auto shaderProgram device-createShaderProgram(programDesc);IShaderProgram是slang-rhi层的程序对象内部持有组合后的 Slang 组件类型供管线创建与参数布局使用。四、宿主端第二阶段设备、管线与缓冲4.1 创建设备与计算管线Slang::ComPtrIDevice device; DeviceDesc deviceDesc {}; device getRHI()-createDevice(deviceDesc); ComputePipelineDesc pipelineDesc {}; pipelineDesc.program shaderProgram.get(); ComPtrIComputePipeline pipelineState; pipelineState device-createComputePipeline(pipelineDesc);getRHI()返回全局渲染接口单例createDevice按DeviceDesc默认即选用平台可用后端创建逻辑设备它屏蔽了 D3D11/D3D12/Vulkan/CUDA/CPU/Metal 等后端的差异。4.2 创建输入缓冲const int numberCount 4; float initialData[] {0.0f, 1.0f, 2.0f, 3.0f}; BufferDesc bufferDesc {}; bufferDesc.size numberCount * sizeof(float); bufferDesc.format Format::Undefined; bufferDesc.elementSize sizeof(float); bufferDesc.usage BufferUsage::ShaderResource | BufferUsage::UnorderedAccess | BufferUsage::CopyDestination | BufferUsage::CopySource; bufferDesc.defaultState ResourceState::UnorderedAccess; bufferDesc.memoryType MemoryType::DeviceLocal; ComPtrIBuffer numbersBuffer; numbersBuffer device-createBuffer(bufferDesc, (void*)initialData);该缓冲同时承担输入ShaderResource与输出UnorderedAccess且具备拷贝目的地/来源能力便于提交后回读。defaultState指定资源初始状态为UnorderedAccess符合计算着色器直接读写的场景。五、Shader Object 模型参数绑定的核心机制这一节是本示例的精华。它回答了绑参数到底绑什么、怎么绑的问题。5.1 根对象与入口点对象命令录制阶段绑定管线会返回一个根着色器对象auto queue device-getQueue(QueueType::Graphics); auto commandEncoder queue-createCommandEncoder(); auto encoder commandEncoder-beginComputePass(); auto rootObject encoder-bindPipeline(pipelineState);bindPipeline在 include/slang-gfx.h 中声明它同时完成管线绑定与根对象创建。根对象是参数树的根入口点参数挂在rootObject-getEntryPoint(i)返回的入口点对象上全局/根作用域参数则直接挂在根对象上。IShaderObject接口include/slang-gfx.h提供了一组按ShaderOffset定位的绑定方法方法作用setData(offset, data, size)写入普通值/结构体如float csetResource(offset, resourceView)绑定缓冲/纹理资源视图setSampler(offset, sampler)绑定采样器setObject(offset, object)绑定嵌套的IShaderObject接口参数或对象参数setSpecializationArgs(offset, args, count)手动指定接口参数的特化类型驱动编译器生成专用 kernelgetEntryPoint(index)/getObject(offset)取子对象而ShaderOffsetinclude/slang-gfx.h由uniformOffset常量缓冲内偏移与bindingRangeIndex/bindingArrayIndex绑定范围索引共同描述参数位置。直接使用这些 API 意味着你必须自己用反射 API 计算每个参数的偏移这正是ShaderCursor存在的理由。5.2 ShaderCursor按路径免偏移绑定的封装ShaderCursor封装了IShaderObject的偏移计算逻辑允许按参数名路径直接寻址。示例中的用法ShaderCursor entryPointCursor( rootObject-getEntryPoint(0)); // 取第一个入口点的游标 // 绑定 buffer 到入口点参数 entryPointCursor.getPath(buffer).setBinding(numbersBuffer); // 绑定 transformer 对象到入口点参数 entryPointCursor.getPath(transformer).setObject(transformer);getPath(buffer)返回指向名为buffer参数的子游标setBinding(...)绑定缓冲资源getPath(transformer).setObject(transformer)把之前创建的IShaderObject实例绑定到接口参数上。ShaderCursor的头文件位于slang-rhi子模块的slang-rhi/shader-cursor.h本示例与 tools/gfx-unit-test/root-shader-parameter.cpp 均通过#include slang-rhi/shader-cursor.h引入该类在rhi命名空间下。仓库内多个单元测试都在使用同样的模式例如 tools/gfx-unit-test/shared-buffers-tests.cpp 中的ShaderCursor rootCursor(rootObject); rootCursor.getPath(buffer).setBinding(Binding(dstBuffer));以及 tools/gfx-unit-test/shared-textures-tests.cpp 的入口点游标用法印证了这是slang-rhi生态的标准绑定姿势。5.3 创建接口参数对象从类型到实例绑定前需要先构造一个具体类型的着色器对象。宿主通过反射按名字查类型slang::TypeReflection* addTransformerType slangReflection-findTypeByName(AddTransformer); ComPtrIShaderObject transformer; transformer device-createShaderObject(addTransformerType, ShaderObjectContainerType::None);findTypeByName是slang::ProgramLayout提供的反射查询接口include/slang.hAddTransformer是着色器里定义的结构体类型名——宿主与着色器之间通过反射信息按名字建立联系这正是 Slang 反射能力的典型应用。createShaderObject的第二个参数是容器类型enum class ShaderObjectContainerType { None, // 普通对象结构体/接口的具体实现 Array, // 数组容器 StructuredBuffer // 结构化缓冲容器 };定义见 include/slang-gfx.htools/gfx中renderer-shared.h的ShaderObjectLayout会按容器类型分派不同的创建路径。本示例创建的AddTransformer是普通结构体对象因此传ShaderObjectContainerType::None。接着给对象的字段赋值——依然用ShaderCursorfloat c 1.0f; ShaderCursor(transformer).getPath(c).setData(c, sizeof(float));setData把c的字节写入对象内部常量缓冲的正确偏移处。这展示了 ShaderCursor 的另一优势对结构体字段的寻址同样不需要手算偏移。5.4 绑定的完整时间线把 5.1–5.3 串起来一次 dispatch 的完整流程是bindPipeline拿到根对象反射findTypeByName(AddTransformer)得到类型createShaderObject创建实例ShaderCursor写入字段c 1.0f入口点游标getPath(buffer).setBinding(numbersBuffer)绑资源入口点游标getPath(transformer).setObject(transformer)绑接口参数dispatchCompute(1, 1, 1)发射 4 线程numthreads(4,1,1)每个线程处理一个元素end()、finish()、submit、waitOnHost完成提交与同步。注意第 5 步绑定的对象类型在运行时才确定slang-rhi会依据绑定的具体类型决定是走编译期特化生成专用 kernel还是运行时多态保留通用 kernel 并按对象内容驱动宿主无需感知这一选择。六、结果回读与运行ComPtrISlangBlob resultBlob; SLANG_RETURN_ON_FAIL( device-readBuffer(numbersBuffer, 0, numberCount * sizeof(float), resultBlob.writeRef())); auto result reinterpret_castconst float*(resultBlob-getBufferPointer()); for (int i 0; i numberCount; i) printf(%f\n, result[i]);readBuffer把 GPU 缓冲内容拷回宿主端ISlangBlob。因为AddTransformer::transform实现为x c 10.0f且c 1.0f预期输出为11.0f、12.0f、13.0f、14.0f。构建与运行方式该示例由 examples/CMakeLists.txt 的add_subdirectory(shader-object)纳入构建构建入口沿用仓库根目录 CMakeLists.txt 的标准流程cmake配置 构建详见 docs/building.md。构建产物为独立可执行程序example()辅助函数examples/CMakeLists.txt会自动把目录下的*.slang等资源拷贝到可执行文件旁因此可直接运行其依赖的slang-rhi层通过 external/slang-rhi 子模块引入。运行需要可用的图形/计算设备后端D3D11/D3D12/Vulkan/CUDA/CPU 等取决于构建配置程序会输出 4 个变换后的浮点数。七、扩展与对比这个模型解决了什么问题7.1 对比预处理器特化传统 HLSL/GLSL 方案要实现多种变换可选常见做法是#define宏 多次编译每增加一种变换就要新增编译变体宿主侧还要维护宏与 kernel 的映射表。Shader Object 模型下新增变换 在 shader 里加一个实现接口的struct宿主侧findTypeByNamecreateShaderObject即可绑定着色器与宿主逻辑零改动shader-object.slang 注释明确承诺了这一点组合变换 构造CompositeTransformer嵌套对象无需新代码路径。7.2 对比手算偏移如果不用ShaderCursor绑定buffer需要先用反射 API 求出ShaderOffsetuniformOffset加bindingRangeIndex再调用setResource。ShaderCursor把这一过程封装为按名字路径查找示例代码因此从数十行反射样板压缩为三行且路径查找失败时能给出更友好的诊断。7.3 特化与多态的取舍slang-rhi的IShaderObject::setSpecializationArgsinclude/slang-gfx.h注释点明了机制特化参数会被传给 shader 编译器用于特化接口参数类型。实际使用中追求极致性能且变换集合固定 → 编译期特化变换集合在运行时变化/数量庞大 → 运行时多态CompositeTransformer这类递归组合场景天然适合运行时多态。两种路径共享同一套宿主绑定代码切换成本几乎为零这正是该示例想传达的核心工程价值。八、延伸阅读接口与泛型的语言级详解docs/user-guide/06-interfaces-generics.mdIShaderObject完整接口与ShaderObjectContainerTypeinclude/slang-gfx.h同类参数绑定模式在测试中的更多用法tools/gfx-unit-test/root-shader-parameter.cpp、tools/gfx-unit-test/shared-buffers-tests.cpp、tools/gfx-unit-test/shared-textures-tests.cpp入口点哈希回归验证的辅助实现examples/example-base/test-base.cpp其他基于组件类型组合 API 的示例loadShaderProgram的组合思路同样出现在 examples/ray-tracing/main.cpp 与 examples/mlp-training/mlp-training.cpp 中可作为多入口点组合的参考。赞分享编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载相关推荐Slang Shader Toy 示例深度解析用接口与泛型实现着色器效果模块化组合Slang Shader Toy 示例深度解析用接口与泛型实现着色器效果模块化组合 导读 本文基于 Slang 官方仓库中的 shader toy 示例 ht编译器图形学编程语言Slang 语言指南着色语言中的模块化导入、显式参数块与泛型接口编程Slang 语言指南着色语言中的模块化导入、显式参数块与泛型接口编程 Slang 是一门以 HLSL 为基座、面向现代 GPU APID3D12 / Vul编译器图形学编程语言ComfyUI_TensorRT核心原理TensorRT如何让AI绘图速度提升3倍ComfyUI_TensorRT核心原理TensorRT如何让AI绘图速度提升3倍 ComfyUI_TensorRT是一款专为AI绘图优化的加速工具通过集上一篇Argo Rollouts与云原生存储集成持久化卷的部署管理下一篇Actual-Server验证工具用户权限与访问控制验证创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表