Task Shader Operational Model in GL_EXT_mesh_shader

This article provides an overview of the primary operational model of task shaders as introduced by the GL_EXT_mesh_shader extension in GLSL. It explains how task shaders function as a programmable, compute-centric amplification and culling stage prior to mesh processing, how they dynamically dispatch mesh shader workgroups, and how data is transferred between stages using task payloads.

Pipeline Position and Purpose

In the GL_EXT_mesh_shader execution model, the task shader acts as an optional, coarse-grained pre-pass to the mesh shader. It replaces fixed-function input assembly and early geometry stages (such as vertex, tessellation, and geometry shaders) with a flexible compute-like execution paradigm.

The primary responsibility of the task shader is to evaluate scene data at a high level—such as bounding volumes, instances, or bounding boxes—and determine how many fine-grained mesh shader workgroups are required to process the actual geometry.

Compute-Style Workgroup Dispatch

Task shaders operate identically to compute shaders in terms of execution hierarchy:

  • Dispatch: Execution is triggered by draw commands such as glDrawMeshTasksEXT, which specify a three-dimensional grid of task workgroups (taskCountX, taskCountY, taskCountZ).
  • Workgroups: Each task shader runs as a workgroup composed of a user-defined number of local invocations defined by the layout(local_size_x, local_size_y, local_size_z) in; declaration.
  • Shared Memory: Invocations within a task workgroup can cooperate using standard workgroup shared memory (shared) and execution barriers (barrier()).

Dynamic Mesh Amplification and Early Culling

The defining operational feature of a task shader is its ability to dynamically control the number of downstream mesh shader workgroups launched on the GPU.

Instead of outputting vertices or primitives directly, a task shader terminates its execution by calling the built-in function:

void EmitMeshTasksEXT(uint groupCountX, uint groupCountY, uint groupCountZ);
  • Amplification: A single task workgroup can spawn thousands of mesh workgroups by passing non-zero dimensions to EmitMeshTasksEXT. This enables multi-level level-of-detail (LOD) expansion and procedural generation directly on hardware.
  • Culling: If a task workgroup determines that an object or cluster is outside the view frustum or occluded, it can call EmitMeshTasksEXT(0, 0, 0). This discards the workload immediately, preventing downstream mesh shaders from ever running and avoiding memory traffic.
  • Execution Constraint: Only one invocation in the task workgroup needs to call EmitMeshTasksEXT, or all invocations must call it with the same arguments.

The Task Payload

To send contextual information to the generated mesh shader workgroups, the task shader writes data to a dedicated memory allocation termed the task payload.

  • Declared using the taskPayloadSharedEXT storage qualifier (e.g., taskPayloadSharedEXT struct TaskData { ... } payload;).
  • The payload acts as per-task-workgroup shared memory that persists across the boundary into the generated mesh shader workgroups.
  • All mesh shader workgroups spawned by a specific task workgroup receive read-only access to this single payload instance, allowing them to index into global memory arrays, determine cluster offsets, or read transform matrices computed by the parent task shader.