Another Minor Annoyance

Instancing

Once more going way out of order since it’s fresh in my mind, today will be a brief look at gl_InstanceID and a problem I found there while hooking up ARB_base_instance.

gl_InstanceID is an input for vertex shaders which provides the current instance being processed by the shader. It has a range of [0, instanceCount], and this breaks the heck out of Vulkan.

Vulkan Instancing

In Vulkan, this same variable instead has a range of [firstInstance, firstInstance + instanceCount]. This difference isn’t explicitly documented anywhere, which means keen readers will need to pick up on the difference as noted between Vulkan spec 14.7. Built-In Variables and the OpenGL wiki, because it isn’t even stated in the GLSL 1.40 spec (or anywhere else that I could find) what the range of this variable is.

Fixing

Here’s the generic int builtin loading function zink uses in ntv to handle instructions which load builtin variables:

static void
emit_load_uint_input(struct ntv_context *ctx, nir_intrinsic_instr *intr, SpvId *var_id, const char *var_name, SpvBuiltIn builtin)
{
   SpvId var_type = spirv_builder_type_uint(&ctx->builder, 32);
   if (!*var_id)
      *var_id = create_builtin_var(ctx, var_type,
                                   SpvStorageClassInput,
                                   var_name,
                                   builtin);

   SpvId result = spirv_builder_emit_load(&ctx->builder, var_type, *var_id);
   assert(1 == nir_dest_num_components(intr->dest));
   store_dest(ctx, &intr->dest, result, nir_type_uint);
}

var_id here is a pointer to the SpvId member of struct ntv_context where the created variable is stored for later use, var_name is the name of the variable, and builtin is the SPIRV name of the builtin that’s being loaded.

gl_InstanceID comes through here as SpvBuiltInInstanceIndex. To fix the difference in semantics here, I added a few lines:

static void
emit_load_uint_input(struct ntv_context *ctx, nir_intrinsic_instr *intr, SpvId *var_id, const char *var_name, SpvBuiltIn builtin)
{
   SpvId var_type = spirv_builder_type_uint(&ctx->builder, 32);
   if (!*var_id)
      *var_id = create_builtin_var(ctx, var_type,
                                   SpvStorageClassInput,
                                   var_name,
                                   builtin);

   SpvId result = spirv_builder_emit_load(&ctx->builder, var_type, *var_id);
   assert(1 == nir_dest_num_components(intr->dest));
   if (builtin == SpvBuiltInInstanceIndex) {
      /* GL's gl_InstanceID always begins at 0, so we have to normalize with gl_BaseInstance */
      SpvId base = spirv_builder_emit_load(&ctx->builder, var_type, ctx->base_instance_var);
      result = emit_binop(ctx, SpvOpISub, var_type, result, base);
   }
   store_dest(ctx, &intr->dest, result, nir_type_uint);
}

Now when loading gl_InstanceID, gl_BaseInstance is also loaded and subtracted to give a zero-indexed value.

Written on August 3, 2020