static struct vrend_linked_shader_program *add_shader_program(struct vrend_sub_context *sub_ctx,
                                                              struct vrend_shader *vs,
                                                              struct vrend_shader *fs,
                                                              struct vrend_shader *gs,
                                                              struct vrend_shader *tcs,
                                                              struct vrend_shader *tes)
{
   struct vrend_linked_shader_program *sprog = CALLOC_STRUCT(vrend_linked_shader_program);
   char name[64];
   int i;
   GLuint prog_id;
   GLint lret;
   int last_shader;
   if (!sprog)
      return NULL;

   prog_id = glCreateProgram();
   glAttachShader(prog_id, vs->id);
   if (tcs && tcs->id > 0)
      glAttachShader(prog_id, tcs->id);
   if (tes && tes->id > 0)
      glAttachShader(prog_id, tes->id);

   if (gs) {
      if (gs->id > 0)
         glAttachShader(prog_id, gs->id);
      set_stream_out_varyings(sub_ctx, prog_id, &gs->sel->sinfo);
   } else if (tes)
      set_stream_out_varyings(sub_ctx, prog_id, &tes->sel->sinfo);
   else
      set_stream_out_varyings(sub_ctx, prog_id, &vs->sel->sinfo);
   glAttachShader(prog_id, fs->id);

   if (fs->sel->sinfo.num_outputs > 1) {
      sprog->dual_src_linked = util_blend_state_is_dual(&sub_ctx->blend_state, 0);
      if (sprog->dual_src_linked) {
         if (has_feature(feat_dual_src_blend)) {
            if (!vrend_state.use_gles) {
               glBindFragDataLocationIndexed(prog_id, 0, 0, "fsout_c0");
               glBindFragDataLocationIndexed(prog_id, 0, 1, "fsout_c1");
            } else {
               glBindFragDataLocationIndexedEXT(prog_id, 0, 0, "fsout_c0");
               glBindFragDataLocationIndexedEXT(prog_id, 0, 1, "fsout_c1");
            }
         } else {
            vrend_report_context_error(sub_ctx->parent, VIRGL_ERROR_CTX_ILLEGAL_DUAL_SRC_BLEND, 0);
         }
      } else if (has_feature(feat_dual_src_blend)) {
         for (int i = 0; i < fs->sel->sinfo.num_outputs; ++i) {
            if (fs->sel->sinfo.fs_output_layout[i] >= 0) {
               char buf[64];
               snprintf(buf, sizeof(buf), "fsout_c%d", fs->sel->sinfo.fs_output_layout[i]);
               if (!vrend_state.use_gles)
                  glBindFragDataLocationIndexed(prog_id, fs->sel->sinfo.fs_output_layout[i], 0, buf);
               else
                  glBindFragDataLocationIndexedEXT(prog_id, fs->sel->sinfo.fs_output_layout[i], 0, buf);
            }
         }
      } else {
         vrend_report_context_error(sub_ctx->parent, VIRGL_ERROR_CTX_UNSUPPORTED_FUNCTION, 0);
      }
   } else
      sprog->dual_src_linked = false;

   if (has_feature(feat_gles31_vertex_attrib_binding)) {
      uint32_t mask = vs->sel->sinfo.attrib_input_mask;
      while (mask) {
         i = u_bit_scan(&mask);
         snprintf(name, 32, "in_%d", i);
         glBindAttribLocation(prog_id, i, name);
      }
   }

   glLinkProgram(prog_id);

   glGetProgramiv(prog_id, GL_LINK_STATUS, &lret);
   if (lret == GL_FALSE) {
      char infolog[65536];
      int len;
      glGetProgramInfoLog(prog_id, 65536, &len, infolog);
      vrend_printf("got error linking\n%s\n", infolog);
      /* dump shaders */
      vrend_report_context_error(sub_ctx->parent, VIRGL_ERROR_CTX_ILLEGAL_SHADER, 0);
      vrend_shader_dump(vs);
      if (tcs)
         vrend_shader_dump(tcs);
      if (tes)
         vrend_shader_dump(tes);
      if (gs)
         vrend_shader_dump(gs);
      vrend_shader_dump(fs);
      glDeleteProgram(prog_id);
      free(sprog);
      return NULL;
   }

