Purpose of callableDataInEXT in GLSL Shaders

In Vulkan ray tracing pipelines using the GL_EXT_ray_tracing extension, the callableDataInEXT storage qualifier designates the incoming parameter block inside a callable shader. Callable shaders act as dynamically dispatched subroutines that can be invoked from other ray tracing stages, such as ray generation, closest hit, or miss shaders. This article explains the role of callableDataInEXT, how it enables bidirectional data sharing between caller and callee, and the rules governing its use.

Role and Functionality

When a shader invokes a callable shader using the executeCallableEXT() intrinsic, it needs a way to pass parameters to that shader and receive processed results in return. The caller shader allocates and populates a variable decorated with the callableDataEXT qualifier.

Inside the executed callable shader stage itself, the incoming data is received via a variable declared with callableDataInEXT. Essentially, callableDataInEXT is the callee's interface to the caller's callableDataEXT memory payload.

Read and Write Semantics

Unlike traditional shader input qualifiers (such as in), which are strictly read-only, callableDataInEXT allows both read and write operations:

  • Inputs (Reading): The callable shader reads initial parameters, configuration values, or state passed in from the calling shader.
  • Outputs (Writing): The callable shader can modify the data structure before it finishes execution. Once control returns to the caller, these modifications are preserved, allowing the payload to serve as a return value.

Usage and Layout Matching

To ensure proper data binding between the calling shader and the callable shader, the layout locations must match:

  1. In the Caller Shader:

    layout(location = 0) callableDataEXT struct ParameterBlock {
        vec3 incomingVector;
        vec4 calculatedResult;
    } callerPayload;
    
    void main() {
        callerPayload.incomingVector = vec3(1.0, 0.0, 0.0);
        executeCallableEXT(sbtRecordIndex, 0); // Invokes callable shader with location 0
        // callerPayload.calculatedResult is now available
    }
  2. In the Callable Shader (.rcall):

    layout(location = 0) callableDataInEXT struct ParameterBlock {
        vec3 incomingVector;
        vec4 calculatedResult;
    } calleePayload;
    
    void main() {
        // Read input data
        vec3 dir = calleePayload.incomingVector;
    
        // Write result back to caller
        calleePayload.calculatedResult = vec4(dir * 2.0, 1.0);
    }

Key Considerations

  • Scope and Lifetime: The variable marked with callableDataInEXT is uniquely associated with the specific invocation of executeCallableEXT(). Its lifetime spans the execution of the callable shader.
  • Matching Layouts: The structure definition and size declared with callableDataInEXT must precisely match the structure defined by the caller's callableDataEXT bound to the corresponding location to prevent undefined behavior and memory corruption.
  • Nesting: A callable shader can itself call another callable shader. When doing so, it receives data via callableDataInEXT, but must use a distinct callableDataEXT declaration for outgoing calls deeper into the chain.