[Mesa-dev] [PATCH] Better GPU program optimization in Mesa front end

Benjamin Segovia benjamin.segovia at intel.com
Mon Jul 19 16:01:02 PDT 2010


- Improved optimization of GPU programs. Now, swizzling is taken into account
  and swizzles are properly transformed while removing mov instructions.
  Removals of mov instructions are now much more effective

- Analysis of control flows is still very primitive and far more too
  conservative. Shaders using a lot of branches will be less optimized than
  straightforward ones

- Main things to do next is:
   * instruction merging like for example merging:
       mul a.x b.x c.x
       mul a.y b.y c.y
       mul a.z b.z c.z
     into
       mul a.xyz b.xyz c.xyz
   * register renaming to avoid some still unecessary movs

- Tested with piglit. I run all the shaders and compare output from the new
  version with the old one. Also, run openarena, nexuiz and warsow. All games
  perfectly run and GPU code is clearly improved. Note that I only use my Intel
  Gen GPU for the backend. So everything was tested using classic Mesa with
  the Intel i965 driver.
---
 src/mesa/program/prog_optimize.c |  446 ++++++++++++++++++++++++++++----------
 1 files changed, 329 insertions(+), 117 deletions(-)

diff --git a/src/mesa/program/prog_optimize.c b/src/mesa/program/prog_optimize.c
index 2941a17..9119b10 100644
--- a/src/mesa/program/prog_optimize.c
+++ b/src/mesa/program/prog_optimize.c
@@ -37,39 +37,109 @@
 
 
 static GLboolean dbg = GL_FALSE;
