mesa: add new program optimizer code
This is pretty simplistic for now, but helps with certain shaders.
This commit is contained in:
parent
faae1994c9
commit
82f1c0be13
|
@ -193,6 +193,7 @@ if env['platform'] != 'winddk':
|
|||
'shader/prog_execute.c',
|
||||
'shader/prog_instruction.c',
|
||||
'shader/prog_noise.c',
|
||||
'shader/prog_optimize.c',
|
||||
'shader/prog_parameter.c',
|
||||
'shader/prog_print.c',
|
||||
'shader/prog_statevars.c',
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Mesa 3-D graphics library
|
||||
* Version: 7.5
|
||||
*
|
||||
* Copyright (C) 2009 VMware, Inc. 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, sublicense,
|
||||
* 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 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 NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* VMWARE 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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "main/glheader.h"
|
||||
#include "main/context.h"
|
||||
#include "main/macros.h"
|
||||
#include "program.h"
|
||||
#include "prog_instruction.h"
|
||||
#include "prog_optimize.h"
|
||||
#include "prog_print.h"
|
||||
|
||||
|
||||
static GLboolean dbg = GL_FALSE;
|
||||
|
||||
|
||||
/**
|
||||
* In 'prog' remove instruction[i] if removeFlags[i] == TRUE.
|
||||
* \return number of instructions removed
|
||||
*/
|
||||
static GLuint
|
||||
remove_instructions(struct gl_program *prog, const GLboolean *removeFlags)
|
||||
{
|
||||
GLint i, removeEnd = 0, removeCount = 0;
|
||||
GLuint totalRemoved = 0;
|
||||
|
||||
/* go backward */
|
||||
for (i = prog->NumInstructions - 1; i >= 0; i--) {
|
||||
if (removeFlags[i]) {
|
||||
totalRemoved++;
|
||||
if (removeCount == 0) {
|
||||
/* begin a run of instructions to remove */
|
||||
removeEnd = i;
|
||||
removeCount = 1;
|
||||
}
|
||||
else {
|
||||
/* extend the run of instructions to remove */
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* don't remove this instruction, but check if the preceeding
|
||||
* instructions are to be removed.
|
||||
*/
|
||||
if (removeCount > 0) {
|
||||
GLint removeStart = removeEnd - removeCount + 1;
|
||||
_mesa_delete_instructions(prog, removeStart, removeCount);
|
||||
removeStart = removeCount = 0; /* reset removal info */
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalRemoved;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consolidate temporary registers to use low numbers. For example, if the
|
||||
* shader only uses temps 4, 5, 8, replace them with 0, 1, 2.
|
||||
*/
|
||||
static void
|
||||
_mesa_consolidate_registers(struct gl_program *prog)
|
||||
{
|
||||
GLboolean tempUsed[MAX_PROGRAM_TEMPS];
|
||||
GLuint tempMap[MAX_PROGRAM_TEMPS];
|
||||
GLuint tempMax = 0, i;
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: Begin register consolidation\n");
|
||||
}
|
||||
|
||||
memset(tempUsed, 0, sizeof(tempUsed));
|
||||
|
||||
/* set tempUsed[i] if temporary [i] is referenced */
|
||||
for (i = 0; i < prog->NumInstructions; i++) {
|
||||
const struct prog_instruction *inst = prog->Instructions + i;
|
||||
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) {
|
||||
const GLuint index = inst->SrcReg[j].Index;
|
||||
ASSERT(index < MAX_PROGRAM_TEMPS);
|
||||
tempUsed[index] = GL_TRUE;
|
||||
tempMax = MAX2(tempMax, index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inst->DstReg.File == PROGRAM_TEMPORARY) {
|
||||
const GLuint index = inst->DstReg.Index;
|
||||
ASSERT(index < MAX_PROGRAM_TEMPS);
|
||||
tempUsed[index] = GL_TRUE;
|
||||
tempMax = MAX2(tempMax, index);
|
||||
}
|
||||
}
|
||||
|
||||
/* allocate a new index for each temp that's used */
|
||||
{
|
||||
GLuint freeTemp = 0;
|
||||
for (i = 0; i <= tempMax; i++) {
|
||||
if (tempUsed[i]) {
|
||||
tempMap[i] = freeTemp++;
|
||||
/*_mesa_printf("replace %u with %u\n", i, tempMap[i]);*/
|
||||
}
|
||||
}
|
||||
if (freeTemp == tempMax + 1) {
|
||||
/* no consolidation possible */
|
||||
return;
|
||||
}
|
||||
if (dbg) {
|
||||
_mesa_printf("Replace regs 0..%u with 0..%u\n", tempMax, freeTemp-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* now replace occurances of old temp indexes with new indexes */
|
||||
for (i = 0; i < prog->NumInstructions; i++) {
|
||||
struct prog_instruction *inst = prog->Instructions + i;
|
||||
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) {
|
||||
GLuint index = inst->SrcReg[j].Index;
|
||||
assert(index <= tempMax);
|
||||
assert(tempUsed[index]);
|
||||
inst->SrcReg[j].Index = tempMap[index];
|
||||
}
|
||||
}
|
||||
if (inst->DstReg.File == PROGRAM_TEMPORARY) {
|
||||
const GLuint index = inst->DstReg.Index;
|
||||
assert(tempUsed[index]);
|
||||
assert(index <= tempMax);
|
||||
inst->DstReg.Index = tempMap[index];
|
||||
}
|
||||
}
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: End register consolidation\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove dead instructions from the given program.
|
||||
* This is very primitive for now. Basically look for temp registers
|
||||
* 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)
|
||||
{
|
||||
GLboolean tempWritten[MAX_PROGRAM_TEMPS], tempRead[MAX_PROGRAM_TEMPS];
|
||||
GLboolean *removeInst; /* per-instruction removal flag */
|
||||
GLuint i, rem;
|
||||
|
||||
memset(tempWritten, 0, sizeof(tempWritten));
|
||||
memset(tempRead, 0, sizeof(tempRead));
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: Begin dead code removal\n");
|
||||
/*_mesa_print_program(prog);*/
|
||||
}
|
||||
|
||||
removeInst = (GLboolean *)
|
||||
_mesa_calloc(prog->NumInstructions * sizeof(GLboolean));
|
||||
|
||||
/* Determine which temps are read and written */
|
||||
for (i = 0; i < prog->NumInstructions; i++) {
|
||||
const struct prog_instruction *inst = prog->Instructions + i;
|
||||
const GLuint numSrc = _mesa_num_inst_src_regs(inst->Opcode);
|
||||
GLuint j;
|
||||
|
||||
/* check src regs */
|
||||
for (j = 0; j < numSrc; j++) {
|
||||
if (inst->SrcReg[j].File == PROGRAM_TEMPORARY) {
|
||||
const GLuint index = inst->SrcReg[j].Index;
|
||||
ASSERT(index < MAX_PROGRAM_TEMPS);
|
||||
|
||||
if (inst->SrcReg[j].RelAddr) {
|
||||
if (dbg)
|
||||
_mesa_printf("abort remove dead code (indirect temp)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
tempRead[index] = GL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* check dst reg */
|
||||
if (inst->DstReg.File == PROGRAM_TEMPORARY) {
|
||||
const GLuint index = inst->DstReg.Index;
|
||||
ASSERT(index < MAX_PROGRAM_TEMPS);
|
||||
|
||||
if (inst->DstReg.RelAddr) {
|
||||
if (dbg)
|
||||
_mesa_printf("abort remove dead code (indirect temp)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
tempWritten[index] = GL_TRUE;
|
||||
if (inst->CondUpdate) {
|
||||
/* If we're writing to this register and setting condition
|
||||
* codes we cannot remove the instruction. Prevent removal
|
||||
* by setting the 'read' flag.
|
||||
*/
|
||||
tempRead[index] = GL_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dbg) {
|
||||
for (i = 0; i < MAX_PROGRAM_TEMPS; i++) {
|
||||
if (tempWritten[i] && !tempRead[i])
|
||||
_mesa_printf("Remove writes to tmp %u\n", i);
|
||||
}
|
||||
}
|
||||
|
||||
/* find instructions that write to dead registers, flag for removal */
|
||||
for (i = 0; i < prog->NumInstructions; i++) {
|
||||
const struct prog_instruction *inst = prog->Instructions + i;
|
||||
if (inst->DstReg.File == PROGRAM_TEMPORARY) {
|
||||
GLint index = inst->DstReg.Index;
|
||||
removeInst[i] = (tempWritten[index] && !tempRead[index]);
|
||||
if (dbg && removeInst[i]) {
|
||||
_mesa_printf("Remove inst %u: ", i);
|
||||
_mesa_print_instruction(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* now remove the instructions which aren't needed */
|
||||
rem = remove_instructions(prog, removeInst);
|
||||
|
||||
_mesa_free(removeInst);
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: End dead code removal. %u instructions removed\n", rem);
|
||||
/*_mesa_print_program(prog);*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum temp_use
|
||||
{
|
||||
READ,
|
||||
WRITE,
|
||||
FLOW,
|
||||
END
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan forward in program from 'start' for the next occurance of TEMP[index].
|
||||
* 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)
|
||||
{
|
||||
GLuint i;
|
||||
|
||||
for (i = start; i < prog->NumInstructions; i++) {
|
||||
const struct prog_instruction *inst = prog->Instructions + i;
|
||||
switch (inst->Opcode) {
|
||||
case OPCODE_BGNLOOP:
|
||||
case OPCODE_ENDLOOP:
|
||||
case OPCODE_BGNSUB:
|
||||
case OPCODE_ENDSUB:
|
||||
return FLOW;
|
||||
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)
|
||||
return READ;
|
||||
}
|
||||
if (inst->DstReg.File == PROGRAM_TEMPORARY &&
|
||||
inst->DstReg.Index == index)
|
||||
return WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return END;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to remove extraneous MOV instructions from the given program.
|
||||
*/
|
||||
static void
|
||||
_mesa_remove_extra_moves(struct gl_program *prog)
|
||||
{
|
||||
GLboolean *removeInst; /* per-instruction removal flag */
|
||||
GLuint i, rem, loopNesting = 0, subroutineNesting = 0;
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: Begin remove extra moves\n");
|
||||
_mesa_print_program(prog);
|
||||
}
|
||||
|
||||
removeInst = (GLboolean *)
|
||||
_mesa_calloc(prog->NumInstructions * sizeof(GLboolean));
|
||||
|
||||
/*
|
||||
* Look for sequences such as this:
|
||||
* FOO tmpX, arg0, arg1;
|
||||
* MOV tmpY, tmpX;
|
||||
* and convert into:
|
||||
* FOO tmpY, arg0, arg1;
|
||||
*/
|
||||
|
||||
for (i = 0; i < prog->NumInstructions; i++) {
|
||||
const struct prog_instruction *inst = prog->Instructions + i;
|
||||
|
||||
switch (inst->Opcode) {
|
||||
case OPCODE_BGNLOOP:
|
||||
loopNesting++;
|
||||
break;
|
||||
case OPCODE_ENDLOOP:
|
||||
loopNesting--;
|
||||
break;
|
||||
case OPCODE_BGNSUB:
|
||||
subroutineNesting++;
|
||||
break;
|
||||
case OPCODE_ENDSUB:
|
||||
subroutineNesting--;
|
||||
break;
|
||||
case OPCODE_MOV:
|
||||
if (i > 0 &&
|
||||
loopNesting == 0 &&
|
||||
subroutineNesting == 0 &&
|
||||
inst->SrcReg[0].File == PROGRAM_TEMPORARY &&
|
||||
inst->SrcReg[0].Swizzle == SWIZZLE_XYZW) {
|
||||
/* see if this MOV can be removed */
|
||||
const GLuint tempIndex = inst->SrcReg[0].Index;
|
||||
struct prog_instruction *prevInst;
|
||||
GLuint prevI;
|
||||
|
||||
/* get pointer to previous instruction */
|
||||
prevI = i - 1;
|
||||
while (removeInst[prevI] && prevI > 0)
|
||||
prevI--;
|
||||
prevInst = prog->Instructions + prevI;
|
||||
|
||||
if (prevInst->DstReg.File == PROGRAM_TEMPORARY &&
|
||||
prevInst->DstReg.Index == tempIndex &&
|
||||
prevInst->DstReg.WriteMask == WRITEMASK_XYZW) {
|
||||
|
||||
enum temp_use next_use =
|
||||
find_next_temp_use(prog, i + 1, tempIndex);
|
||||
|
||||
if (next_use == WRITE || next_use == END) {
|
||||
/* OK, we can safely remove this MOV instruction.
|
||||
* Transform:
|
||||
* prevI: FOO tempIndex, x, y;
|
||||
* i: MOV z, tempIndex;
|
||||
* Into:
|
||||
* prevI: FOO z, x, y;
|
||||
*/
|
||||
|
||||
/* patch up prev inst */
|
||||
prevInst->DstReg.File = inst->DstReg.File;
|
||||
prevInst->DstReg.Index = inst->DstReg.Index;
|
||||
|
||||
/* flag this instruction for removal */
|
||||
removeInst[i] = GL_TRUE;
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Remove MOV at %u\n", i);
|
||||
_mesa_printf("new prev inst %u: ", prevI);
|
||||
_mesa_print_instruction(prevInst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
; /* nothing */
|
||||
}
|
||||
}
|
||||
|
||||
/* now remove the instructions which aren't needed */
|
||||
rem = remove_instructions(prog, removeInst);
|
||||
|
||||
if (dbg) {
|
||||
_mesa_printf("Optimize: End remove extra moves. %u instructions removed\n", rem);
|
||||
/*_mesa_print_program(prog);*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply optimizations to the given program to eliminate unnecessary
|
||||
* instructions, temp regs, etc.
|
||||
*/
|
||||
void
|
||||
_mesa_optimize_program(GLcontext *ctx, struct gl_program *program)
|
||||
{
|
||||
if (1)
|
||||
_mesa_remove_dead_code(program);
|
||||
|
||||
if (0) /* not test much yet */
|
||||
_mesa_remove_extra_moves(program);
|
||||
|
||||
if (1)
|
||||
_mesa_consolidate_registers(program);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Mesa 3-D graphics library
|
||||
* Version: 7.5
|
||||
*
|
||||
* Copyright (C) 2009 VMware, Inc. 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, sublicense,
|
||||
* 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 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 NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* VMWARE 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.
|
||||
*/
|
||||
|
||||
#ifndef PROG_OPT_H
|
||||
#define PROG_OPT_H
|
||||
|
||||
struct gl_program;
|
||||
|
||||
extern void
|
||||
_mesa_optimize_program(GLcontext *ctx, struct gl_program *program);
|
||||
|
||||
#endif
|
|
@ -224,6 +224,7 @@ SHADER_SOURCES = \
|
|||
shader/prog_execute.c \
|
||||
shader/prog_instruction.c \
|
||||
shader/prog_noise.c \
|
||||
shader/prog_optimize.c \
|
||||
shader/prog_parameter.c \
|
||||
shader/prog_print.c \
|
||||
shader/prog_statevars.c \
|
||||
|
|
Loading…
Reference in New Issue