vertex/fragment shader state and linking
authorBrian <brian.paul@tungstengraphics.com>
Tue, 25 Sep 2007 20:21:18 +0000 (14:21 -0600)
committerBrian <brian.paul@tungstengraphics.com>
Tue, 25 Sep 2007 20:21:18 +0000 (14:21 -0600)
src/mesa/state_tracker/st_atom_shader.c [new file with mode: 0644]
src/mesa/state_tracker/st_atom_shader.h [new file with mode: 0644]

diff --git a/src/mesa/state_tracker/st_atom_shader.c b/src/mesa/state_tracker/st_atom_shader.c
new file mode 100644 (file)
index 0000000..f3bb9ef
--- /dev/null
@@ -0,0 +1,422 @@
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * 
+ **************************************************************************/
+
+/**
+ * State validation for vertex/fragment shaders.
+ * Note that we have to delay most vertex/fragment shader translation
+ * until rendering time since the linkage between the vertex outputs and
+ * fragment inputs can vary depending on the pairing of shaders.
+ *
+ * Authors:
+ *   Brian Paul
+ */
+
+
+
+#include "main/imports.h"
+#include "main/mtypes.h"
+
+#include "pipe/p_context.h"
+#include "pipe/tgsi/mesa/mesa_to_tgsi.h"
+#include "pipe/tgsi/exec/tgsi_core.h"
+
+#include "st_context.h"
+#include "st_cache.h"
+#include "st_atom.h"
+#include "st_program.h"
+#include "st_atom_shader.h"
+
+
+
+/**
+ * Structure to describe a (vertex program, fragment program) pair
+ * which is linked together (used together to render something).  This
+ * linkage basically servers the same purpose as the OpenGL Shading
+ * Language linker, but also applies to ARB programs and Mesa's
+ * fixed-function-generated programs.
+ *
+ * More background:
+ *
+ * The translation from Mesa programs to TGSI programs depends on the
+ * linkage between the vertex program and the fragment program.  This is
+ * because we tightly pack the inputs and outputs of shaders into
+ * consecutive "slots".
+ *
+ * Suppose an app uses one vertex program "VP" (outputting pos, color and tex0)
+ * and two fragment programs:
+ *    FP1: uses tex0 input only (input slot 0)
+ *    FP2: uses color input only (input slot 0)
+ *
+ * When VP is used with FP1 we want VP.output[2] to match FP1.input[0], but
+ * when VP is used with FP2 we want VP.output[1] to match FP1.input[0].
+ *
+ * We don't want to re-translate the vertex and/or fragment programs
+ * each time the VP/FP bindings/linkings change.  The solution is this
+ * structure which stores the translated TGSI shaders on a per-linkage
+ * basis.
+ * 
+ */
+struct linked_program_pair
+{
+   struct st_vertex_program *vprog;  /**< never changes */
+   struct st_fragment_program *fprog;  /**< never changes */
+
+   struct tgsi_token vs_tokens[ST_FP_MAX_TOKENS];
+   struct tgsi_token fs_tokens[ST_FP_MAX_TOKENS];
+
+   const struct cso_vertex_shader *vs;
+   const struct cso_fragment_shader *fs;
+
+   GLuint vertSerialNo, fragSerialNo;
+
+   /** maps a Mesa VERT_ATTRIB_x to a packed TGSI input index */
+   GLuint vp_input_to_index[MAX_VERTEX_PROGRAM_ATTRIBS];
+   /** maps a TGSI input index back to a Mesa VERT_ATTRIB_x */
+   GLuint vp_index_to_input[MAX_VERTEX_PROGRAM_ATTRIBS];
+
+   GLuint vp_result_to_slot[VERT_RESULT_MAX];
+
+   struct linked_program_pair *next;
+};
+
+
+/** XXX temporary - use some kind of hash table instead */
+static struct linked_program_pair *Pairs = NULL;
+
+
+static void
+find_and_remove(struct gl_program *prog)
+{
+   struct linked_program_pair *pair, *prev = NULL, *next;
+   for (pair = Pairs; pair; pair = next) {
+      next = pair->next;
+      if (pair->vprog == (struct st_vertex_program *) prog ||
+          pair->fprog == (struct st_fragment_program *) prog) {
+         /* unlink */
+         if (prev)
+            prev->next = next;
+         else
+            Pairs = next;
+         /* delete pair->vs */
+         /* delete pair->fs */
+         free(pair);
+      }
+      else {
+         prev = pair;
+      }
+   }
+}
+
+
+/**
+ * Delete any known program pairs that use the given vertex program.
+ */
+void
+st_remove_vertex_program(struct st_context *st, struct st_vertex_program *stvp)
+{
+   find_and_remove(&stvp->Base.Base);
+}
+
+
+/**
+ * Delete any known program pairs that use the given fragment program.
+ */
+void
+st_remove_fragment_program(struct st_context *st,
+                          struct st_fragment_program *stfp)
+{
+   find_and_remove(&stfp->Base.Base);
+}
+
+
+
+/**
+ * Given a vertex program output attribute, return the corresponding
+ * fragment program input attribute.
+ * \return -1 for vertex outputs that have no corresponding fragment input
+ */
+static GLint
+vp_out_to_fp_in(GLuint vertResult)
+{
+   if (vertResult >= VERT_RESULT_TEX0 &&
+       vertResult < VERT_RESULT_TEX0 + MAX_TEXTURE_COORD_UNITS)
+      return FRAG_ATTRIB_TEX0 + (vertResult - VERT_RESULT_TEX0);
+
+   if (vertResult >= VERT_RESULT_VAR0 &&
+       vertResult < VERT_RESULT_VAR0 + MAX_VARYING)
+      return FRAG_ATTRIB_VAR0 + (vertResult - VERT_RESULT_VAR0);
+
+   switch (vertResult) {
+   case VERT_RESULT_HPOS:
+      return FRAG_ATTRIB_WPOS;
+   case VERT_RESULT_COL0:
+      return FRAG_ATTRIB_COL0;
+   case VERT_RESULT_COL1:
+      return FRAG_ATTRIB_COL1;
+   case VERT_RESULT_FOGC:
+      return FRAG_ATTRIB_FOGC;
+   default:
+      /* Back-face colors, edge flags, etc */
+      return -1;
+   }
+}
+
+
+/**
+ * Examine the outputs written by a vertex program and the inputs read
+ * by a fragment program to determine which match up and where they
+ * should be mapped into the generic shader output/input slots.
+ * \param vert_output_map  returns the vertex output register mapping
+ * \param frag_input_map  returns the fragment input register mapping
+ */
+static GLuint
+link_outputs_to_inputs(GLbitfield outputsWritten,
+                       GLbitfield inputsRead,
+                       GLuint vert_output_map[],
+                       GLuint frag_input_map[])
+{
+   static const GLuint UNUSED = ~0;
+   GLint vert_slot_to_attr[50], frag_slot_to_attr[50];
+   GLuint outAttr, inAttr;
+   GLuint numIn = 0, dummySlot;
+
+   for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) {
+      if (inputsRead & (1 << inAttr)) {
+         frag_input_map[inAttr] = numIn;
+         frag_slot_to_attr[numIn] = inAttr;
+         numIn++;
+      }
+      else {
+         frag_input_map[inAttr] = UNUSED;
+      }
+   }
+
+   for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) {
+      if (outputsWritten & (1 << outAttr)) {
+         /* see if the frag prog wants this vert output */
+         GLint fpIn = vp_out_to_fp_in(outAttr);
+
+         if (fpIn >= 0) {
+            GLuint frag_slot = frag_input_map[fpIn];
+            vert_output_map[outAttr] = frag_slot;
+            vert_slot_to_attr[frag_slot] = outAttr;
+         }
+         else {
+            vert_output_map[outAttr] = UNUSED;
+         }
+      }
+      else { 
+         vert_output_map[outAttr] = UNUSED;
+      }
+   }
+
+   /*
+    * We'll map all unused vertex program outputs to this slot.
+    * We'll also map all undefined fragment program inputs to this slot.
+    */
+   dummySlot = numIn;
+
+   /* Map vert program outputs that aren't used to the dummy slot */
+   for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) {
+      if (outputsWritten & (1 << outAttr)) {
+         if (vert_output_map[outAttr] == UNUSED)
+            vert_output_map[outAttr] = dummySlot;
+      }
+   }
+
+   /* Map frag program inputs that aren't defined to the dummy slot */
+   for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) {
+      if (inputsRead & (1 << inAttr)) {
+         if (frag_input_map[inAttr] == UNUSED)
+            frag_input_map[inAttr] = dummySlot;
+      }
+   }
+
+#if 0
+   printf("vOut  W  slot\n");
+   for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) {
+      printf("%4d  %c %4d\n", outAttr,
+             " *"[(outputsWritten >> outAttr) & 1],
+             vert_output_map[outAttr]);
+   }
+   printf("vIn  R  slot\n");
+   for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) {
+      printf("%3d  %c %4d\n", inAttr,
+             " *"[(inputsRead >> inAttr) & 1],
+             frag_input_map[inAttr]);
+   }
+#endif
+
+   return numIn;
+}
+
+
+static struct linked_program_pair *
+lookup_program_pair(struct st_context *st,
+                    struct st_vertex_program *vprog,
+                    struct st_fragment_program *fprog)
+{
+   struct linked_program_pair *pair;
+
+   /* search */
+   for (pair = Pairs; pair; pair = pair->next) {
+      if (pair->vprog == vprog && pair->fprog == fprog) {
+         /* found it */
+         break;
+      }
+   }
+
+   /*
+    * Examine the outputs of the vertex shader and the inputs of the
+    * fragment shader to determine how to match both to a common set
+    * of slots.
+    */
+   if (!pair) {
+      pair = CALLOC_STRUCT(linked_program_pair);
+      if (pair) {
+         pair->vprog = vprog;
+         pair->fprog = fprog;
+      }
+   }
+
+   return pair;
+}
+
+
+static void
+link_shaders(struct st_context *st, struct linked_program_pair *pair)
+{
+   struct st_vertex_program *vprog = pair->vprog;
+   struct st_fragment_program *fprog = pair->fprog;
+
+   assert(vprog);
+   assert(fprog);
+
+   if (pair->vertSerialNo != vprog->serialNo ||
+       pair->fragSerialNo != fprog->serialNo) {
+      /* re-link and re-translate */
+      GLuint vert_output_mapping[VERT_RESULT_MAX];
+      GLuint frag_input_mapping[FRAG_ATTRIB_MAX];
+
+      link_outputs_to_inputs(vprog->Base.Base.OutputsWritten,
+                             fprog->Base.Base.InputsRead | FRAG_BIT_WPOS,
+                             vert_output_mapping,
+                             frag_input_mapping);
+
+      /* xlate vp to vs + vs tokens */
+      st_translate_vertex_program(st, vprog,
+                                  vert_output_mapping,
+                                  pair->vs_tokens, ST_FP_MAX_TOKENS);
+
+      pair->vprog = vprog;
+      /* temp hacks */
+      pair->vs = vprog->vs;
+      vprog->vs = NULL;
+
+
+      /* xlate fp to fs + fs tokens */
+      st_translate_fragment_program(st, fprog,
+                                    frag_input_mapping,
+                                    pair->fs_tokens, ST_FP_MAX_TOKENS);
+      pair->fprog = fprog;
+      /* temp hacks */
+      pair->fs = fprog->fs;
+      fprog->fs = NULL;
+
+      /* save pair */
+      pair->next = Pairs;
+      Pairs = pair;
+
+      pair->vertSerialNo = vprog->serialNo;
+      pair->fragSerialNo = fprog->serialNo;
+   }
+}
+
+
+static void
+update_linkage( struct st_context *st )
+{
+   struct linked_program_pair *pair;
+   struct st_vertex_program *stvp;
+   struct st_fragment_program *stfp;
+
+   /* find active shader and params -- Should be covered by
+    * ST_NEW_VERTEX_PROGRAM
+    */
+   if (st->ctx->Shader.CurrentProgram &&
+       st->ctx->Shader.CurrentProgram->LinkStatus &&
+       st->ctx->Shader.CurrentProgram->VertexProgram) {
+      struct gl_vertex_program *f
+         = st->ctx->Shader.CurrentProgram->VertexProgram;
+      stvp = st_vertex_program(f);
+   }
+   else {
+      assert(st->ctx->VertexProgram._Current);
+      stvp = st_vertex_program(st->ctx->VertexProgram._Current);
+   }
+
+
+   if (st->ctx->Shader.CurrentProgram &&
+       st->ctx->Shader.CurrentProgram->LinkStatus &&
+       st->ctx->Shader.CurrentProgram->FragmentProgram) {
+      struct gl_fragment_program *f
+         = st->ctx->Shader.CurrentProgram->FragmentProgram;
+      stfp = st_fragment_program(f);
+   }
+   else {
+      assert(st->ctx->FragmentProgram._Current);
+      stfp = st_fragment_program(st->ctx->FragmentProgram._Current);
+   }
+
+
+   pair = lookup_program_pair(st, stvp, stfp);
+   assert(pair);
+   link_shaders(st, pair);
+
+
+   /* Bind the vertex program and TGSI shader */
+   st->vp = stvp;
+   st->state.vs = pair->vs;
+   st->pipe->bind_vs_state(st->pipe, st->state.vs->data);
+
+   /* Bind the fragment program and TGSI shader */
+   st->fp = stfp;
+   st->state.fs = pair->fs;
+   st->pipe->bind_fs_state(st->pipe, st->state.fs->data);
+
+   st->vertex_result_to_slot = pair->vp_result_to_slot;
+}
+
+
+const struct st_tracked_state st_update_shader = {
+   .name = "st_update_shader",
+   .dirty = {
+      .mesa  = 0,
+      .st   = ST_NEW_LINKAGE
+   },
+   .update = update_linkage
+};
diff --git a/src/mesa/state_tracker/st_atom_shader.h b/src/mesa/state_tracker/st_atom_shader.h
new file mode 100644 (file)
index 0000000..48e5348
--- /dev/null
@@ -0,0 +1,9 @@
+
+
+
+extern void
+st_remove_vertex_program(struct st_context *, struct st_vertex_program *);
+
+extern void
+st_remove_fragment_program(struct st_context *, struct st_fragment_program *);
+