+static const int bit_count[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
+static const int bit_scan[16] = {-1,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0};
+static const int bit_clear[4] = {~1, ~2, ~4, ~8};
+static const int expand_one[5] = {0,1,3,7,15};
 
-/* Returns the mask of channels read from the given srcreg in this instruction.
+#define NO_MASK 0xf
+
+/** Returns the mask of channels read from the given src in this instruction, We
+ *  also provide one optional masks which may mask other components in the dst
+ *  register
  */
 static GLuint
-get_src_arg_mask(const struct prog_instruction *inst, int arg)
+get_src_arg_mask(const struct prog_instruction *inst, int arg, int dst_mask)
 {
-   int writemask = inst->DstReg.WriteMask;
-
-   if (inst->CondUpdate)
-      writemask = WRITEMASK_XYZW;
-
-   switch (inst->Opcode) {
-   case OPCODE_MOV:
-   case OPCODE_ABS:
-   case OPCODE_ADD:
-   case OPCODE_MUL:
-   case OPCODE_SUB:
-      return writemask;
-   case OPCODE_RCP:
-   case OPCODE_SIN:
-   case OPCODE_COS:
-   case OPCODE_RSQ:
-   case OPCODE_POW:
-   case OPCODE_EX2:
-      return WRITEMASK_X;
-   case OPCODE_DP2:
-      return WRITEMASK_XY;
-   case OPCODE_DP3:
-   case OPCODE_XPD:
-      return WRITEMASK_XYZ;
-   default:
-      return WRITEMASK_XYZW;
+   int read_mask, channel_mask, read_count;
+   int comp;
+
+   /* Form the dst register, find the written channels */
+   if (inst->CondUpdate) {
+      read_count = 4;
+      channel_mask = WRITEMASK_XYZW;
    }
+   else {
+      switch (inst->Opcode) {
+      case OPCODE_MOV:
+      case OPCODE_ABS:
+      case OPCODE_ADD:
+      case OPCODE_MAD:
+      case OPCODE_MUL:
+      case OPCODE_SUB:
+         read_count = bit_count[inst->DstReg.WriteMask & dst_mask];
+         channel_mask = expand_one[read_count];
+         break;
+      case OPCODE_RCP:
+      case OPCODE_SIN:
+      case OPCODE_COS:
+      case OPCODE_RSQ:
+      case OPCODE_POW:
+      case OPCODE_EX2:
+         read_count = 1;
+         channel_mask = WRITEMASK_X;
+         break;
+      case OPCODE_DP2:
+         read_count = 2;
+         channel_mask = WRITEMASK_XY;
+         break;
+      case OPCODE_DP3:
+      case OPCODE_XPD:
+         read_count = 3;
+         channel_mask = WRITEMASK_XYZ;
+         break;
+      default:
+         read_count = 4;
+         channel_mask = WRITEMASK_XYZW;
+         break;
+      }
+   }
+
+   /* Now, given the src swizzle and the written channels, find which
+    * components are actually read
+    */
+   read_mask = 0;
+   for (comp = 0; comp < read_count; ++comp) {
+      const int coord = GET_SWZ(inst->SrcReg[arg].Swizzle, comp);
+      if (coord <= SWIZZLE_W)
+         read_mask |= 1 << coord;
+   }
+
+   return read_mask;
+}
+
+/** For a MOV instruction, compute a write mask when both src register also has
+ *  a mask
+ */
+static GLuint
+get_dst_mask_for_mov(const struct prog_instruction *mov, int src_mask)
+{
+   int comp;
+   int bit_mask = mov->DstReg.WriteMask;
+   int updated_mask = 0;
+   const int write_count = bit_count[bit_mask];
+
+   for (comp = 0; comp < write_count; ++comp) {
+      const int src_comp = GET_SWZ(mov->SrcReg[0].Swizzle, comp);
+      const int which = bit_scan[bit_mask];
+      bit_mask &= bit_clear[which];
+      if ((src_mask & (1 << src_comp)) == 0)
+         continue;
+      updated_mask |= 1 << which;
+   }
+
+   return updated_mask;
+}
+
+
+/** Ensure that the swizzle is regular */
+static int
+is_swizzle_regular(int swz)
+{
+   return GET_SWZ(swz,0) <= SWIZZLE_W &&
+          GET_SWZ(swz,1) <= SWIZZLE_W &&
+          GET_SWZ(swz,2) <= SWIZZLE_W &&
+          GET_SWZ(swz,3) <= SWIZZLE_W;
 }
 
 /**
@@ -146,7 +216,7 @@ replace_regs(struct gl_program *prog, gl_register_file file, const GLint map[])
    }
 }
 
-
+#if 0
 /**
  * Consolidate temporary registers to use low numbers.  For example, if the
  * shader only uses temps 4, 5, 8, replace them with 0, 1, 2.
@@ -214,6 +284,7 @@ _mesa_consolidate_registers(struct gl_program *prog)
       printf("Optimize: End register consolidation\n");
    }
 }
+#endif
 
 
 /**
@@ -222,8 +293,8 @@ _mesa_consolidate_registers(struct gl_program *prog)
  * that are written to but never read.  Remove any instructions that
  * write to such registers.  Be careful with condition code setters.
  */
-static void
-_mesa_remove_dead_code(struct gl_program *prog)
+static GLboolean
+_mesa_remove_dead_code_global(struct gl_program *prog)
 {
    GLboolean tempRead[MAX_PROGRAM_TEMPS][4];
    GLboolean *removeInst; /* per-instruction removal flag */
@@ -251,7 +322,7 @@ _mesa_remove_dead_code(struct gl_program *prog)
             const GLuint index = inst->SrcReg[j].Index;
             GLuint read_mask;
             ASSERT(index < MAX_PROGRAM_TEMPS);
-	    read_mask = get_src_arg_mask(inst, j);
+	    read_mask = get_src_arg_mask(inst, j, NO_MASK);
 
             if (inst->SrcReg[j].RelAddr) {
                if (dbg)
@@ -348,10 +419,11 @@ _mesa_remove_dead_code(struct gl_program *prog)
 
 done:
    free(removeInst);
+   return rem != 0;
 }
 
 
-enum temp_use
+enum inst_use
 {
    READ,
    WRITE,
@@ -360,12 +432,17 @@ enum temp_use
 };
 
 /**
- * Scan forward in program from 'start' for the next occurance of TEMP[index].
+ * Scan forward in program from 'start' for the next occurances of TEMP[index].
+ * We look if an instruction reads the component given by the masks and if they
+ * are overwritten.
  * Return READ, WRITE, FLOW or END to indicate the next usage or an indicator
  * that we can't look further.
  */
-static enum temp_use
-find_next_temp_use(const struct gl_program *prog, GLuint start, GLuint index)
+static enum inst_use
+find_next_use(const struct gl_program *prog,
+              GLuint start,
+              GLuint index,
+              GLuint mask)
 {
    GLuint i;
 
@@ -373,26 +450,38 @@ find_next_temp_use(const struct gl_program *prog, GLuint start, GLuint index)
       const struct prog_instruction *inst = prog->Instructions + i;
       switch (inst->Opcode) {
       case OPCODE_BGNLOOP:
-      case OPCODE_ENDLOOP:
       case OPCODE_BGNSUB:
+      case OPCODE_BRA:
+      case OPCODE_CAL:
+      case OPCODE_CONT:
+      case OPCODE_IF:
+      case OPCODE_ELSE:
+      case OPCODE_ENDIF:
+      case OPCODE_ENDLOOP:
       case OPCODE_ENDSUB:
+      case OPCODE_RET:
          return FLOW;
+      case OPCODE_END:
+         return END;
       default:
          {
             const GLuint numSrc = _mesa_num_inst_src_regs(inst->Opcode);
             GLuint j;
             for (j = 0; j < numSrc; j++) {
                if (inst->SrcReg[j].File == PROGRAM_TEMPORARY &&
-                   inst->SrcReg[j].Index == index)
+                   inst->SrcReg[j].Index == index &&
+                   (get_src_arg_mask(inst,j,NO_MASK) & mask))
                   return READ;
             }
             if (inst->DstReg.File == PROGRAM_TEMPORARY &&
-                inst->DstReg.Index == index)
-               return WRITE;
+                inst->DstReg.Index == index) {
+               mask &= ~inst->DstReg.WriteMask;
+               if (mask == 0)
+                  return WRITE;
+            }
          }
       }
    }
-
    return END;
 }
 