   sprog->ss[PIPE_SHADER_VERTEX] = vs;
   sprog->ss[PIPE_SHADER_FRAGMENT] = fs;
   sprog->vs_fs_key = (((uint64_t)fs->id) << 32) | (vs->id & ~VREND_PROGRAM_NQUEUE_MASK) |
                      (sprog->dual_src_linked ? 1 : 0);

   sprog->ss[PIPE_SHADER_GEOMETRY] = gs;
   sprog->ss[PIPE_SHADER_TESS_CTRL] = tcs;
   sprog->ss[PIPE_SHADER_TESS_EVAL] = tes;

   list_add(&sprog->sl[PIPE_SHADER_VERTEX], &vs->programs);
   list_add(&sprog->sl[PIPE_SHADER_FRAGMENT], &fs->programs);
   if (gs)
      list_add(&sprog->sl[PIPE_SHADER_GEOMETRY], &gs->programs);
   if (tcs)
      list_add(&sprog->sl[PIPE_SHADER_TESS_CTRL], &tcs->programs);
   if (tes)
      list_add(&sprog->sl[PIPE_SHADER_TESS_EVAL], &tes->programs);

   last_shader = tes ? PIPE_SHADER_TESS_EVAL : (gs ? PIPE_SHADER_GEOMETRY : PIPE_SHADER_FRAGMENT);
   sprog->id = prog_id;

   list_addtail(&sprog->head, &sub_ctx->gl_programs[vs->id & VREND_PROGRAM_NQUEUE_MASK]);

   if (fs->key.pstipple_tex)
      sprog->fs_stipple_loc = glGetUniformLocation(prog_id, "pstipple_sampler");
   else
      sprog->fs_stipple_loc = -1;

   if (vrend_state.use_core_profile) {
       sprog->fs_alpha_ref_val_loc = glGetUniformLocation(prog_id, "alpha_ref_val");
       sprog->fs_alpha_func_loc = glGetUniformLocation(prog_id, "alpha_func");
   } else {
       sprog->fs_alpha_ref_val_loc = -1;
       sprog->fs_alpha_func_loc = -1;
   }

   sprog->vs_ws_adjust_loc = glGetUniformLocation(prog_id, "winsys_adjust_y");

   vrend_use_program(sub_ctx, prog_id);

   int next_ubo_id = 0, next_sampler_id = 0;
   for (int shader_type = PIPE_SHADER_VERTEX; shader_type <= last_shader; shader_type++) {
      if (!sprog->ss[shader_type])
         continue;

      next_sampler_id = bind_sampler_locs(sprog, shader_type, next_sampler_id);
      bind_const_locs(sprog, shader_type);
      next_ubo_id = bind_ubo_locs(sprog, shader_type, next_ubo_id);
      bind_image_locs(sprog, shader_type);
      bind_ssbo_locs(sprog, shader_type);
   }

   if (!has_feature(feat_gles31_vertex_attrib_binding)) {
      if (vs->sel->sinfo.num_inputs) {
         sprog->attrib_locs = calloc(vs->sel->sinfo.num_inputs, sizeof(uint32_t));
         if (sprog->attrib_locs) {
            for (i = 0; i < vs->sel->sinfo.num_inputs; i++) {
               snprintf(name, 32, "in_%d", i);
               sprog->attrib_locs[i] = glGetAttribLocation(prog_id, name);
            }
         }
      } else
         sprog->attrib_locs = NULL;
   }

   sprog->clip_enabled_loc = glGetUniformLocation(prog_id, "clip_plane_enabled");
   for (i = 0; i < VIRGL_NUM_CLIP_PLANES; i++) {
      snprintf(name, 32, "clipp[%d]", i);
      sprog->clip_locs[i] = glGetUniformLocation(prog_id, name);
   }
   return sprog;
}