static struct vrend_linked_shader_program *add_shader_program(struct vrend_context *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 id;
   int last_shader;
   bool do_patch = false;
   if (!sprog)
      return NULL;

   /* need to rewrite VS code to add interpolation params */
   if (gs && gs->compiled_fs_id != fs->id)
      do_patch = true;
   if (!gs && tes && tes->compiled_fs_id != fs->id)
      do_patch = true;
   if (!gs && !tes && vs->compiled_fs_id != fs->id)
      do_patch = true;

   if (do_patch) {
      bool ret;

      if (gs)
         vrend_patch_vertex_shader_interpolants(ctx, &ctx->shader_cfg, &gs->glsl_strings,
                                                &gs->sel->sinfo,
                                                &fs->sel->sinfo, "gso", fs->key.flatshade);
      else if (tes)
         vrend_patch_vertex_shader_interpolants(ctx, &ctx->shader_cfg, &tes->glsl_strings,
                                                &tes->sel->sinfo,
                                                &fs->sel->sinfo, "teo", fs->key.flatshade);
      else
         vrend_patch_vertex_shader_interpolants(ctx, &ctx->shader_cfg, &vs->glsl_strings,
                                                &vs->sel->sinfo,
                                                &fs->sel->sinfo, "vso", fs->key.flatshade);
      ret = vrend_compile_shader(ctx, gs ? gs : (tes ? tes : vs));
      if (ret == false) {
         glDeleteShader(gs ? gs->id : (tes ? tes->id : vs->id));
         free(sprog);
         return NULL;
      }
      if (gs)
         gs->compiled_fs_id = fs->id;
      else if (tes)
         tes->compiled_fs_id = fs->id;
      else
         vs->compiled_fs_id = fs->id;
   }

   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(ctx, prog_id, &gs->sel->sinfo);
   } else if (tes)
      set_stream_out_varyings(ctx, prog_id, &tes->sel->sinfo);
   else
      set_stream_out_varyings(ctx, prog_id, &vs->sel->sinfo);
   glAttachShader(prog_id, fs->id);

   if (fs->sel->sinfo.num_outputs > 1) {
      if (util_blend_state_is_dual(&ctx->sub->blend_state, 0)) {
         glBindFragDataLocationIndexed(prog_id, 0, 0, "fsout_c0");
         glBindFragDataLocationIndexed(prog_id, 0, 1, "fsout_c1");
         sprog->dual_src_linked = true;
      } else {
         glBindFragDataLocationIndexed(prog_id, 0, 0, "fsout_c0");
         glBindFragDataLocationIndexed(prog_id, 1, 0, "fsout_c1");
         sprog->dual_src_linked = false;
      }
   } 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 */
      report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_SHADER, 0);
      vrend_shader_dump(vs);
      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->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, &ctx->sub->programs);

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

   vrend_use_program(ctx, prog_id);

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

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

   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;
   }

   if (vs->sel->sinfo.num_ucp) {
      for (i = 0; i < vs->sel->sinfo.num_ucp; i++) {
         snprintf(name, 32, "clipp[%d]", i);
         sprog->clip_locs[i] = glGetUniformLocation(prog_id, name);
      }
   }
   return sprog;
}