@@ -417,6 +506,30 @@ static GLboolean _mesa_is_flow_control_opcode(enum prog_opcode opcode)
    }
 }
 
+static GLboolean
+can_downward_mov_be_modifed(const struct prog_instruction *mov)
+{
+   return
+      mov->Opcode == OPCODE_MOV &&
+      mov->CondUpdate == GL_FALSE &&
+      mov->SrcReg[0].RelAddr == 0 &&
+      mov->SrcReg[0].Negate == 0 &&
+      mov->SrcReg[0].Abs == 0 &&
+      mov->SrcReg[0].HasIndex2 == 0 &&
+      mov->SrcReg[0].RelAddr2 == 0 &&
+      mov->DstReg.RelAddr == 0 &&
+      mov->DstReg.CondMask == COND_TR &&
+      mov->SaturateMode == SATURATE_OFF;
+}
+
+static GLboolean
+can_upward_mov_be_modifed(const struct prog_instruction *mov)
+{
+   return
+      can_downward_mov_be_modifed(mov) &&
+      mov->DstReg.File == PROGRAM_TEMPORARY;
+}
+
 /**
  * Try to remove use of extraneous MOV instructions, to free them up for dead
  * code removal.
@@ -444,14 +557,15 @@ _mesa_remove_extra_move_use(struct gl_program *prog)
 
    for (i = 0; i + 1 < prog->NumInstructions; i++) {
       const struct prog_instruction *mov = prog->Instructions + i;
+      int dst_mask, src_mask;
+      if (can_upward_mov_be_modifed(mov) == GL_FALSE)
+         continue;
 
-      if (mov->Opcode != OPCODE_MOV ||
-	  mov->DstReg.File != PROGRAM_TEMPORARY ||
-	  mov->DstReg.RelAddr ||
-	  mov->DstReg.CondMask != COND_TR ||
-	  mov->SaturateMode != SATURATE_OFF ||
-	  mov->SrcReg[0].RelAddr)
-	 continue;
+      /* Scanning the code, we maintain the components which are still active in
+       * these two masks
+       */
+      dst_mask = mov->DstReg.WriteMask;
+      src_mask = get_src_arg_mask(mov, 0, NO_MASK);
 
       /* Walk through remaining instructions until the or src reg gets
        * rewritten or we get into some flow-control, eliminating the use of
@@ -459,61 +573,60 @@ _mesa_remove_extra_move_use(struct gl_program *prog)
        */
       for (j = i + 1; j < prog->NumInstructions; j++) {
 	 struct prog_instruction *inst2 = prog->Instructions + j;
-	 GLuint arg;
+         GLuint arg;
 
 	 if (_mesa_is_flow_control_opcode(inst2->Opcode))
 	     break;
 
 	 /* First rewrite this instruction's args if appropriate. */
 	 for (arg = 0; arg < _mesa_num_inst_src_regs(inst2->Opcode); arg++) {
-	    int comp;
-	    int read_mask = get_src_arg_mask(inst2, arg);
+	    int comp, read_mask;
 
 	    if (inst2->SrcReg[arg].File != mov->DstReg.File ||
 		inst2->SrcReg[arg].Index != mov->DstReg.Index ||
 		inst2->SrcReg[arg].RelAddr ||
 		inst2->SrcReg[arg].Abs)
 	       continue;
+            read_mask = get_src_arg_mask(inst2, arg, NO_MASK);
 
-	    /* Check that all the sources for this arg of inst2 come from inst1
-	     * or constants.
-	     */
-	    for (comp = 0; comp < 4; comp++) {
-	       int src_swz = GET_SWZ(inst2->SrcReg[arg].Swizzle, comp);
-
-	       /* If the MOV didn't write that channel, can't use it. */
-	       if ((read_mask & (1 << comp)) &&
-		   src_swz <= SWIZZLE_W &&
-		   (mov->DstReg.WriteMask & (1 << src_swz)) == 0)
-		  break;
-	    }
-	    if (comp != 4)
-	       continue;
-
-	    /* Adjust the swizzles of inst2 to point at MOV's source */
-	    for (comp = 0; comp < 4; comp++) {
-	       int inst2_swz = GET_SWZ(inst2->SrcReg[arg].Swizzle, comp);
-
-	       if (inst2_swz <= SWIZZLE_W) {
-		  GLuint s = GET_SWZ(mov->SrcReg[0].Swizzle, inst2_swz);
-		  inst2->SrcReg[arg].Swizzle &= ~(7 << (3 * comp));
-		  inst2->SrcReg[arg].Swizzle |= s << (3 * comp);
-		  inst2->SrcReg[arg].Negate ^= (((mov->SrcReg[0].Negate >>
-						  inst2_swz) & 0x1) << comp);
-	       }
-	    }
-	    inst2->SrcReg[arg].File = mov->SrcReg[0].File;
-	    inst2->SrcReg[arg].Index = mov->SrcReg[0].Index;
+	    /* Adjust the swizzles of inst2 to point at MOV's source if ALL the
+             * components read still come from the mov instructions
+             */
+            if(is_swizzle_regular(inst2->SrcReg[arg].Swizzle) &&
+               (read_mask & dst_mask) == read_mask) {
+               for (comp = 0; comp < 4; comp++) {
+                  int inst2_swz = GET_SWZ(inst2->SrcReg[arg].Swizzle, comp);
+
+                  if (inst2_swz <= SWIZZLE_W) {
+                     GLuint s = GET_SWZ(mov->SrcReg[0].Swizzle, inst2_swz);
+                     inst2->SrcReg[arg].Swizzle &= ~(7 << (3 * comp));
+                     inst2->SrcReg[arg].Swizzle |= s << (3 * comp);
+                     inst2->SrcReg[arg].Negate ^= (((mov->SrcReg[0].Negate >>
+                                                     inst2_swz) & 0x1) << comp);
+                  }
+               }
+               inst2->SrcReg[arg].File = mov->SrcReg[0].File;
+               inst2->SrcReg[arg].Index = mov->SrcReg[0].Index;
+            }
 	 }
 
-	 /* If this instruction overwrote part of the move, our time is up. */
-	 if ((inst2->DstReg.File == mov->DstReg.File &&
-	      (inst2->DstReg.RelAddr ||
-	       inst2->DstReg.Index == mov->DstReg.Index)) ||
-	     (inst2->DstReg.File == mov->SrcReg[0].File &&
-	      (inst2->DstReg.RelAddr ||
-	       inst2->DstReg.Index == mov->SrcReg[0].Index)))
-	    break;
+	 /* The source of MOV is written. This potentially deactivate some
+          * components from the src and dst of the MOV instruction
+          */
+	 if (inst2->DstReg.File == mov->DstReg.File &&
+	     (inst2->DstReg.RelAddr ||
+	      inst2->DstReg.Index == mov->DstReg.Index)) {
+            dst_mask &= ~inst2->DstReg.WriteMask;
+            src_mask = get_src_arg_mask(inst2, arg, dst_mask);
+         }
+	 if (inst2->DstReg.File == mov->SrcReg[0].File &&
+	     (inst2->DstReg.RelAddr ||
+	      inst2->DstReg.Index == mov->SrcReg[0].Index)) {
+            src_mask &= ~inst2->DstReg.WriteMask;
+            dst_mask &= get_dst_mask_for_mov(mov, src_mask);
+         }
+         if (dst_mask == 0)
+            break;
       }
    }
 
