taskPayloadSharedEXT in GLSL Task to Mesh Communication

This article provides a concise guide to the taskPayloadSharedEXT storage qualifier introduced with the Vulkan mesh shading extension (VK_EXT_mesh_shader) in GLSL. It explains the qualifier's architectural purpose, how it coordinates dynamic data exchange from task shaders to mesh shaders, the memory and synchronization rules governing its use, and best practices to maximize memory throughput.

What is taskPayloadSharedEXT?

The taskPayloadSharedEXT storage qualifier designates a user-defined block of variables as the primary communication payload passed from a task shader workgroup to the downstream mesh shader workgroups it spawns. In the mesh shading pipeline, task shaders (also known as amplification shaders) evaluate geometry at a coarse level—performing tasks such as frustum culling, occlusion culling, and level-of-detail selection—before launching mesh shader workgroups. The taskPayloadSharedEXT qualifier defines the shared memory space that transfers the results of these operations directly between stages without writing to high-latency global device memory (such as SSBOs).

Core Mechanisms and Data Flow

  1. Shared Workspace in the Task Shader: Within the task shader, variables declared with taskPayloadSharedEXT behave similarly to workgroup shared memory (shared). All invocations within a single task shader workgroup can read and write to this payload structure during execution.
  2. Payload Transmission: The task shader issues EmitMeshTasksEXT(x, y, z) to spawn a grid of mesh shader workgroups. When this occurs, the contents of the payload are made available to all spawned mesh shader workgroups.
  3. Read-Only Consumption in Mesh Shaders: In the receiving mesh shaders, the identical payload structure must be declared using taskPayloadSharedEXT. However, within the mesh shader stage, the payload is strictly read-only. All invocations across all spawned mesh workgroups read from this shared dataset to determine which primitives, vertices, or meshlet indices they are responsible for processing.

Typical Syntax and Declaration

The payload definition must be structurally identical in both the task shader and the mesh shader.

Task Shader:

#version 460
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 32) in;

struct TaskData {
    uint meshletIndices[32];
    uint visibleCount;
};

taskPayloadSharedEXT TaskData payload;

void main() {
    uint gID = gl_GlobalInvocationID.x;
    
    // Perform culling and populate payload
    if (gl_LocalInvocationIndex == 0) {
        payload.visibleCount = 0;
    }
    barrier();

    // Cooperative write to payload
    if (/* meshlet is visible */) {
        uint index = atomicAdd(payload.visibleCount, 1);
        payload.meshletIndices[index] = gID;
    }
    barrier();

    // Emit mesh tasks matching the number of visible meshlets
    if (gl_LocalInvocationIndex == 0) {
        EmitMeshTasksEXT(payload.visibleCount, 1, 1);
    }
}

Mesh Shader:

#version 460
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 32) in;
layout(triangles, max_vertices = 64, max_primitives = 126) out;

struct TaskData {
    uint meshletIndices[32];
    uint visibleCount;
};

taskPayloadSharedEXT TaskData payload;

void main() {
    // Read the culled meshlet ID assigned to this mesh workgroup
    uint meshletID = payload.meshletIndices[gl_WorkGroupID.x];
    
    // Process geometry for meshletID...
}

Synchronization and Memory Rules

Because the payload functions as workgroup shared memory during the task shader stage, proper synchronization is required:

  • Task shader invocations writing to taskPayloadSharedEXT must ensure all writes are complete before EmitMeshTasksEXT() is called. A workgroup barrier (barrier()) or memory barrier (memoryBarrierShared()) must precede the emit command if multiple invocations write to the payload.
  • Mesh shaders do not need to synchronize reads from the payload because the driver ensures that payload data is fully committed and visible to the spawned mesh workgroups prior to their execution.

Performance Considerations

  • Size Limits: Hardware imposes a limit on the size of the payload (queryable via maxTaskPayloadSize in Vulkan properties, typically 16 KB). Exceeding hardware thresholds will cause compilation errors or severely limit execution occupancy.
  • Occupancy Impact: Larger payloads reduce the number of active task and mesh workgroups that can reside concurrently on a GPU Compute Unit or Streaming Multiprocessor. Keep the payload footprint minimal by packing data and storing only per-meshlet indices, offsets, or compact draw parameters rather than full vertex records.