From 5f541e24229107b704dfc2ddaeea0f86f2141a43 Mon Sep 17 00:00:00 2001 From: swinston Date: Mon, 21 Jul 2025 14:15:07 -0700 Subject: [PATCH 1/7] Add in the shader authoring guide chapter. (cherry picked from commit 3eb823c22235d8d5948514fa1ce300c7b967bcf5) --- antora/modules/ROOT/nav.adoc | 1 + .../shader_authoring_guide_slang_hlsl.adoc | 1610 +++++++++++++++++ guide.adoc | 2 + 3 files changed, 1613 insertions(+) create mode 100644 chapters/shader_authoring_guide_slang_hlsl.adoc diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index ddfb192..d8a5c7b 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -58,6 +58,7 @@ ** xref:{chapters}image_copies.adoc[] ** xref:{chapters}common_pitfalls.adoc[] ** xref:{chapters}hlsl.adoc[] +** xref:{chapters}shader_authoring_guide_slang_hlsl.adoc[] ** xref:{chapters}high_level_shader_language_comparison.adoc[] * When and Why to use Extensions ** xref:{chapters}extensions/cleanup.adoc[] diff --git a/chapters/shader_authoring_guide_slang_hlsl.adoc b/chapters/shader_authoring_guide_slang_hlsl.adoc new file mode 100644 index 0000000..3f38a7f --- /dev/null +++ b/chapters/shader_authoring_guide_slang_hlsl.adoc @@ -0,0 +1,1610 @@ +// Copyright 2025 Holochip, Inc. +// SPDX-License-Identifier: CC-BY-4.0 + +ifndef::chapters[:chapters:] +ifndef::images[:images: images/] + +[[shader-authoring-guide-slang-hlsl]] += Shader Authoring Guide (Slang/HLSL) +:toc: + +This chapter provides a comprehensive guide for writing shaders in Slang and HLSL for Vulkan applications. It covers best practices, migration from GLSL, Vulkan-specific semantics, bindings, entry points, and compiling to SPIR-V. + +== Introduction to Slang and HLSL in Vulkan + +While Vulkan consumes shaders in xref:{chapters}what_is_spirv.adoc[SPIR-V] format, developers can author shaders in high-level languages like HLSL (High-Level Shading Language) and Slang. This chapter builds upon the xref:{chapters}hlsl.adoc[HLSL in Vulkan] chapter, focusing specifically on authoring shaders for Vulkan applications. + +=== What is Slang? + +link:https://github.com/shader-slang/slang[Slang] is a modern shading language and compiler designed to extend HLSL with advanced features. It's backward compatible with HLSL but adds features like generics, interfaces, and reflection capabilities. Slang can target multiple backends, including SPIR-V for Vulkan. + +Key features of Slang include: + +* Full compatibility with HLSL syntax +* Advanced language features like generics, interfaces, and modules +* Cross-compilation to multiple targets, including SPIR-V for Vulkan +* Powerful metaprogramming capabilities +* Built-in shader reflection + +=== Why Use HLSL or Slang for Vulkan? + +There are several advantages to using HLSL or Slang for Vulkan development: + +* *Cross-API compatibility*: Write shaders that can be used with both Vulkan and DirectX with minimal changes +* *Familiar syntax*: Developers with DirectX experience can leverage their existing knowledge +* *Advanced language features*: Particularly with Slang, access to modern programming constructs +* *Industry adoption*: HLSL is widely used in game engines and graphics applications +* *Tooling support*: Rich ecosystem of tools, debuggers, and IDE integrations + +== Differences Between HLSL and Slang + +While Slang is built on HLSL and maintains backward compatibility, it adds several powerful features that make shader development more efficient, maintainable, and flexible. + +=== Language Feature Differences + +==== Generics and Templates + +Slang adds full support for generics, similar to C# or Java, allowing for type-safe parameterized code: + +[source,slang] +---- +// Generic function in Slang +template +T min(T a, T b) { + return a < b ? a : b; +} + +// Usage +float result = min(1.0, 2.0); +int intResult = min(5, 3); +---- + +HLSL has limited template support, but Slang's implementation is more robust and flexible. + +==== Interfaces and Polymorphism + +Slang introduces interfaces, enabling polymorphic behavior in shaders: + +[source,slang] +---- +// Define an interface +interface IMaterial { + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal); +} + +// Implement the interface +struct LambertianMaterial : IMaterial { + float3 albedo; + + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) { + return albedo / 3.14159; + } +} + +// Use polymorphically +void shadeSurface(IMaterial material, ...) { + // Use the material interface without knowing the concrete type +} +---- + +This feature is not available in standard HLSL. + +==== Modules and Namespaces + +Slang provides a module system for better code organization: + +[source,slang] +---- +// In file: lighting.slang +module Lighting; + +export float3 calculateDirectLighting(...) { ... } +export float3 calculateIndirectLighting(...) { ... } + +// In another file +import Lighting; + +float3 color = Lighting.calculateDirectLighting(...); +---- + +HLSL lacks a formal module system, relying instead on file includes. + +==== Advanced Metaprogramming + +Slang offers powerful compile-time metaprogramming capabilities: + +[source,slang] +---- +// Compile-time reflection +struct Material { + float4 baseColor; + float roughness; + float metallic; +}; + +// Get all fields of a type at compile time +__generic +void bindMaterial(ParameterBlock block, Material material) { + __for(field in getFields(T)) { + block.setField(field.name, getField(material, field.name)); + } +} +---- + +==== Resource Binding Model + +Slang introduces a more flexible resource binding model: + +[source,slang] +---- +// Parameter block concept +ParameterBlock lightingParams; + +// Accessing resources +Texture2D albedoMap = lightingParams.albedoMap; +---- + +This provides better organization and more flexible binding than HLSL's register-based approach. + +=== Syntax Differences + +While Slang maintains HLSL syntax compatibility, it introduces some new syntax elements: + +* *Module declarations*: `module ModuleName;` +* *Import statements*: `import ModuleName;` +* *Interface declarations*: `interface IName { ... }` +* *Generic type parameters*: `` or `__generic` +* *Export keyword*: `export` to make symbols visible outside a module +* *Extended attributes*: Additional attributes for reflection and code generation + +=== Compilation Differences + +Slang provides its own compiler (`slangc`) with different capabilities than the HLSL compiler: + +* *Multi-target compilation*: Compile the same shader for multiple graphics APIs +* *Cross-compilation*: Generate code for different shader stages from a single source +* *Built-in reflection*: Generate reflection data during compilation +* *Shader linking*: Link multiple shader modules together +* *Diagnostic quality*: More detailed error messages and warnings + +Example of multi-target compilation: + +[source,bash] +---- +slangc -profile glsl_spirv -entry main -stage vertex shader.slang -o shader.vert.spv +slangc -profile dxbc -entry main -stage vertex shader.slang -o shader.vert.dxbc +---- + +=== Runtime Behavior Differences + +Slang introduces some runtime behavior differences: + +* *Dynamic dispatch*: Support for interface-based polymorphism +* *Resource binding*: More flexible resource binding model +* *Parameter blocks*: Hierarchical organization of shader parameters +* *Reflection API*: Runtime access to shader structure information + +== Migration Guide: Upgrading from HLSL to Slang + +Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to upgrading your shaders. + +=== Step 1: Setting Up the Slang Compiler + +1. Download and install the Slang compiler from the https://github.com/shader-slang/slang[official repository] +2. Update your build scripts to use `slangc` instead of `dxc` or other HLSL compilers +3. Test compilation of existing HLSL shaders without modifications + +Example build script update: + +[source,bash] +---- +# Before: Using DXC +dxc -spirv -T vs_6_0 -E main shader.hlsl -Fo shader.vert.spv + +# After: Using Slang +slangc -profile glsl_spirv -entry main -stage vertex shader.hlsl -o shader.vert.spv +---- + +=== Step 2: Rename Files and Add Module Declarations + +1. Rename your `.hlsl` files to `.slang` to indicate the language change +2. Add module declarations at the top of each file +3. Add export keywords to functions and types that need to be visible outside the module + +Example transformation: + +Before (shader.hlsl): +[source,hlsl] +---- +struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; +}; + +float4 transformPosition(float3 position) { + return mul(worldViewProj, float4(position, 1.0)); +} +---- + +After (shader.slang): +[source,slang] +---- +module Shaders.Transform; + +export struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; +}; + +export float4 transformPosition(float3 position) { + return mul(worldViewProj, float4(position, 1.0)); +} +---- + +=== Step 3: Leverage Imports Instead of Includes + +Replace `#include` directives with Slang's import system: + +Before (HLSL): +[source,hlsl] +---- +#include "common.hlsl" +#include "lighting.hlsl" + +float3 calculateLighting(...) { + // Use functions from included files +} +---- + +After (Slang): +[source,slang] +---- +module Shaders.Fragment; + +import Shaders.Common; +import Shaders.Lighting; + +export float3 calculateLighting(...) { + // Use functions from imported modules +} +---- + +=== Step 4: Refactor Resource Bindings + +Update resource bindings to use Slang's parameter block system: + +Before (HLSL): +[source,hlsl] +---- +Texture2D albedoMap : register(t0); +SamplerState samplerState : register(s0); +cbuffer MaterialParams : register(b0) { + float4 baseColor; + float roughness; + float metallic; +}; +---- + +After (Slang): +[source,slang] +---- +struct MaterialResources { + Texture2D albedoMap; + SamplerState samplerState; + struct Params { + float4 baseColor; + float roughness; + float metallic; + } constants; +}; + +ParameterBlock material; + +// Usage +float4 albedo = material.albedoMap.Sample(material.samplerState, uv); +float roughness = material.constants.roughness; +---- + +=== Step 5: Introduce Generics and Interfaces + +Identify opportunities to use generics and interfaces for more flexible code: + +Before (HLSL): +[source,hlsl] +---- +float3 evaluateLambert(float3 albedo, float3 normal, float3 lightDir) { + return albedo * max(0, dot(normal, lightDir)) / 3.14159; +} + +float3 evaluateGGX(float3 specColor, float roughness, float3 normal, float3 viewDir, float3 lightDir) { + // GGX implementation +} + +float3 evaluateMaterial(MaterialType type, ...) { + switch(type) { + case MATERIAL_LAMBERT: return evaluateLambert(...); + case MATERIAL_GGX: return evaluateGGX(...); + default: return float3(0,0,0); + } +} +---- + +After (Slang): +[source,slang] +---- +interface IBRDF { + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir); +} + +struct LambertBRDF : IBRDF { + float3 albedo; + + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { + return albedo * max(0, dot(normal, lightDir)) / 3.14159; + } +} + +struct GGXBRDF : IBRDF { + float3 specColor; + float roughness; + + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { + // GGX implementation + } +} + +float3 evaluateMaterial(IBRDF brdf, float3 normal, float3 viewDir, float3 lightDir) { + return brdf.evaluate(normal, viewDir, lightDir); +} +---- + +=== Step 6: Implement Advanced Metaprogramming + +Use Slang's metaprogramming capabilities for more powerful shader generation: + +[source,slang] +---- +// Define shader permutations using compile-time parameters +[shader("vertex")] +[CompileTimeConstant(name="USE_NORMAL_MAPPING", type="bool")] +[CompileTimeConstant(name="LIGHT_COUNT", type="int")] +VSOutput vertexShader(VSInput input) { + VSOutput output; + // Base implementation + + #if USE_NORMAL_MAPPING + // Normal mapping specific code + #endif + + for (int i = 0; i < LIGHT_COUNT; i++) { + // Per-light calculations + } + + return output; +} +---- + +=== Common Migration Challenges + +==== Resource Binding Compatibility + +**Challenge**: Slang's resource binding model differs from HLSL's register-based approach. + +**Solution**: + +- Use Slang's `register` compatibility syntax during transition +- Gradually migrate to parameter blocks +- Update shader binding code in your application + +==== Module Organization + +**Challenge**: Deciding how to organize code into modules. + +**Solution**: + +- Group related functionality into modules +- Use hierarchical naming (e.g., `Rendering.Lighting`) +- Start with coarse-grained modules and refine as needed + +==== Interface Performance + +**Challenge**: Concerns about runtime performance of interfaces. + +**Solution**: + +- Interfaces are often resolved at compile-time +- Use interfaces for flexibility in high-level code +- Profile performance-critical paths + +==== Compilation Pipeline Integration + +**Challenge**: Integrating Slang into existing build systems. + +**Solution**: + +- Create wrapper scripts to maintain command-line compatibility +- Update build tools to support both HLSL and Slang during transition +- Consider using Slang's API for deeper integration + +=== Best Practices for Migration + +1. **Incremental Approach**: Migrate one shader or shader module at a time +2. **Maintain Compatibility**: Use Slang's HLSL compatibility features during transition +3. **Test Thoroughly**: Verify visual output after each migration step +4. **Refactor Gradually**: Start with simple syntax changes, then introduce advanced features +5. **Leverage Modules**: Use the module system to improve code organization +6. **Document Changes**: Keep track of migration decisions and patterns +7. **Performance Profiling**: Monitor performance before and after migration + +=== Example: Complete HLSL to Slang Migration + +Below is a complete example of migrating a simple shader from HLSL to Slang: + +HLSL Version (pbr.hlsl): +[source,hlsl] +---- +// PBR shader in HLSL +#include "common.hlsl" + +struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texCoord : TEXCOORD0; +}; + +struct PSInput { + float4 position : SV_POSITION; + float3 worldPos : POSITION; + float3 normal : NORMAL; + float2 texCoord : TEXCOORD0; +}; + +Texture2D albedoMap : register(t0); +Texture2D normalMap : register(t1); +Texture2D metallicRoughnessMap : register(t2); +SamplerState textureSampler : register(s0); + +cbuffer MaterialParams : register(b0) { + float4 baseColor; + float metallic; + float roughness; + float2 padding; +}; + +cbuffer SceneParams : register(b1) { + float4x4 viewProj; + float4x4 world; + float3 cameraPosition; + float padding2; +}; + +PSInput VSMain(VSInput input) { + PSInput output; + float4 worldPos = mul(world, float4(input.position, 1.0)); + output.position = mul(viewProj, worldPos); + output.worldPos = worldPos.xyz; + output.normal = normalize(mul((float3x3)world, input.normal)); + output.texCoord = input.texCoord; + return output; +} + +float4 PSMain(PSInput input) : SV_TARGET { + float4 albedo = albedoMap.Sample(textureSampler, input.texCoord) * baseColor; + float2 metallicRoughness = metallicRoughnessMap.Sample(textureSampler, input.texCoord).rg; + float metalness = metallicRoughness.r * metallic; + float roughnessValue = metallicRoughness.g * roughness; + + float3 normal = normalize(input.normal); + float3 viewDir = normalize(cameraPosition - input.worldPos); + + // PBR calculation + float3 color = calculatePBRLighting(albedo.rgb, metalness, roughnessValue, normal, viewDir); + + return float4(color, albedo.a); +} + +float3 calculatePBRLighting(float3 albedo, float metalness, float roughness, float3 normal, float3 viewDir) { + // Simplified PBR calculation + float3 lightDir = normalize(float3(1, 1, 1)); + float3 halfVector = normalize(viewDir + lightDir); + + float NdotL = max(dot(normal, lightDir), 0.0); + float NdotV = max(dot(normal, viewDir), 0.0); + float NdotH = max(dot(normal, halfVector), 0.0); + float VdotH = max(dot(viewDir, halfVector), 0.0); + + float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo, metalness); + + // Simplified lighting equation + float3 diffuse = (1.0 - metalness) * albedo / 3.14159; + float3 specular = calculateSpecular(F0, roughness, NdotH, NdotV, NdotL, VdotH); + + return (diffuse + specular) * NdotL * float3(1, 1, 1); // Light color = white +} + +float3 calculateSpecular(float3 F0, float roughness, float NdotH, float NdotV, float NdotL, float VdotH) { + // Simplified specular calculation + float alpha = roughness * roughness; + float D = alpha * alpha / (3.14159 * pow(NdotH * NdotH * (alpha * alpha - 1.0) + 1.0, 2.0)); + float G = NdotV * NdotL; + float3 F = F0 + (float3(1, 1, 1) - F0) * pow(1.0 - VdotH, 5.0); + + return D * G * F / max(0.0001, 4.0 * NdotV * NdotL); +} +---- + +Slang Version (pbr.slang): +[source,slang] +---- +// PBR shader in Slang +module Rendering.PBR; + +import Rendering.Common; + +// Input/output structures +export struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texCoord : TEXCOORD0; +}; + +export struct PSInput { + float4 position : SV_POSITION; + float3 worldPos : POSITION; + float3 normal : NORMAL; + float2 texCoord : TEXCOORD0; +}; + +// Resource definitions using parameter blocks +struct MaterialResources { + Texture2D albedoMap; + Texture2D normalMap; + Texture2D metallicRoughnessMap; + SamplerState textureSampler; + + struct Constants { + float4 baseColor; + float metallic; + float roughness; + float2 padding; + } params; +}; + +struct SceneResources { + struct Constants { + float4x4 viewProj; + float4x4 world; + float3 cameraPosition; + float padding; + } params; +}; + +// Parameter blocks +ParameterBlock material; +ParameterBlock scene; + +// BRDF interface for different lighting models +interface IBRDF { + float3 evaluate(float3 albedo, float3 normal, float3 viewDir, float3 lightDir); +} + +// PBR BRDF implementation +struct PBRBRDF : IBRDF { + float metalness; + float roughness; + + float3 evaluate(float3 albedo, float3 normal, float3 viewDir, float3 lightDir) { + float3 halfVector = normalize(viewDir + lightDir); + + float NdotL = max(dot(normal, lightDir), 0.0); + float NdotV = max(dot(normal, viewDir), 0.0); + float NdotH = max(dot(normal, halfVector), 0.0); + float VdotH = max(dot(viewDir, halfVector), 0.0); + + float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo, metalness); + + // Simplified lighting equation + float3 diffuse = (1.0 - metalness) * albedo / 3.14159; + float3 specular = calculateSpecular(F0, roughness, NdotH, NdotV, NdotL, VdotH); + + return (diffuse + specular) * NdotL; + } + + float3 calculateSpecular(float3 F0, float roughness, float NdotH, float NdotV, float NdotL, float VdotH) { + // Simplified specular calculation + float alpha = roughness * roughness; + float D = alpha * alpha / (3.14159 * pow(NdotH * NdotH * (alpha * alpha - 1.0) + 1.0, 2.0)); + float G = NdotV * NdotL; + float3 F = F0 + (float3(1, 1, 1) - F0) * pow(1.0 - VdotH, 5.0); + + return D * G * F / max(0.0001, 4.0 * NdotV * NdotL); + } +} + +// Generic lighting calculation function +template +float3 calculateLighting(B brdf, float3 albedo, float3 normal, float3 viewDir, float3 lightColor) { + float3 lightDir = normalize(float3(1, 1, 1)); + return brdf.evaluate(albedo, normal, viewDir, lightDir) * lightColor; +} + +// Shader entry points +[shader("vertex")] +export PSInput VSMain(VSInput input) { + PSInput output; + float4 worldPos = mul(scene.params.world, float4(input.position, 1.0)); + output.position = mul(scene.params.viewProj, worldPos); + output.worldPos = worldPos.xyz; + output.normal = normalize(mul((float3x3)scene.params.world, input.normal)); + output.texCoord = input.texCoord; + return output; +} + +[shader("pixel")] +export float4 PSMain(PSInput input) : SV_TARGET { + float4 albedo = material.albedoMap.Sample(material.textureSampler, input.texCoord) * material.params.baseColor; + float2 metallicRoughness = material.metallicRoughnessMap.Sample(material.textureSampler, input.texCoord).rg; + float metalness = metallicRoughness.r * material.params.metallic; + float roughnessValue = metallicRoughness.g * material.params.roughness; + + float3 normal = normalize(input.normal); + float3 viewDir = normalize(scene.params.cameraPosition - input.worldPos); + + // Create BRDF with material parameters + PBRBRDF brdf; + brdf.metalness = metalness; + brdf.roughness = roughnessValue; + + // Calculate lighting using the generic function + float3 color = calculateLighting(brdf, albedo.rgb, normal, viewDir, float3(1, 1, 1)); + + return float4(color, albedo.a); +} +---- + +The Slang version demonstrates several improvements: + +- Organized code with module system +- Parameter blocks for resource organization +- Interface-based polymorphism for BRDFs +- Generic lighting calculation function +- Explicit shader stage annotations +- Better separation of concerns + +These improvements make the code more maintainable, flexible, and reusable while preserving the core functionality of the original HLSL shader. + +== Best Practices for Writing Shaders in Slang/HLSL for Vulkan + +=== Code Organization + +* *Separate shader stages*: Keep different shader stages (vertex, fragment, compute, etc.) in separate files +* *Use structures for inputs and outputs*: Define clear structures for shader inputs and outputs +* *Consistent naming conventions*: Adopt a consistent naming scheme for variables, functions, and types +* *Modular design*: Break complex shaders into reusable functions and components + +Example of a well-organized HLSL shader: + +[source,hlsl] +---- +// Common structures and constants +struct VSInput { + [[vk::location(0)]] float3 Position : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; + [[vk::location(2)]] float2 TexCoord : TEXCOORD0; +}; + +struct VSOutput { + float4 Position : SV_POSITION; + [[vk::location(0)]] float3 WorldPos : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; + [[vk::location(2)]] float2 TexCoord : TEXCOORD0; +}; + +// Uniform buffer with transformation matrices +struct SceneUBO { + float4x4 model; + float4x4 view; + float4x4 projection; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer ubo; + +// Vertex shader main function +VSOutput main(VSInput input) { + VSOutput output = (VSOutput)0; + + // Transform position to clip space + float4 worldPos = mul(ubo.model, float4(input.Position, 1.0)); + output.Position = mul(ubo.projection, mul(ubo.view, worldPos)); + + // Pass through other attributes + output.WorldPos = worldPos.xyz; + output.Normal = mul((float3x3)ubo.model, input.Normal); + output.TexCoord = input.TexCoord; + + return output; +} +---- + +=== Performance Considerations + +* *Minimize divergent control flow*: Avoid complex branching within shader wavefronts +* *Optimize memory access patterns*: Group related data together to improve cache coherency +* *Reduce register pressure*: Limit the number of variables in high-register-usage sections +* *Use appropriate precision*: Use lower precision types (`half`, `min16float`) when full precision isn't needed +* *Leverage subgroup operations*: Use subgroup/wave intrinsics for efficient parallel operations +* *Prefer compile-time constants*: Use specialization constants for values known at pipeline creation time + +Example of using specialization constants: + +[source,hlsl] +---- +// Define specialization constants +[[vk::constant_id(0)]] const bool USE_NORMAL_MAPPING = true; +[[vk::constant_id(1)]] const int LIGHT_COUNT = 4; +[[vk::constant_id(2)]] const float SPECULAR_POWER = 32.0; + +// Use in conditional code +float3 CalculateNormal(float3 normal, float3 tangent, float2 texCoord) { + if (USE_NORMAL_MAPPING) { + // Complex normal mapping calculation + return CalculateNormalFromMap(normal, tangent, texCoord); + } else { + // Simple pass-through + return normalize(normal); + } +} +---- + +=== Debugging and Validation + +* *Add debug markers*: Use comments or debug variables to mark important sections +* *Validate inputs*: Check for NaN or invalid values in critical calculations +* *Use validation layers*: Enable Vulkan validation layers during development +* *Leverage shader debugging tools*: Use tools like RenderDoc or NVIDIA Nsight for shader debugging +* *Implement fallbacks*: Provide simpler code paths for debugging complex algorithms + +=== Vulkan-Specific Best Practices + +* *Explicit bindings*: Always specify explicit descriptor set and binding indices +* *Consistent descriptor layouts*: Maintain consistent descriptor layouts across shader stages +* *Minimize descriptor set changes*: Group resources to minimize descriptor set changes during rendering +* *Consider push constants*: Use push constants for frequently changing small data +* *Be mindful of SPIR-V limitations*: Some HLSL features may not translate directly to SPIR-V + +== Migration from GLSL to HLSL/Slang + +=== Conceptual Differences + +GLSL and HLSL have different programming paradigms: + +* *GLSL*: More procedural, similar to C +* *HLSL*: More object-oriented, similar to C++ + +Key conceptual differences include: + +* *Entry points*: GLSL uses `void main()`, HLSL uses typed functions with explicit inputs/outputs +* *Built-ins vs. semantics*: GLSL uses built-in variables, HLSL uses semantics +* *Resource binding*: Different syntax for binding resources +* *Matrix layout*: GLSL uses column-major by default, HLSL uses row-major by default + +=== Syntax Translation Guide + +==== Data Types + +[options="header"] +|==== +| GLSL | HLSL | Example +| vec_n_ | float_n_ | vec4 → float4 +| ivec_n_ | int_n_ | ivec3 → int3 +| uvec_n_ | uint_n_ | uvec2 → uint2 +| mat_nxm_ | float_nxm_ | mat4 → float4x4 +|==== + +==== Shader Inputs/Outputs + +GLSL: +[source,glsl] +---- +// Vertex shader inputs +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; + +// Vertex shader outputs +layout(location = 0) out vec3 outNormal; +layout(location = 1) out vec2 outUV; + +void main() { + gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPosition, 1.0); + outNormal = inNormal; + outUV = inUV; +} +---- + +HLSL: +[source,hlsl] +---- +// Input/output structures +struct VSInput { + [[vk::location(0)]] float3 Position : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; +}; + +struct VSOutput { + float4 Position : SV_POSITION; + [[vk::location(0)]] float3 Normal : NORMAL0; + [[vk::location(1)]] float2 UV : TEXCOORD0; +}; + +// Main function with explicit input/output +VSOutput main(VSInput input) { + VSOutput output = (VSOutput)0; + output.Position = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Position, 1.0)))); + output.Normal = input.Normal; + output.UV = input.UV; + return output; +} +---- + +==== Resource Binding + +GLSL: +[source,glsl] +---- +// Uniform buffer +layout(set = 0, binding = 0) uniform UBO { + mat4 model; + mat4 view; + mat4 projection; +} ubo; + +// Texture and sampler +layout(set = 0, binding = 1) uniform sampler2D texAlbedo; + +// Storage buffer +layout(set = 0, binding = 2) buffer SSBO { + vec4 data[]; +} ssbo; +---- + +HLSL: +[source,hlsl] +---- +// Uniform buffer +struct UBO { + float4x4 model; + float4x4 view; + float4x4 projection; +}; +[[vk::binding(0, 0)]] +ConstantBuffer ubo; + +// Texture and sampler +[[vk::binding(1, 0)]] +Texture2D texAlbedo; +[[vk::binding(1, 0)]] +SamplerState sampAlbedo; + +// Storage buffer +struct SSBO { + float4 data[]; +}; +[[vk::binding(2, 0)]] +RWStructuredBuffer ssbo; +---- + +==== Built-ins and Special Variables + +[options="header"] +|==== +| GLSL | HLSL | Description +| gl_Position | SV_Position | Vertex position output +| gl_FragCoord | SV_Position | Fragment position +| gl_VertexIndex | SV_VertexID | Vertex index +| gl_InstanceIndex | SV_InstanceID | Instance index +| gl_FragDepth | SV_Depth | Fragment depth output +| gl_FrontFacing | SV_IsFrontFace | Front-facing flag +| gl_PrimitiveID | SV_PrimitiveID | Primitive ID +|==== + +==== Common Functions + +[options="header"] +|==== +| GLSL | HLSL | Description +| mix(x, y, a) | lerp(x, y, a) | Linear interpolation +| fract(x) | frac(x) | Fractional part +| dFdx(p) | ddx(p) | Derivative in x direction +| dFdy(p) | ddy(p) | Derivative in y direction +| texture(sampler, coord) | sampler.Sample(texture, coord) | Texture sampling +|==== + +=== Common Migration Challenges + +* *Matrix multiplication*: GLSL uses `*` operator, HLSL uses `mul()` function +* *Texture sampling*: Different syntax for texture access +* *Swizzling*: Both support swizzling but with subtle differences +* *Preprocessor directives*: Different preprocessor capabilities +* *Extension handling*: GLSL requires explicit extension enabling, HLSL doesn't + +=== Migration Tools + +* *DXC*: The DirectX Shader Compiler can help validate HLSL code +* *SPIRV-Cross*: Can convert between GLSL and HLSL via SPIR-V +* *Automated translation tools*: Various tools can assist with bulk translation +* *IDE plugins*: Some editors have plugins to help with shader language conversion + +== Vulkan-Specific Semantics, Bindings, and Entry Points + +=== Descriptor Binding in HLSL/Slang + +HLSL offers two approaches for binding resources in Vulkan: + +* *HLSL register syntax*: +[source,hlsl] +---- +Texture2D albedoMap : register(t0, space1); +SamplerState samplerState : register(s0, space1); +---- + +* *Vulkan-specific attributes*: +[source,hlsl] +---- +[[vk::binding(0, 1)]] +Texture2D albedoMap; +[[vk::binding(0, 1)]] +SamplerState samplerState; +---- + +You can also combine both approaches for cross-API compatibility: +[source,hlsl] +---- +[[vk::binding(0, 1)]] +Texture2D albedoMap : register(t0, space1); +---- + +=== Resource Types and Register Spaces + +[options="header"] +|==== +| Resource Type | HLSL Type | Register Type | Vulkan Equivalent +| Uniform Buffer | ConstantBuffer | b | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER +| Storage Buffer | RWStructuredBuffer | u | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER +| Texture | Texture2D, Texture3D, etc. | t | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE +| Storage Image | RWTexture2D, etc. | u | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE +| Sampler | SamplerState | s | VK_DESCRIPTOR_TYPE_SAMPLER +| Combined Image Sampler | N/A (separate in HLSL) | N/A | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER +|==== + +=== Push Constants + +Push constants provide a way to quickly update small amounts of shader data without descriptor sets: + +[source,hlsl] +---- +// Define push constant structure +struct PushConstants { + float4x4 transform; + float4 color; + float time; +}; + +// Declare push constants block +[[vk::push_constant]] +PushConstants pushConstants; + +// Use in shader +float4 TransformedPosition = mul(pushConstants.transform, float4(position, 1.0)); +---- + +=== Specialization Constants + +Specialization constants allow you to specialize shaders at pipeline creation time: + +[source,hlsl] +---- +// Define specialization constants with IDs and default values +[[vk::constant_id(0)]] const bool USE_LIGHTING = true; +[[vk::constant_id(1)]] const int LIGHT_COUNT = 4; +[[vk::constant_id(2)]] const float AMBIENT_INTENSITY = 0.1; + +// Use in shader code +float3 CalculateLighting() { + float3 result = float3(AMBIENT_INTENSITY, AMBIENT_INTENSITY, AMBIENT_INTENSITY); + + if (USE_LIGHTING) { + for (int i = 0; i < LIGHT_COUNT; i++) { + // Add light contribution + result += CalculateLightContribution(i); + } + } + + return result; +} +---- + +=== Shader Entry Points + +In HLSL for Vulkan, you can specify custom entry point names: + +[source,hlsl] +---- +// Vertex shader entry point +VSOutput vs_main(VSInput input) { + // Vertex shader code +} + +// Fragment shader entry point +float4 ps_main(VSOutput input) : SV_TARGET { + // Fragment shader code +} +---- + +When compiling with DXC, specify the entry point: +[source,bash] +---- +dxc -spirv -T vs_6_0 -E vs_main shader.hlsl -Fo shader.vert.spv +dxc -spirv -T ps_6_0 -E ps_main shader.hlsl -Fo shader.frag.spv +---- + +=== Vulkan-Specific Features + +==== Subgroup Operations + +HLSL provides wave intrinsics that map to Vulkan subgroup operations: + +[source,hlsl] +---- +// Check if this is the first lane in the subgroup +if (WaveIsFirstLane()) { + // Do something only once per subgroup +} + +// Compute sum across all lanes in the subgroup +float total = WaveActiveSum(value); + +// Broadcast a value from a specific lane +float broadcastValue = WaveReadLaneAt(value, laneIndex); +---- + +==== Buffer Device Address + +For Vulkan's buffer device address feature: + +[source,hlsl] +---- +// In application code, pass buffer address via push constants +struct PushConstants { + uint64_t bufferAddress; +}; + +// In shader +[[vk::push_constant]] +PushConstants pushConstants; + +// Load data from the buffer address +float4 data = vk::RawBufferLoad(pushConstants.bufferAddress); +---- + +==== Ray Tracing + +For ray tracing shaders, use the `[shader("type")]` attribute: + +[source,hlsl] +---- +// Ray generation shader +[shader("raygeneration")] +void RayGen() { + // Ray generation code +} + +// Closest hit shader +[shader("closesthit")] +void ClosestHit(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attribs) { + // Closest hit code +} +---- + +== Compiling to SPIR-V + +=== Using DirectX Shader Compiler (DXC) + +The DirectX Shader Compiler (DXC) is the recommended tool for compiling HLSL and Slang to SPIR-V for Vulkan. + +==== Command-Line Compilation + +Basic command-line usage: + +[source,bash] +---- +# Compile vertex shader +dxc -spirv -T vs_6_0 -E main shader.hlsl -Fo shader.vert.spv + +# Compile fragment shader +dxc -spirv -T ps_6_0 -E main shader.hlsl -Fo shader.frag.spv + +# Compile compute shader +dxc -spirv -T cs_6_0 -E main shader.hlsl -Fo shader.comp.spv +---- + +Important command-line options: + +* `-spirv`: Generate SPIR-V code +* `-T `: Specify shader profile (e.g., vs_6_0, ps_6_0) +* `-E `: Specify entry point name +* `-Fo `: Specify output file +* `-fvk-use-dx-layout`: Use DirectX memory layout rules +* `-fspv-extension=`: Enable specific SPIR-V extension +* `-fspv-reflect`: Generate reflection information + +==== Shader Profiles + +[options="header"] +|==== +| Vulkan Shader Stage | DXC Profile | Minimum Shader Model +| Vertex | vs | 6.0 +| Fragment | ps | 6.0 +| Compute | cs | 6.0 +| Geometry | gs | 6.0 +| Tessellation Control | hs | 6.0 +| Tessellation Evaluation | ds | 6.0 +| Task | as | 6.5 +| Mesh | ms | 6.5 +| Ray Generation | lib | 6.3 +| Any Hit | lib | 6.3 +| Closest Hit | lib | 6.3 +| Miss | lib | 6.3 +| Intersection | lib | 6.3 +| Callable | lib | 6.3 +|==== + +==== Runtime Compilation + +For runtime compilation, use the DXC API: + +[source,cpp] +---- +#include "dxc/dxcapi.h" + +// Initialize DXC +CComPtr library; +CComPtr compiler; +CComPtr utils; +DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&library)); +DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler)); +DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils)); + +// Load shader source +CComPtr sourceBlob; +utils->LoadFile(L"shader.hlsl", nullptr, &sourceBlob); + +// Compile arguments +std::vector arguments = { + L"-spirv", // Generate SPIR-V + L"-T", L"ps_6_0", // Shader profile + L"-E", L"main", // Entry point + // Additional options as needed +}; + +// Compile shader +DxcBuffer buffer = {}; +buffer.Ptr = sourceBlob->GetBufferPointer(); +buffer.Size = sourceBlob->GetBufferSize(); +buffer.Encoding = DXC_CP_ACP; + +CComPtr result; +compiler->Compile(&buffer, arguments.data(), arguments.size(), nullptr, IID_PPV_ARGS(&result)); + +// Check for errors +HRESULT status; +result->GetStatus(&status); +if (FAILED(status)) { + CComPtr errors; + result->GetErrorBuffer(&errors); + // Handle error +} + +// Get compiled SPIR-V +CComPtr spirvBlob; +result->GetResult(&spirvBlob); + +// Create Vulkan shader module +VkShaderModuleCreateInfo createInfo = {}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = spirvBlob->GetBufferSize(); +createInfo.pCode = reinterpret_cast(spirvBlob->GetBufferPointer()); + +VkShaderModule shaderModule; +vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); +---- + +=== Using Slang Compiler + +For Slang shaders, use the Slang compiler with SPIR-V output: + +[source,bash] +---- +slangc -profile glsl_spirv -entry main -stage vertex shader.slang -o shader.vert.spv +---- + +=== Debugging and Validation + +==== SPIR-V Tools + +The SPIR-V Tools suite provides utilities for working with SPIR-V: + +* *spirv-val*: Validates SPIR-V binaries +* *spirv-dis*: Disassembles SPIR-V to human-readable form +* *spirv-opt*: Optimizes SPIR-V binaries +* *spirv-cfg*: Generates control flow graphs + +Example usage: +[source,bash] +---- +# Validate SPIR-V binary +spirv-val shader.spv + +# Disassemble SPIR-V for inspection +spirv-dis shader.spv -o shader.spvasm + +# Optimize SPIR-V binary +spirv-opt -O shader.spv -o shader.opt.spv +---- + +==== Common Compilation Issues + +* *Unsupported HLSL features*: Some HLSL features may not be supported in SPIR-V +* *Resource binding conflicts*: Ensure descriptor bindings don't conflict +* *Entry point mismatches*: Verify entry point names match between compilation and application +* *Shader model compatibility*: Ensure shader model is compatible with required features +* *Extension requirements*: Some features require specific SPIR-V extensions + +== Advanced Topics + +=== Cross-Compilation and Portability + +For maximum portability between Vulkan and DirectX: + +* Use conditional compilation with `#ifdef __spirv__` +* Maintain separate binding declarations for each API +* Use abstraction layers for API-specific features +* Consider shader generation tools for complex cases + +Example of a cross-API shader: +[source,hlsl] +---- +// Resource bindings for both APIs +#ifdef __spirv__ +[[vk::binding(0, 0)]] +#endif +ConstantBuffer uniforms : register(b0); + +#ifdef __spirv__ +[[vk::binding(1, 0)]] +#endif +Texture2D albedoTexture : register(t0); + +// API-specific code paths +float4 SampleTexture(Texture2D tex, SamplerState samp, float2 uv) { +#ifdef __spirv__ + // Vulkan-specific sampling code if needed + return tex.Sample(samp, uv); +#else + // DirectX-specific sampling code if needed + return tex.Sample(samp, uv); +#endif +} +---- + +=== Shader Interoperability + +For interoperability between GLSL and HLSL/Slang in the same application: + +* Maintain consistent descriptor set layouts +* Use explicit locations for all shader inputs/outputs +* Be mindful of matrix layout differences +* Consider using a shader generation system + +=== Performance Optimization + +Advanced optimization techniques: + +* *Shader permutations*: Generate specialized shader variants for different feature combinations +* *Workgroup size tuning*: Optimize compute shader workgroup sizes for specific hardware +* *Memory layout optimization*: Align data structures to hardware requirements +* *Instruction scheduling*: Organize instructions to maximize parallelism +* *Register pressure management*: Minimize register usage in critical sections + +=== Debugging Techniques + +Advanced debugging approaches: + +* *Debug prints*: Some implementations support debug printf in shaders +* *Debug buffers*: Write debug values to storage buffers for inspection +* *Shader instrumentation*: Add code to validate intermediate results +* *GPU debugging tools*: Use RenderDoc, NVIDIA Nsight, or AMD Radeon GPU Profiler + +== Distribution Considerations + +When deploying applications that use HLSL or Slang shaders with Vulkan, several distribution-related factors need to be considered to ensure optimal performance, compatibility, and user experience across different platforms and devices. + +=== Precompilation vs. Runtime Compilation + +Both approaches have advantages and trade-offs: + +==== Precompilation + +* *Advantages*: +** Faster application startup time +** No runtime dependency on shader compilers +** Validation errors caught during build rather than at runtime +** Opportunity for offline optimization + +* *Disadvantages*: +** Increased package size when supporting multiple hardware targets +** Less flexibility for runtime adaptation +** Need to manage multiple precompiled variants + +Example pipeline for precompilation: +[source,bash] +---- +# Build script example +for shader in shaders/*.hlsl; do + # Extract base name + base=$(basename $shader .hlsl) + + # Determine shader type from filename suffix + if [[ $base == *_vs ]]; then + profile="vs_6_0" + output="${base}.vert.spv" + elif [[ $base == *_ps ]]; then + profile="ps_6_0" + output="${base}.frag.spv" + elif [[ $base == *_cs ]]; then + profile="cs_6_0" + output="${base}.comp.spv" + fi + + # Compile with optimization + dxc -spirv -T $profile -E main $shader -O3 -Fo build/shaders/$output + + # Optionally validate + spirv-val build/shaders/$output +done +---- + +==== Runtime Compilation + +* *Advantages*: +** Ability to adapt to specific hardware capabilities at runtime +** Smaller distribution size (ship source instead of binaries) +** Easier to update and patch shaders +** Can generate specialized variants based on runtime conditions + +* *Disadvantages*: +** Increased startup time or loading times +** Runtime dependency on shader compiler +** Potential for runtime shader compilation errors +** Additional memory usage during compilation + +Example runtime compilation integration: +[source,cpp] +---- +// Shader manager class that handles runtime compilation +class ShaderManager { +public: + // Initialize DXC compiler once + ShaderManager() { + DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&m_library)); + DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&m_compiler)); + DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&m_utils)); + } + + // Compile shader at runtime + VkShaderModule CompileShader(const std::string& source, + const std::string& entryPoint, + const std::string& profile) { + // Compilation logic here + // ... + + // Return shader module + return shaderModule; + } + +private: + CComPtr m_library; + CComPtr m_compiler; + CComPtr m_utils; +}; +---- + +=== Hybrid Approaches + +Many applications use a hybrid approach: + +* Precompile common shaders for supported platforms +* Include fallback runtime compilation for edge cases +* Use shader caching to avoid recompilation + +=== Binary Distribution Formats + +When distributing precompiled SPIR-V shaders: + +* *Raw SPIR-V binaries*: Direct output from DXC or Slang compiler +* *Compressed SPIR-V*: Apply compression to reduce distribution size +* *Custom container formats*: Package multiple shader variants with metadata +* *Embedded in application*: Include SPIR-V as binary arrays in application code + +Example of embedding SPIR-V in C++ code: +[source,cpp] +---- +// Generated header with embedded shader data +#include "compiled_shaders.h" + +// Create shader module from embedded data +VkShaderModuleCreateInfo createInfo = {}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = sizeof(g_VertexShader); +createInfo.pCode = reinterpret_cast(g_VertexShader); + +VkShaderModule shaderModule; +vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); +---- + +=== Shader Caching Strategies + +Implementing an effective shader cache can significantly improve performance: + +* *Disk-based caching*: Store compiled shaders on disk between application runs +* *Memory caching*: Keep frequently used shaders in memory +* *Pipeline caching*: Use Vulkan's `VkPipelineCache` to store compiled pipelines +* *Warm-up phase*: Precompile critical shaders during loading screens + +Example of implementing a simple shader cache: +[source,cpp] +---- +class ShaderCache { +public: + // Try to load cached shader + VkShaderModule GetCachedShader(const std::string& key) { + auto it = m_cache.find(key); + if (it != m_cache.end()) { + return it->second; + } + return VK_NULL_HANDLE; + } + + // Add shader to cache + void CacheShader(const std::string& key, VkShaderModule module) { + m_cache[key] = module; + + // Optionally persist to disk + SaveToDisk(key, module); + } + +private: + std::unordered_map m_cache; + + // Implementation details for disk persistence + void SaveToDisk(const std::string& key, VkShaderModule module); + VkShaderModule LoadFromDisk(const std::string& key); +}; +---- + +=== Version Management + +Managing shader versions is crucial for maintenance and updates: + +* *Versioning scheme*: Include version information in shader metadata +* *Compatibility checks*: Verify shader compatibility with application version +* *Incremental updates*: Support updating only changed shaders +* *Fallback mechanisms*: Provide fallback shaders for backward compatibility + +Example versioning approach: +[source,cpp] +---- +struct ShaderMetadata { + uint32_t version; + uint32_t minAppVersion; + uint32_t featureFlags; + char name[64]; +}; + +// Shader package header +struct ShaderPackageHeader { + uint32_t magic; // Magic number for validation + uint32_t version; // Package version + uint32_t shaderCount; // Number of shaders in package + uint32_t compatFlags; // Compatibility flags +}; +---- + +=== Platform-Specific Considerations + +Different platforms may require special handling: + +* *Desktop vs. mobile*: Optimize shader complexity based on target hardware +* *Vendor-specific optimizations*: Consider optimizations for specific GPU vendors +* *Feature detection*: Adapt to available hardware features +* *Memory constraints*: Be mindful of memory limitations, especially on mobile + +Example of platform-specific shader selection: +[source,cpp] +---- +VkShaderModule GetAppropriateShader(const DeviceCapabilities& caps) { + if (caps.isLowPowerDevice) { + return m_lowPowerShaderVariant; + } else if (caps.vendorID == VENDOR_AMD) { + return m_amdOptimizedVariant; + } else if (caps.vendorID == VENDOR_NVIDIA) { + return m_nvidiaOptimizedVariant; + } else { + return m_defaultShaderVariant; + } +} +---- + +=== Size Optimization Techniques + +Reducing shader size is important, especially for mobile applications: + +* *SPIR-V optimization*: Use `spirv-opt` to optimize SPIR-V binaries +* *Strip debug information*: Remove debug information for release builds +* *On-demand loading*: Load shaders only when needed +* *Feature culling*: Remove unused features based on target platform +* *Compression*: Apply compression to shader binaries + +Example optimization pipeline: +[source,bash] +---- +# Optimize SPIR-V binary +spirv-opt -O --strip-debug shader.spv -o shader.opt.spv + +# Check size reduction +echo "Original size: $(stat -c%s shader.spv) bytes" +echo "Optimized size: $(stat -c%s shader.opt.spv) bytes" + +# Optionally compress +zstd -19 shader.opt.spv -o shader.opt.spv.zst +echo "Compressed size: $(stat -c%s shader.opt.spv.zst) bytes" +---- + +=== Distribution Security Considerations + +It's important to understand the fundamental limitations of shader intellectual property protection: + +* *Limited protection window*: Shader protection is effective only until the shader is loaded onto the GPU +* *Extraction vulnerability*: Once loaded, shaders can be extracted using GPU debuggers (like RenderDoc) or by recording Vulkan API calls +* *SPIR-V disassembly*: SPIR-V binaries can be disassembled to reveal shader logic +* *Inherent transparency*: The GPU execution model requires shaders to be in a readable format for the hardware + +Common protection approaches and their limitations: + +* *Obfuscation*: Can make shaders harder to understand but doesn't prevent extraction +* *Encryption*: Only protects shaders during distribution; they must be decrypted before GPU submission +* *Signature verification*: Helps ensure integrity but doesn't prevent reverse engineering +* *Anti-tampering measures*: Can detect modifications but not prevent shader analysis + +Alternative protection strategies: + +* *Legal protection*: Rely on licenses, patents, and legal agreements rather than technical measures +* *CPU-side algorithms*: Keep truly sensitive algorithms on the CPU where they're harder to extract +* *Split processing*: Divide algorithms between CPU and GPU to hide the complete picture +* *Regular updates*: Frequently update shaders to reduce the value of reverse engineering +* *Focus on unique data*: Often the data (textures, models, etc.) is more valuable than shader code + +== Conclusion + +HLSL and Slang provide powerful alternatives to GLSL for Vulkan shader development. By following the best practices and guidelines in this chapter, you can create efficient, maintainable, and portable shaders that leverage the strengths of these languages while taking full advantage of Vulkan's abilities. + +The migration from GLSL to HLSL/Slang may require some effort, but the benefits in terms of code reuse, language features, and cross-API compatibility can be significant for many projects. + +== References and Further Reading + +* link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst[HLSL to SPIR-V Feature Mapping Manual] +* link:https://github.com/shader-slang/slang[Slang Shader Language] +* link:https://github.com/KhronosGroup/SPIRV-Guide[SPIR-V Guide] +* link:https://www.khronos.org/blog/hlsl-first-class-vulkan-shading-language[HLSL as a First Class Vulkan Shading Language] +* link:https://docs.vulkan.org/spec/latest/chapters/interfaces.html[Vulkan Interfaces with SPIR-V] +* link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl[Microsoft HLSL Documentation] diff --git a/guide.adoc b/guide.adoc index 8416a1c..9883a27 100644 --- a/guide.adoc +++ b/guide.adoc @@ -145,6 +145,8 @@ include::{chapters}common_pitfalls.adoc[] include::{chapters}hlsl.adoc[] +include::{chapters}shader_authoring_guide_slang_hlsl.adoc[] + include::{chapters}high_level_shader_language_comparison.adoc[] = When and Why to use Extensions From 39f7b14a90f40253c4c3466c61f9fe87f71bd273 Mon Sep 17 00:00:00 2001 From: swinston Date: Mon, 21 Jul 2025 14:47:31 -0700 Subject: [PATCH 2/7] add the missing entry in README.adoc --- README.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.adoc b/README.adoc index 39f594f..6545cc1 100644 --- a/README.adoc +++ b/README.adoc @@ -64,6 +64,8 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc` = Using Vulkan +== xref:{chapters}shader_authoring_guide_slang_hlsl.adoc[Shader Authoring Guide (Slang/HLSL)] + == xref:{chapters}loader.adoc[Loader] == xref:{chapters}layers.adoc[Layers] From ab0b94695bdfc65c9920e96d6784d91a34a4740e Mon Sep 17 00:00:00 2001 From: swinston Date: Mon, 21 Jul 2025 16:05:56 -0700 Subject: [PATCH 3/7] Address Shannon's comments. --- chapters/shader_authoring_guide_slang_hlsl.adoc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/chapters/shader_authoring_guide_slang_hlsl.adoc b/chapters/shader_authoring_guide_slang_hlsl.adoc index 3f38a7f..7c384d5 100644 --- a/chapters/shader_authoring_guide_slang_hlsl.adoc +++ b/chapters/shader_authoring_guide_slang_hlsl.adoc @@ -16,11 +16,14 @@ While Vulkan consumes shaders in xref:{chapters}what_is_spirv.adoc[SPIR-V] forma === What is Slang? -link:https://github.com/shader-slang/slang[Slang] is a modern shading language and compiler designed to extend HLSL with advanced features. It's backward compatible with HLSL but adds features like generics, interfaces, and reflection capabilities. Slang can target multiple backends, including SPIR-V for Vulkan. +link:https://github.com/shader-slang/slang[Slang] is a modern shading +language and compiler designed to extend HLSL with advanced features. It's +largely backward-compatible with HLSL 2018 but adds features like generics, +interfaces, and reflection capabilities. Slang can target multiple backends, including SPIR-V for Vulkan. Key features of Slang include: -* Full compatibility with HLSL syntax +* Largely compatible with HLSL syntax * Advanced language features like generics, interfaces, and modules * Cross-compilation to multiple targets, including SPIR-V for Vulkan * Powerful metaprogramming capabilities @@ -59,7 +62,7 @@ float result = min(1.0, 2.0); int intResult = min(5, 3); ---- -HLSL has limited template support, but Slang's implementation is more robust and flexible. +HLSL has limited template support, but Slang's generics are more robust and flexible than HLSL's templates. ==== Interfaces and Polymorphism @@ -148,7 +151,8 @@ This provides better organization and more flexible binding than HLSL's register === Syntax Differences -While Slang maintains HLSL syntax compatibility, it introduces some new syntax elements: +While Slang largely maintains HLSL syntax compatibility, it introduces some +new syntax elements: * *Module declarations*: `module ModuleName;` * *Import statements*: `import ModuleName;` @@ -184,9 +188,9 @@ Slang introduces some runtime behavior differences: * *Parameter blocks*: Hierarchical organization of shader parameters * *Reflection API*: Runtime access to shader structure information -== Migration Guide: Upgrading from HLSL to Slang +== Migration Guide: Transitioning from HLSL to Slang -Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to upgrading your shaders. +Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to transitioning your shaders. === Step 1: Setting Up the Slang Compiler From b70aa2bb908e05b8c1949bf61a466f12ededa6e0 Mon Sep 17 00:00:00 2001 From: swinston Date: Tue, 22 Jul 2025 00:31:46 -0700 Subject: [PATCH 4/7] Try to incorporate the new chapter into the existing chapters and add slang.adoc to match hlsl.adoc --- README.adoc | 3 +- antora/modules/ROOT/nav.adoc | 2 +- ...high_level_shader_language_comparison.adoc | 639 ++++++- chapters/hlsl.adoc | 588 ++++++ .../shader_authoring_guide_slang_hlsl.adoc | 1614 ----------------- chapters/slang.adoc | 737 ++++++++ guide.adoc | 2 +- 7 files changed, 1941 insertions(+), 1644 deletions(-) delete mode 100644 chapters/shader_authoring_guide_slang_hlsl.adoc create mode 100644 chapters/slang.adoc diff --git a/README.adoc b/README.adoc index 6545cc1..f3ed2eb 100644 --- a/README.adoc +++ b/README.adoc @@ -64,7 +64,6 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc` = Using Vulkan -== xref:{chapters}shader_authoring_guide_slang_hlsl.adoc[Shader Authoring Guide (Slang/HLSL)] == xref:{chapters}loader.adoc[Loader] @@ -158,6 +157,8 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc` == xref:{chapters}hlsl.adoc[Using HLSL shaders] +== xref:{chapters}slang.adoc[Using Slang shaders] + == xref:{chapters}high_level_shader_language_comparison.adoc[High Level Shader Language Comparison] = When and Why to use Extensions diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index d8a5c7b..9d71e95 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -58,7 +58,7 @@ ** xref:{chapters}image_copies.adoc[] ** xref:{chapters}common_pitfalls.adoc[] ** xref:{chapters}hlsl.adoc[] -** xref:{chapters}shader_authoring_guide_slang_hlsl.adoc[] +** xref:{chapters}slang.adoc[] ** xref:{chapters}high_level_shader_language_comparison.adoc[] * When and Why to use Extensions ** xref:{chapters}extensions/cleanup.adoc[] diff --git a/chapters/high_level_shader_language_comparison.adoc b/chapters/high_level_shader_language_comparison.adoc index d814022..60cefa9 100644 --- a/chapters/high_level_shader_language_comparison.adoc +++ b/chapters/high_level_shader_language_comparison.adoc @@ -8,41 +8,57 @@ ifndef::images[:images: images/] = Vulkan High Level Shader Language Comparison :toc: -While Vulkan itself consumes shaders in a binary format called xref:{chapters}what_is_spirv.adoc[SPIR-V], shaders are usually written in a high level language. This section provides a mapping between shader functionality for the most common ones used with Vulkan: GLSL and HLSL. This is mostly aimed at people wanting to migrate from one high level shader language to another. It's meant as a starting point and not as a complete porting guide to one language from another +While Vulkan itself consumes shaders in a binary format called xref:{chapters}what_is_spirv.adoc[SPIR-V], shaders are usually written in a high level language. This section provides a mapping between shader functionality for the most common ones used with Vulkan: GLSL, HLSL, and Slang. This is mostly aimed at people wanting to migrate from one high level shader language to another. It's meant as a starting point and not as a complete porting guide to one language from another. [TIP] ==== For more details on using HLSL with Vulkan, visit xref:{chapters}hlsl.adoc[this chapter]. + +For more details on using Slang with Vulkan, visit xref:{chapters}slang.adoc[this chapter]. ==== [NOTE] ==== -The following listings are by no means complete, and mappings for newer extensions may be missing. Also note that concepts do not always map 1:1 between GLSL and HLSL. E.g. there are no semantic in GLSL while some newer GLSL functionality may not (yet) be available in HLSL. +The following listings are by no means complete, and mappings for newer extensions may be missing. Also note that concepts do not always map 1:1 between languages. E.g. there are no semantics in GLSL, while some newer GLSL functionality may not (yet) be available in HLSL or Slang. + +Slang is largely compatible with HLSL 2018 but adds several advanced features like generics, interfaces, and modules. In most cases where HLSL syntax is shown, Slang supports the same syntax, but may offer additional options. ==== == Extensions -In GLSL extensions need to be explicitly enabled using the `#extension` directive. This is **not** necessary in HLSL. The compiler will implicitly select suitable SPIR-V extensions based on the shader. If required one can use `-fspv-extension` arguments to explicitly select extensions. +In GLSL extensions need to be explicitly enabled using the `#extension` directive. This is **not** necessary in HLSL or Slang. Both compilers will implicitly select suitable SPIR-V extensions based on the shader. + +For HLSL, if required one can use `-fspv-extension` arguments to explicitly select extensions. + +For Slang, extensions can be explicitly enabled using command-line options like `-D` or through the Slang API. Slang also supports SPIR-V intrinsics similar to HLSL. == Data types [NOTE] ==== -Types work similar in GLSL and HLSL. But where GLSL e.g. has explicit vector or matrix types, HLSL uses basic types. On the other hand HLSL offers advanced type features like C++ templates. This paragraph contains a basic summary with some examples to show type differences between the two languages. +Types work similarly across GLSL, HLSL, and Slang. GLSL has explicit vector or matrix types, while HLSL and Slang use basic types with numeric suffixes. HLSL offers some advanced type features like C++ templates, and Slang extends this further with full generic support. This section contains a basic summary with examples to show type differences between the languages. ==== [options="header"] |==== -| *GLSL* | *HLSL* | *Example* -| vec__n__ | float__n__ | vec4 -> float4 -| ivec__n__ | int__n__ | ivec3 --> int3 -| mat__nxm__ or shorthand mat__n__ | float__nxm__ | mat4 -> float4x4 +| *GLSL* | *HLSL* | *Example* | *Slang-specific* +| vec__n__ | float__n__ | vec4 -> float4 | +| ivec__n__ | int__n__ | ivec3 -> int3 | +| mat__nxm__ or shorthand mat__n__ | float__nxm__ | mat4 -> float4x4 | +| n/a | n/a | | interface types +| n/a | limited templates | | full generic support |==== +[NOTE] +==== +Slang uses the same basic data types as HLSL (float__n__, int__n__, float__nxm__, etc.) but adds several advanced features like interfaces and full generic support. +==== + * link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-data-types[HLSL data types (Microsoft)] * link:https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)[GLSL data types (OpenGL wiki)] +* link:https://docs.shader-slang.org/en/latest/external/core-module-reference/types/index.html[Slang data types] -The syntax for casting types also differs: +The syntax for casting types differs: GLSL: [source,glsl] @@ -56,15 +72,89 @@ HLSL: float4x3 mat = (float4x3)(ubo.view); ---- +Slang: +[source,slang] +---- +float4x3 mat = (float4x3)(ubo.view); +// Or using generics +let mat = cast(ubo.view); +---- + [NOTE] ==== -An important difference: Matrices in GLSL are column-major, while matrices in HLSL are row-major. This affects things like matrix construction. +An important difference: Matrices in GLSL are column-major, while matrices in HLSL and Slang are row-major by default. This affects things like matrix construction. ==== +=== Slang-specific Types + +Slang extends HLSL's type system with several advanced features: + +==== Interfaces + +Slang supports interfaces similar to C# or Java: + +[source,slang] +---- +interface IMaterial { + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal); +} + +struct LambertianMaterial : IMaterial { + float3 albedo; + + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) { + return albedo * max(0, dot(normal, lightDir)) / 3.14159; + } +} +---- + +==== Generics + +Slang provides full generic support: + +[source,slang] +---- +// Generic function +generic +T min(T a, T b) { + return a < b ? a : b; +} + +// Generic struct +generic +struct Array { + T data[N]; + + T getElement(int index) { return data[index]; } +} + +// Usage +float result = min(1.0, 2.0); +Array myArray; +---- + == Implicit vk Namespace For Vulkan concepts that are not available in DirectX, an link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#the-implicit-vk-namespace[implicit namespace] has been added that marks Vulkan specific features. +Slang supports the same `vk` namespace as HLSL for Vulkan-specific functionality. This allows Slang code to use the same Vulkan-specific attributes and functions as HLSL: + +[source,slang] +---- +// Binding a resource in Slang using vk namespace +[[vk::binding(0, 0)]] +Texture2D albedoMap; + +// Push constants in Slang +struct PushConstants { + float4x4 transform; +}; +[[vk::push_constant]] +PushConstants pushConstants; +---- + +In addition to supporting the HLSL `vk` namespace, Slang also provides its own parameter block system that can be used alongside the `vk` namespace for more organized resource binding (see the Parameter Blocks section below). + == SPIR-V macro When using xref:{chapters}hlsl.adoc#DirectXShaderCompiler[DXC] to compile HLSL to SPIR-V you can use the `\\__spirv__` macro for Vulkan specific code. This is useful if HLSL shaders need to work with both Vulkan and D3D: @@ -77,9 +167,34 @@ When using xref:{chapters}hlsl.adoc#DirectXShaderCompiler[DXC] to compile HLSL t ConstantBuffer node : register(b0, space1); ---- +Slang provides similar conditional compilation capabilities, but with more options for cross-API development: + +[source,slang] +---- +// Using __spirv__ macro (same as HLSL) +#ifdef __spirv__ +[[vk::binding(0, 1)]] +#endif +ConstantBuffer node : register(b0, space1); + +// Using Slang's target-specific compilation +#if SLANG_VULKAN +[[vk::binding(0, 1)]] +#elif SLANG_D3D12 +// D3D12-specific code +#endif +ConstantBuffer node; + +// Using Slang's target-agnostic parameter blocks +[[vk::binding(0, 1)]] +ParameterBlock resources; +---- + +Slang's multi-target compilation system allows you to write shaders that can be compiled for multiple graphics APIs from a single source file, with conditional compilation to handle API-specific differences. + == SPIR-V intrinsics -DXC supports link:https://github.com/microsoft/DirectXShaderCompiler/wiki/GL_EXT_spirv_intrinsics-for-SPIR-V-code-gen[SPIR-V intrinsics] with the `GL_EXT_spirv_intrinsics` extension. This adds support for embedding arbitrary SPIR-V in the middle of of GLSL for features not available in DirectX. For this new keywords are added to the `vk` namespace that map SPIR-V opcodes, incl. `vk::ext_extension`, `vk::ext_capability`, `vk::ext_builtin_input`, `vk::ext_execution_mode` and `vk::ext_instruction`. +DXC supports link:https://github.com/microsoft/DirectXShaderCompiler/wiki/GL_EXT_spirv_intrinsics-for-SPIR-V-code-gen[SPIR-V intrinsics] with the `GL_EXT_spirv_intrinsics` extension. This adds support for embedding arbitrary SPIR-V in the middle of GLSL for features not available in DirectX. For this new keywords are added to the `vk` namespace that map SPIR-V opcodes, incl. `vk::ext_extension`, `vk::ext_capability`, `vk::ext_builtin_input`, `vk::ext_execution_mode` and `vk::ext_instruction`. Example for using the stencil export SPIR-V extension in HLSL: @@ -100,13 +215,101 @@ Example for setting up the built-in to access vertex positions in ray tracing: const static float3 gl_HitTriangleVertexPositions[3]; ---- +Slang supports the same SPIR-V intrinsics capabilities as HLSL, allowing you to access Vulkan-specific features that don't have direct mappings in the language. The syntax is identical to HLSL: + +[source,slang] +---- +[[vk::ext_capability(/* StencilExportEXT */ 5013)]] +[[vk::ext_extension("SPV_EXT_shader_stencil_export")]] +vk::ext_execution_mode(/* StencilRefReplacingEXT */ 5027); +---- + +In addition, Slang provides a more structured approach to extension capabilities through its module system, allowing you to encapsulate extension-specific code in dedicated modules: + +[source,slang] +---- +// In a dedicated module for stencil export functionality +module Extensions.StencilExport; + +[[vk::ext_capability(/* StencilExportEXT */ 5013)]] +[[vk::ext_extension("SPV_EXT_shader_stencil_export")]] +vk::ext_execution_mode(/* StencilRefReplacingEXT */ 5027); + +export void writeStencil(uint value) { + // Implementation using stencil export +} + +// In main shader code +import Extensions.StencilExport; + +void main() { + // Use the extension functionality + writeStencil(42); +} +---- + == Built-ins vs. Semantics [NOTE] ==== -While GLSL makes heavy use of input and output variables built into the languages called "built-ins", there is no such concept in HLSL. HLSL instead uses link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics[semantics], strings that are attached to inputs or inputs that contain information about the intended use of that variable. They are prefixed with `SV_`. For HLSL input values are explicit arguments for the main entry point and the shader needs to explicitly return an output. +While GLSL makes heavy use of input and output variables built into the languages called "built-ins", there is no such concept in HLSL or Slang. HLSL and Slang instead use link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics[semantics], strings that are attached to inputs or outputs that contain information about the intended use of that variable. They are prefixed with `SV_`. For HLSL and Slang, input values are explicit arguments for the main entry point and the shader needs to explicitly return an output. + +Slang follows HLSL's semantic-based approach but provides additional features through its interface system that can abstract away some of the semantic details. ==== +=== Slang Interface Abstractions + +Slang allows you to define interfaces that can abstract away some of the semantic details: + +[source,slang] +---- +// Define a vertex shader interface +interface IVertexShader +{ + // Input structure with semantics + struct Input + { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texCoord : TEXCOORD0; + }; + + // Output structure with semantics + struct Output + { + float4 position : SV_POSITION; + float3 worldPos : POSITION0; + float3 normal : NORMAL0; + float2 texCoord : TEXCOORD0; + }; + + // Vertex shader function + Output computeVertex(Input input); +} + +// Implement the interface +struct StandardVertexShader : IVertexShader +{ + // Implementation of the vertex shader function + IVertexShader.Output computeVertex(IVertexShader.Input input) + { + IVertexShader.Output output; + // Implementation... + return output; + } +} + +// Use the interface in a shader entry point +[shader("vertex")] +IVertexShader.Output vertexMain(IVertexShader.Input input) +{ + StandardVertexShader vertexShader; + return vertexShader.computeVertex(input); +} +---- + +This approach allows for more modular and reusable shader code while still leveraging the semantic system. + === Examples Writing positions from the vertex shader: @@ -122,7 +325,7 @@ void main() { } ---- -HLSL +HLSL: [source,hlsl] ---- struct VSOutput @@ -139,6 +342,49 @@ VSOutput main(VSInput input) } ---- +Slang: +[source,slang] +---- +struct VSOutput +{ + // Same as HLSL, using SV_POSITION semantic + float4 Pos : SV_POSITION; +}; + +// Standard approach (identical to HLSL) +VSOutput main(VSInput input) +{ + VSOutput output = (VSOutput)0; + output.Pos = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, input.Pos))); + return output; +} + +// Alternative using interfaces +interface IVertexTransform +{ + float4x4 getTransform(); +} + +struct StandardTransform : IVertexTransform +{ + float4x4 projectionMatrix; + float4x4 viewMatrix; + float4x4 modelMatrix; + + float4x4 getTransform() + { + return mul(projectionMatrix, mul(viewMatrix, modelMatrix)); + } +} + +VSOutput transformedMain(VSInput input, IVertexTransform transform) +{ + VSOutput output = (VSOutput)0; + output.Pos = mul(transform.getTransform(), float4(input.Pos, 1.0)); + return output; +} +---- + Reading the vertex index: GLSL: @@ -151,7 +397,7 @@ void main() } ---- -HLSL +HLSL: [source,hlsl] ---- struct VSInput @@ -168,13 +414,87 @@ VSOutput main(VSInput input) } ---- +Slang: +[source,slang] +---- +struct VSInput +{ + // Same as HLSL, using SV_VertexID semantic + uint VertexIndex : SV_VertexID +}; + +VSOutput main(VSInput input) +{ + VSOutput output = (VSOutput)0; + output.UV = float2((input.VertexIndex << 1) & 2, input.VertexIndex & 2); + return output; +} + +// Alternative using a utility function +float2 calculateUVFromVertexID(uint vertexID) +{ + return float2((vertexID << 1) & 2, vertexID & 2); +} + +VSOutput alternativeMain(VSInput input) +{ + VSOutput output = (VSOutput)0; + output.UV = calculateUVFromVertexID(input.VertexIndex); + return output; +} +---- + == Shader interface [NOTE] ==== -Shader interfaces greatly differ between GLSL and HLSL. +Shader interfaces greatly differ between GLSL, HLSL, and Slang. GLSL uses a more procedural approach with global variables, while HLSL uses a more object-oriented approach with explicit structures and semantics. Slang extends HLSL's approach with additional features like parameter blocks, interfaces, and modules. ==== +=== Slang Parameter Blocks + +Slang introduces a powerful concept called parameter blocks that provides a more structured way to organize shader resources: + +[source,slang] +---- +// Define a parameter block type +struct MaterialResources +{ + Texture2D albedoMap; + SamplerState samplerState; + + struct Constants { + float4 baseColor; + float roughness; + float metallic; + } constants; +}; + +// Declare a parameter block with explicit binding +[[vk::binding(0, 0)]] +ParameterBlock material; + +// Usage in shader code +float4 sampleAlbedo(float2 uv) +{ + // Access resources through the parameter block + return material.albedoMap.Sample(material.samplerState, uv); +} + +float getRoughness() +{ + // Access constants through the parameter block + return material.constants.roughness; +} +---- + +Parameter blocks offer several advantages: +* Logical grouping of related resources +* Cleaner shader code with hierarchical access +* Better compatibility across different graphics APIs +* Support for nested resources and constants +* Improved reflection capabilities + === Descriptor bindings ==== GLSL @@ -607,6 +927,35 @@ float4 main(VSOutput input) : SV_TARGET { | sparseTexelsResidentARB | CheckAccessFullyMapped |==== +[NOTE] +==== +Slang supports the same texture operations as HLSL, with identical function names and behavior. +==== + +Slang also supports more advanced texture operations through its parameter block system: + +[source,slang] +---- +// Define a parameter block with textures +struct TextureResources +{ + Texture2D albedoMap; + Texture2D normalMap; + Texture2D roughnessMap; + SamplerState samplerState; +} + +// Declare a parameter block +[[vk::binding(0, 0)]] +ParameterBlock textures; + +// Sample textures through the parameter block +float4 sampleAlbedo(float2 uv) +{ + return textures.albedoMap.Sample(textures.samplerState, uv); +} +---- + === Image formats ==== GLSL @@ -723,7 +1072,7 @@ ConstantBuffer sbt; // @todo: some of the stuff in here is used across different stages (e.g. gl_PrimitiveID) [options="header"] |==== -| *GLSL* | *HLSL* | Note +| *GLSL* | *HLSL* | *Note* | accelerationStructureEXT | RaytracingAccelerationStructure | | executeCallableEXT | CallShader | | ignoreIntersectionEXT | IgnoreHit | @@ -755,19 +1104,19 @@ ConstantBuffer sbt; | gl_WorldToObjectEXT | WorldToObject4x3 | | gl_WorldToObject3x4EXT | WorldToObject3x4 | | gl_ObjectToWorld3x4EXT | ObjectToWorld3x4 | -| gl_RayFlagsNoneEXT | RAY_FLAG_NONE | +| gl_RayFlagsNoneEXT | RAY_FLAG_NONE | | gl_RayFlagsOpaqueEXT | RAY_FLAG_FORCE_OPAQUE | | gl_RayFlagsNoOpaqueEXT | RAY_FLAG_FORCE_NON_OPAQUE | | gl_RayFlagsTerminateOnFirstHitEXT | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | | gl_RayFlagsSkipClosestHitShaderEXT | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | | gl_RayFlagsCullBackFacingTrianglesEXT | RAY_FLAG_CULL_BACK_FACING_TRIANGLES | -| gl_RayFlagsCullFrontFacingTrianglesEXT | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES | +| gl_RayFlagsCullFrontFacingTrianglesEXT | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES | | gl_RayFlagsCullOpaqueEXT | RAY_FLAG_CULL_OPAQUE | | gl_RayFlagsCullNoOpaqueEXT | RAY_FLAG_CULL_NON_OPAQUE | requires `GL_EXT_ray_flags_primitive_culling` | gl_RayFlagsSkipTrianglesEXT | RAY_FLAG_SKIP_TRIANGLES | requires `GL_EXT_ray_flags_primitive_culling` | gl_RayFlagsSkipAABBEXT | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | -| gl_HitKindFrontFacingTriangleEXT | HIT_KIND_TRIANGLE_FRONT_FACE | -| gl_HitKindBackFacingTriangleEXT | HIT_KIND_TRIANGLE_BACK_FACE | +| gl_HitKindFrontFacingTriangleEXT | HIT_KIND_TRIANGLE_FRONT_FACE | +| gl_HitKindBackFacingTriangleEXT | HIT_KIND_TRIANGLE_BACK_FACE | | gl_HitTriangleVertexPositionsEXT a| Requires <>: [,hlsl] ---- @@ -775,10 +1124,61 @@ ConstantBuffer sbt; [[vk::ext_capability(RayTracingPositionFetchKHR)]] [[vk::ext_builtin_input(HitTriangleVertexPositionsKHR)]] ---- - | Requires `GL_EXT_ray_tracing_position_fetch` +| Requires `GL_EXT_ray_tracing_position_fetch` | shadercallcoherent | n.a. | |==== +[NOTE] +==== +Slang supports the same raytracing built-ins as HLSL, with identical function names and behavior. Additionally, Slang's interface and module system can be used to create more modular and reusable raytracing code. +==== + +[source,slang] +---- +// Define a ray tracing interface +interface IRayTracer +{ + struct RayPayload + { + float3 color; + float distance; + int materialID; + }; + + void traceScene(inout RayPayload payload, float3 origin, float3 direction); +} + +// Implement the interface +struct StandardRayTracer : IRayTracer +{ + RaytracingAccelerationStructure accelerationStructure; + + void traceScene(inout IRayTracer.RayPayload payload, float3 origin, float3 direction) + { + // Configure ray + uint rayFlags = RAY_FLAG_NONE; + uint instanceMask = 0xFF; + uint rayContributionToHitGroupIndex = 0; + uint multiplierForGeometryContributionToHitGroupIndex = 1; + uint missShaderIndex = 0; + + // Trace ray + TraceRay( + accelerationStructure, + rayFlags, + instanceMask, + rayContributionToHitGroupIndex, + multiplierForGeometryContributionToHitGroupIndex, + missShaderIndex, + origin, + 0.001f, // Min t + direction, + 10000.0f, // Max t + payload); + } +} +---- + === Compute ==== Local workgroup size @@ -841,6 +1241,51 @@ groupshared float4 sharedData[1024]; | gl_WorkGroupSize | n.a. |==== +[NOTE] +==== +Slang supports the same compute shader semantics as HLSL, with identical names and behavior. Additionally, Slang's interface and module system can be used to create more modular and reusable compute shader code. +==== + +[source,slang] +---- +// Define a compute kernel interface +interface IComputeKernel +{ + void execute(uint3 globalID, uint3 groupID, uint3 localID); +} + +// Implement a specific compute kernel +struct ImageProcessingKernel : IComputeKernel +{ + RWTexture2D outputImage; + Texture2D inputImage; + SamplerState samplerState; + + void execute(uint3 globalID, uint3 groupID, uint3 localID) + { + // Process image at the current pixel + uint2 pixelCoord = globalID.xy; + float2 uv = float2(pixelCoord) / float2(1920, 1080); // Example resolution + + // Sample input and write to output + float4 color = inputImage.Sample(samplerState, uv); + outputImage[pixelCoord] = color; + } +} + +// Main compute shader entry point +[numthreads(16, 16, 1)] +void main( + uint3 dispatchThreadID : SV_DispatchThreadID, + uint3 groupID : SV_GroupID, + uint3 groupThreadID : SV_GroupThreadID) +{ + // Create and use the kernel + ImageProcessingKernel kernel; + kernel.execute(dispatchThreadID, groupID, groupThreadID); +} +---- + ==== Barriers [NOTE] @@ -962,12 +1407,12 @@ These shader stages share several functions and built-ins [options="header"] |==== | *GLSL* | *HLSL* | *Note* -| gl_PointSize | [[vk::builtin("PointSize")]] | Vulkan only, no direct HLSL equivalent -| gl_BaseVertexARB | [[vk::builtin("BaseVertex")]] | Vulkan only, no direct HLSL equivalent -| gl_BaseInstanceARB | [[vk::builtin("BaseInstance")]] | Vulkan only, no direct HLSL equivalent -| gl_DrawID | [[vk::builtin("DrawIndex")]] | Vulkan only, no direct HLSL equivalent -| gl_DeviceIndex | [[vk::builtin("DeviceIndex")]] | Vulkan only, no direct HLSL equivalent -| gl_ViewportMask | [[vk::builtin("ViewportMaskNV")]] | Vulkan only, no direct HLSL equivalent +| gl_PointSize | `vk::builtin("PointSize")` | Vulkan only, no direct HLSL equivalent +| gl_BaseVertexARB | `vk::builtin("BaseVertex")` | Vulkan only, no direct HLSL equivalent +| gl_BaseInstanceARB | `vk::builtin("BaseInstance")` | Vulkan only, no direct HLSL equivalent +| gl_DrawID | `vk::builtin("DrawIndex")` | Vulkan only, no direct HLSL equivalent +| gl_DeviceIndex | `vk::builtin("DeviceIndex")` | Vulkan only, no direct HLSL equivalent +| gl_ViewportMask | `vk::builtin("ViewportMaskNV")` | Vulkan only, no direct HLSL equivalent | gl_FragCoord | SV_Position | | gl_FragDepth | SV_Depth | | gl_FrontFacing | SV_IsFrontFace | @@ -993,6 +1438,90 @@ These shader stages share several functions and built-ins | gl_BaryCoordNoPerspEXT | SV_Barycentrics with noperspective | |==== +== Slang Modules and Imports + +One of Slang's most distinctive features is its module system, which allows for better code organization and reuse. This feature is not available in either GLSL or standard HLSL. + +=== Module Declaration + +In Slang, you can organize code into modules: + +[source,slang] +---- +// File: Lighting.slang +module Lighting; + +// Public functions must be marked with 'export' +export float3 calculateDirectLighting(float3 normal, float3 lightDir, float3 color) +{ + float NdotL = max(0, dot(normal, lightDir)); + return color * NdotL; +} + +// Private function (not exported) +float calculateAttenuation(float distance) +{ + return 1.0 / (distance * distance); +} +---- + +=== Importing Modules + +You can import modules to use their exported functionality: + +[source,slang] +---- +// File: Fragment.slang +module Fragment; + +// Import another module +import Lighting; + +[shader("fragment")] +float4 fragmentMain(float3 normal : NORMAL, float3 worldPos : POSITION) : SV_TARGET +{ + float3 lightDir = normalize(float3(1, 1, 1)); + float3 lightColor = float3(1, 1, 1); + + // Use function from imported module + float3 directLighting = Lighting::calculateDirectLighting(normal, lightDir, lightColor); + + return float4(directLighting, 1.0); +} +---- + +=== Hierarchical Modules + +Slang supports hierarchical module organization: + +[source,slang] +---- +// File: Rendering/Materials/PBR.slang +module Rendering.Materials.PBR; + +export interface IMaterial { ... } +export struct PBRMaterial : IMaterial { ... } + +// File: Main.slang +module Main; + +// Import specific module +import Rendering.Materials.PBR; + +// Use imported types +PBR::PBRMaterial material; +---- + +=== Module Benefits + +The module system offers several advantages: + +* Better code organization +* Encapsulation of implementation details +* Reduced name conflicts +* Explicit dependencies between components +* Improved compilation times through separate compilation + == Functions [NOTE] @@ -1014,5 +1543,61 @@ Most GLSL functions are also available in HLSL and vice-versa. This chapter list | mix | lerp |==== +[NOTE] +==== +Slang supports all the same intrinsic functions as HLSL, with identical names. Additionally, Slang allows you to define your own generic functions that can work with multiple types and supports operator overloading and custom function definitions within interfaces. +==== + * link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-intrinsic-functions[HLSL intrinsic function (Microsoft)] * link:https://registry.khronos.org/OpenGL-Refpages/gl4/index.php[OpenGL reference pages] +* link:https://github.com/shader-slang/spec[Slang Language Reference] + +Here's an example of Slang's generic functions: + +[source,slang] +---- +// Generic interpolation function +generic +T interpolate(T a, T b, float t) +{ + return lerp(a, b, t); +} + +// Usage with different types +float result1 = interpolate(1.0f, 2.0f, 0.5f); // Returns 1.5 +float3 result2 = interpolate(float3(1,0,0), float3(0,1,0), 0.5f); // Returns (0.5, 0.5, 0) +---- + +Slang also supports operator overloading and custom function definitions within interfaces, allowing for more expressive and reusable code: + +[source,slang] +---- +// Define an interface with operations +interface IVector +{ + T dot(T other); + T normalize(); + T scale(float factor); +} + +// Implement for float3 +struct Float3Vector : IVector +{ + float3 value; + + float3 dot(float3 other) + { + return dot(value, other); + } + + float3 normalize() + { + return normalize(value); + } + + float3 scale(float factor) + { + return value * factor; + } +} +---- diff --git a/chapters/hlsl.adoc b/chapters/hlsl.adoc index cf1422d..9ff855f 100644 --- a/chapters/hlsl.adoc +++ b/chapters/hlsl.adoc @@ -7,6 +7,7 @@ ifndef::images[:images: images/] [[hlsl-in-vulkan]] = HLSL in Vulkan +:toc: Vulkan does not directly consume shaders in a human-readable text format, but instead uses xref:{chapters}what_is_spirv.adoc[SPIR-V] as an intermediate representation. This opens the option to use shader languages other than e.g. GLSL, as long as they can target the Vulkan SPIR-V environment. @@ -21,11 +22,307 @@ image::{images}what_is_spirv_dxc.png[what_is_spriv_dxc.png] If you are new to HLSL, a good starting point are the HLSL resources over at link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl[Microsoft Learn]. Another great source is the link:https://microsoft.github.io/DirectX-Specs/[DirectX-Specs document]. It contains valuable information on recent shader features and HLSL's shader models. +== Why Use HLSL for Vulkan? + +There are several advantages to using HLSL for Vulkan development: + +* *Cross-API compatibility*: Write shaders that can be used with both Vulkan and DirectX with minimal changes +* *Familiar syntax*: Developers with DirectX experience can leverage their existing knowledge +* *Industry adoption*: HLSL is widely used in game engines and graphics applications +* *Tooling support*: Rich ecosystem of tools, debuggers, and IDE integrations + [[applications-pov]] == From the application's point-of-view From the application's point-of-view, using HLSL is exactly the same as using GLSL. As the application always consumes shaders in the SPIR-V format, the only difference is in the tooling to generate the SPIR-V shaders from the desired shading language. +== Best Practices for Writing Shaders in HLSL for Vulkan + +=== Code Organization + +* *Separate shader stages*: Keep different shader stages (vertex, fragment, compute, etc.) in separate files +* *Use structures for inputs and outputs*: Define clear structures for shader inputs and outputs +* *Consistent naming conventions*: Adopt a consistent naming scheme for variables, functions, and types +* *Modular design*: Break complex shaders into reusable functions and components + +Example of a well-organized HLSL shader: + +[source,hlsl] +---- +// Common structures and constants +struct VSInput { + [[vk::location(0)]] float3 Position : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; + [[vk::location(2)]] float2 TexCoord : TEXCOORD0; +}; +struct VSOutput { + float4 Position : SV_POSITION; + [[vk::location(0)]] float3 WorldPos : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; + [[vk::location(2)]] float2 TexCoord : TEXCOORD0; +}; +// Uniform buffer with transformation matrices +struct SceneUBO { + float4x4 model; + float4x4 view; + float4x4 projection; +}; +[[vk::binding(0, 0)]] +ConstantBuffer ubo; +// Vertex shader main function +VSOutput main(VSInput input) { + VSOutput output = (VSOutput)0; + // Transform position to clip space + float4 worldPos = mul(ubo.model, float4(input.Position, 1.0)); + output.Position = mul(ubo.projection, mul(ubo.view, worldPos)); + // Pass through other attributes + output.WorldPos = worldPos.xyz; + output.Normal = mul((float3x3)ubo.model, input.Normal); + output.TexCoord = input.TexCoord; + return output; +} +---- + +=== Performance Considerations + +* *Minimize divergent control flow*: Avoid complex branching within shader wavefronts +* *Optimize memory access patterns*: Group related data together to improve cache coherency +* *Reduce register pressure*: Limit the number of variables in high-register-usage sections +* *Use appropriate precision*: Use lower precision types (`half`, `min16float`) when full precision isn't needed +* *Leverage subgroup operations*: Use subgroup/wave intrinsics for efficient parallel operations +* *Prefer compile-time constants*: Use specialization constants for values known at pipeline creation time + +Example of using specialization constants: + +[source,hlsl] +---- +// Define specialization constants +[[vk::constant_id(0)]] const bool USE_NORMAL_MAPPING = true; +[[vk::constant_id(1)]] const int LIGHT_COUNT = 4; +[[vk::constant_id(2)]] const float SPECULAR_POWER = 32.0; +// Use in conditional code +float3 CalculateNormal(float3 normal, float3 tangent, float2 texCoord) { + if (USE_NORMAL_MAPPING) { + // Complex normal mapping calculation + return CalculateNormalFromMap(normal, tangent, texCoord); + } else { + // Simple pass-through + return normalize(normal); + } +} +---- + +=== Debugging and Validation + +* *Add debug markers*: Use comments or debug variables to mark important sections +* *Validate inputs*: Check for NaN or invalid values in critical calculations +* *Use validation layers*: Enable Vulkan validation layers during development +* *Leverage shader debugging tools*: Use tools like RenderDoc or NVIDIA Nsight for shader debugging +* *Implement fallbacks*: Provide simpler code paths for debugging complex algorithms + +=== Vulkan-Specific Best Practices + +* *Explicit bindings*: Always specify explicit descriptor set and binding indices +* *Consistent descriptor layouts*: Maintain consistent descriptor layouts across shader stages +* *Minimize descriptor set changes*: Group resources to minimize descriptor set changes during rendering +* *Consider push constants*: Use push constants for frequently changing small data +* *Be mindful of SPIR-V limitations*: Some HLSL features may not translate directly to SPIR-V + +== Migration from GLSL to HLSL + +=== Conceptual Differences + +GLSL and HLSL have different programming paradigms: + +* *GLSL*: More procedural, similar to C +* *HLSL*: More object-oriented, similar to C++ + +Key conceptual differences include: + +* *Entry points*: GLSL uses `void main()`, HLSL uses typed functions with explicit inputs/outputs +* *Built-ins vs. semantics*: GLSL uses built-in variables, HLSL uses semantics +* *Resource binding*: Different syntax for binding resources +* *Matrix layout*: GLSL uses column-major by default, HLSL uses row-major by default + +=== Syntax Translation Guide + +==== Data Types + +[options="header"] +|==== +| GLSL | HLSL | Example +| vec_n_ | float_n_ | vec4 → float4 +| ivec_n_ | int_n_ | ivec3 → int3 +| uvec_n_ | uint_n_ | uvec2 → uint2 +| mat_nxm_ | float_nxm_ | mat4 → float4x4 +|==== + +==== Shader Inputs/Outputs + +GLSL: +[source,glsl] +---- +// Vertex shader inputs +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +// Vertex shader outputs +layout(location = 0) out vec3 outNormal; +layout(location = 1) out vec2 outUV; +void main() { + gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPosition, 1.0); + outNormal = inNormal; + outUV = inUV; +} +---- + +HLSL: +[source,hlsl] +---- +// Input/output structures +struct VSInput { + [[vk::location(0)]] float3 Position : POSITION0; + [[vk::location(1)]] float3 Normal : NORMAL0; +}; +struct VSOutput { + float4 Position : SV_POSITION; + [[vk::location(0)]] float3 Normal : NORMAL0; + [[vk::location(1)]] float2 UV : TEXCOORD0; +}; +// Main function with explicit input/output +VSOutput main(VSInput input) { + VSOutput output = (VSOutput)0; + output.Position = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Position, 1.0)))); + output.Normal = input.Normal; + output.UV = input.UV; + return output; +} +---- + +==== Resource Binding + +GLSL: +[source,glsl] +---- +// Uniform buffer +layout(set = 0, binding = 0) uniform UBO { + mat4 model; + mat4 view; + mat4 projection; +} ubo; +// Texture and sampler +layout(set = 0, binding = 1) uniform sampler2D texAlbedo; +// Storage buffer +layout(set = 0, binding = 2) buffer SSBO { + vec4 data[]; +} ssbo; +---- + +HLSL: +[source,hlsl] +---- +// Uniform buffer +struct UBO { + float4x4 model; + float4x4 view; + float4x4 projection; +}; +[[vk::binding(0, 0)]] +ConstantBuffer ubo; +// Texture and sampler +[[vk::binding(1, 0)]] +Texture2D texAlbedo; +[[vk::binding(1, 0)]] +SamplerState sampAlbedo; +// Storage buffer +struct SSBO { + float4 data[]; +}; +[[vk::binding(2, 0)]] +RWStructuredBuffer ssbo; +---- + +==== Built-ins and Special Variables + +[options="header"] +|==== +| GLSL | HLSL | Description +| gl_Position | SV_Position | Vertex position output +| gl_FragCoord | SV_Position | Fragment position +| gl_VertexIndex | SV_VertexID | Vertex index +| gl_InstanceIndex | SV_InstanceID | Instance index +| gl_FragDepth | SV_Depth | Fragment depth output +| gl_FrontFacing | SV_IsFrontFace | Front-facing flag +| gl_PrimitiveID | SV_PrimitiveID | Primitive ID +|==== + +==== Common Functions + +[options="header"] +|==== +| GLSL | HLSL | Description +| mix(x, y, a) | lerp(x, y, a) | Linear interpolation +| fract(x) | frac(x) | Fractional part +| dFdx(p) | ddx(p) | Derivative in x direction +| dFdy(p) | ddy(p) | Derivative in y direction +| texture(sampler, coord) | sampler.Sample(texture, coord) | Texture sampling +|==== + +=== Common Migration Challenges + +* *Matrix multiplication*: GLSL uses `*` operator, HLSL uses `mul()` function +* *Texture sampling*: Different syntax for texture access +* *Swizzling*: Both support swizzling but with subtle differences +* *Preprocessor directives*: Different preprocessor capabilities +* *Extension handling*: GLSL requires explicit extension enabling, HLSL doesn't + +=== Migration Tools + +* *DXC*: The DirectX Shader Compiler can help validate HLSL code +* *SPIRV-Cross*: Can convert between GLSL and HLSL via SPIR-V +* *Automated translation tools*: Various tools can assist with bulk translation +* *IDE plugins*: Some editors have plugins to help with shader language conversion + +== Vulkan-Specific Semantics, Bindings, and Entry Points + +=== Descriptor Binding in HLSL + +HLSL offers two approaches for binding resources in Vulkan: + +* *HLSL register syntax*: +[source,hlsl] +---- +Texture2D albedoMap : register(t0, space1); +SamplerState samplerState : register(s0, space1); +---- + +* *Vulkan-specific attributes*: +[source,hlsl] +---- +[[vk::binding(0, 1)]] +Texture2D albedoMap; +[[vk::binding(0, 1)]] +SamplerState samplerState; +---- + +You can also combine both approaches for cross-API compatibility: +[source,hlsl] +---- +[[vk::binding(0, 1)]] +Texture2D albedoMap : register(t0, space1); +---- + +=== Resource Types and Register Spaces + +[options="header"] +|==== +| Resource Type | HLSL Type | Register Type | Vulkan Equivalent +| Uniform Buffer | ConstantBuffer | b | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER +| Storage Buffer | RWStructuredBuffer | u | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER +| Texture | Texture2D, Texture3D, etc. | t | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE +| Storage Image | RWTexture2D, etc. | u | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE +| Sampler | SamplerState | s | VK_DESCRIPTOR_TYPE_SAMPLER +|==== + [[hlsl-spirv-mapping-manual]] == HLSL to SPIR-V feature mapping manual A great starting point on using HLSL in Vulkan via SPIR-V is the link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst[HLSL to SPIR-V feature mapping manual]. It contains detailed information on semantics, syntax, supported features and extensions and much more and is a must-read. The xref:{chapters}decoder_ring.adoc[decoder ring] also has a translation table for concepts and terms used in Vulkan and DirectX. @@ -342,3 +639,294 @@ DirectX and HLSL use a fixed shader model notion to describe the supported featu | VK_KHR_shader_quad_control, VkPhysicalDeviceFeatures::shaderStorageImageMultisample |=== + +== Advanced Topics + +=== Cross-Compilation and Portability + +For maximum portability between Vulkan and DirectX: + +* Use conditional compilation with `#ifdef __spirv__` +* Maintain separate binding declarations for each API +* Use abstraction layers for API-specific features +* Consider shader generation tools for complex cases + +Example of a cross-API shader: + +[source,hlsl] +---- +// Resource bindings for both APIs +#ifdef __spirv__ +[[vk::binding(0, 0)]] +#endif +ConstantBuffer uniforms : register(b0); +#ifdef __spirv__ +[[vk::binding(1, 0)]] +#endif +Texture2D albedoTexture : register(t0); +// API-specific code paths +float4 SampleTexture(Texture2D tex, SamplerState samp, float2 uv) { +#ifdef __spirv__ + // Vulkan-specific sampling code if needed + return tex.Sample(samp, uv); +#else + // DirectX-specific sampling code if needed + return tex.Sample(samp, uv); +#endif +} +---- + +=== Shader Interoperability + +For interoperability between GLSL and HLSL in the same application: + +* Maintain consistent descriptor set layouts +* Use explicit locations for all shader inputs/outputs +* Be mindful of matrix layout differences +* Consider using a shader generation system + +=== Performance Optimization + +Advanced optimization techniques: + +* *Shader permutations*: Generate specialized shader variants for different feature combinations +* *Workgroup size tuning*: Optimize compute shader workgroup sizes for specific hardware +* *Memory layout optimization*: Align data structures to hardware requirements +* *Instruction scheduling*: Organize instructions to maximize parallelism +* *Register pressure management*: Minimize register usage in critical sections + +=== Debugging Techniques + +Advanced debugging approaches: + +* *Debug prints*: Some implementations support debug printf in shaders +* *Debug buffers*: Write debug values to storage buffers for inspection +* *Shader instrumentation*: Add code to validate intermediate results +* *GPU debugging tools*: Use RenderDoc, NVIDIA Nsight, or AMD Radeon GPU Profiler + +== Distribution Considerations + +When deploying applications that use HLSL shaders with Vulkan, several distribution-related factors need to be considered to ensure optimal performance, compatibility, and user experience across different platforms and devices. + +=== Precompilation vs. Runtime Compilation + +Both approaches have advantages and trade-offs: + +==== Precompilation + +* *Advantages*: +** Faster application startup time +** No runtime dependency on shader compilers +** Validation errors caught during build rather than at runtime +** Opportunity for offline optimization +* *Disadvantages*: +** Increased package size when supporting multiple hardware targets +** Less flexibility for runtime adaptation +** Need to manage multiple precompiled variants + +Example pipeline for precompilation: + +[source,bash] +---- +# Build script example +for shader in shaders/*.hlsl; do + # Extract base name + base=$(basename $shader .hlsl) + # Determine shader type from filename suffix + if [[ $base == *_vs ]]; then + profile="vs_6_0" + output="${base}.vert.spv" + elif [[ $base == *_ps ]]; then + profile="ps_6_0" + output="${base}.frag.spv" + elif [[ $base == *_cs ]]; then + profile="cs_6_0" + output="${base}.comp.spv" + fi + # Compile with optimization + dxc -spirv -T $profile -E main $shader -O3 -Fo build/shaders/$output + # Optionally validate + spirv-val build/shaders/$output +done +---- + +==== Runtime Compilation + +* *Advantages*: +** Ability to adapt to specific hardware capabilities at runtime +** Smaller distribution size (ship source instead of binaries) +** Easier to update and patch shaders +** Can generate specialized variants based on runtime conditions +* *Disadvantages*: +** Increased startup time or loading times +** Runtime dependency on shader compiler +** Potential for runtime shader compilation errors +** Additional memory usage during compilation + +Example runtime compilation integration: + +[source,cpp] +---- +// Shader manager class that handles runtime compilation +class ShaderManager { +public: + // Initialize DXC compiler once + ShaderManager() { + DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&m_library)); + DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&m_compiler)); + DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&m_utils)); + } + // Compile shader at runtime + VkShaderModule CompileShader(const std::string& source, + const std::string& entryPoint, + const std::string& profile) { + // Compilation logic here + // ... + // Return shader module + return shaderModule; + } +private: + CComPtr m_library; + CComPtr m_compiler; + CComPtr m_utils; +}; +---- + +=== Hybrid Approaches + +Many applications use a hybrid approach: + +* Precompile common shaders for supported platforms +* Include fallback runtime compilation for edge cases +* Use shader caching to avoid recompilation + +=== Binary Distribution Formats + +When distributing precompiled SPIR-V shaders: + +* *Raw SPIR-V binaries*: Direct output from DXC compiler +* *Compressed SPIR-V*: Apply compression to reduce distribution size +* *Custom container formats*: Package multiple shader variants with metadata +* *Embedded in application*: Include SPIR-V as binary arrays in application code + +Example of embedding SPIR-V in C++ code: + +[source,cpp] +---- +// Generated header with embedded shader data +#include "compiled_shaders.h" +// Create shader module from embedded data +VkShaderModuleCreateInfo createInfo = {}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = sizeof(g_VertexShader); +createInfo.pCode = reinterpret_cast(g_VertexShader); +VkShaderModule shaderModule; +vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); +---- + +=== Shader Caching Strategies + +Implementing an effective shader cache can significantly improve performance: + +* *Disk-based caching*: Store compiled shaders on disk between application runs +* *Memory caching*: Keep frequently used shaders in memory +* *Pipeline caching*: Use Vulkan's `VkPipelineCache` to store compiled pipelines +* *Warm-up phase*: Precompile critical shaders during loading screens + +Example of implementing a simple shader cache: + +[source,cpp] +---- +class ShaderCache { +public: + // Try to load cached shader + VkShaderModule GetCachedShader(const std::string& key) { + auto it = m_cache.find(key); + if (it != m_cache.end()) { + return it->second; + } + return VK_NULL_HANDLE; + } + // Add shader to cache + void CacheShader(const std::string& key, VkShaderModule module) { + m_cache[key] = module; + // Optionally persist to disk + SaveToDisk(key, module); + } +private: + std::unordered_map m_cache; + // Implementation details for disk persistence + void SaveToDisk(const std::string& key, VkShaderModule module); + VkShaderModule LoadFromDisk(const std::string& key); +}; +---- + +=== Version Management + +Managing shader versions is crucial for maintenance and updates: + +* *Versioning scheme*: Include version information in shader metadata +* *Compatibility checks*: Verify shader compatibility with application version +* *Incremental updates*: Support updating only changed shaders +* *Fallback mechanisms*: Provide fallback shaders for backward compatibility + +Example versioning approach: + +[source,cpp] +---- +struct ShaderMetadata { + uint32_t version; + uint32_t minAppVersion; + uint32_t featureFlags; + char name[64]; +}; +// Shader package header +struct ShaderPackageHeader { + uint32_t magic; // Magic number for validation + uint32_t version; // Package version + uint32_t shaderCount; // Number of shaders in package + uint32_t compatFlags; // Compatibility flags +}; +---- + +=== Platform-Specific Considerations + +Different platforms may require special handling: + +* *Desktop vs. mobile*: Optimize shader complexity based on target hardware +* *Vendor-specific optimizations*: Consider optimizations for specific GPU vendors +* *Memory constraints*: Be mindful of memory limitations on mobile devices +* *Power efficiency*: Optimize shaders for power efficiency on battery-powered devices + +=== Size Optimization Techniques + +Reducing shader size can be important for distribution: + +* *Dead code elimination*: Remove unused code paths +* *Constant folding*: Evaluate constant expressions at compile time +* *Function inlining*: Inline small functions to reduce call overhead +* *Variable packing*: Pack multiple variables into larger types +* *Compression*: Apply compression to shader binaries + +=== Distribution Security Considerations + +Protecting your shader intellectual property: + +* *Obfuscation*: Obfuscate shader source code before distribution +* *Encryption*: Encrypt shader binaries +* *Runtime decryption*: Decrypt shaders only when needed +* *Watermarking*: Include hidden watermarks in shaders + +== Conclusion + +HLSL provides a powerful alternative to GLSL for Vulkan shader development. By following the best practices and guidelines in this chapter, you can create efficient, maintainable, and portable shaders that leverage the strengths of HLSL while taking full advantage of Vulkan's abilities. + +The migration from GLSL to HLSL may require some effort, but the benefits in terms of code reuse, language features, and cross-API compatibility can be significant for many projects. + +== References and Further Reading + +* link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst[HLSL to SPIR-V Feature Mapping Manual] +* link:https://github.com/KhronosGroup/SPIRV-Guide[SPIR-V Guide] +* link:https://www.khronos.org/blog/hlsl-first-class-vulkan-shading-language[HLSL as a First Class Vulkan Shading Language] +* link:https://docs.vulkan.org/spec/latest/chapters/interfaces.html[Vulkan Interfaces with SPIR-V] +* link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl[Microsoft HLSL Documentation] diff --git a/chapters/shader_authoring_guide_slang_hlsl.adoc b/chapters/shader_authoring_guide_slang_hlsl.adoc deleted file mode 100644 index 7c384d5..0000000 --- a/chapters/shader_authoring_guide_slang_hlsl.adoc +++ /dev/null @@ -1,1614 +0,0 @@ -// Copyright 2025 Holochip, Inc. -// SPDX-License-Identifier: CC-BY-4.0 - -ifndef::chapters[:chapters:] -ifndef::images[:images: images/] - -[[shader-authoring-guide-slang-hlsl]] -= Shader Authoring Guide (Slang/HLSL) -:toc: - -This chapter provides a comprehensive guide for writing shaders in Slang and HLSL for Vulkan applications. It covers best practices, migration from GLSL, Vulkan-specific semantics, bindings, entry points, and compiling to SPIR-V. - -== Introduction to Slang and HLSL in Vulkan - -While Vulkan consumes shaders in xref:{chapters}what_is_spirv.adoc[SPIR-V] format, developers can author shaders in high-level languages like HLSL (High-Level Shading Language) and Slang. This chapter builds upon the xref:{chapters}hlsl.adoc[HLSL in Vulkan] chapter, focusing specifically on authoring shaders for Vulkan applications. - -=== What is Slang? - -link:https://github.com/shader-slang/slang[Slang] is a modern shading -language and compiler designed to extend HLSL with advanced features. It's -largely backward-compatible with HLSL 2018 but adds features like generics, -interfaces, and reflection capabilities. Slang can target multiple backends, including SPIR-V for Vulkan. - -Key features of Slang include: - -* Largely compatible with HLSL syntax -* Advanced language features like generics, interfaces, and modules -* Cross-compilation to multiple targets, including SPIR-V for Vulkan -* Powerful metaprogramming capabilities -* Built-in shader reflection - -=== Why Use HLSL or Slang for Vulkan? - -There are several advantages to using HLSL or Slang for Vulkan development: - -* *Cross-API compatibility*: Write shaders that can be used with both Vulkan and DirectX with minimal changes -* *Familiar syntax*: Developers with DirectX experience can leverage their existing knowledge -* *Advanced language features*: Particularly with Slang, access to modern programming constructs -* *Industry adoption*: HLSL is widely used in game engines and graphics applications -* *Tooling support*: Rich ecosystem of tools, debuggers, and IDE integrations - -== Differences Between HLSL and Slang - -While Slang is built on HLSL and maintains backward compatibility, it adds several powerful features that make shader development more efficient, maintainable, and flexible. - -=== Language Feature Differences - -==== Generics and Templates - -Slang adds full support for generics, similar to C# or Java, allowing for type-safe parameterized code: - -[source,slang] ----- -// Generic function in Slang -template -T min(T a, T b) { - return a < b ? a : b; -} - -// Usage -float result = min(1.0, 2.0); -int intResult = min(5, 3); ----- - -HLSL has limited template support, but Slang's generics are more robust and flexible than HLSL's templates. - -==== Interfaces and Polymorphism - -Slang introduces interfaces, enabling polymorphic behavior in shaders: - -[source,slang] ----- -// Define an interface -interface IMaterial { - float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal); -} - -// Implement the interface -struct LambertianMaterial : IMaterial { - float3 albedo; - - float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) { - return albedo / 3.14159; - } -} - -// Use polymorphically -void shadeSurface(IMaterial material, ...) { - // Use the material interface without knowing the concrete type -} ----- - -This feature is not available in standard HLSL. - -==== Modules and Namespaces - -Slang provides a module system for better code organization: - -[source,slang] ----- -// In file: lighting.slang -module Lighting; - -export float3 calculateDirectLighting(...) { ... } -export float3 calculateIndirectLighting(...) { ... } - -// In another file -import Lighting; - -float3 color = Lighting.calculateDirectLighting(...); ----- - -HLSL lacks a formal module system, relying instead on file includes. - -==== Advanced Metaprogramming - -Slang offers powerful compile-time metaprogramming capabilities: - -[source,slang] ----- -// Compile-time reflection -struct Material { - float4 baseColor; - float roughness; - float metallic; -}; - -// Get all fields of a type at compile time -__generic -void bindMaterial(ParameterBlock block, Material material) { - __for(field in getFields(T)) { - block.setField(field.name, getField(material, field.name)); - } -} ----- - -==== Resource Binding Model - -Slang introduces a more flexible resource binding model: - -[source,slang] ----- -// Parameter block concept -ParameterBlock lightingParams; - -// Accessing resources -Texture2D albedoMap = lightingParams.albedoMap; ----- - -This provides better organization and more flexible binding than HLSL's register-based approach. - -=== Syntax Differences - -While Slang largely maintains HLSL syntax compatibility, it introduces some -new syntax elements: - -* *Module declarations*: `module ModuleName;` -* *Import statements*: `import ModuleName;` -* *Interface declarations*: `interface IName { ... }` -* *Generic type parameters*: `` or `__generic` -* *Export keyword*: `export` to make symbols visible outside a module -* *Extended attributes*: Additional attributes for reflection and code generation - -=== Compilation Differences - -Slang provides its own compiler (`slangc`) with different capabilities than the HLSL compiler: - -* *Multi-target compilation*: Compile the same shader for multiple graphics APIs -* *Cross-compilation*: Generate code for different shader stages from a single source -* *Built-in reflection*: Generate reflection data during compilation -* *Shader linking*: Link multiple shader modules together -* *Diagnostic quality*: More detailed error messages and warnings - -Example of multi-target compilation: - -[source,bash] ----- -slangc -profile glsl_spirv -entry main -stage vertex shader.slang -o shader.vert.spv -slangc -profile dxbc -entry main -stage vertex shader.slang -o shader.vert.dxbc ----- - -=== Runtime Behavior Differences - -Slang introduces some runtime behavior differences: - -* *Dynamic dispatch*: Support for interface-based polymorphism -* *Resource binding*: More flexible resource binding model -* *Parameter blocks*: Hierarchical organization of shader parameters -* *Reflection API*: Runtime access to shader structure information - -== Migration Guide: Transitioning from HLSL to Slang - -Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to transitioning your shaders. - -=== Step 1: Setting Up the Slang Compiler - -1. Download and install the Slang compiler from the https://github.com/shader-slang/slang[official repository] -2. Update your build scripts to use `slangc` instead of `dxc` or other HLSL compilers -3. Test compilation of existing HLSL shaders without modifications - -Example build script update: - -[source,bash] ----- -# Before: Using DXC -dxc -spirv -T vs_6_0 -E main shader.hlsl -Fo shader.vert.spv - -# After: Using Slang -slangc -profile glsl_spirv -entry main -stage vertex shader.hlsl -o shader.vert.spv ----- - -=== Step 2: Rename Files and Add Module Declarations - -1. Rename your `.hlsl` files to `.slang` to indicate the language change -2. Add module declarations at the top of each file -3. Add export keywords to functions and types that need to be visible outside the module - -Example transformation: - -Before (shader.hlsl): -[source,hlsl] ----- -struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; -}; - -float4 transformPosition(float3 position) { - return mul(worldViewProj, float4(position, 1.0)); -} ----- - -After (shader.slang): -[source,slang] ----- -module Shaders.Transform; - -export struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; -}; - -export float4 transformPosition(float3 position) { - return mul(worldViewProj, float4(position, 1.0)); -} ----- - -=== Step 3: Leverage Imports Instead of Includes - -Replace `#include` directives with Slang's import system: - -Before (HLSL): -[source,hlsl] ----- -#include "common.hlsl" -#include "lighting.hlsl" - -float3 calculateLighting(...) { - // Use functions from included files -} ----- - -After (Slang): -[source,slang] ----- -module Shaders.Fragment; - -import Shaders.Common; -import Shaders.Lighting; - -export float3 calculateLighting(...) { - // Use functions from imported modules -} ----- - -=== Step 4: Refactor Resource Bindings - -Update resource bindings to use Slang's parameter block system: - -Before (HLSL): -[source,hlsl] ----- -Texture2D albedoMap : register(t0); -SamplerState samplerState : register(s0); -cbuffer MaterialParams : register(b0) { - float4 baseColor; - float roughness; - float metallic; -}; ----- - -After (Slang): -[source,slang] ----- -struct MaterialResources { - Texture2D albedoMap; - SamplerState samplerState; - struct Params { - float4 baseColor; - float roughness; - float metallic; - } constants; -}; - -ParameterBlock material; - -// Usage -float4 albedo = material.albedoMap.Sample(material.samplerState, uv); -float roughness = material.constants.roughness; ----- - -=== Step 5: Introduce Generics and Interfaces - -Identify opportunities to use generics and interfaces for more flexible code: - -Before (HLSL): -[source,hlsl] ----- -float3 evaluateLambert(float3 albedo, float3 normal, float3 lightDir) { - return albedo * max(0, dot(normal, lightDir)) / 3.14159; -} - -float3 evaluateGGX(float3 specColor, float roughness, float3 normal, float3 viewDir, float3 lightDir) { - // GGX implementation -} - -float3 evaluateMaterial(MaterialType type, ...) { - switch(type) { - case MATERIAL_LAMBERT: return evaluateLambert(...); - case MATERIAL_GGX: return evaluateGGX(...); - default: return float3(0,0,0); - } -} ----- - -After (Slang): -[source,slang] ----- -interface IBRDF { - float3 evaluate(float3 normal, float3 viewDir, float3 lightDir); -} - -struct LambertBRDF : IBRDF { - float3 albedo; - - float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { - return albedo * max(0, dot(normal, lightDir)) / 3.14159; - } -} - -struct GGXBRDF : IBRDF { - float3 specColor; - float roughness; - - float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { - // GGX implementation - } -} - -float3 evaluateMaterial(IBRDF brdf, float3 normal, float3 viewDir, float3 lightDir) { - return brdf.evaluate(normal, viewDir, lightDir); -} ----- - -=== Step 6: Implement Advanced Metaprogramming - -Use Slang's metaprogramming capabilities for more powerful shader generation: - -[source,slang] ----- -// Define shader permutations using compile-time parameters -[shader("vertex")] -[CompileTimeConstant(name="USE_NORMAL_MAPPING", type="bool")] -[CompileTimeConstant(name="LIGHT_COUNT", type="int")] -VSOutput vertexShader(VSInput input) { - VSOutput output; - // Base implementation - - #if USE_NORMAL_MAPPING - // Normal mapping specific code - #endif - - for (int i = 0; i < LIGHT_COUNT; i++) { - // Per-light calculations - } - - return output; -} ----- - -=== Common Migration Challenges - -==== Resource Binding Compatibility - -**Challenge**: Slang's resource binding model differs from HLSL's register-based approach. - -**Solution**: - -- Use Slang's `register` compatibility syntax during transition -- Gradually migrate to parameter blocks -- Update shader binding code in your application - -==== Module Organization - -**Challenge**: Deciding how to organize code into modules. - -**Solution**: - -- Group related functionality into modules -- Use hierarchical naming (e.g., `Rendering.Lighting`) -- Start with coarse-grained modules and refine as needed - -==== Interface Performance - -**Challenge**: Concerns about runtime performance of interfaces. - -**Solution**: - -- Interfaces are often resolved at compile-time -- Use interfaces for flexibility in high-level code -- Profile performance-critical paths - -==== Compilation Pipeline Integration - -**Challenge**: Integrating Slang into existing build systems. - -**Solution**: - -- Create wrapper scripts to maintain command-line compatibility -- Update build tools to support both HLSL and Slang during transition -- Consider using Slang's API for deeper integration - -=== Best Practices for Migration - -1. **Incremental Approach**: Migrate one shader or shader module at a time -2. **Maintain Compatibility**: Use Slang's HLSL compatibility features during transition -3. **Test Thoroughly**: Verify visual output after each migration step -4. **Refactor Gradually**: Start with simple syntax changes, then introduce advanced features -5. **Leverage Modules**: Use the module system to improve code organization -6. **Document Changes**: Keep track of migration decisions and patterns -7. **Performance Profiling**: Monitor performance before and after migration - -=== Example: Complete HLSL to Slang Migration - -Below is a complete example of migrating a simple shader from HLSL to Slang: - -HLSL Version (pbr.hlsl): -[source,hlsl] ----- -// PBR shader in HLSL -#include "common.hlsl" - -struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; - float2 texCoord : TEXCOORD0; -}; - -struct PSInput { - float4 position : SV_POSITION; - float3 worldPos : POSITION; - float3 normal : NORMAL; - float2 texCoord : TEXCOORD0; -}; - -Texture2D albedoMap : register(t0); -Texture2D normalMap : register(t1); -Texture2D metallicRoughnessMap : register(t2); -SamplerState textureSampler : register(s0); - -cbuffer MaterialParams : register(b0) { - float4 baseColor; - float metallic; - float roughness; - float2 padding; -}; - -cbuffer SceneParams : register(b1) { - float4x4 viewProj; - float4x4 world; - float3 cameraPosition; - float padding2; -}; - -PSInput VSMain(VSInput input) { - PSInput output; - float4 worldPos = mul(world, float4(input.position, 1.0)); - output.position = mul(viewProj, worldPos); - output.worldPos = worldPos.xyz; - output.normal = normalize(mul((float3x3)world, input.normal)); - output.texCoord = input.texCoord; - return output; -} - -float4 PSMain(PSInput input) : SV_TARGET { - float4 albedo = albedoMap.Sample(textureSampler, input.texCoord) * baseColor; - float2 metallicRoughness = metallicRoughnessMap.Sample(textureSampler, input.texCoord).rg; - float metalness = metallicRoughness.r * metallic; - float roughnessValue = metallicRoughness.g * roughness; - - float3 normal = normalize(input.normal); - float3 viewDir = normalize(cameraPosition - input.worldPos); - - // PBR calculation - float3 color = calculatePBRLighting(albedo.rgb, metalness, roughnessValue, normal, viewDir); - - return float4(color, albedo.a); -} - -float3 calculatePBRLighting(float3 albedo, float metalness, float roughness, float3 normal, float3 viewDir) { - // Simplified PBR calculation - float3 lightDir = normalize(float3(1, 1, 1)); - float3 halfVector = normalize(viewDir + lightDir); - - float NdotL = max(dot(normal, lightDir), 0.0); - float NdotV = max(dot(normal, viewDir), 0.0); - float NdotH = max(dot(normal, halfVector), 0.0); - float VdotH = max(dot(viewDir, halfVector), 0.0); - - float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo, metalness); - - // Simplified lighting equation - float3 diffuse = (1.0 - metalness) * albedo / 3.14159; - float3 specular = calculateSpecular(F0, roughness, NdotH, NdotV, NdotL, VdotH); - - return (diffuse + specular) * NdotL * float3(1, 1, 1); // Light color = white -} - -float3 calculateSpecular(float3 F0, float roughness, float NdotH, float NdotV, float NdotL, float VdotH) { - // Simplified specular calculation - float alpha = roughness * roughness; - float D = alpha * alpha / (3.14159 * pow(NdotH * NdotH * (alpha * alpha - 1.0) + 1.0, 2.0)); - float G = NdotV * NdotL; - float3 F = F0 + (float3(1, 1, 1) - F0) * pow(1.0 - VdotH, 5.0); - - return D * G * F / max(0.0001, 4.0 * NdotV * NdotL); -} ----- - -Slang Version (pbr.slang): -[source,slang] ----- -// PBR shader in Slang -module Rendering.PBR; - -import Rendering.Common; - -// Input/output structures -export struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; - float2 texCoord : TEXCOORD0; -}; - -export struct PSInput { - float4 position : SV_POSITION; - float3 worldPos : POSITION; - float3 normal : NORMAL; - float2 texCoord : TEXCOORD0; -}; - -// Resource definitions using parameter blocks -struct MaterialResources { - Texture2D albedoMap; - Texture2D normalMap; - Texture2D metallicRoughnessMap; - SamplerState textureSampler; - - struct Constants { - float4 baseColor; - float metallic; - float roughness; - float2 padding; - } params; -}; - -struct SceneResources { - struct Constants { - float4x4 viewProj; - float4x4 world; - float3 cameraPosition; - float padding; - } params; -}; - -// Parameter blocks -ParameterBlock material; -ParameterBlock scene; - -// BRDF interface for different lighting models -interface IBRDF { - float3 evaluate(float3 albedo, float3 normal, float3 viewDir, float3 lightDir); -} - -// PBR BRDF implementation -struct PBRBRDF : IBRDF { - float metalness; - float roughness; - - float3 evaluate(float3 albedo, float3 normal, float3 viewDir, float3 lightDir) { - float3 halfVector = normalize(viewDir + lightDir); - - float NdotL = max(dot(normal, lightDir), 0.0); - float NdotV = max(dot(normal, viewDir), 0.0); - float NdotH = max(dot(normal, halfVector), 0.0); - float VdotH = max(dot(viewDir, halfVector), 0.0); - - float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo, metalness); - - // Simplified lighting equation - float3 diffuse = (1.0 - metalness) * albedo / 3.14159; - float3 specular = calculateSpecular(F0, roughness, NdotH, NdotV, NdotL, VdotH); - - return (diffuse + specular) * NdotL; - } - - float3 calculateSpecular(float3 F0, float roughness, float NdotH, float NdotV, float NdotL, float VdotH) { - // Simplified specular calculation - float alpha = roughness * roughness; - float D = alpha * alpha / (3.14159 * pow(NdotH * NdotH * (alpha * alpha - 1.0) + 1.0, 2.0)); - float G = NdotV * NdotL; - float3 F = F0 + (float3(1, 1, 1) - F0) * pow(1.0 - VdotH, 5.0); - - return D * G * F / max(0.0001, 4.0 * NdotV * NdotL); - } -} - -// Generic lighting calculation function -template -float3 calculateLighting(B brdf, float3 albedo, float3 normal, float3 viewDir, float3 lightColor) { - float3 lightDir = normalize(float3(1, 1, 1)); - return brdf.evaluate(albedo, normal, viewDir, lightDir) * lightColor; -} - -// Shader entry points -[shader("vertex")] -export PSInput VSMain(VSInput input) { - PSInput output; - float4 worldPos = mul(scene.params.world, float4(input.position, 1.0)); - output.position = mul(scene.params.viewProj, worldPos); - output.worldPos = worldPos.xyz; - output.normal = normalize(mul((float3x3)scene.params.world, input.normal)); - output.texCoord = input.texCoord; - return output; -} - -[shader("pixel")] -export float4 PSMain(PSInput input) : SV_TARGET { - float4 albedo = material.albedoMap.Sample(material.textureSampler, input.texCoord) * material.params.baseColor; - float2 metallicRoughness = material.metallicRoughnessMap.Sample(material.textureSampler, input.texCoord).rg; - float metalness = metallicRoughness.r * material.params.metallic; - float roughnessValue = metallicRoughness.g * material.params.roughness; - - float3 normal = normalize(input.normal); - float3 viewDir = normalize(scene.params.cameraPosition - input.worldPos); - - // Create BRDF with material parameters - PBRBRDF brdf; - brdf.metalness = metalness; - brdf.roughness = roughnessValue; - - // Calculate lighting using the generic function - float3 color = calculateLighting(brdf, albedo.rgb, normal, viewDir, float3(1, 1, 1)); - - return float4(color, albedo.a); -} ----- - -The Slang version demonstrates several improvements: - -- Organized code with module system -- Parameter blocks for resource organization -- Interface-based polymorphism for BRDFs -- Generic lighting calculation function -- Explicit shader stage annotations -- Better separation of concerns - -These improvements make the code more maintainable, flexible, and reusable while preserving the core functionality of the original HLSL shader. - -== Best Practices for Writing Shaders in Slang/HLSL for Vulkan - -=== Code Organization - -* *Separate shader stages*: Keep different shader stages (vertex, fragment, compute, etc.) in separate files -* *Use structures for inputs and outputs*: Define clear structures for shader inputs and outputs -* *Consistent naming conventions*: Adopt a consistent naming scheme for variables, functions, and types -* *Modular design*: Break complex shaders into reusable functions and components - -Example of a well-organized HLSL shader: - -[source,hlsl] ----- -// Common structures and constants -struct VSInput { - [[vk::location(0)]] float3 Position : POSITION0; - [[vk::location(1)]] float3 Normal : NORMAL0; - [[vk::location(2)]] float2 TexCoord : TEXCOORD0; -}; - -struct VSOutput { - float4 Position : SV_POSITION; - [[vk::location(0)]] float3 WorldPos : POSITION0; - [[vk::location(1)]] float3 Normal : NORMAL0; - [[vk::location(2)]] float2 TexCoord : TEXCOORD0; -}; - -// Uniform buffer with transformation matrices -struct SceneUBO { - float4x4 model; - float4x4 view; - float4x4 projection; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer ubo; - -// Vertex shader main function -VSOutput main(VSInput input) { - VSOutput output = (VSOutput)0; - - // Transform position to clip space - float4 worldPos = mul(ubo.model, float4(input.Position, 1.0)); - output.Position = mul(ubo.projection, mul(ubo.view, worldPos)); - - // Pass through other attributes - output.WorldPos = worldPos.xyz; - output.Normal = mul((float3x3)ubo.model, input.Normal); - output.TexCoord = input.TexCoord; - - return output; -} ----- - -=== Performance Considerations - -* *Minimize divergent control flow*: Avoid complex branching within shader wavefronts -* *Optimize memory access patterns*: Group related data together to improve cache coherency -* *Reduce register pressure*: Limit the number of variables in high-register-usage sections -* *Use appropriate precision*: Use lower precision types (`half`, `min16float`) when full precision isn't needed -* *Leverage subgroup operations*: Use subgroup/wave intrinsics for efficient parallel operations -* *Prefer compile-time constants*: Use specialization constants for values known at pipeline creation time - -Example of using specialization constants: - -[source,hlsl] ----- -// Define specialization constants -[[vk::constant_id(0)]] const bool USE_NORMAL_MAPPING = true; -[[vk::constant_id(1)]] const int LIGHT_COUNT = 4; -[[vk::constant_id(2)]] const float SPECULAR_POWER = 32.0; - -// Use in conditional code -float3 CalculateNormal(float3 normal, float3 tangent, float2 texCoord) { - if (USE_NORMAL_MAPPING) { - // Complex normal mapping calculation - return CalculateNormalFromMap(normal, tangent, texCoord); - } else { - // Simple pass-through - return normalize(normal); - } -} ----- - -=== Debugging and Validation - -* *Add debug markers*: Use comments or debug variables to mark important sections -* *Validate inputs*: Check for NaN or invalid values in critical calculations -* *Use validation layers*: Enable Vulkan validation layers during development -* *Leverage shader debugging tools*: Use tools like RenderDoc or NVIDIA Nsight for shader debugging -* *Implement fallbacks*: Provide simpler code paths for debugging complex algorithms - -=== Vulkan-Specific Best Practices - -* *Explicit bindings*: Always specify explicit descriptor set and binding indices -* *Consistent descriptor layouts*: Maintain consistent descriptor layouts across shader stages -* *Minimize descriptor set changes*: Group resources to minimize descriptor set changes during rendering -* *Consider push constants*: Use push constants for frequently changing small data -* *Be mindful of SPIR-V limitations*: Some HLSL features may not translate directly to SPIR-V - -== Migration from GLSL to HLSL/Slang - -=== Conceptual Differences - -GLSL and HLSL have different programming paradigms: - -* *GLSL*: More procedural, similar to C -* *HLSL*: More object-oriented, similar to C++ - -Key conceptual differences include: - -* *Entry points*: GLSL uses `void main()`, HLSL uses typed functions with explicit inputs/outputs -* *Built-ins vs. semantics*: GLSL uses built-in variables, HLSL uses semantics -* *Resource binding*: Different syntax for binding resources -* *Matrix layout*: GLSL uses column-major by default, HLSL uses row-major by default - -=== Syntax Translation Guide - -==== Data Types - -[options="header"] -|==== -| GLSL | HLSL | Example -| vec_n_ | float_n_ | vec4 → float4 -| ivec_n_ | int_n_ | ivec3 → int3 -| uvec_n_ | uint_n_ | uvec2 → uint2 -| mat_nxm_ | float_nxm_ | mat4 → float4x4 -|==== - -==== Shader Inputs/Outputs - -GLSL: -[source,glsl] ----- -// Vertex shader inputs -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; - -// Vertex shader outputs -layout(location = 0) out vec3 outNormal; -layout(location = 1) out vec2 outUV; - -void main() { - gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPosition, 1.0); - outNormal = inNormal; - outUV = inUV; -} ----- - -HLSL: -[source,hlsl] ----- -// Input/output structures -struct VSInput { - [[vk::location(0)]] float3 Position : POSITION0; - [[vk::location(1)]] float3 Normal : NORMAL0; -}; - -struct VSOutput { - float4 Position : SV_POSITION; - [[vk::location(0)]] float3 Normal : NORMAL0; - [[vk::location(1)]] float2 UV : TEXCOORD0; -}; - -// Main function with explicit input/output -VSOutput main(VSInput input) { - VSOutput output = (VSOutput)0; - output.Position = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Position, 1.0)))); - output.Normal = input.Normal; - output.UV = input.UV; - return output; -} ----- - -==== Resource Binding - -GLSL: -[source,glsl] ----- -// Uniform buffer -layout(set = 0, binding = 0) uniform UBO { - mat4 model; - mat4 view; - mat4 projection; -} ubo; - -// Texture and sampler -layout(set = 0, binding = 1) uniform sampler2D texAlbedo; - -// Storage buffer -layout(set = 0, binding = 2) buffer SSBO { - vec4 data[]; -} ssbo; ----- - -HLSL: -[source,hlsl] ----- -// Uniform buffer -struct UBO { - float4x4 model; - float4x4 view; - float4x4 projection; -}; -[[vk::binding(0, 0)]] -ConstantBuffer ubo; - -// Texture and sampler -[[vk::binding(1, 0)]] -Texture2D texAlbedo; -[[vk::binding(1, 0)]] -SamplerState sampAlbedo; - -// Storage buffer -struct SSBO { - float4 data[]; -}; -[[vk::binding(2, 0)]] -RWStructuredBuffer ssbo; ----- - -==== Built-ins and Special Variables - -[options="header"] -|==== -| GLSL | HLSL | Description -| gl_Position | SV_Position | Vertex position output -| gl_FragCoord | SV_Position | Fragment position -| gl_VertexIndex | SV_VertexID | Vertex index -| gl_InstanceIndex | SV_InstanceID | Instance index -| gl_FragDepth | SV_Depth | Fragment depth output -| gl_FrontFacing | SV_IsFrontFace | Front-facing flag -| gl_PrimitiveID | SV_PrimitiveID | Primitive ID -|==== - -==== Common Functions - -[options="header"] -|==== -| GLSL | HLSL | Description -| mix(x, y, a) | lerp(x, y, a) | Linear interpolation -| fract(x) | frac(x) | Fractional part -| dFdx(p) | ddx(p) | Derivative in x direction -| dFdy(p) | ddy(p) | Derivative in y direction -| texture(sampler, coord) | sampler.Sample(texture, coord) | Texture sampling -|==== - -=== Common Migration Challenges - -* *Matrix multiplication*: GLSL uses `*` operator, HLSL uses `mul()` function -* *Texture sampling*: Different syntax for texture access -* *Swizzling*: Both support swizzling but with subtle differences -* *Preprocessor directives*: Different preprocessor capabilities -* *Extension handling*: GLSL requires explicit extension enabling, HLSL doesn't - -=== Migration Tools - -* *DXC*: The DirectX Shader Compiler can help validate HLSL code -* *SPIRV-Cross*: Can convert between GLSL and HLSL via SPIR-V -* *Automated translation tools*: Various tools can assist with bulk translation -* *IDE plugins*: Some editors have plugins to help with shader language conversion - -== Vulkan-Specific Semantics, Bindings, and Entry Points - -=== Descriptor Binding in HLSL/Slang - -HLSL offers two approaches for binding resources in Vulkan: - -* *HLSL register syntax*: -[source,hlsl] ----- -Texture2D albedoMap : register(t0, space1); -SamplerState samplerState : register(s0, space1); ----- - -* *Vulkan-specific attributes*: -[source,hlsl] ----- -[[vk::binding(0, 1)]] -Texture2D albedoMap; -[[vk::binding(0, 1)]] -SamplerState samplerState; ----- - -You can also combine both approaches for cross-API compatibility: -[source,hlsl] ----- -[[vk::binding(0, 1)]] -Texture2D albedoMap : register(t0, space1); ----- - -=== Resource Types and Register Spaces - -[options="header"] -|==== -| Resource Type | HLSL Type | Register Type | Vulkan Equivalent -| Uniform Buffer | ConstantBuffer | b | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER -| Storage Buffer | RWStructuredBuffer | u | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER -| Texture | Texture2D, Texture3D, etc. | t | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE -| Storage Image | RWTexture2D, etc. | u | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE -| Sampler | SamplerState | s | VK_DESCRIPTOR_TYPE_SAMPLER -| Combined Image Sampler | N/A (separate in HLSL) | N/A | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER -|==== - -=== Push Constants - -Push constants provide a way to quickly update small amounts of shader data without descriptor sets: - -[source,hlsl] ----- -// Define push constant structure -struct PushConstants { - float4x4 transform; - float4 color; - float time; -}; - -// Declare push constants block -[[vk::push_constant]] -PushConstants pushConstants; - -// Use in shader -float4 TransformedPosition = mul(pushConstants.transform, float4(position, 1.0)); ----- - -=== Specialization Constants - -Specialization constants allow you to specialize shaders at pipeline creation time: - -[source,hlsl] ----- -// Define specialization constants with IDs and default values -[[vk::constant_id(0)]] const bool USE_LIGHTING = true; -[[vk::constant_id(1)]] const int LIGHT_COUNT = 4; -[[vk::constant_id(2)]] const float AMBIENT_INTENSITY = 0.1; - -// Use in shader code -float3 CalculateLighting() { - float3 result = float3(AMBIENT_INTENSITY, AMBIENT_INTENSITY, AMBIENT_INTENSITY); - - if (USE_LIGHTING) { - for (int i = 0; i < LIGHT_COUNT; i++) { - // Add light contribution - result += CalculateLightContribution(i); - } - } - - return result; -} ----- - -=== Shader Entry Points - -In HLSL for Vulkan, you can specify custom entry point names: - -[source,hlsl] ----- -// Vertex shader entry point -VSOutput vs_main(VSInput input) { - // Vertex shader code -} - -// Fragment shader entry point -float4 ps_main(VSOutput input) : SV_TARGET { - // Fragment shader code -} ----- - -When compiling with DXC, specify the entry point: -[source,bash] ----- -dxc -spirv -T vs_6_0 -E vs_main shader.hlsl -Fo shader.vert.spv -dxc -spirv -T ps_6_0 -E ps_main shader.hlsl -Fo shader.frag.spv ----- - -=== Vulkan-Specific Features - -==== Subgroup Operations - -HLSL provides wave intrinsics that map to Vulkan subgroup operations: - -[source,hlsl] ----- -// Check if this is the first lane in the subgroup -if (WaveIsFirstLane()) { - // Do something only once per subgroup -} - -// Compute sum across all lanes in the subgroup -float total = WaveActiveSum(value); - -// Broadcast a value from a specific lane -float broadcastValue = WaveReadLaneAt(value, laneIndex); ----- - -==== Buffer Device Address - -For Vulkan's buffer device address feature: - -[source,hlsl] ----- -// In application code, pass buffer address via push constants -struct PushConstants { - uint64_t bufferAddress; -}; - -// In shader -[[vk::push_constant]] -PushConstants pushConstants; - -// Load data from the buffer address -float4 data = vk::RawBufferLoad(pushConstants.bufferAddress); ----- - -==== Ray Tracing - -For ray tracing shaders, use the `[shader("type")]` attribute: - -[source,hlsl] ----- -// Ray generation shader -[shader("raygeneration")] -void RayGen() { - // Ray generation code -} - -// Closest hit shader -[shader("closesthit")] -void ClosestHit(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attribs) { - // Closest hit code -} ----- - -== Compiling to SPIR-V - -=== Using DirectX Shader Compiler (DXC) - -The DirectX Shader Compiler (DXC) is the recommended tool for compiling HLSL and Slang to SPIR-V for Vulkan. - -==== Command-Line Compilation - -Basic command-line usage: - -[source,bash] ----- -# Compile vertex shader -dxc -spirv -T vs_6_0 -E main shader.hlsl -Fo shader.vert.spv - -# Compile fragment shader -dxc -spirv -T ps_6_0 -E main shader.hlsl -Fo shader.frag.spv - -# Compile compute shader -dxc -spirv -T cs_6_0 -E main shader.hlsl -Fo shader.comp.spv ----- - -Important command-line options: - -* `-spirv`: Generate SPIR-V code -* `-T `: Specify shader profile (e.g., vs_6_0, ps_6_0) -* `-E `: Specify entry point name -* `-Fo `: Specify output file -* `-fvk-use-dx-layout`: Use DirectX memory layout rules -* `-fspv-extension=`: Enable specific SPIR-V extension -* `-fspv-reflect`: Generate reflection information - -==== Shader Profiles - -[options="header"] -|==== -| Vulkan Shader Stage | DXC Profile | Minimum Shader Model -| Vertex | vs | 6.0 -| Fragment | ps | 6.0 -| Compute | cs | 6.0 -| Geometry | gs | 6.0 -| Tessellation Control | hs | 6.0 -| Tessellation Evaluation | ds | 6.0 -| Task | as | 6.5 -| Mesh | ms | 6.5 -| Ray Generation | lib | 6.3 -| Any Hit | lib | 6.3 -| Closest Hit | lib | 6.3 -| Miss | lib | 6.3 -| Intersection | lib | 6.3 -| Callable | lib | 6.3 -|==== - -==== Runtime Compilation - -For runtime compilation, use the DXC API: - -[source,cpp] ----- -#include "dxc/dxcapi.h" - -// Initialize DXC -CComPtr library; -CComPtr compiler; -CComPtr utils; -DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&library)); -DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler)); -DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils)); - -// Load shader source -CComPtr sourceBlob; -utils->LoadFile(L"shader.hlsl", nullptr, &sourceBlob); - -// Compile arguments -std::vector arguments = { - L"-spirv", // Generate SPIR-V - L"-T", L"ps_6_0", // Shader profile - L"-E", L"main", // Entry point - // Additional options as needed -}; - -// Compile shader -DxcBuffer buffer = {}; -buffer.Ptr = sourceBlob->GetBufferPointer(); -buffer.Size = sourceBlob->GetBufferSize(); -buffer.Encoding = DXC_CP_ACP; - -CComPtr result; -compiler->Compile(&buffer, arguments.data(), arguments.size(), nullptr, IID_PPV_ARGS(&result)); - -// Check for errors -HRESULT status; -result->GetStatus(&status); -if (FAILED(status)) { - CComPtr errors; - result->GetErrorBuffer(&errors); - // Handle error -} - -// Get compiled SPIR-V -CComPtr spirvBlob; -result->GetResult(&spirvBlob); - -// Create Vulkan shader module -VkShaderModuleCreateInfo createInfo = {}; -createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; -createInfo.codeSize = spirvBlob->GetBufferSize(); -createInfo.pCode = reinterpret_cast(spirvBlob->GetBufferPointer()); - -VkShaderModule shaderModule; -vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); ----- - -=== Using Slang Compiler - -For Slang shaders, use the Slang compiler with SPIR-V output: - -[source,bash] ----- -slangc -profile glsl_spirv -entry main -stage vertex shader.slang -o shader.vert.spv ----- - -=== Debugging and Validation - -==== SPIR-V Tools - -The SPIR-V Tools suite provides utilities for working with SPIR-V: - -* *spirv-val*: Validates SPIR-V binaries -* *spirv-dis*: Disassembles SPIR-V to human-readable form -* *spirv-opt*: Optimizes SPIR-V binaries -* *spirv-cfg*: Generates control flow graphs - -Example usage: -[source,bash] ----- -# Validate SPIR-V binary -spirv-val shader.spv - -# Disassemble SPIR-V for inspection -spirv-dis shader.spv -o shader.spvasm - -# Optimize SPIR-V binary -spirv-opt -O shader.spv -o shader.opt.spv ----- - -==== Common Compilation Issues - -* *Unsupported HLSL features*: Some HLSL features may not be supported in SPIR-V -* *Resource binding conflicts*: Ensure descriptor bindings don't conflict -* *Entry point mismatches*: Verify entry point names match between compilation and application -* *Shader model compatibility*: Ensure shader model is compatible with required features -* *Extension requirements*: Some features require specific SPIR-V extensions - -== Advanced Topics - -=== Cross-Compilation and Portability - -For maximum portability between Vulkan and DirectX: - -* Use conditional compilation with `#ifdef __spirv__` -* Maintain separate binding declarations for each API -* Use abstraction layers for API-specific features -* Consider shader generation tools for complex cases - -Example of a cross-API shader: -[source,hlsl] ----- -// Resource bindings for both APIs -#ifdef __spirv__ -[[vk::binding(0, 0)]] -#endif -ConstantBuffer uniforms : register(b0); - -#ifdef __spirv__ -[[vk::binding(1, 0)]] -#endif -Texture2D albedoTexture : register(t0); - -// API-specific code paths -float4 SampleTexture(Texture2D tex, SamplerState samp, float2 uv) { -#ifdef __spirv__ - // Vulkan-specific sampling code if needed - return tex.Sample(samp, uv); -#else - // DirectX-specific sampling code if needed - return tex.Sample(samp, uv); -#endif -} ----- - -=== Shader Interoperability - -For interoperability between GLSL and HLSL/Slang in the same application: - -* Maintain consistent descriptor set layouts -* Use explicit locations for all shader inputs/outputs -* Be mindful of matrix layout differences -* Consider using a shader generation system - -=== Performance Optimization - -Advanced optimization techniques: - -* *Shader permutations*: Generate specialized shader variants for different feature combinations -* *Workgroup size tuning*: Optimize compute shader workgroup sizes for specific hardware -* *Memory layout optimization*: Align data structures to hardware requirements -* *Instruction scheduling*: Organize instructions to maximize parallelism -* *Register pressure management*: Minimize register usage in critical sections - -=== Debugging Techniques - -Advanced debugging approaches: - -* *Debug prints*: Some implementations support debug printf in shaders -* *Debug buffers*: Write debug values to storage buffers for inspection -* *Shader instrumentation*: Add code to validate intermediate results -* *GPU debugging tools*: Use RenderDoc, NVIDIA Nsight, or AMD Radeon GPU Profiler - -== Distribution Considerations - -When deploying applications that use HLSL or Slang shaders with Vulkan, several distribution-related factors need to be considered to ensure optimal performance, compatibility, and user experience across different platforms and devices. - -=== Precompilation vs. Runtime Compilation - -Both approaches have advantages and trade-offs: - -==== Precompilation - -* *Advantages*: -** Faster application startup time -** No runtime dependency on shader compilers -** Validation errors caught during build rather than at runtime -** Opportunity for offline optimization - -* *Disadvantages*: -** Increased package size when supporting multiple hardware targets -** Less flexibility for runtime adaptation -** Need to manage multiple precompiled variants - -Example pipeline for precompilation: -[source,bash] ----- -# Build script example -for shader in shaders/*.hlsl; do - # Extract base name - base=$(basename $shader .hlsl) - - # Determine shader type from filename suffix - if [[ $base == *_vs ]]; then - profile="vs_6_0" - output="${base}.vert.spv" - elif [[ $base == *_ps ]]; then - profile="ps_6_0" - output="${base}.frag.spv" - elif [[ $base == *_cs ]]; then - profile="cs_6_0" - output="${base}.comp.spv" - fi - - # Compile with optimization - dxc -spirv -T $profile -E main $shader -O3 -Fo build/shaders/$output - - # Optionally validate - spirv-val build/shaders/$output -done ----- - -==== Runtime Compilation - -* *Advantages*: -** Ability to adapt to specific hardware capabilities at runtime -** Smaller distribution size (ship source instead of binaries) -** Easier to update and patch shaders -** Can generate specialized variants based on runtime conditions - -* *Disadvantages*: -** Increased startup time or loading times -** Runtime dependency on shader compiler -** Potential for runtime shader compilation errors -** Additional memory usage during compilation - -Example runtime compilation integration: -[source,cpp] ----- -// Shader manager class that handles runtime compilation -class ShaderManager { -public: - // Initialize DXC compiler once - ShaderManager() { - DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&m_library)); - DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&m_compiler)); - DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&m_utils)); - } - - // Compile shader at runtime - VkShaderModule CompileShader(const std::string& source, - const std::string& entryPoint, - const std::string& profile) { - // Compilation logic here - // ... - - // Return shader module - return shaderModule; - } - -private: - CComPtr m_library; - CComPtr m_compiler; - CComPtr m_utils; -}; ----- - -=== Hybrid Approaches - -Many applications use a hybrid approach: - -* Precompile common shaders for supported platforms -* Include fallback runtime compilation for edge cases -* Use shader caching to avoid recompilation - -=== Binary Distribution Formats - -When distributing precompiled SPIR-V shaders: - -* *Raw SPIR-V binaries*: Direct output from DXC or Slang compiler -* *Compressed SPIR-V*: Apply compression to reduce distribution size -* *Custom container formats*: Package multiple shader variants with metadata -* *Embedded in application*: Include SPIR-V as binary arrays in application code - -Example of embedding SPIR-V in C++ code: -[source,cpp] ----- -// Generated header with embedded shader data -#include "compiled_shaders.h" - -// Create shader module from embedded data -VkShaderModuleCreateInfo createInfo = {}; -createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; -createInfo.codeSize = sizeof(g_VertexShader); -createInfo.pCode = reinterpret_cast(g_VertexShader); - -VkShaderModule shaderModule; -vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); ----- - -=== Shader Caching Strategies - -Implementing an effective shader cache can significantly improve performance: - -* *Disk-based caching*: Store compiled shaders on disk between application runs -* *Memory caching*: Keep frequently used shaders in memory -* *Pipeline caching*: Use Vulkan's `VkPipelineCache` to store compiled pipelines -* *Warm-up phase*: Precompile critical shaders during loading screens - -Example of implementing a simple shader cache: -[source,cpp] ----- -class ShaderCache { -public: - // Try to load cached shader - VkShaderModule GetCachedShader(const std::string& key) { - auto it = m_cache.find(key); - if (it != m_cache.end()) { - return it->second; - } - return VK_NULL_HANDLE; - } - - // Add shader to cache - void CacheShader(const std::string& key, VkShaderModule module) { - m_cache[key] = module; - - // Optionally persist to disk - SaveToDisk(key, module); - } - -private: - std::unordered_map m_cache; - - // Implementation details for disk persistence - void SaveToDisk(const std::string& key, VkShaderModule module); - VkShaderModule LoadFromDisk(const std::string& key); -}; ----- - -=== Version Management - -Managing shader versions is crucial for maintenance and updates: - -* *Versioning scheme*: Include version information in shader metadata -* *Compatibility checks*: Verify shader compatibility with application version -* *Incremental updates*: Support updating only changed shaders -* *Fallback mechanisms*: Provide fallback shaders for backward compatibility - -Example versioning approach: -[source,cpp] ----- -struct ShaderMetadata { - uint32_t version; - uint32_t minAppVersion; - uint32_t featureFlags; - char name[64]; -}; - -// Shader package header -struct ShaderPackageHeader { - uint32_t magic; // Magic number for validation - uint32_t version; // Package version - uint32_t shaderCount; // Number of shaders in package - uint32_t compatFlags; // Compatibility flags -}; ----- - -=== Platform-Specific Considerations - -Different platforms may require special handling: - -* *Desktop vs. mobile*: Optimize shader complexity based on target hardware -* *Vendor-specific optimizations*: Consider optimizations for specific GPU vendors -* *Feature detection*: Adapt to available hardware features -* *Memory constraints*: Be mindful of memory limitations, especially on mobile - -Example of platform-specific shader selection: -[source,cpp] ----- -VkShaderModule GetAppropriateShader(const DeviceCapabilities& caps) { - if (caps.isLowPowerDevice) { - return m_lowPowerShaderVariant; - } else if (caps.vendorID == VENDOR_AMD) { - return m_amdOptimizedVariant; - } else if (caps.vendorID == VENDOR_NVIDIA) { - return m_nvidiaOptimizedVariant; - } else { - return m_defaultShaderVariant; - } -} ----- - -=== Size Optimization Techniques - -Reducing shader size is important, especially for mobile applications: - -* *SPIR-V optimization*: Use `spirv-opt` to optimize SPIR-V binaries -* *Strip debug information*: Remove debug information for release builds -* *On-demand loading*: Load shaders only when needed -* *Feature culling*: Remove unused features based on target platform -* *Compression*: Apply compression to shader binaries - -Example optimization pipeline: -[source,bash] ----- -# Optimize SPIR-V binary -spirv-opt -O --strip-debug shader.spv -o shader.opt.spv - -# Check size reduction -echo "Original size: $(stat -c%s shader.spv) bytes" -echo "Optimized size: $(stat -c%s shader.opt.spv) bytes" - -# Optionally compress -zstd -19 shader.opt.spv -o shader.opt.spv.zst -echo "Compressed size: $(stat -c%s shader.opt.spv.zst) bytes" ----- - -=== Distribution Security Considerations - -It's important to understand the fundamental limitations of shader intellectual property protection: - -* *Limited protection window*: Shader protection is effective only until the shader is loaded onto the GPU -* *Extraction vulnerability*: Once loaded, shaders can be extracted using GPU debuggers (like RenderDoc) or by recording Vulkan API calls -* *SPIR-V disassembly*: SPIR-V binaries can be disassembled to reveal shader logic -* *Inherent transparency*: The GPU execution model requires shaders to be in a readable format for the hardware - -Common protection approaches and their limitations: - -* *Obfuscation*: Can make shaders harder to understand but doesn't prevent extraction -* *Encryption*: Only protects shaders during distribution; they must be decrypted before GPU submission -* *Signature verification*: Helps ensure integrity but doesn't prevent reverse engineering -* *Anti-tampering measures*: Can detect modifications but not prevent shader analysis - -Alternative protection strategies: - -* *Legal protection*: Rely on licenses, patents, and legal agreements rather than technical measures -* *CPU-side algorithms*: Keep truly sensitive algorithms on the CPU where they're harder to extract -* *Split processing*: Divide algorithms between CPU and GPU to hide the complete picture -* *Regular updates*: Frequently update shaders to reduce the value of reverse engineering -* *Focus on unique data*: Often the data (textures, models, etc.) is more valuable than shader code - -== Conclusion - -HLSL and Slang provide powerful alternatives to GLSL for Vulkan shader development. By following the best practices and guidelines in this chapter, you can create efficient, maintainable, and portable shaders that leverage the strengths of these languages while taking full advantage of Vulkan's abilities. - -The migration from GLSL to HLSL/Slang may require some effort, but the benefits in terms of code reuse, language features, and cross-API compatibility can be significant for many projects. - -== References and Further Reading - -* link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst[HLSL to SPIR-V Feature Mapping Manual] -* link:https://github.com/shader-slang/slang[Slang Shader Language] -* link:https://github.com/KhronosGroup/SPIRV-Guide[SPIR-V Guide] -* link:https://www.khronos.org/blog/hlsl-first-class-vulkan-shading-language[HLSL as a First Class Vulkan Shading Language] -* link:https://docs.vulkan.org/spec/latest/chapters/interfaces.html[Vulkan Interfaces with SPIR-V] -* link:https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl[Microsoft HLSL Documentation] diff --git a/chapters/slang.adoc b/chapters/slang.adoc new file mode 100644 index 0000000..b3e70ed --- /dev/null +++ b/chapters/slang.adoc @@ -0,0 +1,737 @@ +// Copyright 2021-2024 The Khronos Group, Inc. +// Copyright 2025 Holochip, Inc. +// SPDX-License-Identifier: CC-BY-4.0 + +ifndef::chapters[:chapters:] +ifndef::images[:images: images/] + +[[slang-in-vulkan]] += Slang in Vulkan +:toc: + +Vulkan does not directly consume shaders in a human-readable text format, but instead uses xref:{chapters}what_is_spirv.adoc[SPIR-V] as an intermediate representation. This opens the option to use shader languages other than e.g. GLSL, as long as they can target the Vulkan SPIR-V environment. + +link:https://github.com/shader-slang/slang[Slang] is a modern shading language and compiler designed to extend xref:{chapters}hlsl.adoc[HLSL] with advanced features. It's largely backward-compatible with HLSL 2018 but adds features like generics, interfaces, and reflection capabilities. Slang can target multiple backends, including SPIR-V for Vulkan. + +With link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#unsupported-hlsl-features[a few exceptions], all Vulkan features and shader stages available with GLSL can be used with Slang too, including recent Vulkan additions like hardware accelerated ray tracing. On the other hand, Slang to SPIR-V supports Vulkan exclusive features that are not (yet) available in DirectX. + +image::{images}what_is_spirv_dxc.png[what_is_spriv_dxc.png] + +[[educational-resources]] +== Educational resources + +For Slang, the official link:https://github.com/shader-slang/slang[GitHub repository] and link:https://docs.shader-slang.org/en/latest/[documentation] are the best resources to get started. + +== Why Use Slang for Vulkan? + +There are several advantages to using Slang for Vulkan development: + +* *Cross-API compatibility*: Write shaders that can be used with both Vulkan and DirectX with minimal changes +* *Familiar syntax*: Developers with HLSL experience can leverage their existing knowledge +* *Advanced language features*: Access to modern programming constructs like generics and interfaces +* *Industry adoption*: Growing adoption in game engines and graphics applications +* *Tooling support*: Rich ecosystem of tools and IDE integrations + +== Differences Between HLSL and Slang + +While Slang is built on HLSL and maintains backward compatibility, it adds several powerful features that make shader development more efficient, maintainable, and flexible. + +=== Generics and Templates + +Slang adds full support for generics, similar to C# or Java, allowing for type-safe parameterized code: + +[source,slang] +---- +// Generic function in Slang +template +T min(T a, T b) { + return a < b ? a : b; +} + +// Usage +float result = min(1.0, 2.0); +int intResult = min(5, 3); +---- + +HLSL has limited template support, but Slang's generics are more robust and flexible than HLSL's templates. + +=== Interfaces and Polymorphism + +Slang introduces interfaces, enabling polymorphic behavior in shaders: + +[source,slang] +---- +// Define an interface +interface IMaterial { + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal); +} + +// Implement the interface +struct LambertianMaterial : IMaterial { + float3 albedo; + + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) { + return albedo / 3.14159; + } +} + +// Use polymorphically +void shadeSurface(IMaterial material, ...) { + // Use the material interface without knowing the concrete type +} +---- + +This feature is not available in standard HLSL. + +=== Modules and Namespaces + +Slang provides a module system for better code organization: + +[source,slang] +---- +// In file: lighting.slang +module Lighting; + +// Export functions to be used by other modules +export float3 calculateDirectLighting(...) { ... } + +// In file: main.slang +import Lighting; + +// Use imported functions +float3 directLight = Lighting::calculateDirectLighting(...); +---- + +Standard HLSL lacks this module system, making it harder to organize large shader codebases. + +=== Advanced Metaprogramming + +Slang offers powerful compile-time metaprogramming capabilities: + +[source,slang] +---- +// Compile-time reflection +struct Material { + float4 baseColor; + float roughness; + float metallic; +}; +// Get all fields of a type at compile time +__generic +void bindMaterial(ParameterBlock block, Material material) { + __for(field in getFields(T)) { + block.setField(field.name, getField(material, field.name)); + } +} +---- + +=== Resource Binding Model + +Slang introduces a more flexible resource binding model: + +[source,slang] +---- +// Parameter block concept +ParameterBlock lightingParams; +// Accessing resources +Texture2D albedoMap = lightingParams.albedoMap; +---- + +This provides better organization and more flexible binding than HLSL's register-based approach. + +=== Syntax Differences + +While Slang maintains HLSL syntax compatibility, it introduces some new syntax elements: + +* *Module declarations*: `module ModuleName;` +* *Import statements*: `import ModuleName;` + +=== Compilation Differences + +Slang provides its own compiler (`slangc`) with different capabilities than the HLSL compiler: + +* *Multi-target compilation*: Compile the same shader for multiple graphics APIs +* *Cross-compilation*: Generate code for different shader stages from a single source +* *Built-in reflection*: Generate reflection data during compilation +* *Shader linking*: Link multiple shader modules together +* *Diagnostic quality*: More detailed error messages and warnings + +Example of multi-target compilation: + +[source,bash] +---- +slangc -profile glsl_spirv -entry main -stage vertex shader.slang -o shader.vert.spv +slangc -profile dxbc -entry main -stage vertex shader.slang -o shader.vert.dxbc +---- + +=== Runtime Behavior Differences + +Slang introduces some runtime behavior differences: + +* *Interface dispatch*: Runtime polymorphism through interfaces +* *Generic specialization*: Automatic specialization of generic code +* *Reflection capabilities*: Runtime access to shader structure + +[[applications-pov]] +== From the application's point-of-view + +From the application's point-of-view, using Slang is exactly the same as using GLSL or HLSL. As the application always consumes shaders in the SPIR-V format, the only difference is in the tooling to generate the SPIR-V shaders from the desired shading language. + +== Migration Guide: from HLSL to Slang + +Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to migrating your shaders. + +[TIP] +==== +For a comprehensive guide on migrating from HLSL to Slang, see the official link:https://docs.shader-slang.org/en/latest/coming-from-hlsl.html[Coming from HLSL] documentation. +==== + +=== Step 1: Setting Up the Slang Compiler + +1. Download and install the Slang compiler from the https://github.com/shader-slang/slang[official repository] +2. Update your build scripts to use `slangc` instead of `dxc` or other HLSL compilers +3. Test compilation of existing HLSL shaders without modifications + +Example build script update: + +[source,bash] +---- +# Before: Using DXC +dxc -spirv -T vs_6_0 -E main shader.hlsl -Fo shader.vert.spv +# After: Using Slang +slangc -profile glsl_spirv -entry main -stage vertex shader.hlsl -o shader.vert.spv +---- + +=== Step 2: Rename Files and Add Module Declarations + +1. Rename your `.hlsl` files to `.slang` to indicate the language change +2. Add module declarations at the top of each file +3. Add export keywords to functions and types that need to be visible outside the module + +Example transformation: + +Before (shader.hlsl): +[source,hlsl] +---- +struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; +}; +float4 transformPosition(float3 position) { + return mul(worldViewProj, float4(position, 1.0)); +} +---- + +After (shader.slang): +[source,slang] +---- +module Shaders.Transform; +export struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; +}; +export float4 transformPosition(float3 position) { + return mul(worldViewProj, float4(position, 1.0)); +} +---- + +=== Step 3: Leverage Imports Instead of Includes + +Replace `#include` directives with Slang's import system: + +Before (HLSL): +[source,hlsl] +---- +#include "common.hlsl" +#include "lighting.hlsl" +float3 calculateLighting(...) { + // Use functions from included files +} +---- + +After (Slang): +[source,slang] +---- +module Shaders.Fragment; +import Shaders.Common; +import Shaders.Lighting; +export float3 calculateLighting(...) { + // Use functions from imported modules +} +---- + +=== Step 4: Refactor Resource Bindings + +Update resource bindings to use Slang's parameter block system: + +Before (HLSL): +[source,hlsl] +---- +Texture2D albedoMap : register(t0); +SamplerState samplerState : register(s0); +cbuffer MaterialParams : register(b0) { + float4 baseColor; + float roughness; + float metallic; +}; +---- + +After (Slang): +[source,slang] +---- +struct MaterialResources { + Texture2D albedoMap; + SamplerState samplerState; + struct Params { + float4 baseColor; + float roughness; + float metallic; + } constants; +}; +ParameterBlock material; +// Usage +float4 albedo = material.albedoMap.Sample(material.samplerState, uv); +float roughness = material.constants.roughness; +---- + +=== Step 5: Introduce Generics and Interfaces + +Identify opportunities to use generics and interfaces for more flexible code: + +Before (HLSL): +[source,hlsl] +---- +float3 evaluateLambert(float3 albedo, float3 normal, float3 lightDir) { + return albedo * max(0, dot(normal, lightDir)) / 3.14159; +} +float3 evaluateGGX(float3 specColor, float roughness, float3 normal, float3 viewDir, float3 lightDir) { + // GGX implementation +} +float3 evaluateMaterial(MaterialType type, ...) { + switch(type) { + case MATERIAL_LAMBERT: return evaluateLambert(...); + case MATERIAL_GGX: return evaluateGGX(...); + default: return float3(0,0,0); + } +} +---- + +After (Slang): +[source,slang] +---- +interface IBRDF { + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir); +} +struct LambertBRDF : IBRDF { + float3 albedo; + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { + return albedo * max(0, dot(normal, lightDir)) / 3.14159; + } +} +struct GGXBRDF : IBRDF { + float3 specColor; + float roughness; + float3 evaluate(float3 normal, float3 viewDir, float3 lightDir) { + // GGX implementation + } +} +float3 evaluateMaterial(IBRDF brdf, float3 normal, float3 viewDir, float3 lightDir) { + return brdf.evaluate(normal, viewDir, lightDir); +} +---- + +=== Step 6: Implement Advanced Metaprogramming + +Use Slang's metaprogramming capabilities for more powerful shader generation: + +[source,slang] +---- +// Define shader permutations using compile-time parameters +[shader("vertex")] +[CompileTimeConstant(name="USE_NORMAL_MAPPING", type="bool")] +[CompileTimeConstant(name="LIGHT_COUNT", type="int")] +VSOutput vertexShader(VSInput input) { + VSOutput output; + // Base implementation + #if USE_NORMAL_MAPPING + // Normal mapping specific code + #endif + for (int i = 0; i < LIGHT_COUNT; i++) { + // Per-light calculations + } + return output; +} +---- + +=== Common Migration Challenges + +==== Resource Binding Compatibility + +**Challenge**: Slang's resource binding model differs from HLSL's register-based approach. + +**Solution**: + +- Use Slang's `register` compatibility syntax during transition +- Gradually migrate to parameter blocks +- Update shader binding code in your application + +==== Module Organization + +**Challenge**: Deciding how to organize code into modules. + +**Solution**: +- Group related functionality into modules +- Use hierarchical naming (e.g., `Rendering.Lighting`) +- Start with coarse-grained modules and refine as needed + +==== Interface Performance + +**Challenge**: Concerns about runtime performance of interfaces. + +**Solution**: +- Interfaces are often resolved at compile-time +- Use interfaces for flexibility in high-level code +- Profile performance-critical paths + +==== Compilation Pipeline Integration + +**Challenge**: Integrating Slang into existing build systems. + +**Solution**: +- Create wrapper scripts to maintain command-line compatibility +- Update build tools to support both HLSL and Slang during transition +- Consider using Slang's API for deeper integration + +=== Best Practices for Migration + +1. **Incremental Approach**: Migrate one shader or shader module at a time +2. **Maintain Compatibility**: Use Slang's HLSL compatibility features during transition +3. **Test Thoroughly**: Verify visual output after each migration step +4. **Refactor Gradually**: Start with simple syntax changes, then introduce advanced features +5. **Leverage Modules**: Use the module system to improve code organization +6. **Document Changes**: Keep track of migration decisions and patterns +7. **Performance Profiling**: Monitor performance before and after migration + +== Migration Guide: from GLSL to Slang + +Migrating from GLSL to Slang involves more significant changes than migrating from HLSL, as the languages have different syntax and programming models. However, Slang's powerful features can make the transition worthwhile for many projects. + +[TIP] +==== +For a comprehensive guide on migrating from GLSL to Slang, see the official link:https://docs.shader-slang.org/en/latest/coming-from-glsl.html[Coming from GLSL] documentation. +==== + +=== Key Differences to Consider + +* *Syntax*: GLSL uses a more procedural style similar to C, while Slang uses an object-oriented style similar to C++ +* *Entry Points*: GLSL uses `void main()`, Slang uses typed functions with explicit inputs/outputs +* *Resource Binding*: Different approaches to binding resources to shaders +* *Matrix Layout*: GLSL uses column-major by default, Slang uses row-major by default +* *Built-ins vs. Semantics*: GLSL uses built-in variables, Slang uses semantics + +=== Migration Strategy + +1. **Start with a Small Shader**: Begin with a simple shader to understand the migration process +2. **Understand Semantic Mapping**: Map GLSL built-ins to Slang semantics +3. **Restructure Resource Bindings**: Convert GLSL uniform blocks to Slang parameter blocks +4. **Refactor Entry Points**: Convert GLSL's `main()` function to Slang's typed entry points +5. **Leverage Slang Features**: Gradually introduce Slang-specific features like interfaces and generics + +== Slang Compiler + +The link:https://github.com/shader-slang/slang[Slang compiler] is used for compiling Slang shaders. The Slang compiler can also compile standard HLSL code, making it a versatile tool for shader development. + +=== Where to get + +The Slang compiler can be downloaded from the link:https://github.com/shader-slang/slang/releases[official Slang repository]. Pre-built binaries are available for Windows, Linux, and macOS. + +=== Offline compilation using the stand-alone compiler + +Compiling a Slang shader offline is straightforward: + +[source] +---- +slangc -profile glsl_spirv -entry main -o output.spv input.slang +---- + +For HLSL compatibility mode: + +[source] +---- +slangc -profile glsl_spirv -entry main -o output.spv -language hlsl input.hlsl +---- + +Key command-line options include: + +* `-profile`: Specifies the target profile (use `glsl_spirv` for Vulkan) +* `-entry`: Specifies the entry point function +* `-o`: Specifies the output file +* `-language`: Specifies the input language (default is Slang) +* `-target`: Specifies the target platform (e.g., `vulkan`) + +=== Runtime compilation using the library + +Slang can also be integrated into a Vulkan application for runtime compilation using the Slang API: + +[source, cpp] +---- +#include "slang.h" + +// Initialize Slang session +slang::IGlobalSession* slangSession = nullptr; +slang::createGlobalSession(&slangSession); + +// Create a compilation session +slang::SessionDesc sessionDesc = {}; +sessionDesc.targetCount = 1; +sessionDesc.targets = ⌖ + +slang::ISession* session = nullptr; +slangSession->createSession(sessionDesc, &session); + +// Load and compile shader code +slang::IBlob* diagnosticsBlob = nullptr; +slang::IComponentType* program = nullptr; + +SlangCompileRequest* compileRequest = nullptr; +session->createCompileRequest(&compileRequest); + +// Add source code +int translationUnitIndex = spAddTranslationUnit(compileRequest, SLANG_SOURCE_LANGUAGE_SLANG, "shaderCode"); +spAddTranslationUnitSourceString(compileRequest, translationUnitIndex, "shaderCode", shaderSource.c_str()); + +// Set entry point +spAddEntryPoint(compileRequest, translationUnitIndex, "main", SLANG_STAGE_FRAGMENT); + +// Compile +int compileResult = spCompile(compileRequest); +if (compileResult != 0) +{ + const char* diagnostics = spGetDiagnosticOutput(compileRequest); + // Handle compilation error +} + +// Get compiled code +size_t codeSize = 0; +const void* codeData = spGetEntryPointCode(compileRequest, 0, &codeSize); + +// Create Vulkan shader module +VkShaderModuleCreateInfo shaderModuleCI{}; +shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +shaderModuleCI.codeSize = codeSize; +shaderModuleCI.pCode = static_cast(codeData); +VkShaderModule shaderModule; +vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule); +---- + +=== Slang-specific features for Vulkan + +When using Slang with Vulkan, you can take advantage of several Slang-specific features: + +* **Cross-compilation**: Slang can target multiple backends from the same source code +* **Shader parameters**: Slang provides a more structured way to declare shader parameters +* **Resource binding**: Slang offers more flexible resource binding options +* **Reflection**: Slang provides rich reflection capabilities for shader introspection + +Example of Slang shader parameters: + +[source, slang] +---- +// Define a parameter block for Vulkan +[[vk::binding(0, 0)]] +ParameterBlock sceneData; + +// Define a struct for scene data +struct SceneData +{ + float4x4 viewProj; + float3 cameraPosition; + float time; + Texture2D diffuseMap; + SamplerState samplerState; +}; +---- + +== Best Practices for Writing Shaders in Slang for Vulkan + +=== Code Organization + +* *Use modules*: Organize code into logical modules with clear responsibilities +* *Leverage imports*: Use imports instead of includes for better dependency management +* *Separate shader stages*: Keep different shader stages in separate files +* *Use interfaces*: Define interfaces for common functionality to enable polymorphism + +Example of a well-organized Slang shader: + +[source,slang] +---- +module Rendering.PBR; + +import Rendering.Common; +import Rendering.Lighting; + +export interface IMaterial { + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal); +} + +export struct PBRMaterial : IMaterial { + float3 albedo; + float roughness; + float metallic; + + float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) { + // PBR implementation + return calculatePBR(albedo, roughness, metallic, viewDir, lightDir, normal); + } +} + +[shader("fragment")] +export float4 fragmentMain(VSOutput input) { + // Shader implementation +} +---- + +=== Performance Considerations + +* *Use generics judiciously*: While powerful, excessive use of generics can increase compile times +* *Profile interface dispatch*: Test performance impact of interface-based polymorphism +* *Leverage compile-time evaluation*: Use Slang's metaprogramming to move work to compile time +* *Consider specialization*: Generate specialized shader variants for performance-critical paths + +=== Vulkan-Specific Best Practices + +* *Use parameter blocks*: Organize resources into parameter blocks for better organization +* *Explicit bindings*: Always specify explicit descriptor set and binding indices +* *Cross-API compatibility*: Use conditional compilation for Vulkan-specific code + +Example of Vulkan-specific resource binding: + +[source,slang] +---- +// Define parameter blocks with explicit bindings +[[vk::binding(0, 0)]] +ParameterBlock globals; + +[[vk::binding(0, 1)]] +ParameterBlock material; + +// Access resources through parameter blocks +float4 albedo = material.albedoMap.Sample(material.sampler, uv); +float4x4 viewProj = globals.viewProj; +---- + +== Advanced Topics + +=== Cross-Compilation and Portability + +Slang excels at cross-API compatibility: + +* *Multi-target compilation*: Compile the same shader for multiple APIs +* *Conditional compilation*: Use `#if` directives for API-specific code +* *Parameter blocks*: Use parameter blocks for consistent resource binding across APIs + +Example of cross-API shader: + +[source,slang] +---- +// Common shader code +module Shaders.PBR; + +// API-specific resource binding +#if SLANG_VULKAN +[[vk::binding(0, 0)]] +#endif +ParameterBlock globals; + +// Common shader logic +float4 calculateLighting(float3 position, float3 normal) { + // Implementation that works across APIs +} +---- + +=== Shader Composition and Reuse + +Slang's module system enables powerful composition patterns: + +* *Interface-based composition*: Define interfaces for pluggable components +* *Module imports*: Import and reuse code across shaders +* *Generics for reusable algorithms*: Write generic algorithms that work with different types + +Example of shader composition: + +[source,slang] +---- +// In lighting.slang +module Lighting; + +export interface ILight { + float3 calculateLighting(float3 position, float3 normal, float3 viewDir); +} + +// In directional_light.slang +module Lights.Directional; +import Lighting; + +export struct DirectionalLight : ILight { + float3 direction; + float3 color; + + float3 calculateLighting(float3 position, float3 normal, float3 viewDir) { + // Implementation + } +} + +// In main shader +module Main; +import Lighting; +import Lights.Directional; + +float3 shadeSurface(ILight light, float3 position, float3 normal, float3 viewDir) { + return light.calculateLighting(position, normal, viewDir); +} +---- + +=== Reflection and Shader Introspection + +Slang provides powerful reflection capabilities: + +* *Compile-time reflection*: Inspect types and fields at compile time +* *Runtime reflection*: Generate reflection data for runtime use +* *Automatic binding generation*: Use reflection to automate resource binding + +Example of compile-time reflection: + +[source,slang] +---- +struct Material { + float4 baseColor; + float roughness; + float metallic; + Texture2D albedoMap; +} + +__generic +void bindResources(ParameterBlock block, T data) { + __for(field in getFields(T)) { + block.setField(field.name, getField(data, field.name)); + } +} +---- + +== Conclusion + +Slang provides a powerful alternative to GLSL and HLSL for Vulkan shader development, offering advanced language features while maintaining compatibility with HLSL. By leveraging Slang's unique capabilities like generics, interfaces, and modules, developers can create more maintainable, flexible, and reusable shader code. + +The migration from HLSL to Slang can be done incrementally, allowing teams to adopt Slang at their own pace while preserving existing investments in HLSL code. + +== References and Further Reading + +* link:https://github.com/shader-slang/slang[Slang GitHub Repository] +* link:https://docs.shader-slang.org/en/latest/[Slang Documentation] +* link:https://docs.shader-slang.org/en/latest/external/slang/docs/user-guide/index.html[Slang User Guide] +* link:https://github.com/shader-slang/spec[Slang Language Guide] +* link:https://docs.shader-slang.org/en/latest/coming-from-hlsl.html[Coming from HLSL Guide] +* link:https://docs.shader-slang.org/en/latest/coming-from-glsl.html[Coming from GLSL Guide] +* link:https://github.com/KhronosGroup/SPIRV-Guide[SPIR-V Guide] +* link:https://docs.vulkan.org/spec/latest/chapters/interfaces.html[Vulkan Interfaces with SPIR-V] +* xref:{chapters}hlsl.adoc[HLSL in Vulkan] diff --git a/guide.adoc b/guide.adoc index 9883a27..0a9517d 100644 --- a/guide.adoc +++ b/guide.adoc @@ -145,7 +145,7 @@ include::{chapters}common_pitfalls.adoc[] include::{chapters}hlsl.adoc[] -include::{chapters}shader_authoring_guide_slang_hlsl.adoc[] +include::{chapters}slang.adoc[] include::{chapters}high_level_shader_language_comparison.adoc[] From f9a20f334de1c42e53bfd773b06c1f78294e6076 Mon Sep 17 00:00:00 2001 From: swinston Date: Tue, 22 Jul 2025 00:39:08 -0700 Subject: [PATCH 5/7] Didn't mean to change this. --- chapters/high_level_shader_language_comparison.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chapters/high_level_shader_language_comparison.adoc b/chapters/high_level_shader_language_comparison.adoc index 60cefa9..c04bff3 100644 --- a/chapters/high_level_shader_language_comparison.adoc +++ b/chapters/high_level_shader_language_comparison.adoc @@ -1407,12 +1407,12 @@ These shader stages share several functions and built-ins [options="header"] |==== | *GLSL* | *HLSL* | *Note* -| gl_PointSize | `vk::builtin("PointSize")` | Vulkan only, no direct HLSL equivalent -| gl_BaseVertexARB | `vk::builtin("BaseVertex")` | Vulkan only, no direct HLSL equivalent -| gl_BaseInstanceARB | `vk::builtin("BaseInstance")` | Vulkan only, no direct HLSL equivalent -| gl_DrawID | `vk::builtin("DrawIndex")` | Vulkan only, no direct HLSL equivalent -| gl_DeviceIndex | `vk::builtin("DeviceIndex")` | Vulkan only, no direct HLSL equivalent -| gl_ViewportMask | `vk::builtin("ViewportMaskNV")` | Vulkan only, no direct HLSL equivalent +| gl_PointSize | [[vk::builtin("PointSize")]] | Vulkan only, no direct HLSL equivalent +| gl_BaseVertexARB | [[vk::builtin("BaseVertex")]] | Vulkan only, no direct HLSL equivalent +| gl_BaseInstanceARB | [[vk::builtin("BaseInstance")]] | Vulkan only, no direct HLSL equivalent +| gl_DrawID | [[vk::builtin("DrawIndex")]] | Vulkan only, no direct HLSL equivalent +| gl_DeviceIndex | [[vk::builtin("DeviceIndex")]] | Vulkan only, no direct HLSL equivalent +| gl_ViewportMask | [[vk::builtin("ViewportMaskNV")]] | Vulkan only, no direct HLSL equivalent | gl_FragCoord | SV_Position | | gl_FragDepth | SV_Depth | | gl_FrontFacing | SV_IsFrontFace | From 13bc017ebe3ccf111e89b99191fc0a8f2757b70d Mon Sep 17 00:00:00 2001 From: swinston Date: Wed, 10 Dec 2025 12:47:26 -0800 Subject: [PATCH 6/7] Refine Slang documentation: improve wording, add Discord link, and include GLSL compatibility example. --- .../high_level_shader_language_comparison.adoc | 7 +++---- chapters/hlsl.adoc | 3 +-- chapters/slang.adoc | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/chapters/high_level_shader_language_comparison.adoc b/chapters/high_level_shader_language_comparison.adoc index c04bff3..586cba7 100644 --- a/chapters/high_level_shader_language_comparison.adoc +++ b/chapters/high_level_shader_language_comparison.adoc @@ -269,9 +269,9 @@ interface IVertexShader // Input structure with semantics struct Input { - float3 position : POSITION; - float3 normal : NORMAL; - float2 texCoord : TEXCOORD0; + float3 position; + float3 normal; + float2 texCoord; }; // Output structure with semantics @@ -471,7 +471,6 @@ struct MaterialResources }; // Declare a parameter block with explicit binding -[[vk::binding(0, 0)]] ParameterBlock material; // Usage in shader code diff --git a/chapters/hlsl.adoc b/chapters/hlsl.adoc index 9ff855f..45112ed 100644 --- a/chapters/hlsl.adoc +++ b/chapters/hlsl.adoc @@ -7,7 +7,6 @@ ifndef::images[:images: images/] [[hlsl-in-vulkan]] = HLSL in Vulkan -:toc: Vulkan does not directly consume shaders in a human-readable text format, but instead uses xref:{chapters}what_is_spirv.adoc[SPIR-V] as an intermediate representation. This opens the option to use shader languages other than e.g. GLSL, as long as they can target the Vulkan SPIR-V environment. @@ -68,7 +67,7 @@ struct SceneUBO { float4x4 projection; }; [[vk::binding(0, 0)]] -ConstantBuffer ubo; +ConstantBuffer ubo : register(0, 0); // Vertex shader main function VSOutput main(VSInput input) { VSOutput output = (VSOutput)0; diff --git a/chapters/slang.adoc b/chapters/slang.adoc index b3e70ed..571e28e 100644 --- a/chapters/slang.adoc +++ b/chapters/slang.adoc @@ -7,7 +7,7 @@ ifndef::images[:images: images/] [[slang-in-vulkan]] = Slang in Vulkan -:toc: + Vulkan does not directly consume shaders in a human-readable text format, but instead uses xref:{chapters}what_is_spirv.adoc[SPIR-V] as an intermediate representation. This opens the option to use shader languages other than e.g. GLSL, as long as they can target the Vulkan SPIR-V environment. @@ -15,12 +15,11 @@ link:https://github.com/shader-slang/slang[Slang] is a modern shading language a With link:https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#unsupported-hlsl-features[a few exceptions], all Vulkan features and shader stages available with GLSL can be used with Slang too, including recent Vulkan additions like hardware accelerated ray tracing. On the other hand, Slang to SPIR-V supports Vulkan exclusive features that are not (yet) available in DirectX. -image::{images}what_is_spirv_dxc.png[what_is_spriv_dxc.png] [[educational-resources]] == Educational resources -For Slang, the official link:https://github.com/shader-slang/slang[GitHub repository] and link:https://docs.shader-slang.org/en/latest/[documentation] are the best resources to get started. +For Slang, the official link:https://github.com/shader-slang/slang[GitHub repository] and link:https://docs.shader-slang.org/en/latest/[documentation] are the best resources to get started. Additionally, the best place to get help is the link:https://khr.io/slangdiscord[slang discord server]. == Why Use Slang for Vulkan? @@ -34,7 +33,7 @@ There are several advantages to using Slang for Vulkan development: == Differences Between HLSL and Slang -While Slang is built on HLSL and maintains backward compatibility, it adds several powerful features that make shader development more efficient, maintainable, and flexible. +While Slang is built on HLSL, it adds several powerful features that make shader development more efficient, maintainable, and flexible. === Generics and Templates @@ -148,7 +147,7 @@ While Slang maintains HLSL syntax compatibility, it introduces some new syntax e === Compilation Differences -Slang provides its own compiler (`slangc`) with different capabilities than the HLSL compiler: +Slang provides its own compiler (`slangc`) and runtime compilation API with different capabilities than the HLSL compiler: * *Multi-target compilation*: Compile the same shader for multiple graphics APIs * *Cross-compilation*: Generate code for different shader stages from a single source @@ -179,7 +178,7 @@ From the application's point-of-view, using Slang is exactly the same as using G == Migration Guide: from HLSL to Slang -Migrating from HLSL to Slang can be done incrementally, as Slang maintains backward compatibility with HLSL. This guide provides a step-by-step approach to migrating your shaders. +Migrating from HLSL to Slang can be done incrementally, as Slang is similar enough to HLSL that it mostly supports HLSL. This guide provides a step-by-step approach to migrating your shaders. [TIP] ==== @@ -461,6 +460,13 @@ For HLSL compatibility mode: slangc -profile glsl_spirv -entry main -o output.spv -language hlsl input.hlsl ---- +For GLSL compatibility mode: + +[source] +---- +slangc -profile glsl_spirv -entry main -o output.spv -language glsl input.glsl +---- + Key command-line options include: * `-profile`: Specifies the target profile (use `glsl_spirv` for Vulkan) From 1bc6875ed1e2ad1d839ef9a9b021f05dfb9ae0b2 Mon Sep 17 00:00:00 2001 From: swinston Date: Wed, 10 Dec 2025 13:39:04 -0800 Subject: [PATCH 7/7] use allow-glsl option for slangc for GLSL compatibility example --- chapters/slang.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chapters/slang.adoc b/chapters/slang.adoc index 571e28e..04f367f 100644 --- a/chapters/slang.adoc +++ b/chapters/slang.adoc @@ -464,7 +464,7 @@ For GLSL compatibility mode: [source] ---- -slangc -profile glsl_spirv -entry main -o output.spv -language glsl input.glsl +slangc -profile glsl_spirv -entry main -o output.spv -allow-glsl input.glsl ---- Key command-line options include: @@ -473,6 +473,7 @@ Key command-line options include: * `-entry`: Specifies the entry point function * `-o`: Specifies the output file * `-language`: Specifies the input language (default is Slang) +* `-allow-glsl`: Enables GLSL as an input language * `-target`: Specifies the target platform (e.g., `vulkan`) === Runtime compilation using the library