@@ -523,14 +636,98 @@ _mesa_remove_extra_move_use(struct gl_program *prog)
    }
 }
 
+static GLboolean
+is_src_translatable_into_dst(const struct prog_instruction *inst, int arg)
+{
+   int comp, coord;
+   const int read_count = bit_count[inst->DstReg.WriteMask];
+   if (read_count == 0)
+      return GL_FALSE;
+   coord = GET_SWZ(inst->SrcReg[arg].Swizzle, 0);
+
+   /* Coordinates properly ordered: .xy is ok .yx is not */
+   for (comp = 1; comp < read_count; ++comp) {
+      const int next = GET_SWZ(inst->SrcReg[arg].Swizzle, comp);
+      if (next <= coord)
+         return GL_FALSE;
+      coord = next;
+   }
+
+   return GL_TRUE;
+}
+
+static void
+_mesa_merge_mov_to_inst(struct prog_instruction *inst,
+                        const struct prog_instruction *mov)
+{
+   int mov_mask = mov->DstReg.WriteMask;
+   int inst_mask = inst->DstReg.WriteMask;
+   GLuint comp = 0;
+   GLuint translate_table[4];
+
+   while (mov_mask) {
+      const int src = GET_SWZ(mov->SrcReg[0].Swizzle, comp);
+      const int dst = bit_scan[mov_mask];
+      translate_table[src] = dst;
+      comp++;
+      mov_mask &= bit_clear[dst];
+   }
+
+   comp = 0;
+   inst->DstReg.WriteMask = 0;
+   while (inst_mask) {
+      const int src_coord = bit_scan[inst_mask];
+      const int new_coord = translate_table[src_coord];
+      inst->DstReg.WriteMask |= 1 << new_coord;
+      comp++;
+      inst_mask &= bit_clear[src_coord];
+   }
+}
+
+
+/**
+ * Complements dead_local_global. Try to remove code in block of code by
+ * carefully monitoring the swizzles. Both functions should be merged into one
+ * with a proper control flow graph
+ */
+static GLboolean
+_mesa_remove_dead_code_local(struct gl_program *prog)
+{
+   GLboolean *removeInst;
+   GLuint i, rem = 0;
+
+   removeInst = (GLboolean *)
+      calloc(1, prog->NumInstructions * sizeof(GLboolean));
+
+   for (i = 0; i < prog->NumInstructions; i++) {
+      const struct prog_instruction *inst = prog->Instructions + i;
+      const GLuint index = inst->DstReg.Index;
+      const GLuint mask = inst->DstReg.WriteMask;
+      enum inst_use use;
+
+      if (_mesa_is_flow_control_opcode(inst->Opcode) ||
+          inst->DstReg.File != PROGRAM_TEMPORARY ||
+          inst->DstReg.RelAddr)
+         continue;
+
+      use = find_next_use(prog, i+1, index, mask);
+      if (use == WRITE || use == END)
+         removeInst[i] = GL_TRUE;
+   }
+
+   rem = remove_instructions(prog, removeInst);
+   free(removeInst);
+   return rem != 0;
+}
+
 /**
  * Try to remove extraneous MOV instructions from the given program.
  */
