EmitMeshTasksEXT Execution Syntax and Thread Model
This article provides an overview of the
EmitMeshTasksEXT function in GLSL task shaders under the
VK_EXT_mesh_shader extension. It details the precise
execution syntax, rules governing invocation behavior and control flow,
the thread allocation model from task workgroups to mesh workgroups, and
payload data synchronization requirements.
Execution Syntax
The EmitMeshTasksEXT function is available exclusively
within GLSL task shaders enabled by the GL_EXT_mesh_shader
extension. Its function signature is defined as follows:
void EmitMeshTasksEXT(uint groupCountX, uint groupCountY, uint groupCountZ);Parameters:
groupCountX: The number of mesh shader workgroups to spawn along the X dimension.groupCountY: The number of mesh shader workgroups to spawn along the Y dimension.groupCountZ: The number of mesh shader workgroups to spawn along the Z dimension.
Setting any dimension to 0 results in zero mesh shader
workgroups being spawned, effectively culling all downstream geometry
for that task workgroup.
Basic Usage Example:
#version 460
#extension GL_EXT_mesh_shader : require
layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
struct TaskPayload {
uint baseIndex;
uint meshletIndices[64];
};
taskPayloadSharedEXT TaskPayload payload;
void main() {
// Perform culling or compute meshlet distribution across invocations
uint threadID = gl_LocalInvocationIndex;
// Write shared payload data
if (threadID == 0) {
payload.baseIndex = 128;
}
// Ensure all payload writes are complete
memoryBarrierShared();
barrier();
// Spawn 16 mesh shader workgroups across a 1D grid
EmitMeshTasksEXT(16, 1, 1);
}Control Flow and Execution Rules
The execution of EmitMeshTasksEXT differs significantly
from standard shader functions due to specific architectural and
compiler constraints:
Workgroup Uniform Control Flow: All invocations within the task shader workgroup must execute
EmitMeshTasksEXTin uniform control flow. It cannot be conditionally placed within a dynamic branch where only a subset of threads execute it (such as insideif (gl_LocalInvocationIndex == 0)). Divergent execution leads to undefined behavior.Workgroup Termination: Executing
EmitMeshTasksEXTimplicitly terminates the execution of the calling task shader workgroup. Any code placed afterEmitMeshTasksEXTwill either not be executed or cause compilation/validation errors depending on the driver implementation.Single Execution Limit: Exactly one dynamic execution of
EmitMeshTasksEXTis permitted per task shader workgroup. Calling it multiple times in loops or separate control paths is invalid.
Thread Allocation and Scheduling Model
The task-to-mesh pipeline establishes a hierarchical compute model consisting of two linked workgroup stages:
[ DrawMeshTasksEXT Command ]
│
▼
┌───────────────────────────┐
│ Task Shader Workgroup Grid│
│ (User-defined XYZ count) │
└─────────────┬─────────────┘
│ EmitMeshTasksEXT(gx, gy, gz)
▼
┌───────────────────────────┐
│ Mesh Shader Workgroup Grid│
│ (gx × gy × gz per task WG)│
└───────────────────────────┘
1. Input Workgroup Sizing
The task shader itself is dispatched via Vulkan API commands such as
vkCmdDrawMeshTasksEXT. The local thread count per task
workgroup is defined by the standard compute layout qualifier:
layout(local_size_x = X, local_size_y = Y, local_size_z = Z) in;2. Downstream Mesh Workgroup Generation
When EmitMeshTasksEXT(gx, gy, gz) runs, the GPU
allocates a 3D grid of mesh shader workgroups totaling
gx * gy * gz.
- Each spawned mesh workgroup inherits the execution context of the specific task workgroup that emitted it.
- Inside the spawned mesh shader, the workgroup coordinates are read
using
gl_WorkGroupID, ranging from(0, 0, 0)up to(gx - 1, gy - 1, gz - 1). - Each mesh workgroup operates with its own independent thread count
as defined by the mesh shader’s
layout(local_size_x, ...)directive.
3. Payload Allocation and Synchronization
Data is passed from the task shader workgroup to the downstream mesh
shader workgroups via variables marked with the
taskPayloadSharedEXT storage qualifier.
- Lifetime and Scope: The payload memory persists until all mesh shader workgroups launched by that specific task workgroup have completed.
- Synchronization: Writes to
taskPayloadSharedEXTmemory must be made visible to all threads usingmemoryBarrierShared()and synchronized usingbarrier()prior to callingEmitMeshTasksEXT. Downstream mesh shader workgroups can read this data concurrently as read-only memory.