blob: 38182c1ba1ce493c769ba501895f68c842c1b4ee [file] [log] [blame]
/*
* Mesa 3-D graphics library
* Version: 6.5
*
* Copyright (C) 1999-2005 Brian Paul 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
* BRIAN PAUL 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.
*/
/**
* \file t_arb_program.c
* Compile vertex programs to an intermediate representation.
* Execute vertex programs over a buffer of vertices.
* \author Keith Whitwell, Brian Paul
*/
#include "glheader.h"
#include "context.h"
#include "imports.h"
#include "macros.h"
#include "mtypes.h"
#include "arbprogparse.h"
#include "light.h"
#include "program.h"
#include "math/m_matrix.h"
#include "math/m_translate.h"
#include "t_context.h"
#include "t_pipeline.h"
#include "t_vb_arbprogram.h"
#include "tnl.h"
#include "program_instruction.h"
#define DISASSEM 0
struct compilation {
GLuint reg_active;
union instruction *csr;
};
#define ARB_VP_MACHINE(stage) ((struct arb_vp_machine *)(stage->privatePtr))
#define PUFF(x) ((x)[1] = (x)[2] = (x)[3] = (x)[0])
/* Lower precision functions for the EXP, LOG and LIT opcodes. The
* LOG2() implementation is probably not accurate enough, and the
* attempted optimization for Exp2 is definitely not accurate
* enough - it discards all of t's fractional bits!
*/
static GLfloat RoughApproxLog2(GLfloat t)
{
return LOG2(t);
}
static GLfloat RoughApproxExp2(GLfloat t)
{
#if 0
fi_type fi;
fi.i = (GLint) t;
fi.i = (fi.i << 23) + 0x3f800000;
return fi.f;
#else
return (GLfloat) _mesa_pow(2.0, t);
#endif
}
static GLfloat RoughApproxPower(GLfloat x, GLfloat y)
{
if (x == 0.0 && y == 0.0)
return 1.0; /* spec requires this */
else
return RoughApproxExp2(y * RoughApproxLog2(x));
}
/* Higher precision functions for the EX2, LG2 and POW opcodes:
*/
static GLfloat ApproxLog2(GLfloat t)
{
return (GLfloat) (LOGF(t) * 1.442695F);
}
static GLfloat ApproxExp2(GLfloat t)
{
return (GLfloat) _mesa_pow(2.0, t);
}
static GLfloat ApproxPower(GLfloat x, GLfloat y)
{
return (GLfloat) _mesa_pow(x, y);
}
static GLfloat rough_approx_log2_0_1(GLfloat x)
{
return LOG2(x);
}
/**
* Perform a reduced swizzle:
*/
static void do_RSW( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.rsw.dst];
const GLfloat *arg0 = m->File[op.rsw.file0][op.rsw.idx0];
GLuint swz = op.rsw.swz;
GLuint neg = op.rsw.neg;
GLfloat tmp[4];
/* Need a temporary to be correct in the case where result == arg0.
*/
COPY_4V(tmp, arg0);
result[0] = tmp[GET_RSW(swz, 0)];
result[1] = tmp[GET_RSW(swz, 1)];
result[2] = tmp[GET_RSW(swz, 2)];
result[3] = tmp[GET_RSW(swz, 3)];
if (neg) {
if (neg & 0x1) result[0] = -result[0];
if (neg & 0x2) result[1] = -result[1];
if (neg & 0x4) result[2] = -result[2];
if (neg & 0x8) result[3] = -result[3];
}
}
/* Used to implement write masking. To make things easier for the sse
* generator I've gone back to a 1 argument version of this function
* (dst.msk = arg), rather than the semantically cleaner (dst = SEL
* arg0, arg1, msk)
*
* That means this is the only instruction which doesn't write a full
* 4 dwords out. This would make such a program harder to analyse,
* but it looks like analysis is going to take place on a higher level
* anyway.
*/
static void do_MSK( struct arb_vp_machine *m, union instruction op )
{
GLfloat *dst = m->File[0][op.msk.dst];
const GLfloat *arg = m->File[op.msk.file][op.msk.idx];
if (op.msk.mask & 0x1) dst[0] = arg[0];
if (op.msk.mask & 0x2) dst[1] = arg[1];
if (op.msk.mask & 0x4) dst[2] = arg[2];
if (op.msk.mask & 0x8) dst[3] = arg[3];
}
static void do_PRT( struct arb_vp_machine *m, union instruction op )
{
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
_mesa_printf("%d: %f %f %f %f\n", m->vtx_nr,
arg0[0], arg0[1], arg0[2], arg0[3]);
}
/**
* The traditional ALU and texturing instructions. All operate on
* internal registers and ignore write masks and swizzling issues.
*/
static void do_ABS( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = (arg0[0] < 0.0) ? -arg0[0] : arg0[0];
result[1] = (arg0[1] < 0.0) ? -arg0[1] : arg0[1];
result[2] = (arg0[2] < 0.0) ? -arg0[2] : arg0[2];
result[3] = (arg0[3] < 0.0) ? -arg0[3] : arg0[3];
}
static void do_ADD( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = arg0[0] + arg1[0];
result[1] = arg0[1] + arg1[1];
result[2] = arg0[2] + arg1[2];
result[3] = arg0[3] + arg1[3];
}
static void do_DP3( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] * arg1[0] +
arg0[1] * arg1[1] +
arg0[2] * arg1[2]);
PUFF(result);
}
static void do_DP4( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] * arg1[0] +
arg0[1] * arg1[1] +
arg0[2] * arg1[2] +
arg0[3] * arg1[3]);
PUFF(result);
}
static void do_DPH( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] * arg1[0] +
arg0[1] * arg1[1] +
arg0[2] * arg1[2] +
1.0 * arg1[3]);
PUFF(result);
}
static void do_DST( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
/* This should be ok even if result == arg0 or result == arg1.
*/
result[0] = 1.0F;
result[1] = arg0[1] * arg1[1];
result[2] = arg0[2];
result[3] = arg1[3];
}
/* Intended to be high precision:
*/
static void do_EX2( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = (GLfloat)ApproxExp2(arg0[0]);
PUFF(result);
}
/* Allowed to be lower precision:
*/
static void do_EXP( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
GLfloat tmp = arg0[0];
GLfloat flr_tmp = FLOORF(tmp);
GLfloat frac_tmp = tmp - flr_tmp;
result[0] = LDEXPF(1.0, (int)flr_tmp);
result[1] = frac_tmp;
result[2] = LDEXPF(rough_approx_log2_0_1(frac_tmp), (int)flr_tmp);
result[3] = 1.0F;
}
static void do_FLR( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = FLOORF(arg0[0]);
result[1] = FLOORF(arg0[1]);
result[2] = FLOORF(arg0[2]);
result[3] = FLOORF(arg0[3]);
}
static void do_FRC( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = arg0[0] - FLOORF(arg0[0]);
result[1] = arg0[1] - FLOORF(arg0[1]);
result[2] = arg0[2] - FLOORF(arg0[2]);
result[3] = arg0[3] - FLOORF(arg0[3]);
}
/* High precision log base 2:
*/
static void do_LG2( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = ApproxLog2(arg0[0]);
PUFF(result);
}
static void do_LIT( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
GLfloat tmp[4];
tmp[0] = 1.0;
tmp[1] = arg0[0];
if (arg0[0] > 0.0) {
tmp[2] = RoughApproxPower(arg0[1], arg0[3]);
}
else {
tmp[2] = 0.0;
}
tmp[3] = 1.0;
COPY_4V(result, tmp);
}
/* Intended to allow a lower precision than required for LG2 above.
*/
static void do_LOG( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
GLfloat tmp = FABSF(arg0[0]);
int exponent;
GLfloat mantissa = FREXPF(tmp, &exponent);
result[0] = (GLfloat) (exponent - 1);
result[1] = 2.0 * mantissa; /* map [.5, 1) -> [1, 2) */
result[2] = exponent + LOG2(mantissa);
result[3] = 1.0;
}
static void do_MAX( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] > arg1[0]) ? arg0[0] : arg1[0];
result[1] = (arg0[1] > arg1[1]) ? arg0[1] : arg1[1];
result[2] = (arg0[2] > arg1[2]) ? arg0[2] : arg1[2];
result[3] = (arg0[3] > arg1[3]) ? arg0[3] : arg1[3];
}
static void do_MIN( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] < arg1[0]) ? arg0[0] : arg1[0];
result[1] = (arg0[1] < arg1[1]) ? arg0[1] : arg1[1];
result[2] = (arg0[2] < arg1[2]) ? arg0[2] : arg1[2];
result[3] = (arg0[3] < arg1[3]) ? arg0[3] : arg1[3];
}
static void do_MOV( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = arg0[0];
result[1] = arg0[1];
result[2] = arg0[2];
result[3] = arg0[3];
}
static void do_MUL( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = arg0[0] * arg1[0];
result[1] = arg0[1] * arg1[1];
result[2] = arg0[2] * arg1[2];
result[3] = arg0[3] * arg1[3];
}
/* Intended to be "high" precision
*/
static void do_POW( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (GLfloat)ApproxPower(arg0[0], arg1[0]);
PUFF(result);
}
static void do_REL( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
GLuint idx = (op.alu.idx0 + (GLint)m->File[0][REG_ADDR][0]) & (MAX_NV_VERTEX_PROGRAM_PARAMS-1);
const GLfloat *arg0 = m->File[op.alu.file0][idx];
result[0] = arg0[0];
result[1] = arg0[1];
result[2] = arg0[2];
result[3] = arg0[3];
}
static void do_RCP( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = 1.0F / arg0[0];
PUFF(result);
}
static void do_RSQ( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
result[0] = INV_SQRTF(FABSF(arg0[0]));
PUFF(result);
}
static void do_SGE( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] >= arg1[0]) ? 1.0F : 0.0F;
result[1] = (arg0[1] >= arg1[1]) ? 1.0F : 0.0F;
result[2] = (arg0[2] >= arg1[2]) ? 1.0F : 0.0F;
result[3] = (arg0[3] >= arg1[3]) ? 1.0F : 0.0F;
}
static void do_SLT( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = (arg0[0] < arg1[0]) ? 1.0F : 0.0F;
result[1] = (arg0[1] < arg1[1]) ? 1.0F : 0.0F;
result[2] = (arg0[2] < arg1[2]) ? 1.0F : 0.0F;
result[3] = (arg0[3] < arg1[3]) ? 1.0F : 0.0F;
}
static void do_SUB( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
result[0] = arg0[0] - arg1[0];
result[1] = arg0[1] - arg1[1];
result[2] = arg0[2] - arg1[2];
result[3] = arg0[3] - arg1[3];
}
static void do_XPD( struct arb_vp_machine *m, union instruction op )
{
GLfloat *result = m->File[0][op.alu.dst];
const GLfloat *arg0 = m->File[op.alu.file0][op.alu.idx0];
const GLfloat *arg1 = m->File[op.alu.file1][op.alu.idx1];
GLfloat tmp[3];
tmp[0] = arg0[1] * arg1[2] - arg0[2] * arg1[1];
tmp[1] = arg0[2] * arg1[0] - arg0[0] * arg1[2];
tmp[2] = arg0[0] * arg1[1] - arg0[1] * arg1[0];
/* Need a temporary to be correct in the case where result == arg0
* or result == arg1.
*/
result[0] = tmp[0];
result[1] = tmp[1];
result[2] = tmp[2];
}
static void do_NOP( struct arb_vp_machine *m, union instruction op )
{
}
/* Some useful debugging functions:
*/
static void print_mask( GLuint mask )
{
_mesa_printf(".");
if (mask&0x1) _mesa_printf("x");
if (mask&0x2) _mesa_printf("y");
if (mask&0x4) _mesa_printf("z");
if (mask&0x8) _mesa_printf("w");
}
static void print_reg( GLuint file, GLuint reg )
{
static const char *reg_file[] = {
"REG",
"LOCAL_PARAM",
"ENV_PARAM",
"STATE_VAR",
};
if (file == 0) {
if (reg == REG_RES)
_mesa_printf("RES");
else if (reg >= REG_ARG0 && reg <= REG_ARG1)
_mesa_printf("ARG%d", reg - REG_ARG0);
else if (reg >= REG_TMP0 && reg <= REG_TMP11)
_mesa_printf("TMP%d", reg - REG_TMP0);
else if (reg >= REG_IN0 && reg <= REG_IN31)
_mesa_printf("IN%d", reg - REG_IN0);
else if (reg >= REG_OUT0 && reg <= REG_OUT14)
_mesa_printf("OUT%d", reg - REG_OUT0);
else if (reg == REG_ADDR)
_mesa_printf("ADDR");
else if (reg == REG_ID)
_mesa_printf("ID");
else
_mesa_printf("REG%d", reg);
}
else
_mesa_printf("%s:%d", reg_file[file], reg);
}
static void print_RSW( union instruction op )
{
GLuint swz = op.rsw.swz;
GLuint neg = op.rsw.neg;
GLuint i;
_mesa_printf("RSW ");
print_reg(0, op.rsw.dst);
_mesa_printf(", ");
print_reg(op.rsw.file0, op.rsw.idx0);
_mesa_printf(".");
for (i = 0; i < 4; i++, swz >>= 2) {
const char *cswz = "xyzw";
if (neg & (1<<i))
_mesa_printf("-");
_mesa_printf("%c", cswz[swz&0x3]);
}
_mesa_printf("\n");
}
static void print_ALU( union instruction op )
{
_mesa_printf("%s ", _mesa_opcode_string((enum prog_opcode) op.alu.opcode));
print_reg(0, op.alu.dst);
_mesa_printf(", ");
print_reg(op.alu.file0, op.alu.idx0);
if (_mesa_num_inst_src_regs((enum prog_opcode) op.alu.opcode) > 1) {
_mesa_printf(", ");
print_reg(op.alu.file1, op.alu.idx1);
}
_mesa_printf("\n");
}
static void print_MSK( union instruction op )
{
_mesa_printf("MSK ");
print_reg(0, op.msk.dst);
print_mask(op.msk.mask);
_mesa_printf(", ");
print_reg(op.msk.file, op.msk.idx);
_mesa_printf("\n");
}
static void print_NOP( union instruction op )
{
}
void
_tnl_disassem_vba_insn( union instruction op )
{
switch (op.alu.opcode) {
case OPCODE_ABS:
case OPCODE_ADD:
case OPCODE_DP3:
case OPCODE_DP4:
case OPCODE_DPH:
case OPCODE_DST:
case OPCODE_EX2:
case OPCODE_EXP:
case OPCODE_FLR:
case OPCODE_FRC:
case OPCODE_LG2:
case OPCODE_LIT:
case OPCODE_LOG:
case OPCODE_MAX:
case OPCODE_MIN:
case OPCODE_MOV:
case OPCODE_MUL:
case OPCODE_POW:
case OPCODE_PRINT:
case OPCODE_RCP:
case OPCODE_RSQ:
case OPCODE_SGE:
case OPCODE_SLT:
case OPCODE_SUB:
case OPCODE_XPD:
print_ALU(op);
break;
case OPCODE_ARA:
case OPCODE_ARL:
case OPCODE_ARL_NV:
case OPCODE_ARR:
case OPCODE_BRA:
case OPCODE_CAL:
case OPCODE_END:
case OPCODE_MAD:
case OPCODE_POPA:
case OPCODE_PUSHA:
case OPCODE_RCC:
case OPCODE_RET:
case OPCODE_SSG:
case OPCODE_SWZ:
print_NOP(op);
break;
case RSW:
print_RSW(op);
break;
case MSK:
print_MSK(op);
break;
case REL:
print_ALU(op);
break;
default:
_mesa_problem(NULL, "Bad opcode in _tnl_disassem_vba_insn()");
}
}
static void (* const opcode_func[MAX_OPCODE+3])(struct arb_vp_machine *, union instruction) =
{
do_ABS,
do_ADD,
do_NOP,/*ARA*/
do_NOP,/*ARL*/
do_NOP,/*ARL_NV*/
do_NOP,/*ARR*/
do_NOP,/*BRA*/
do_NOP,/*CAL*/
do_NOP,/*CMP*/
do_NOP,/*COS*/
do_NOP,/*DDX*/
do_NOP,/*DDY*/
do_DP3,
do_DP4,
do_DPH,
do_DST,
do_NOP,
do_EX2,
do_EXP,
do_FLR,
do_FRC,
do_NOP,/*KIL*/
do_NOP,/*KIL_NV*/
do_LG2,
do_LIT,
do_LOG,
do_NOP,/*LRP*/
do_NOP,/*MAD*/
do_MAX,
do_MIN,
do_MOV,
do_MUL,
do_NOP,/*PK2H*/
do_NOP,/*PK2US*/
do_NOP,/*PK4B*/
do_NOP,/*PK4UB*/
do_POW,
do_NOP,/*POPA*/
do_PRT,
do_NOP,/*PUSHA*/
do_NOP,/*RCC*/
do_RCP,/*RCP*/
do_NOP,/*RET*/
do_NOP,/*RFL*/
do_RSQ,
do_NOP,/*SCS*/
do_NOP,/*SEQ*/
do_NOP,/*SFL*/
do_SGE,
do_NOP,/*SGT*/
do_NOP,/*SIN*/
do_NOP,/*SLE*/
do_SLT,
do_NOP,/*SNE*/
do_NOP,/*SSG*/
do_NOP,/*STR*/
do_SUB,
do_RSW,/*SWZ*/
do_NOP,/*TEX*/
do_NOP,/*TXB*/
do_NOP,/*TXD*/
do_NOP,/*TXL*/
do_NOP,/*TXP*/
do_NOP,/*TXP_NV*/
do_NOP,/*UP2H*/
do_NOP,/*UP2US*/
do_NOP,/*UP4B*/
do_NOP,/*UP4UB*/
do_NOP,/*X2D*/
do_XPD,
do_RSW,
do_MSK,
do_REL,
};
static union instruction *cvp_next_instruction( struct compilation *cp )
{
union instruction *op = cp->csr++;
op->dword = 0;
return op;
}
static struct reg cvp_make_reg( GLuint file, GLuint idx )
{
struct reg reg;
reg.file = file;
reg.idx = idx;
return reg;
}
static struct reg cvp_emit_rel( struct compilation *cp,
struct reg reg,
struct reg tmpreg )
{
union instruction *op = cvp_next_instruction(cp);
op->alu.opcode = REL;
op->alu.file0 = reg.file;
op->alu.idx0 = reg.idx;
op->alu.dst = tmpreg.idx;
return tmpreg;
}
static struct reg cvp_load_reg( struct compilation *cp,
GLuint file,
GLuint index,
GLuint rel,
GLuint tmpidx )
{
struct reg tmpreg = cvp_make_reg(FILE_REG, tmpidx);
struct reg reg;
switch (file) {
case PROGRAM_TEMPORARY:
return cvp_make_reg(FILE_REG, REG_TMP0 + index);
case PROGRAM_INPUT:
return cvp_make_reg(FILE_REG, REG_IN0 + index);
case PROGRAM_OUTPUT:
return cvp_make_reg(FILE_REG, REG_OUT0 + index);
/* These two aren't populated by the parser?
*/
case PROGRAM_LOCAL_PARAM:
reg = cvp_make_reg(FILE_LOCAL_PARAM, index);
if (rel)
return cvp_emit_rel(cp, reg, tmpreg);
else
return reg;
case PROGRAM_ENV_PARAM:
reg = cvp_make_reg(FILE_ENV_PARAM, index);
if (rel)
return cvp_emit_rel(cp, reg, tmpreg);
else
return reg;
case PROGRAM_STATE_VAR:
reg = cvp_make_reg(FILE_STATE_PARAM, index);
if (rel)
return cvp_emit_rel(cp, reg, tmpreg);
else
return reg;
/* Invalid values:
*/
case PROGRAM_WRITE_ONLY:
case PROGRAM_ADDRESS:
default:
_mesa_problem(NULL, "Invalid register file %d in cvp_load_reg()");
assert(0);
return tmpreg; /* can't happen */
}
}
static struct reg cvp_emit_arg( struct compilation *cp,
const struct prog_src_register *src,
GLuint arg )
{
struct reg reg = cvp_load_reg( cp, src->File, src->Index, src->RelAddr, arg );
union instruction rsw, noop;
/* Emit any necessary swizzling.
*/
rsw.dword = 0;
rsw.rsw.neg = src->NegateBase ? WRITEMASK_XYZW : 0;
/* we're expecting 2-bit swizzles below... */
ASSERT(GET_SWZ(src->Swizzle, 0) < 4);
ASSERT(GET_SWZ(src->Swizzle, 1) < 4);
ASSERT(GET_SWZ(src->Swizzle, 2) < 4);
ASSERT(GET_SWZ(src->Swizzle, 3) < 4);
rsw.rsw.swz = ((GET_SWZ(src->Swizzle, 0) << 0) |
(GET_SWZ(src->Swizzle, 1) << 2) |
(GET_SWZ(src->Swizzle, 2) << 4) |
(GET_SWZ(src->Swizzle, 3) << 6));
noop.dword = 0;
noop.rsw.neg = 0;
noop.rsw.swz = RSW_NOOP;
if (rsw.dword != noop.dword) {
union instruction *op = cvp_next_instruction(cp);
struct reg rsw_reg = cvp_make_reg(FILE_REG, REG_ARG0 + arg);
op->dword = rsw.dword;
op->rsw.opcode = RSW;
op->rsw.file0 = reg.file;
op->rsw.idx0 = reg.idx;
op->rsw.dst = rsw_reg.idx;
return rsw_reg;
}
else
return reg;
}
static GLuint cvp_choose_result( struct compilation *cp,
const struct prog_dst_register *dst,
union instruction *fixup )
{
GLuint mask = dst->WriteMask;
GLuint idx;
switch (dst->File) {
case PROGRAM_TEMPORARY:
idx = REG_TMP0 + dst->Index;
break;
case PROGRAM_OUTPUT:
idx = REG_OUT0 + dst->Index;
break;
default:
assert(0);
return REG_RES; /* can't happen */
}
/* Optimization: When writing (with a writemask) to an undefined
* value for the first time, the writemask may be ignored.
*/
if (mask != WRITEMASK_XYZW && (cp->reg_active & (1 << idx))) {
fixup->msk.opcode = MSK;
fixup->msk.dst = idx;
fixup->msk.file = FILE_REG;
fixup->msk.idx = REG_RES;
fixup->msk.mask = mask;
cp->reg_active |= 1 << idx;
return REG_RES;
}
else {
fixup->dword = 0;
cp->reg_active |= 1 << idx;
return idx;
}
}
static struct reg cvp_emit_rsw( struct compilation *cp,
GLuint dst,
struct reg src,
GLuint neg,
GLuint swz,
GLboolean force)
{
struct reg retval;
if (swz != RSW_NOOP || neg != 0) {
union instruction *op = cvp_next_instruction(cp);
op->rsw.opcode = RSW;
op->rsw.dst = dst;
op->rsw.file0 = src.file;
op->rsw.idx0 = src.idx;
op->rsw.neg = neg;
op->rsw.swz = swz;
retval.file = FILE_REG;
retval.idx = dst;
return retval;
}
else if (force) {
/* Oops. Degenerate case:
*/
union instruction *op = cvp_next_instruction(cp);
op->alu.opcode = OPCODE_MOV;
op->alu.dst = dst;
op->alu.file0 = src.file;
op->alu.idx0 = src.idx;
retval.file = FILE_REG;
retval.idx = dst;
return retval;
}
else {
return src;
}
}
static void cvp_emit_inst( struct compilation *cp,
const struct prog_instruction *inst )
{
union instruction *op;
union instruction fixup;
struct reg reg[3];
GLuint result, nr_args, i;
assert(sizeof(*op) == sizeof(long long));
/* Need to handle SWZ, ARL specially.
*/
switch (inst->Opcode) {
/* Split into mul and add:
*/
case OPCODE_MAD:
result = cvp_choose_result( cp, &inst->DstReg, &fixup );
for (i = 0; i < 3; i++)
reg[i] = cvp_emit_arg( cp, &inst->SrcReg[i], REG_ARG0+i );
op = cvp_next_instruction(cp);
op->alu.opcode = OPCODE_MUL;
op->alu.file0 = reg[0].file;
op->alu.idx0 = reg[0].idx;
op->alu.file1 = reg[1].file;
op->alu.idx1 = reg[1].idx;
op->alu.dst = REG_ARG0;
op = cvp_next_instruction(cp);
op->alu.opcode = OPCODE_ADD;
op->alu.file0 = FILE_REG;
op->alu.idx0 = REG_ARG0;
op->alu.file1 = reg[2].file;
op->alu.idx1 = reg[2].idx;
op->alu.dst = result;
if (result == REG_RES) {
op = cvp_next_instruction(cp);
op->dword = fixup.dword;
}
break;
case OPCODE_ARL:
reg[0] = cvp_emit_arg( cp, &inst->SrcReg[0], REG_ARG0 );
op = cvp_next_instruction(cp);
op->alu.opcode = OPCODE_FLR;
op->alu.dst = REG_ADDR;
op->alu.file0 = reg[0].file;
op->alu.idx0 = reg[0].idx;
break;
case OPCODE_SWZ: {
GLuint swz0 = 0, swz1 = 0;
GLuint neg0 = 0, neg1 = 0;
GLuint mask = 0;
/* Translate 3-bit-per-element swizzle into two 2-bit swizzles,
* one from the source register the other from a constant
* {0,0,0,1}.
*/
for (i = 0; i < 4; i++) {
GLuint swzelt = GET_SWZ(inst->SrcReg[0].Swizzle, i);
if (swzelt >= SWIZZLE_ZERO) {
neg0 |= inst->SrcReg[0].NegateBase & (1<<i);
if (swzelt == SWIZZLE_ONE)
swz0 |= SWIZZLE_W << (i*2);
else if (i < SWIZZLE_W)
swz0 |= i << (i*2);
}
else {
mask |= 1<<i;
neg1 |= inst->SrcReg[0].NegateBase & (1<<i);
swz1 |= swzelt << (i*2);
}
}
result = cvp_choose_result( cp, &inst->DstReg, &fixup );
reg[0].file = FILE_REG;
reg[0].idx = REG_ID;
reg[1] = cvp_emit_arg( cp, &inst->SrcReg[0], REG_ARG0 );
if (mask == WRITEMASK_XYZW) {
cvp_emit_rsw(cp, result, reg[0], neg0, swz0, GL_TRUE);
}
else if (mask == 0) {
cvp_emit_rsw(cp, result, reg[1], neg1, swz1, GL_TRUE);
}
else {
cvp_emit_rsw(cp, result, reg[0], neg0, swz0, GL_TRUE);
reg[1] = cvp_emit_rsw(cp, REG_ARG0, reg[1], neg1, swz1, GL_FALSE);
op = cvp_next_instruction(cp);
op->msk.opcode = MSK;
op->msk.dst = result;
op->msk.file = reg[1].file;
op->msk.idx = reg[1].idx;
op->msk.mask = mask;
}
if (result == REG_RES) {
op = cvp_next_instruction(cp);
op->dword = fixup.dword;
}
break;
}
case OPCODE_END:
break;
default:
result = cvp_choose_result( cp, &inst->DstReg, &fixup );
nr_args = _mesa_num_inst_src_regs(inst->Opcode);
for (i = 0; i < nr_args; i++)
reg[i] = cvp_emit_arg( cp, &inst->SrcReg[i], REG_ARG0 + i );
op = cvp_next_instruction(cp);
op->alu.opcode = inst->Opcode;
op->alu.file0 = reg[0].file;
op->alu.idx0 = reg[0].idx;
op->alu.file1 = reg[1].file;
op->alu.idx1 = reg[1].idx;
op->alu.dst = result;
if (result == REG_RES) {
op = cvp_next_instruction(cp);
op->dword = fixup.dword;
}
break;
}
}
static void free_tnl_data( struct vertex_program *program )
{
struct tnl_compiled_program *p = (struct tnl_compiled_program *) program->TnlData;
if (p->compiled_func)
_mesa_free((void *)p->compiled_func);
_mesa_free(p);
program->TnlData = NULL;
}
static void compile_vertex_program( struct vertex_program *program,
GLboolean try_codegen )
{
struct compilation cp;
struct tnl_compiled_program *p = CALLOC_STRUCT(tnl_compiled_program);
GLuint i;
if (program->TnlData)
free_tnl_data( program );
program->TnlData = p;
/* Initialize cp. Note that ctx and VB aren't used in compilation
* so we don't have to worry about statechanges:
*/
_mesa_memset(&cp, 0, sizeof(cp));
cp.csr = p->instructions;
/* Compile instructions:
*/
for (i = 0; i < program->Base.NumInstructions; i++) {
cvp_emit_inst(&cp, &program->Base.Instructions[i]);
}
/* Finish up:
*/
p->nr_instructions = cp.csr - p->instructions;
/* Print/disassemble:
*/
if (DISASSEM) {
for (i = 0; i < p->nr_instructions; i++) {
_tnl_disassem_vba_insn(p->instructions[i]);
}
_mesa_printf("\n\n");
}
#ifdef USE_SSE_ASM
if (try_codegen)
_tnl_sse_codegen_vertex_program(p);
#endif
}
/* ----------------------------------------------------------------------
* Execution
*/
static void userclip( GLcontext *ctx,
GLvector4f *clip,
GLubyte *clipmask,
GLubyte *clipormask,
GLubyte *clipandmask )
{
GLuint p;
for (p = 0; p < ctx->Const.MaxClipPlanes; p++) {
if (ctx->Transform.ClipPlanesEnabled & (1 << p)) {
GLuint nr, i;
const GLfloat a = ctx->Transform._ClipUserPlane[p][0];
const GLfloat b = ctx->Transform._ClipUserPlane[p][1];
const GLfloat c = ctx->Transform._ClipUserPlane[p][2];
const GLfloat d = ctx->Transform._ClipUserPlane[p][3];
GLfloat *coord = (GLfloat *)clip->data;
GLuint stride = clip->stride;
GLuint count = clip->count;
for (nr = 0, i = 0 ; i < count ; i++) {
GLfloat dp = (coord[0] * a +
coord[1] * b +
coord[2] * c +
coord[3] * d);
if (dp < 0) {
nr++;
clipmask[i] |= CLIP_USER_BIT;
}
STRIDE_F(coord, stride);
}
if (nr > 0) {
*clipormask |= CLIP_USER_BIT;
if (nr == count) {
*clipandmask |= CLIP_USER_BIT;
return;
}
}
}
}
}
static GLboolean
do_ndc_cliptest(GLcontext *ctx, struct arb_vp_machine *m)
{
TNLcontext *tnl = TNL_CONTEXT(ctx);
struct vertex_buffer *VB = m->VB;
/* Cliptest and perspective divide. Clip functions must clear
* the clipmask.
*/
m->ormask = 0;
m->andmask = CLIP_ALL_BITS;
if (tnl->NeedNdcCoords) {
VB->NdcPtr =
_mesa_clip_tab[VB->ClipPtr->size]( VB->ClipPtr,
&m->ndcCoords,
m->clipmask,
&m->ormask,
&m->andmask );
}
else {
VB->NdcPtr = NULL;
_mesa_clip_np_tab[VB->ClipPtr->size]( VB->ClipPtr,
NULL,
m->clipmask,
&m->ormask,
&m->andmask );
}
if (m->andmask) {
/* All vertices are outside the frustum */
return GL_FALSE;
}
/* Test userclip planes. This contributes to VB->ClipMask.
*/
if (ctx->Transform.ClipPlanesEnabled && !ctx->VertexProgram._Enabled) {
userclip( ctx,
VB->ClipPtr,
m->clipmask,
&m->ormask,
&m->andmask );
if (m->andmask) {
return GL_FALSE;
}
}
VB->ClipAndMask = m->andmask;
VB->ClipOrMask = m->ormask;
VB->ClipMask = m->clipmask;
return GL_TRUE;
}
static INLINE void call_func( struct tnl_compiled_program *p,
struct arb_vp_machine *m )
{
p->compiled_func(m);
}
/**
* Execute the given vertex program.
*
* TODO: Integrate the t_vertex.c code here, to build machine vertices
* directly at this point.
*
* TODO: Eliminate the VB struct entirely and just use
* struct arb_vertex_machine.
*/
static GLboolean
run_arb_vertex_program(GLcontext *ctx, struct tnl_pipeline_stage *stage)
{
struct vertex_program *program = (ctx->VertexProgram._Enabled ?
ctx->VertexProgram.Current :
ctx->_TnlProgram);
struct vertex_buffer *VB = &TNL_CONTEXT(ctx)->vb;
struct arb_vp_machine *m = ARB_VP_MACHINE(stage);
struct tnl_compiled_program *p;
GLuint i, j;
GLbitfield outputs;
if (!program || program->IsNVProgram)
return GL_TRUE;
if (program->Base.Parameters) {
_mesa_load_state_parameters(ctx, program->Base.Parameters);
}
p = (struct tnl_compiled_program *)program->TnlData;
assert(p);
m->nr_inputs = m->nr_outputs = 0;
for (i = 0; i < _TNL_ATTRIB_MAX; i++) {
if (program->Base.InputsRead & (1<<i)) {
GLuint j = m->nr_inputs++;
m->input[j].idx = i;
m->input[j].data = (GLfloat *)m->VB->AttribPtr[i]->data;
m->input[j].stride = m->VB->AttribPtr[i]->stride;
m->input[j].size = m->VB->AttribPtr[i]->size;
ASSIGN_4V(m->File[0][REG_IN0 + i], 0, 0, 0, 1);
}
}
for (i = 0; i < VERT_RESULT_MAX; i++) {
if (program->Base.OutputsWritten & (1 << i)) {
GLuint j = m->nr_outputs++;
m->output[j].idx = i;
m->output[j].data = (GLfloat *)m->attribs[i].data;
}
}
/* Run the actual program:
*/
for (m->vtx_nr = 0; m->vtx_nr < VB->Count; m->vtx_nr++) {
for (j = 0; j < m->nr_inputs; j++) {
GLuint idx = REG_IN0 + m->input[j].idx;
switch (m->input[j].size) {
case 4: m->File[0][idx][3] = m->input[j].data[3];
case 3: m->File[0][idx][2] = m->input[j].data[2];
case 2: m->File[0][idx][1] = m->input[j].data[1];
case 1: m->File[0][idx][0] = m->input[j].data[0];
}
STRIDE_F(m->input[j].data, m->input[j].stride);
}
if (p->compiled_func) {
call_func( p, m );
}
else {
for (j = 0; j < p->nr_instructions; j++) {
union instruction inst = p->instructions[j];
opcode_func[inst.alu.opcode]( m, inst );
}
}
for (j = 0; j < m->nr_outputs; j++) {
GLuint idx = REG_OUT0 + m->output[j].idx;
m->output[j].data[0] = m->File[0][idx][0];
m->output[j].data[1] = m->File[0][idx][1];
m->output[j].data[2] = m->File[0][idx][2];
m->output[j].data[3] = m->File[0][idx][3];
m->output[j].data += 4;
}
}
/* Setup the VB pointers so that the next pipeline stages get
* their data from the right place (the program output arrays).
*
* TODO: 1) Have tnl use these RESULT values for outputs rather
* than trying to shoe-horn inputs and outputs into one set of
* values.
*
* TODO: 2) Integrate t_vertex.c so that we just go straight ahead
* and build machine vertices here.
*/
VB->ClipPtr = &m->attribs[VERT_RESULT_HPOS];
VB->ClipPtr->count = VB->Count;
outputs = program->Base.OutputsWritten;
if (outputs & (1<<VERT_RESULT_COL0)) {
VB->ColorPtr[0] = &m->attribs[VERT_RESULT_COL0];
VB->AttribPtr[VERT_ATTRIB_COLOR0] = VB->ColorPtr[0];
}
if (outputs & (1<<VERT_RESULT_BFC0)) {
VB->ColorPtr[1] = &m->attribs[VERT_RESULT_BFC0];
}
if (outputs & (1<<VERT_RESULT_COL1)) {
VB->SecondaryColorPtr[0] = &m->attribs[VERT_RESULT_COL1];
VB->AttribPtr[VERT_ATTRIB_COLOR1] = VB->SecondaryColorPtr[0];
}
if (outputs & (1<<VERT_RESULT_BFC1)) {
VB->SecondaryColorPtr[1] = &m->attribs[VERT_RESULT_BFC1];
}
if (outputs & (1<<VERT_RESULT_FOGC)) {
VB->FogCoordPtr = &m->attribs[VERT_RESULT_FOGC];
VB->AttribPtr[VERT_ATTRIB_FOG] = VB->FogCoordPtr;
}
if (outputs & (1<<VERT_RESULT_PSIZ)) {
VB->PointSizePtr = &m->attribs[VERT_RESULT_PSIZ];
VB->AttribPtr[_TNL_ATTRIB_POINTSIZE] = &m->attribs[VERT_RESULT_PSIZ];
}
for (i = 0; i < ctx->Const.MaxTextureUnits; i++) {
if (outputs & (1<<(VERT_RESULT_TEX0+i))) {
VB->TexCoordPtr[i] = &m->attribs[VERT_RESULT_TEX0 + i];
VB->AttribPtr[VERT_ATTRIB_TEX0+i] = VB->TexCoordPtr[i];
}
}
#if 0
for (i = 0; i < VB->Count; i++) {
printf("Out %d: %f %f %f %f %f %f %f %f\n", i,
VEC_ELT(VB->ClipPtr, GLfloat, i)[0],
VEC_ELT(VB->ClipPtr, GLfloat, i)[1],
VEC_ELT(VB->ClipPtr, GLfloat, i)[2],
VEC_ELT(VB->ClipPtr, GLfloat, i)[3],
VEC_ELT(VB->TexCoordPtr[0], GLfloat, i)[0],
VEC_ELT(VB->TexCoordPtr[0], GLfloat, i)[1],
VEC_ELT(VB->TexCoordPtr[0], GLfloat, i)[2],
VEC_ELT(VB->TexCoordPtr[0], GLfloat, i)[3]);
}
#endif
/* Perform NDC and cliptest operations:
*/
return do_ndc_cliptest(ctx, m);
}
static void
validate_vertex_program( GLcontext *ctx, struct tnl_pipeline_stage *stage )
{
struct arb_vp_machine *m = ARB_VP_MACHINE(stage);
struct vertex_program *program =
(ctx->VertexProgram._Enabled ? ctx->VertexProgram.Current : 0);
if (!program && ctx->_MaintainTnlProgram) {
program = ctx->_TnlProgram;
}
if (program) {
if (!program->TnlData)
compile_vertex_program( program, m->try_codegen );
/* Grab the state GL state and put into registers:
*/
m->File[FILE_LOCAL_PARAM] = program->Base.LocalParams;
m->File[FILE_ENV_PARAM] = ctx->VertexProgram.Parameters;
/* GL_NV_vertex_programs can't reference GL state */
if (program->Base.Parameters)
m->File[FILE_STATE_PARAM] = program->Base.Parameters->ParameterValues;
else
m->File[FILE_STATE_PARAM] = NULL;
}
}
/**
* Called the first time stage->run is called. In effect, don't
* allocate data until the first time the stage is run.
*/
static GLboolean init_vertex_program( GLcontext *ctx,
struct tnl_pipeline_stage *stage )
{
TNLcontext *tnl = TNL_CONTEXT(ctx);
struct vertex_buffer *VB = &(tnl->vb);
struct arb_vp_machine *m;
const GLuint size = VB->Size;
GLuint i;
stage->privatePtr = _mesa_malloc(sizeof(*m));
m = ARB_VP_MACHINE(stage);
if (!m)
return GL_FALSE;
/* arb_vertex_machine struct should subsume the VB:
*/
m->VB = VB;
m->File[0] = (GLfloat(*)[4])ALIGN_MALLOC(REG_MAX * sizeof(GLfloat) * 4, 16);
/* Initialize regs where necessary:
*/
ASSIGN_4V(m->File[0][REG_ID], 0, 0, 0, 1);
ASSIGN_4V(m->File[0][REG_ONES], 1, 1, 1, 1);
ASSIGN_4V(m->File[0][REG_SWZ], -1, 1, 0, 0);
ASSIGN_4V(m->File[0][REG_NEG], -1, -1, -1, -1);
ASSIGN_4V(m->File[0][REG_LIT], 1, 0, 0, 1);
ASSIGN_4V(m->File[0][REG_LIT2], 1, .5, .2, 1); /* debug value */
if (_mesa_getenv("MESA_EXPERIMENTAL"))
m->try_codegen = 1;
/* Allocate arrays of vertex output values */
for (i = 0; i < VERT_RESULT_MAX; i++) {
_mesa_vector4f_alloc( &m->attribs[i], 0, size, 32 );
m->attribs[i].size = 4;
}
/* a few other misc allocations */
_mesa_vector4f_alloc( &m->ndcCoords, 0, size, 32 );
m->clipmask = (GLubyte *) ALIGN_MALLOC(sizeof(GLubyte)*size, 32 );
if (ctx->_MaintainTnlProgram)
_mesa_allow_light_in_model( ctx, GL_FALSE );
m->fpucntl_rnd_neg = RND_NEG_FPU; /* const value */
m->fpucntl_restore = RESTORE_FPU; /* const value */
return GL_TRUE;
}
/**
* Destructor for this pipeline stage.
*/
static void dtr( struct tnl_pipeline_stage *stage )
{
struct arb_vp_machine *m = ARB_VP_MACHINE(stage);
if (m) {
GLuint i;
/* free the vertex program result arrays */
for (i = 0; i < VERT_RESULT_MAX; i++)
_mesa_vector4f_free( &m->attribs[i] );
/* free misc arrays */
_mesa_vector4f_free( &m->ndcCoords );
ALIGN_FREE( m->clipmask );
ALIGN_FREE( m->File[0] );
_mesa_free( m );
stage->privatePtr = NULL;
}
}
/**
* Public description of this pipeline stage.
*/
const struct tnl_pipeline_stage _tnl_arb_vertex_program_stage =
{
"vertex-program",
NULL, /* private_data */
init_vertex_program, /* create */
dtr, /* destroy */
validate_vertex_program, /* validate */
run_arb_vertex_program /* run */
};
/**
* Called via ctx->Driver.ProgramStringNotify() after a new vertex program
* string has been parsed.
*/
void
_tnl_program_string(GLcontext *ctx, GLenum target, struct program *program)
{
if (program->Target == GL_VERTEX_PROGRAM_ARB) {
/* free any existing tnl data hanging off the program */
struct vertex_program *vprog = (struct vertex_program *) program;
if (vprog->TnlData) {
free_tnl_data(vprog);
}
}
}