-static void
+static GLboolean
 _mesa_remove_extra_moves(struct gl_program *prog)
 {
    GLboolean *removeInst; /* per-instruction removal flag */
-   GLuint i, rem, loopNesting = 0, subroutineNesting = 0;
+   GLuint i, rem = 0, nesting = 0;
 
    if (dbg) {
       printf("Optimize: Begin remove extra moves\n");
@@ -553,25 +750,24 @@ _mesa_remove_extra_moves(struct gl_program *prog)
 
       switch (inst->Opcode) {
       case OPCODE_BGNLOOP:
-         loopNesting++;
-         break;
-      case OPCODE_ENDLOOP:
-         loopNesting--;
-         break;
       case OPCODE_BGNSUB:
-         subroutineNesting++;
+      case OPCODE_IF:
+         nesting++;
          break;
+      case OPCODE_ENDLOOP:
       case OPCODE_ENDSUB:
-         subroutineNesting--;
+      case OPCODE_ENDIF:
+         nesting--;
          break;
       case OPCODE_MOV:
          if (i > 0 &&
-             loopNesting == 0 &&
-             subroutineNesting == 0 &&
-             inst->SrcReg[0].File == PROGRAM_TEMPORARY &&
-             inst->SrcReg[0].Swizzle == SWIZZLE_XYZW) {
+            can_downward_mov_be_modifed(inst) == GL_TRUE &&
+            nesting == 0 &&
+            is_src_translatable_into_dst(inst, 0))
+         {
             /* see if this MOV can be removed */
-            const GLuint tempIndex = inst->SrcReg[0].Index;
+            const GLuint mask = get_src_arg_mask(inst, 0, NO_MASK);
+            const GLuint id = inst->SrcReg[0].Index;
             struct prog_instruction *prevInst;
             GLuint prevI;
 
@@ -582,11 +778,12 @@ _mesa_remove_extra_moves(struct gl_program *prog)
             prevInst = prog->Instructions + prevI;
 
             if (prevInst->DstReg.File == PROGRAM_TEMPORARY &&
-                prevInst->DstReg.Index == tempIndex &&
-                prevInst->DstReg.WriteMask == WRITEMASK_XYZW) {
+                prevInst->DstReg.Index == id &&
+                prevInst->DstReg.RelAddr == 0 &&
+                mask == (prevInst->DstReg.WriteMask & mask)) {
 
-               enum temp_use next_use =
-                  find_next_temp_use(prog, i + 1, tempIndex);
+               const GLuint dst_mask = prevInst->DstReg.WriteMask;
+               enum inst_use next_use = find_next_use(prog, i+1, id, dst_mask);
 
                if (next_use == WRITE || next_use == END) {
                   /* OK, we can safely remove this MOV instruction.
@@ -600,6 +797,7 @@ _mesa_remove_extra_moves(struct gl_program *prog)
                   /* patch up prev inst */
                   prevInst->DstReg.File = inst->DstReg.File;
                   prevInst->DstReg.Index = inst->DstReg.Index;
+                  _mesa_merge_mov_to_inst(prevInst, inst);
 
                   /* flag this instruction for removal */
                   removeInst[i] = GL_TRUE;
@@ -627,6 +825,8 @@ _mesa_remove_extra_moves(struct gl_program *prog)
       printf("Optimize: End remove extra moves.  %u instructions removed\n", rem);
       /*_mesa_print_program(prog);*/
    }
+
+   return rem != 0;
 }
 
 
@@ -1012,6 +1212,15 @@ _mesa_reallocate_registers(struct gl_program *prog)
    }
 }
 
+#if 0
+static void
+print_it(GLcontext *ctx, struct gl_program *program, const char *txt) {
+   fprintf(stderr, "%s (%u inst):\n", txt, program->NumInstructions);
+   _mesa_print_program(program);
+   _mesa_print_program_parameters(ctx, program);
+   fprintf(stderr, "\n\n");
+}
+#endif
 
 /**
  * Apply optimizations to the given program to eliminate unnecessary
@@ -1020,16 +1229,19 @@ _mesa_reallocate_registers(struct gl_program *prog)
 void
 _mesa_optimize_program(GLcontext *ctx, struct gl_program *program)
 {
-   _mesa_remove_extra_move_use(program);
-
-   if (1)
-      _mesa_remove_dead_code(program);
-
-   if (0) /* not tested much yet */
-      _mesa_remove_extra_moves(program);
-
-   if (0)
-      _mesa_consolidate_registers(program);
-   else
+   GLboolean any_change;
+
+   /* Stop when no modifications were output */
+   do {
+      any_change = GL_FALSE;
+      _mesa_remove_extra_move_use(program);
+      if (_mesa_remove_dead_code_global(program))
+         any_change = GL_TRUE;
+      if (_mesa_remove_extra_moves(program))
+         any_change = GL_TRUE;
+      if (_mesa_remove_dead_code_local(program))
+         any_change = GL_TRUE;
       _mesa_reallocate_registers(program);
+   } while (any_change);
 }
+
-- 
1.7.1



More information about the mesa-dev mailing list