yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Yong HeAdd legalization for 0-sized arrays. (#7327)2d7106640

master
28.3 KiB1012 linesraw

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 getter and setter methods. 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, use DescriptorHandle<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 ValueVulkan Descriptor TypeBinding Index
SamplerVK_DESCRIPTOR_TYPE_SAMPLER0
CombinedTextureSamplerVK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER1
Texture_ReadVK_DESCRIPTOR_TYPE_SAMPLED_IMAGE2
Texture_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_IMAGE2
TexelBuffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER2
TexelBuffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER2
Buffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER2
Buffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_BUFFER2
UnknownOther3

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.

Note

The default implementation for SPIRV may change in the future if SPIRV is extended to provide what is equivalent to D3D's ResourceDescriptorHeap construct.

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 ValueVulkan Descriptor TypeBinding Index
SamplerVK_DESCRIPTOR_TYPE_SAMPLER0
CombinedTextureSamplerVK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER1
Texture_ReadVK_DESCRIPTOR_TYPE_SAMPLED_IMAGE2
Texture_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_IMAGE3
TexelBuffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER4
TexelBuffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER5
Buffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER6
Buffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_BUFFER7
UnknownOther8

VkMutable provides the following bindings for descriptor types:

Enum ValueVulkan Descriptor TypeBinding Index
SamplerVK_DESCRIPTOR_TYPE_SAMPLER0
CombinedTextureSamplerVK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER1
Texture_ReadVK_DESCRIPTOR_TYPE_SAMPLED_IMAGE2
Texture_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_IMAGE2
TexelBuffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER2
TexelBuffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER2
Buffer_ReadVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER2
Buffer_ReadWriteVK_DESCRIPTOR_TYPE_STORAGE_BUFFER2
UnknownOther3

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{
46    int f();
47}
48```
49
50Slang also supports the abbreviated syntax for defining nested namespaces:
51```csharp
52namespace ns1.ns2
53{
54    int f();
55}
56// equivalent to:
57namespace ns1::ns2
58{
59    int f();
60}
61// equivalent to:
62namespace ns1
63{
64    namespace ns2
65    {
66        int 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{
75    ns1.ns2.f();
76    ns1::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{
84    int f();
85    int 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{
94    int 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{
111    int compute(int a, int b)
112    {
113        return 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{
129    static int staticMethod(int a, int b)
130    {
131        return 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{
157    int count;
158    
159    [mutating]
160    void setCount(int x) { count = x; }
161
162    // This would fail to compile.
163    // void setCount2(int x) { count = x; }
164}
165
166void test()
167{
168    Foo f;
169    f.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{
179    uint flag;
180
181    property uint highBits
182    {
183        get { return flag >> 16; }
184        set { flag = (flag & 0xFF) + (newValue << 16); }
185    }
186};
187```
188
189Or equivalently in a "modern" syntax:
190
191```csharp
192struct MyType
193{
194    uint flag;
195
196    property highBits : uint
197    {
198        get { return flag >> 16; }
199        set { 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{
208    set(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{
230    int myVal;
231    __init(int a, int b)
232    {
233        myVal = 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{
253    int myVal;
254    __init()
255    {
256        myVal = 10;
257    }
258};
259
260int test()
261{
262    MyType test;
263    return 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{
271    int myVal1;
272    __init() {myVal1 = 22;}
273}
274
275struct MyType1 : MyType_Base
276{
277    int myVal2;
278    __init()
279    {
280        // implicitly calls `MyType_Base::__init()`
281        myVal2 = 15;
282    }
283}
284testMyType1()
285{
286    MyType1 a;
287    // a.myVal1 == 22
288    // a.myVal2 == 15
289}
290
291struct MyType2 : MyType_Base
292{
293}
294testMyType2()
295{
296    MyType2 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{
307    int myVal = 5;
308}
309```
310
311## Operator Overloading
312
313Slang allows defining operator overloads as global methods:
314```csharp
315struct MyType
316{
317    int val;
318    __init(int x) { val = x; }
319}
320
321MyType operator+(MyType a, MyType b)
322{
323    return MyType(a.val + b.val);
324}
325
326int test()
327{
328    MyType rs = MyType(1) + MyType(2);
329    return 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{
338    int operator()(float v)
339    {
340        // ...
341    }
342}
343void test()
344{
345    MyFunctor f;
346    int x = f(1.0f); // calls MyFunctor::operator().
347    int 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{
357    int val[12];
358    __subscript(int x, int y) -> int
359    {
360        get { return val[x*3 + y]; }
361        set { val[x*3+y] = newValue; }
362    }
363}
364int test()
365{
366    MyType rs;
367    rs[0, 0] = 1;
368    rs[1, 0] = rs[0, 0] + 1;
369    return 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{
429    int val;
430}
431
432int useVal(Optional<MyType> p)
433{
434    if (p == none)        // Equivalent to `!p.hasValue`
435        return 0;
436    return p.value.val;
437}
438
439int caller()
440{
441    MyType v;
442    v.val = 0;
443    useVal(v);  // OK to pass `MyType` to `Optional<MyType>`.
444    useVal(none);  // OK to pass `none` to `Optional<MyType>`.
445    return 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{
460    property float3 position{get;}
461    property Optional<float3> normal{get;}
462    property Optional<float3> color{get;}
463}
464
465struct Vertex<bool hasNormal, bool hasColor> : IVertex
466{
467    private float3 m_position;
468    private Conditional<float3, hasNormal> m_normal;
469    private Conditional<float3, hasColor> m_color;
470
471    __init(float3 position, float3 normal, float3 color)
472    {
473        m_position = position;
474        m_normal = normal;
475        m_color = color;
476    }
477
478    property float3 position
479    {
480        get { return m_position; }
481    }
482    property Optional<float3> normal
483    {
484        get { return m_normal; }
485    }
486    property Optional<float3> color
487    {
488        get { 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{
514    if (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{
528    int a;
529    float2 b;
530    uint 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{
552    int a;
553};
554
555int test(MyType* pObj)
556{
557    MyType* pNext = pObj + 1;
558    MyType* pNext2 = &pNext[1];
559    return pNext.a + pNext->a + (*pNext2).a + pNext2[0].a;
560}
561
562cbuffer Constants
563{
564    MyType *ptr;
565};
566
567int validTest()
568{
569    return test(ptr);
570}
571
572int invalidTest()
573{
574    // cannot produce a pointer from a local variable 
575    MyType obj;
576    return 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{
627    output[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{
642    s.Sample(uv);
643}
644```
645
646translates to:
647
648```hlsl
649uniform uint2 s;
650void test()
651{
652    Texture2D(ResourceDescriptorHeap[s.x]).Sample(
653        SamplerState(SamplerDescriptorHeap[s.y]),
654        uv
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    {
699    case spirv:
700        if (T.kind == ResourceKind.Sampler)
701            return samplerHandles[((uint2)handle).x].asOpaqueDescriptor<T>();
702        else
703            return resourceHandles[((uint2)handle).x].asOpaqueDescriptor<T>();
704    default:
705        return 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.
720    static const DescriptorKind kind;
721    static 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{
732    None = 0,      /// Bind assuming regular binding model rules.
733    VkMutable = 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{
771    Unknown, /// Unknown descriptor kind.
772    Texture, /// A texture descriptor.
773    CombinedTextureSampler, /// A combined texture and sampler state descriptor.
774    Buffer, /// A buffer descriptor.
775    Sampler, /// A sampler state descriptor.
776    AccelerationStructure, /// A ray tracing acceleration structure descriptor.
777    TexelBuffer /// A texel buffer descriptor.
778}
779
780enum DescriptorAccess
781{
782    Unknown = -1,
783    Read = 0,
784    Write = 1,
785    ReadWrite = 2,
786    RasterizerOrdered = 3,
787    Feedback = 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{
797    nonuniform(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{
813    int field;
814    int get() { return field; }
815}
816```
817
818You can extend `MyType` with new method members:
819```csharp
820extension MyType
821{
822    float 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{
831    MyType t;
832    float 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{
850    inner:
851    for (int j = 0; j < 10; j++)
852    {
853        if (someCondition)
854            break 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{
884    Failure,
885    CatastrophicFailure
886}
887
888int f() throws MyError
889{
890    if (computerIsBroken())
891        throw MyError.CatastrophicFailure;
892    return 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.
910    let result = try f();
911    printf("Success: %d\n", result);
912}
913```
914
915To catch an error, you can use a `do-catch` statement:
916
917```
918void g()
919{
920    do
921    {
922        let result = try f();
923        printf("Success: %d\n", result);
924    }
925    catch(err: MyError)
926    {
927        printf("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{
943    arbitrary content in the source file,
944    will be ignored by the compiler as if it is a comment.
945    Can 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    {
957        int myFunc() { return 0; }
958    }
959}
960```
961Is equivalent to:
962```csharp
963struct MyType
964{
965    int 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{
976    void f1()
977    {
978    }
979}
980__file_decl
981{
982    void f2()
983    {
984        f1(); // 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{
998    int value;
999    string 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.