yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
2d7106640
master
layout: user-guide permalink: /user-guide/convenience-features
Basic Convenience Features
This topic covers a series of nice-to-have language features in Slang. These features are not supported by HLSL but are introduced to Slang to simplify code development. Many of these features are added to Slang per request of our users.
Type Inference in Variable Definitions
Slang supports automatic variable type inference:
var a = 1 ; // OK, `a` is an `int`. var b = float3 ( 0 , 1 , 2 ); // OK, `b` is a `float3`.
Automatic type inference requires an initialization expression to be present. Without an initial value, the compiler is not able to infer the type of the variable. The following code will result in a compiler error:
var a ; // Error, cannot infer the type of `a`.
You may use the var keyword to define a variable in a modern syntax:
var a : int = 1 ; // OK. var b : int ; // OK.
Immutable Values
The var syntax and the traditional C-style variable definition introduce a mutable variable whose value can be changed after its definition. If you wish to introduce an immutable or constant value, you may use the let keyword:
let a =5 ; // OK, `a` is `int`. let b: int =5 ; // OK.
Attempting to change an immutable value will result in a compiler error:
let a =5 ; a =6 ; // Error, `a` is immutable.
Namespaces
You can use the namespace syntax to define symbols in a namespace:
namespace ns { int f (); }
Slang also supports the abbreviated syntax for defining nested namespaces:
namespace ns1 . ns2 { int f (); } // equivalent to: namespace ns1 ::ns2 { int f (); } // equivalent to: namespace ns1 { namespace ns2 { int f (); } }
To access symbols defined in a namespace, you can use their qualified name with namespace prefixes:
void test () { ns1 . ns2 . f (); ns1 ::ns2 ::f (); // equivalent syntax. }
Symbols defined in the same namespace can access each other without a qualified name, this is true even if the referenced symbol is defined in a different file or module:
namespace ns { int f (); int g () { f (); } // OK. }
You can also use the using keyword to pull symbols defined in a different namespace to
the current scope, removing the requirement for using fully qualified names.
namespace ns1.ns2 {int f (); }using ns1 .ns2 ;// or: using namespace ns1 .ns2 ;// alternative syntax. void test () {f (); }// OK.
Member functions
Slang supports defining member functions in structs. For example, it is allowed to write:
struct Foo { int compute ( int a , int b ) { return a + b ; } }
You can use the . syntax to invoke member functions:
Foo foo ; int rs = foo . compute ( 1 , 2 );
Slang also supports static member functions, For example:
struct Foo
{
static int staticMethod(int a, int b)
{
return a + b;
}
}
Static member functions are accessed the same way as other static members, via either the type name or an instance of the type:
int rs = Foo . staticMethod ( a , b );
or
Foo foo ; ...int rs = foo . staticMethod ( a , b );
Mutability of member function
For GPU performance considerations, the this argument in a member function is immutable by default. Attempting to modify this will result in a compile error. If you intend to define a member function that mutates the object, use [mutating] attribute on the member function as shown in the following example.
struct Foo { int count ; [ mutating ] void setCount ( int x ) { count = x ; } // This would fail to compile. // void setCount2(int x) { count = x; } } void test () { Foo f ; f . setCount ( 1 ); // Compiles }
Properties
Properties provide a convenient way to access values exposed by a type, where the logic behind accessing the value is defined in getter and setter function pairs. Slang's property feature is similar to C# and Swift.
struct MyType { uint flag ; property uint highBits{ get { return flag >> 16 ; } set { flag = ( flag & 0xFF ) + ( newValue << 16 ); } } } ;
Or equivalently in a "modern" syntax:
struct MyType { uint flag ; property highBits : uint { get { return flag >> 16 ; } set { flag = ( flag & 0xFF ) + ( newValue << 16 ); } } } ;
You may also use an explicit parameter for the setter method:
property uint highBits{ set ( uint x ) { flag = ( flag & 0xFF ) + ( x << 16 ); } }
Note
Slang currently does not support automatically synthesized
getterandsettermethods. For example, the following code is not supported:property uint highBits {get;set;} // Not supported yet.
Initializers
Constructors
Note
The syntax for defining constructors is subject to future change.
Slang supports defining constructors in struct types. You can write:
struct MyType { int myVal ; __init ( int a , int b ) { myVal = a + b ; } }
You can use a constructor to construct a new instance by using the type name in a function call expression:
MyType instance = MyType ( 1 , 2 ); // instance.myVal is 3.
You may also use C++ style initializer list to invoke a constructor:
MyType instance = { 1 , 2 };
If a constructor does not define any parameters, it will be recognized as default constructor that will be automatically called at the definition of a variable:
struct MyType { int myVal ; __init () { myVal = 10 ; } } ;int test () { MyType test ; return test . myVal ; // returns 10. }
Slang will also implicitly call a default constructor of all parents of a derived struct (same as C++):
struct MyType_Base { int myVal1 ; __init () { myVal1 = 22 ;} } struct MyType1 : MyType_Base { int myVal2 ; __init () { // implicitly calls `MyType_Base::__init()` myVal2 = 15 ; } } testMyType1 () { MyType1 a ; // a.myVal1 == 22 // a.myVal2 == 15 } struct MyType2 : MyType_Base { } testMyType2 () { MyType2 b ; // implicitly calls `MyType_Base::__init()` // b.myVal1 == 22 }
Member Init Expressions
Slang supports member init expressions:
struct MyType { int myVal = 5 ; }
Operator Overloading
Slang allows defining operator overloads as global methods:
struct MyType { int val ; __init ( int x ) { val = x ; } } MyType operator + ( MyType a , MyType b ) { return MyType ( a . val + b . val ); } int test () { MyType rs = MyType ( 1 ) + MyType ( 2 ); return rs . val ; // returns 3. }
Slang currently supports overloading the following operators: +, -, *, /, %, &, |, <, >, <=, >=, ==, !=, unary -, ~ and !. Please note that the && and || operators are not supported.
In addition, you can overload operator () as a member method:
struct MyFunctor { int operator ()( float v ) { // ... } } void test () { MyFunctor f ; int x = f ( 1.0f ); // calls MyFunctor::operator(). int y = f . operator ()( 1.0f ); // explicitly calling operator(). }
Subscript Operator
Slang allows overriding operator[] with __subscript syntax:
struct MyType { int val [ 12 ]; __subscript ( int x , int y ) ->int { get { return val [ x * 3 + y ]; } set { val [ x * 3 + y ] = newValue ; } } } int test () { MyType rs ; rs [ 0 , 0 ] = 1 ; rs [ 1 , 0 ] = rs [ 0 , 0 ] + 1 ; return rs [ 1 , 0 ]; // returns 2. }
Tuple Types
Tuple types can hold collection of values of different types.
Tuples types are defined in Slang with the Tuple<...> syntax, and
constructed with either a constructor or the makeTuple function:
Tuple < int , float , bool > t0 = Tuple < int , float , bool > ( 5 , 2.0f , false ); Tuple < int , float , bool > t1 = makeTuple ( 3 , 1.0f , true );
Tuple elements can be accessed with _0, _1 member names:
int i = t0 . _0 ; // 5 bool b = t1 . _2 ; // true
You can use the swizzle syntax similar to vectors and matrices to form new tuples:
t0 . _0_0_1 // evaluates to (5, 5, 2.0f)
You can concatenate two tuples:
concat ( t0 , t1 ) // evaluates to (5, 2.0f, false, 3, 1.0f, true)
If all element types of a tuple conforms to IComparable, then the tuple itself
will conform to IComparable, and you can use comparison operators on the tuples
to compare them:
let cmp = t0 < t1 ; // false
You can use countof() on a tuple type or a tuple value to obtain the number of
elements in a tuple. This is considered a compile-time constant.
int n = countof ( Tuple < int , float > ); // 2 int n1 = countof ( makeTuple ( 1 , 2 , 3 )); // 3
All tuple types will be translated to struct types, and receive the same layout
as struct types.
Optional<T> type
Slang supports the Optional<T> type to represent a value that may not exist.
The dedicated none value can be used for any Optional<T> to represent no value.
Optional<T>::value property can be used to retrieve the value.
struct MyType { int val ; } int useVal ( Optional < MyType > p ) { if ( p == none ) // Equivalent to `!p.hasValue` return 0 ; return p . value . val ; } int caller () { MyType v ; v . val = 0 ; useVal ( v ); // OK to pass `MyType` to `Optional<MyType>`. useVal ( none ); // OK to pass `none` to `Optional<MyType>`. return 0 ; }
Conditional<T, bool condition> Type
A Conditional type can be used to define struct fields that can be specialized away. If condition is false, the field will be removed
by the compiler from the target code. This is useful for scenarios where a developer would like to make sure a field is not defined in a
specialized shader variant when it is not used by the shader.
For example, a common use case is to define the vertex shader output / fragment shader input:
interface IVertex
{
property float3 position{get;}
property Optional<float3> normal{get;}
property Optional<float3> color{get;}
}
struct Vertex<bool hasNormal, bool hasColor> : IVertex
{
private float3 m_position;
private Conditional<float3, hasNormal> m_normal;
private Conditional<float3, hasColor> m_color;
__init(float3 position, float3 normal, float3 color)
{
m_position = position;
m_normal = normal;
m_color = color;
}
property float3 position
{
get { return m_position; }
}
property Optional<float3> normal
{
get { return m_normal; }
}
property Optional<float3> color
{
get { return m_color; }
}
}
In this example, Vertex type is parameterized on hasNormal and hasColor. If hasNormal is false, the m_normal field will be eliminated in the target code, allowing a specialized vertex shader to declare minimum output fields. For example, a vertex shader
can be defined as follows:
[shader("vertex")]
Vertex<hasNormal, hasColor> vertMain<bool hasNormal, bool hasColor>(VertexIn inputVertex)
{
...
}
if_let syntax
Slang supports if (let name = expr) syntax to simplify the code when working with Optional<T> or Conditional<T, hasValue> value. The syntax is similar to Rust's
if let syntax, the value expression must be an Optional<T> or Conditional<T, hasValue> type, for example:
Optional < int > getOptInt () { ... } void test () { if ( let x = getOptInt()) { // if we are here, `getOptInt` returns a value `int`. // and `x` represents the `int` value. } }
reinterpret<T> operation
Sometimes it is useful to reinterpret the bits of one type as another type, for example:
struct MyType { int a ; float2 b ; uint c ; } MyType myVal ; float4 myPackedVector = packMyTypeToFloat4 ( myVal );
The packMyTypeToFloat4 function is usually implemented by bit casting each field in the source type and assigning it into the corresponding field in the target type,
by calling intAsFloat, floatAsInt and using bit operations to shift things in the right place.
Instead of writing packMyTypeToFloat4 function yourself, you can use Slang's built-in reinterpret<T> to do just that for you:
float4 myPackedVector = reinterpret<float4>(myVal);
reinterpret can pack any type into any other type as long as the target type is no smaller than the source type.
Pointers (limited)
Slang supports pointers when generating code for SPIRV, C++ and CUDA targets. The syntax for pointers is similar to C, with the exception that operator . can also be used to dereference a member from a pointer. For example:
struct MyType { int a ; } ;int test ( MyType * pObj ) { MyType * pNext = pObj + 1 ; MyType * pNext2 = & pNext [ 1 ]; return pNext . a + pNext ->a + ( * pNext2 ). a + pNext2 [ 0 ]. a ; } cbuffer Constants { MyType * ptr ; }; int validTest () { return test ( ptr ); } int invalidTest () { // cannot produce a pointer from a local variable MyType obj ; return test ( & obj ); // !! ERROR !! }
Pointer types can also be specified using the generic syntax Ptr<MyType>. Ptr<MyType> is equivalent to MyType*.
Limitations
-
Slang supports pointers to global memory, but not shared or local memory. For example, it is invalid to define a pointer to a local variable.
-
Slang supports pointers that are defined as shader parameters (e.g. as a constant buffer field).
-
Slang can produce pointers using the & operator from data in global memory.
-
Slang doesn't support forming pointers to opaque handle types, e.g.
Texture2D. For handle pointers, useDescriptorHandle<T>instead. -
Slang doesn't support coherent load/stores.
-
Slang doesn't support custom alignment specification.
-
Slang currently does not support pointers to immutable values, i.e.
const T*.
DescriptorHandle for Bindless Descriptor Access
Slang supports the DescriptorHandle<T> type that represents a bindless handle to a resource. This feature provides a portable way of implementing
the bindless resource idiom. When targeting HLSL, GLSL and SPIRV where descriptor types (e.g. textures, samplers and buffers) are opaque handles,
DescriptorHandle<T> will translate into a uint2 so it can be defined in any memory location. The underlying uint2 value is treated as an index
to access the global descriptor heap or resource array in order to obtain the actual resource handle. On targets with where resource handles
are not opaque handles, DescriptorHandle<T> maps to T and will have the same size and alignment defined by the target.
DescriptorHandle<T> is declared as:
struct DescriptorHandle<T> where T:IOpaqueDescriptor {}
where IOpaqueDescriptor is an interface implemented by all resource types, including textures,
ConstantBuffer, RaytracingAccelerationStructure, SamplerState, SamplerComparisonState and all types of StructuredBuffer.
You may also write Texture2D.Handle as a short-hand of DescriptorHandle<Texture2D>.
DescriptorHandle<T> supports operator *, operator ->, and can implicitly convert to T, for example:
uniform StructuredBuffer<DescriptorHandle<Texture2D>> textures;
uniform int textureIndex;
// define a descriptor handle using builtin convenience typealias:
uniform StructuredBuffer<float4>.Handle output;
[numthreads(1,1,1)]
void main()
{
output[0] = textures[textureIndex].Load(int3(0));
// Alternatively, this syntax is also valid:
(*output)[0] = textures[textureIndex]->Load(int3(0));
}
By default, when targeting HLSL, DescriptorHandle<T> translates to uses of ResourceDescriptorHeap[index] and SamplerDescriptorHeap[index].
In particular, when combined with combined texture sampler types (e.g. Sampler2D), Slang will fetch the texture using the first
component of the handle, and the sampler state from the second component of the handle. For example:
uniform DescriptorHandle<Sampler2D> s;
void test()
{
s.Sample(uv);
}
translates to:
uniform uint2 s ; void test () { Texture2D ( ResourceDescriptorHeap [ s . x ]). Sample ( SamplerState ( SamplerDescriptorHeap [ s . y ]), uv ); }
When targeting SPIRV, Slang will introduce a global array of descriptors and fetch from the global array.
The descriptor set ID of the global descriptor array can be configured with the -bindless-space-index
(or CompilerOptionName::BindlessSpaceIndex when using the API) option.
Default behavior assigns binding-indicies based on descriptor types:
| Enum Value | Vulkan Descriptor Type | Binding Index |
|---|---|---|
| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 |
| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 |
| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 |
| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 2 |
| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 2 |
| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 2 |
| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 2 |
| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 2 |
| Unknown | Other | 3 |
ACCELERATION_STRUCTUREis excluded from the list of types since Slang by default uses the handle to aRaytracingAccelerationStructureas a GPU address, casting the handle to aRaytracingAccelerationStructure. This removes the need for a binding-slot ofRaytracingAccelerationStructure.
Note
The default implementation for SPIRV may change in the future if SPIRV is extended to provide what is equivalent to D3D's
ResourceDescriptorHeapconstruct.
Users can override the default behavior of convering from bindless handle to resource handle, by providing a
getDescriptorFromHandle in user code. For example:
// All texture and buffer handles are defined in descriptor set 100.
[vk::binding(0, 100)]
__DynamicResource<__DynamicResourceKind.General> resourceHandles[];
// All sampler handles are defined in descriptor set 101.
[vk::binding(0, 101)]
__DynamicResource<__DynamicResourceKind.Sampler> samplerHandles[];
export T getDescriptorFromHandle<T>(DescriptorHandle<T> handle) where T : IOpaqueDescriptor
{
__target_switch
{
case spirv:
if (T.kind == ResourceKind.Sampler)
return samplerHandles[((uint2)handle).x].asOpaqueDescriptor<T>();
else
return resourceHandles[((uint2)handle).x].asOpaqueDescriptor<T>();
default:
return defaultGetDescriptorFromHandle(handle);
}
}
Note that the getDescriptorFromHandle is not supposed to be called from the user code directly,
it will be automatically called by the compiler to dereference a DescriptorHandle<T> to get T.
Think about providing getDescriptorFromHandle as a way to override operator-> for DescriptorHandle<T>.
The IOpaqueDescriptor interface is defined as:
interface IOpaqueDescriptor
{
/// The kind of the descriptor.
static const DescriptorKind kind;
static const DescriptorAccess descriptorAccess;
}
The user can call defaultGetDescriptorFromHandle function from their implementation of
getDescriptorFromHandle to dispatch to the default behavior.
Additionally, defaultGetDescriptorFromHandle() takes an optional argument whose type is constexpr BindlessDescriptorOptions. This parameter allows to specify alternative standard presets for how bindless-indexes are assigned. Note that this is currently only relevant to SPIRV:
public enum BindlessDescriptorOptions
{
None = 0, /// Bind assuming regular binding model rules.
VkMutable = 1, /// **Current Default** Bind assuming `VK_EXT_mutable_descriptor_type`
}
None provides the following bindings for descriptor types:
| Enum Value | Vulkan Descriptor Type | Binding Index |
|---|---|---|
| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 |
| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 |
| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 |
| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 3 |
| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 4 |
| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 5 |
| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 6 |
| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 7 |
| Unknown | Other | 8 |
VkMutable provides the following bindings for descriptor types:
| Enum Value | Vulkan Descriptor Type | Binding Index |
|---|---|---|
| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 |
| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 |
| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 |
| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 2 |
| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 2 |
| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 2 |
| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 2 |
| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 2 |
| Unknown | Other | 3 |
The kind and descriptorAccess constants allows user code to fetch resources from different locations depending on the type and access of the resource being requested. The DescriptorKind and
DescriptorAccess enums are defined as:
enum DescriptorKind
{
Unknown, /// Unknown descriptor kind.
Texture, /// A texture descriptor.
CombinedTextureSampler, /// A combined texture and sampler state descriptor.
Buffer, /// A buffer descriptor.
Sampler, /// A sampler state descriptor.
AccelerationStructure, /// A ray tracing acceleration structure descriptor.
TexelBuffer /// A texel buffer descriptor.
}
enum DescriptorAccess
{
Unknown = -1,
Read = 0,
Write = 1,
ReadWrite = 2,
RasterizerOrdered = 3,
Feedback = 4,
}
By default, the value of a DescriptorHandle<T> object is assumed to be dynamically uniform across all
execution threads. If this is not the case, the user is required to mark the DescriptorHandle as nonuniform
immediately before dereferencing it:
void test(DescriptorHandle<Texture2D> t)
{
nonuniform(t)->Sample(...);
}
If the resource pointer value is not uniform and nonuniform is not called, the result may be
undefined.
Extensions
Slang allows defining additional methods for a type outside its initial definition. For example, suppose we already have a type defined:
struct MyType { int field ; int get () { return field ; } }
You can extend MyType with new method members:
extension MyType { float getNewField () { return newField ; } }
All locations that sees the definition of the extension can access the new members:
void test () { MyType t ; float val = t . getNewField (); }
This feature is similar to extensions in Swift and extension methods in C#.
Note:
You can only extend a type with additional methods. Extending with additional data fields is not allowed.
Multi-level break
Slang allows break statements with a label to jump into any ancestor control flow break points, and not just the immediate parent.
Example:
outer:
for (int i = 0; i < 5; i++)
{
inner:
for (int j = 0; j < 10; j++)
{
if (someCondition)
break outer;
}
}
Force inlining
Most of the downstream shader compilers will inline all the function calls. However you can instruct Slang compiler to do the inlining
by using the [ForceInline] decoration:
[ForceInline]
int f(int x) { return x + 1; }
Error handling
Slang supports an error handling mechanism that is superficially similar to exceptions in many other languages, but has some unique characteristics.
In contrast to C++ exceptions, this mechanism makes the control flow of errors more explicit, and the performance charasteristics are similar to adding an if-statement after every potentially throwing function call to check and handle the error.
In order to be able to throw an error, a function must declare the type of that
error with throws:
enum MyError
{
Failure,
CatastrophicFailure
}
int f() throws MyError
{
if (computerIsBroken())
throw MyError.CatastrophicFailure;
return 42;
}
Currently, functions may only throw a single type of error.
To call a function that may throw, you must prepend it with try:
let result = try f();
If you don't catch the try, related errors are re-thrown and the calling
function must declare that it throws that error type:
void g() throws MyError
{
// This would not compile if `g()` wasn't declared to throw MyError as well.
let result = try f();
printf("Success: %d\n", result);
}
To catch an error, you can use a do-catch statement:
void g()
{
do
{
let result = try f();
printf("Success: %d\n", result);
}
catch(err: MyError)
{
printf("Not good!\n");
}
}
You can chain multiple catch statements for different types of errors.
Special Scoping Syntax
Slang supports three special scoping syntax to allow users to mix in custom decorators and content in the shader code. These constructs allow a rendering engine to define custom meta-data in the shader, or map engine-specific block syntax to a meaningful block that is understood by the compiler via proper #defines.
__ignored_block
An ignored block will be parsed and ignored by the compiler:
__ignored_block
{
arbitrary content in the source file,
will be ignored by the compiler as if it is a comment.
Can have nested {} here.
}
__transparent_block
Symbols defined in a transparent block will be treated as if they are defined in the parent scope:
struct MyType { __transparent_block { int myFunc () { return 0 ; } } }
Is equivalent to:
struct MyType { int myFunc () { return 0 ; } }
__file_decl
Symbols defined in a __file_decl will be treated as if they are defined in
the global scope. However, symbols defined in different __file_decls is not visible
to each other. For example:
__file_decl { void f1 () { } } __file_decl { void f2 () { f1 (); // error: f1 is not visible from here. } }
User Defined Attributes (Experimental)
In addition to many system defined attributes, users can define their own custom attribute types to be used in the [UserDefinedAttribute(args...)] syntax. The following example shows how to define a custom attribute type.
[ __AttributeUsage ( _AttributeTargets . Var )] struct MaxValueAttribute { int value ; string description ; } ;[ MaxValue ( 12 , "the scale factor" )] uniform int scaleFactor;
In the above code, the MaxValueAttribute struct type is decorated with the [__AttributeUsage] attribute, which informs that MaxValueAttribute type should be interpreted as a definition for a user-defined attribute, [MaxValue], that can be used to decorate all variables or fields. The members of the struct defines the argument list for the attribute.
The scaleFactor uniform parameter is declared with the user defined [MaxValue] attribute, providing two arguments for value and description.
The _AttributeTargets enum is used to restrict the type of decls the attribute can apply. Possible values of _AttributeTargets can be Function, Param, Struct or Var.
The usage of user-defined attributes can be queried via Slang's reflection API through TypeReflection or VariableReflection's getUserAttributeCount, getUserAttributeByIndex and findUserAttributeByName methods.
1--- 2layout : user-guide 3permalink : /user-guide/convenience-features 4--- 5 6# Basic Convenience Features 7 8This topic covers a series of nice-to-have language features in Slang. These features are not supported by HLSL but are introduced to Slang to simplify code development. Many of these features are added to Slang per request of our users. 9 10## Type Inference in Variable Definitions 11Slang supports automatic variable type inference: 12``` csharp 13var a = 1; // OK, `a` is an `int`. 14var b = float3(0, 1, 2); // OK, `b` is a `float3`. 15``` 16Automatic type inference requires an initialization expression to be present. Without an initial value, the compiler is not able to infer the type of the variable. The following code will result in a compiler error: 17``` csharp 18var a; // Error, cannot infer the type of `a`. 19``` 20 21You may use the `var` keyword to define a variable in a modern syntax: 22``` csharp 23var a : int = 1; // OK. 24var b : int; // OK. 25``` 26 27## Immutable Values 28The `var` syntax and the traditional C-style variable definition introduce a _mutable_ variable whose value can be changed after its definition. If you wish to introduce an immutable or constant value, you may use the `let` keyword: 29``` rust 30let a = 5 ; // OK, `a` is `int`. 31let b : int = 5 ; // OK. 32``` 33Attempting to change an immutable value will result in a compiler error: 34``` rust 35let a = 5 ; 36a = 6 ; // Error, `a` is immutable. 37``` 38 39 40## Namespaces 41 42You can use the `namespace` syntax to define symbols in a namespace: 43``` csharp 44namespace ns 45{ 46int f(); 47} 48``` 49 50Slang also supports the abbreviated syntax for defining nested namespaces: 51``` csharp 52namespace ns1.ns2 53{ 54int f(); 55} 56// equivalent to: 57namespace ns1::ns2 58{ 59int f(); 60} 61// equivalent to: 62namespace ns1 63{ 64namespace ns2 65{ 66int f(); 67} 68} 69``` 70 71To access symbols defined in a namespace, you can use their qualified name with namespace prefixes: 72``` csharp 73void test() 74{ 75ns1.ns2.f(); 76ns1::ns2::f(); // equivalent syntax. 77} 78``` 79 80Symbols defined in the same namespace can access each other without a qualified name, this is true even if the referenced symbol is defined in a different file or module: 81``` csharp 82namespace ns 83{ 84int f(); 85int g() { f(); } // OK. 86} 87``` 88 89You can also use the `using` keyword to pull symbols defined in a different namespace to 90the current scope, removing the requirement for using fully qualified names. 91``` cpp 92namespace ns1. ns2 93{ 94int f (); 95} 96 97using ns1 . ns2 ; 98// or: 99using namespace ns1 . ns2 ; // alternative syntax. 100 101void test () { f (); } // OK. 102``` 103 104## Member functions 105 106Slang supports defining member functions in `struct`s. For example, it is allowed to write: 107 108``` hlsl 109struct Foo 110{ 111int compute ( int a , int b ) 112{ 113return a + b ; 114} 115} 116``` 117 118You can use the `.` syntax to invoke member functions: 119 120``` hlsl 121Foo foo ; 122int rs = foo . compute ( 1 , 2 ); 123``` 124 125Slang also supports static member functions, For example: 126``` 127struct Foo 128{ 129static int staticMethod(int a, int b) 130{ 131return a + b; 132} 133} 134``` 135 136Static member functions are accessed the same way as other static members, via either the type name or an instance of the type: 137 138``` hlsl 139int rs = Foo . staticMethod ( a , b ); 140``` 141 142or 143 144``` hlsl 145Foo foo ; 146... 147int rs = foo . staticMethod ( a , b ); 148``` 149 150### Mutability of member function 151 152For GPU performance considerations, the `this` argument in a member function is immutable by default. Attempting to modify `this` will result in a compile error. If you intend to define a member function that mutates the object, use `[mutating]` attribute on the member function as shown in the following example. 153 154``` hlsl 155struct Foo 156{ 157int count ; 158159 [ mutating ] 160void setCount ( int x ) { count = x ; } 161 162// This would fail to compile. 163// void setCount2(int x) { count = x; } 164} 165 166void test () 167{ 168Foo f ; 169f . setCount ( 1 ); // Compiles 170} 171``` 172 173## Properties 174 175Properties provide a convenient way to access values exposed by a type, where the logic behind accessing the value is defined in `getter` and `setter` function pairs. Slang's `property` feature is similar to C# and Swift. 176``` csharp 177struct MyType 178{ 179uint flag; 180 181property uint highBits 182{ 183get { return flag >> 16; } 184set { flag = (flag & 0xFF) + (newValue << 16); } 185} 186}; 187``` 188 189Or equivalently in a "modern" syntax: 190 191``` csharp 192struct MyType 193{ 194uint flag; 195 196property highBits : uint 197{ 198get { return flag >> 16; } 199set { flag = (flag & 0xFF) + (newValue << 16); } 200} 201}; 202``` 203 204You may also use an explicit parameter for the setter method: 205``` csharp 206property uint highBits 207{ 208set(uint x) { flag = (flag & 0xFF) + (x << 16); } 209} 210``` 211 212> #### Note #### 213> Slang currently does not support automatically synthesized `getter` and `setter` methods. For example, 214> the following code is not supported: 215> ``` 216> property uint highBits {get;set;} // Not supported yet. 217> ``` 218 219## Initializers 220 221### Constructors 222> #### Note #### 223> The syntax for defining constructors is subject to future change. 224 225 226Slang supports defining constructors in `struct` types. You can write: 227``` csharp 228struct MyType 229{ 230int myVal; 231__init(int a, int b) 232{ 233myVal = a + b; 234} 235} 236``` 237 238You can use a constructor to construct a new instance by using the type name in a function call expression: 239``` csharp 240MyType instance = MyType(1,2); // instance.myVal is 3. 241``` 242 243You may also use C++ style initializer list to invoke a constructor: 244``` csharp 245MyType instance = {1, 2}; 246``` 247 248If a constructor does not define any parameters, it will be recognized as *default* constructor that will be automatically called at the definition of a variable: 249 250``` csharp 251struct MyType 252{ 253int myVal; 254__init() 255{ 256myVal = 10; 257} 258}; 259 260int test() 261{ 262MyType test; 263return test.myVal; // returns 10. 264} 265``` 266 267Slang will also implicitly call a *default* constructor of all parents of a derived struct (same as C++): 268``` csharp 269struct MyType_Base 270{ 271int myVal1; 272__init() {myVal1 = 22;} 273} 274 275struct MyType1 : MyType_Base 276{ 277int myVal2; 278__init() 279{ 280// implicitly calls `MyType_Base::__init()` 281myVal2 = 15; 282} 283} 284testMyType1() 285{ 286MyType1 a; 287// a.myVal1 == 22 288// a.myVal2 == 15 289} 290 291struct MyType2 : MyType_Base 292{ 293} 294testMyType2() 295{ 296MyType2 b; // implicitly calls `MyType_Base::__init()` 297// b.myVal1 == 22 298} 299``` 300 301### Member Init Expressions 302 303Slang supports member init expressions: 304``` csharp 305struct MyType 306{ 307int myVal = 5; 308} 309``` 310 311## Operator Overloading 312 313Slang allows defining operator overloads as global methods: 314``` csharp 315struct MyType 316{ 317int val; 318__init(int x) { val = x; } 319} 320 321MyType operator+(MyType a, MyType b) 322{ 323return MyType(a.val + b.val); 324} 325 326int test() 327{ 328MyType rs = MyType(1) + MyType(2); 329return rs.val; // returns 3. 330} 331``` 332Slang currently supports overloading the following operators: `+`, `-`, `*`, `/`, `%`, `&`, `|`, `<`, `>`, `<=`, `>=`, `==`, `!=`, unary `-`, `~` and `!`. Please note that the `&&` and `||` operators are not supported. 333 334In addition, you can overload operator `()` as a member method: 335``` csharp 336struct MyFunctor 337{ 338int operator()(float v) 339{ 340// ... 341} 342} 343void test() 344{ 345MyFunctor f; 346int x = f(1.0f); // calls MyFunctor::operator(). 347int y = f.operator()(1.0f); // explicitly calling operator(). 348} 349``` 350 351## Subscript Operator 352 353Slang allows overriding `operator[]` with `__subscript` syntax: 354``` csharp 355struct MyType 356{ 357int val[12]; 358__subscript(int x, int y) -> int 359{ 360get { return val[x*3 + y]; } 361set { val[x*3+y] = newValue; } 362} 363} 364int test() 365{ 366MyType rs; 367rs[0, 0] = 1; 368rs[1, 0] = rs[0, 0] + 1; 369return rs[1, 0]; // returns 2. 370} 371``` 372 373## Tuple Types 374 375Tuple types can hold collection of values of different types. 376Tuples types are defined in Slang with the `Tuple<...>` syntax, and 377constructed with either a constructor or the `makeTuple` function: 378``` csharp 379Tuple<int, float, bool> t0 = Tuple<int, float, bool>(5, 2.0f, false); 380Tuple<int, float, bool> t1 = makeTuple(3, 1.0f, true); 381``` 382 383Tuple elements can be accessed with `_0`, `_1` member names: 384``` csharp 385int i = t0._0; // 5 386bool b = t1._2; // true 387``` 388 389You can use the swizzle syntax similar to vectors and matrices to form new 390tuples: 391 392``` csharp 393t0._0_0_1 // evaluates to (5, 5, 2.0f) 394``` 395 396You can concatenate two tuples: 397 398``` csharp 399concat(t0, t1) // evaluates to (5, 2.0f, false, 3, 1.0f, true) 400``` 401 402If all element types of a tuple conforms to `IComparable`, then the tuple itself 403will conform to `IComparable`, and you can use comparison operators on the tuples 404to compare them: 405 406``` csharp 407let cmp = t0 < t1; // false 408``` 409 410You can use `countof()` on a tuple type or a tuple value to obtain the number of 411elements in a tuple. This is considered a compile-time constant. 412``` csharp 413int n = countof(Tuple<int, float>); // 2 414int n1 = countof(makeTuple(1,2,3)); // 3 415``` 416 417All tuple types will be translated to `struct` types, and receive the same layout 418as `struct` types. 419 420## `Optional<T>` type 421 422Slang supports the `Optional<T>` type to represent a value that may not exist. 423The dedicated `none` value can be used for any `Optional<T>` to represent no value. 424`Optional<T>::value` property can be used to retrieve the value. 425 426``` csharp 427struct MyType 428{ 429int val; 430} 431 432int useVal(Optional<MyType> p) 433{ 434if (p == none) // Equivalent to `!p.hasValue` 435return 0; 436return p.value.val; 437} 438 439int caller() 440{ 441MyType v; 442v.val = 0; 443useVal(v); // OK to pass `MyType` to `Optional<MyType>`. 444useVal(none); // OK to pass `none` to `Optional<MyType>`. 445return 0; 446} 447``` 448 449## `Conditional<T, bool condition>` Type 450 451A `Conditional` type can be used to define struct fields that can be specialized away. If `condition` is `false`, the field will be removed 452by the compiler from the target code. This is useful for scenarios where a developer would like to make sure a field is not defined in a 453specialized shader variant when it is not used by the shader. 454 455For example, a common use case is to define the vertex shader output / fragment shader input: 456 457``` slang 458interface IVertex 459{ 460property float3 position{get;} 461property Optional<float3> normal{get;} 462property Optional<float3> color{get;} 463} 464 465struct Vertex<bool hasNormal, bool hasColor> : IVertex 466{ 467private float3 m_position; 468private Conditional<float3, hasNormal> m_normal; 469private Conditional<float3, hasColor> m_color; 470 471__init(float3 position, float3 normal, float3 color) 472{ 473m_position = position; 474m_normal = normal; 475m_color = color; 476} 477 478property float3 position 479{ 480get { return m_position; } 481} 482property Optional<float3> normal 483{ 484get { return m_normal; } 485} 486property Optional<float3> color 487{ 488get { return m_color; } 489} 490} 491``` 492 493In this example, `Vertex` type is parameterized on `hasNormal` and `hasColor`. If `hasNormal` is false, the `m_normal` field will be eliminated in the target code, allowing a specialized vertex shader to declare minimum output fields. For example, a vertex shader 494can be defined as follows: 495 496``` slang 497[shader("vertex")] 498Vertex<hasNormal, hasColor> vertMain<bool hasNormal, bool hasColor>(VertexIn inputVertex) 499{ 500... 501} 502``` 503 504 505## `if_let` syntax 506Slang supports `if (let name = expr)` syntax to simplify the code when working with `Optional<T>` or `Conditional<T, hasValue>` value. The syntax is similar to Rust's 507`if let` syntax, the value expression must be an `Optional<T>` or `Conditional<T, hasValue>` type, for example: 508 509``` csharp 510Optional<int> getOptInt() { ... } 511 512void test() 513{ 514if (let x = getOptInt()) 515{ 516// if we are here, `getOptInt` returns a value `int`. 517// and `x` represents the `int` value. 518} 519} 520``` 521 522## `reinterpret<T>` operation 523 524Sometimes it is useful to reinterpret the bits of one type as another type, for example: 525``` csharp 526struct MyType 527{ 528int a; 529float2 b; 530uint c; 531} 532 533MyType myVal; 534float4 myPackedVector = packMyTypeToFloat4(myVal); 535``` 536 537The `packMyTypeToFloat4` function is usually implemented by bit casting each field in the source type and assigning it into the corresponding field in the target type, 538by calling `intAsFloat`, `floatAsInt` and using bit operations to shift things in the right place. 539Instead of writing `packMyTypeToFloat4` function yourself, you can use Slang's built-in `reinterpret<T>` to do just that for you: 540``` 541float4 myPackedVector = reinterpret<float4>(myVal); 542``` 543 544`reinterpret` can pack any type into any other type as long as the target type is no smaller than the source type. 545 546## Pointers (limited) 547 548Slang supports pointers when generating code for SPIRV, C++ and CUDA targets. The syntax for pointers is similar to C, with the exception that operator `.` can also be used to dereference a member from a pointer. For example: 549``` csharp 550struct MyType 551{ 552int a; 553}; 554 555int test(MyType* pObj) 556{ 557MyType* pNext = pObj + 1; 558MyType* pNext2 = &pNext[1]; 559return pNext.a + pNext->a + (*pNext2).a + pNext2[0].a; 560} 561 562cbuffer Constants 563{ 564MyType *ptr; 565}; 566 567int validTest() 568{ 569return test(ptr); 570} 571 572int invalidTest() 573{ 574// cannot produce a pointer from a local variable 575MyType obj; 576return test(&obj); // !! ERROR !! 577} 578``` 579 580Pointer types can also be specified using the generic syntax `Ptr<MyType>`. `Ptr<MyType>` is equivalent to `MyType*`. 581 582### Limitations 583 584- Slang supports pointers to global memory, but not shared or local memory. For example, it is invalid to define a pointer to a local variable. 585 586- Slang supports pointers that are defined as shader parameters (e.g. as a constant buffer field). 587 588- Slang can produce pointers using the & operator from data in global memory. 589 590- Slang doesn't support forming pointers to opaque handle types, e.g. `Texture2D`. For handle pointers, use `DescriptorHandle<T>` instead. 591 592- Slang doesn't support coherent load/stores. 593 594- Slang doesn't support custom alignment specification. 595 596- Slang currently does not support pointers to immutable values, i.e. `const T*`. 597 598## `DescriptorHandle` for Bindless Descriptor Access 599 600Slang supports the `DescriptorHandle<T>` type that represents a bindless handle to a resource. This feature provides a portable way of implementing 601the bindless resource idiom. When targeting HLSL, GLSL and SPIRV where descriptor types (e.g. textures, samplers and buffers) are opaque handles, 602`DescriptorHandle<T>` will translate into a `uint2` so it can be defined in any memory location. The underlying `uint2` value is treated as an index 603to access the global descriptor heap or resource array in order to obtain the actual resource handle. On targets with where resource handles 604are not opaque handles, `DescriptorHandle<T>` maps to `T` and will have the same size and alignment defined by the target. 605 606`DescriptorHandle<T>` is declared as: 607``` slang 608struct DescriptorHandle<T> where T:IOpaqueDescriptor {} 609``` 610where `IOpaqueDescriptor` is an interface implemented by all resource types, including textures, 611`ConstantBuffer`, `RaytracingAccelerationStructure`, `SamplerState`, `SamplerComparisonState` and all types of `StructuredBuffer`. 612 613You may also write `Texture2D.Handle` as a short-hand of `DescriptorHandle<Texture2D>`. 614 615`DescriptorHandle<T>` supports `operator *`, `operator ->`, and can implicitly convert to `T`, for example: 616 617``` slang 618uniform StructuredBuffer<DescriptorHandle<Texture2D>> textures; 619uniform int textureIndex; 620 621// define a descriptor handle using builtin convenience typealias: 622uniform StructuredBuffer<float4>.Handle output; 623 624[numthreads(1,1,1)] 625void main() 626{ 627output[0] = textures[textureIndex].Load(int3(0)); 628 629// Alternatively, this syntax is also valid: 630(*output)[0] = textures[textureIndex]->Load(int3(0)); 631} 632``` 633 634By default, when targeting HLSL, `DescriptorHandle<T>` translates to uses of `ResourceDescriptorHeap[index]` and `SamplerDescriptorHeap[index]`. 635In particular, when combined with combined texture sampler types (e.g. `Sampler2D`), Slang will fetch the texture using the first 636component of the handle, and the sampler state from the second component of the handle. For example: 637 638``` 639uniform DescriptorHandle<Sampler2D> s; 640void test() 641{ 642s.Sample(uv); 643} 644``` 645 646translates to: 647 648``` hlsl 649uniform uint2 s ; 650void test () 651{ 652Texture2D ( ResourceDescriptorHeap [ s . x ]). Sample ( 653SamplerState ( SamplerDescriptorHeap [ s . y ]), 654uv 655); 656} 657``` 658 659When targeting SPIRV, Slang will introduce a global array of descriptors and fetch from the global array. 660The descriptor set ID of the global descriptor array can be configured with the `-bindless-space-index` 661(or `CompilerOptionName::BindlessSpaceIndex` when using the API) option. 662 663Default behavior assigns binding-indicies based on descriptor types: 664 665| Enum Value | Vulkan Descriptor Type | Binding Index | 666|------------------------|-------------------------------------------|---------------| 667| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 | 668| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 | 669| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 | 670| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 2 | 671| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 2 | 672| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 2 | 673| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 2 | 674| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 2 | 675| Unknown | Other | 3 | 676 677> `ACCELERATION_STRUCTURE` is excluded from the list of types since Slang by default uses the handle to a `RaytracingAccelerationStructure` as a GPU address, casting the handle to a `RaytracingAccelerationStructure`. This removes the need for a binding-slot of `RaytracingAccelerationStructure`. 678 679> #### Note 680> The default implementation for SPIRV may change in the future if SPIRV is extended to provide what is 681> equivalent to D3D's `ResourceDescriptorHeap` construct. 682 683Users can override the default behavior of convering from bindless handle to resource handle, by providing a 684`getDescriptorFromHandle` in user code. For example: 685 686``` slang 687// All texture and buffer handles are defined in descriptor set 100. 688[vk::binding(0, 100)] 689__DynamicResource<__DynamicResourceKind.General> resourceHandles[]; 690 691// All sampler handles are defined in descriptor set 101. 692[vk::binding(0, 101)] 693__DynamicResource<__DynamicResourceKind.Sampler> samplerHandles[]; 694 695export T getDescriptorFromHandle<T>(DescriptorHandle<T> handle) where T : IOpaqueDescriptor 696{ 697__target_switch 698{ 699case spirv: 700if (T.kind == ResourceKind.Sampler) 701return samplerHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); 702else 703return resourceHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); 704default: 705return defaultGetDescriptorFromHandle(handle); 706} 707} 708``` 709 710Note that the `getDescriptorFromHandle` is not supposed to be called from the user code directly, 711it will be automatically called by the compiler to dereference a `DescriptorHandle<T>` to get `T`. 712Think about providing `getDescriptorFromHandle` as a way to override `operator->` for `DescriptorHandle<T>`. 713 714The `IOpaqueDescriptor` interface is defined as: 715 716``` slang 717interface IOpaqueDescriptor 718{ 719/// The kind of the descriptor. 720static const DescriptorKind kind; 721static const DescriptorAccess descriptorAccess; 722} 723``` 724 725The user can call `defaultGetDescriptorFromHandle` function from their implementation of 726`getDescriptorFromHandle` to dispatch to the default behavior. 727 728Additionally, `defaultGetDescriptorFromHandle()` takes an optional argument whose type is `constexpr BindlessDescriptorOptions`. This parameter allows to specify alternative standard presets for how bindless-indexes are assigned. Note that this is currently only relevant to SPIRV: 729``` slang 730public enum BindlessDescriptorOptions 731{ 732None = 0, /// Bind assuming regular binding model rules. 733VkMutable = 1, /// **Current Default** Bind assuming `VK_EXT_mutable_descriptor_type` 734} 735``` 736 737`None` provides the following bindings for descriptor types: 738 739| Enum Value | Vulkan Descriptor Type | Binding Index | 740|------------------------|-------------------------------------------|---------------| 741| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 | 742| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 | 743| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 | 744| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 3 | 745| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 4 | 746| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 5 | 747| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 6 | 748| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 7 | 749| Unknown | Other | 8 | 750 751`VkMutable` provides the following bindings for descriptor types: 752 753| Enum Value | Vulkan Descriptor Type | Binding Index | 754|------------------------|-------------------------------------------|---------------| 755| Sampler | VK_DESCRIPTOR_TYPE_SAMPLER | 0 | 756| CombinedTextureSampler | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | 1 | 757| Texture_Read | VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE | 2 | 758| Texture_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_IMAGE | 2 | 759| TexelBuffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER | 2 | 760| TexelBuffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER | 2 | 761| Buffer_Read | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | 2 | 762| Buffer_ReadWrite | VK_DESCRIPTOR_TYPE_STORAGE_BUFFER | 2 | 763| Unknown | Other | 3 | 764 765The `kind` and `descriptorAccess` constants allows user code to fetch resources from different locations depending on the type and access of the resource being requested. The `DescriptorKind` and 766`DescriptorAccess` enums are defined as: 767 768``` slang 769enum DescriptorKind 770{ 771Unknown, /// Unknown descriptor kind. 772Texture, /// A texture descriptor. 773CombinedTextureSampler, /// A combined texture and sampler state descriptor. 774Buffer, /// A buffer descriptor. 775Sampler, /// A sampler state descriptor. 776AccelerationStructure, /// A ray tracing acceleration structure descriptor. 777TexelBuffer /// A texel buffer descriptor. 778} 779 780enum DescriptorAccess 781{ 782Unknown = -1, 783Read = 0, 784Write = 1, 785ReadWrite = 2, 786RasterizerOrdered = 3, 787Feedback = 4, 788} 789``` 790 791By default, the value of a `DescriptorHandle<T>` object is assumed to be dynamically uniform across all 792execution threads. If this is not the case, the user is required to mark the `DescriptorHandle` as `nonuniform` 793*immediately* before dereferencing it: 794``` slang 795void test(DescriptorHandle<Texture2D> t) 796{ 797nonuniform(t)->Sample(...); 798} 799``` 800 801If the resource pointer value is not uniform and `nonuniform` is not called, the result may be 802undefined. 803 804 805 806Extensions 807-------------------- 808Slang allows defining additional methods for a type outside its initial definition. For example, suppose we already have a type defined: 809 810``` csharp 811struct MyType 812{ 813int field; 814int get() { return field; } 815} 816``` 817 818You can extend `MyType` with new method members: 819``` csharp 820extension MyType 821{ 822float getNewField() { return newField; } 823} 824``` 825 826All locations that sees the definition of the `extension` can access the new members: 827 828``` csharp 829void test() 830{ 831MyType t; 832float val = t.getNewField(); 833} 834``` 835 836This feature is similar to extensions in Swift and extension methods in C#. 837 838> #### Note: 839> You can only extend a type with additional methods. Extending with additional data fields is not allowed. 840 841Multi-level break 842------------------- 843 844Slang allows `break` statements with a label to jump into any ancestor control flow break points, and not just the immediate parent. 845Example: 846``` 847outer: 848for (int i = 0; i < 5; i++) 849{ 850inner: 851for (int j = 0; j < 10; j++) 852{ 853if (someCondition) 854break outer; 855} 856} 857``` 858 859Force inlining 860----------------- 861Most of the downstream shader compilers will inline all the function calls. However you can instruct Slang compiler to do the inlining 862by using the `[ForceInline]` decoration: 863``` 864[ForceInline] 865int f(int x) { return x + 1; } 866``` 867 868Error handling 869----------------- 870 871Slang supports an error handling mechanism that is superficially similar to 872exceptions in many other languages, but has some unique characteristics. 873 874In contrast to C++ exceptions, this mechanism makes the control flow of errors 875more explicit, and the performance charasteristics are similar to adding an 876if-statement after every potentially throwing function call to check and handle 877the error. 878 879In order to be able to throw an error, a function must declare the type of that 880error with `throws`: 881``` 882enum MyError 883{ 884Failure, 885CatastrophicFailure 886} 887 888int f() throws MyError 889{ 890if (computerIsBroken()) 891throw MyError.CatastrophicFailure; 892return 42; 893} 894``` 895Currently, functions may only throw a single type of error. 896 897To call a function that may throw, you must prepend it with `try`: 898 899``` 900let result = try f(); 901``` 902 903If you don't catch the `try`, related errors are re-thrown and the calling 904function must declare that it `throws` that error type: 905 906``` 907void g() throws MyError 908{ 909// This would not compile if `g()` wasn't declared to throw MyError as well. 910let result = try f(); 911printf("Success: %d\n", result); 912} 913``` 914 915To catch an error, you can use a `do-catch` statement: 916 917``` 918void g() 919{ 920do 921{ 922let result = try f(); 923printf("Success: %d\n", result); 924} 925catch(err: MyError) 926{ 927printf("Not good!\n"); 928} 929} 930``` 931 932You can chain multiple catch statements for different types of errors. 933 934Special Scoping Syntax 935------------------- 936Slang supports three special scoping syntax to allow users to mix in custom decorators and content in the shader code. These constructs allow a rendering engine to define custom meta-data in the shader, or map engine-specific block syntax to a meaningful block that is understood by the compiler via proper `#define`s. 937 938### `__ignored_block` 939An ignored block will be parsed and ignored by the compiler: 940``` 941__ignored_block 942{ 943arbitrary content in the source file, 944will be ignored by the compiler as if it is a comment. 945Can have nested {} here. 946} 947``` 948 949### `__transparent_block` 950Symbols defined in a transparent block will be treated as if they are defined 951in the parent scope: 952``` csharp 953struct MyType 954{ 955__transparent_block 956{ 957int myFunc() { return 0; } 958} 959} 960``` 961Is equivalent to: 962``` csharp 963struct MyType 964{ 965int myFunc() { return 0; } 966} 967``` 968 969### `__file_decl` 970Symbols defined in a `__file_decl` will be treated as if they are defined in 971the global scope. However, symbols defined in different `__file_decl`s is not visible 972to each other. For example: 973``` csharp 974__file_decl 975{ 976void f1() 977{ 978} 979} 980__file_decl 981{ 982void f2() 983{ 984f1(); // error: f1 is not visible from here. 985} 986} 987``` 988 989User Defined Attributes (Experimental) 990------------------- 991 992In addition to many system defined attributes, users can define their own custom attribute types to be used in the `[UserDefinedAttribute(args...)]` syntax. The following example shows how to define a custom attribute type. 993 994``` csharp 995[__AttributeUsage(_AttributeTargets.Var)] 996struct MaxValueAttribute 997{ 998int value; 999string description; 1000}; 1001 1002[MaxValue(12, "the scale factor")] 1003uniform int scaleFactor; 1004``` 1005 1006In the above code, the `MaxValueAttribute` struct type is decorated with the `[__AttributeUsage]` attribute, which informs that `MaxValueAttribute` type should be interpreted as a definition for a user-defined attribute, `[MaxValue]`, that can be used to decorate all variables or fields. The members of the struct defines the argument list for the attribute. 1007 1008The `scaleFactor` uniform parameter is declared with the user defined `[MaxValue]` attribute, providing two arguments for `value` and `description`. 1009 1010The `_AttributeTargets` enum is used to restrict the type of decls the attribute can apply. Possible values of `_AttributeTargets` can be `Function`, `Param`, `Struct` or `Var`. 1011 1012The usage of user-defined attributes can be queried via Slang's reflection API through `TypeReflection` or `VariableReflection`'s `getUserAttributeCount`, `getUserAttributeByIndex` and `findUserAttributeByName` methods.