/* =========================================================================== maya2q3 - export .md3 files from maya Copyright (C) 2005 HermitWorks Entertainment Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =========================================================================== */ #include "common.h" namespace shader { #define NL "\n" void Shader::OptimizeTexMods( ShaderStage::TextureBundle &texstage ) { for( int i = 0; i < texstage.numTexMods; i++ ) { //if i can be matrixed, flag it to be matrixed switch( texstage.texMods[i].type ) { case TexMod::Rotate: case TexMod::Scale: case TexMod::Stretch: case TexMod::Transform: //really no point matrixing this one, except that if an //adjacent mod can also be matrixed they can be combined texstage.texMods[i].type |= (TexMod)(TexMod::Combine_0 + i); } } //combine adjacent pairs of tex mod matrices for( int i = 0; i + 1 < texstage.numTexMods; i++ ) if( texstage.texMods[i].type & TexMod::Combine_Bits && texstage.texMods[i + 1].type & TexMod::Combine_Bits ) //two combinable texture mods in a row texstage.texMods[i].type |= TexMod::Combine_Forward; //clear unnecessary texture matrix flags for( int i = 0; i < texstage.numTexMods; i++ ) if( (texstage.texMods[i].type & TexMod::Type_Bits) == TexMod::Transform && !(texstage.texMods[i].type & TexMod::Combine_Forward) && (i == 0 || i > 0 && !(texstage.texMods[i - 1].type & TexMod::Combine_Forward)) ) //transform that isn't collapsing into any other texmods shouldn't be matrixed texstage.texMods[i].type &= ~TexMod::Combine_Bits; } void Shader::BuildWaveEval( const WaveForm &wave, const char *timeReg, const char *ofsReg, const char *outReg, shader_src &prog ) { //generates max 9 instructions, 8 if 1 of the registers is missing //offset (optionally) comes in at ofsReg //output in outReg if( ofsReg ) { if( timeReg ) prog.Append( //adjust for phase NL "ADD tmp.x, %s, %f;" NL "MAD tmp.x, %s, %f, tmp.x;" //put into 0..1 NL "FRC tmp.x, tmp.x;", ofsReg, wave.phase, timeReg, wave.frequency ); else { if( wave.phase != 0.0F ) prog.Append( //adjust for phase NL "ADD tmp.x, %s, %f;" //put into 0..1 NL "FRC tmp.x, tmp.x;", ofsReg, wave.phase ); else prog.Append( //put into 0..1 NL "FRC tmp.x, %s;", ofsReg ); } } else prog.Append( //adjust for phase NL "MAD tmp.x, %s, %f, %f;" //put into 0..1 NL "FRC tmp.x, tmp.x;", timeReg, wave.frequency, wave.phase ); //function param is now in [0, 1] bool endmad = wave.amplitude != 1.0F || wave.base != 0.0F; switch( wave.type ) { case WaveFunc::Sin: //taylor series approximation prog.Append( //put tmp.x into [0,2pi] NL "MUL tmp.x, tmp.x, pConsts.w;" //get a vector in the form of x, x^3, x^5, x^7 NL "MUL tmp.yzw, tmp.x, tmp.x;" //x, x^2, x^2, x^2 NL "MUL tmp.yw, tmp, tmp.xxzz;" //x, x^3, x^2, x^4 NL "MUL tmp.zw, tmp, tmp.y;" //x, x^3, x^5, x^7 NL "DP4 %s, tmp, pConstsSin;", endmad ? "tmp" : outReg ); break; case WaveFunc::Square: prog.Append( NL "SLT tmp.x, tmp.x, 0.5;" //1 or 0 NL "MAD %s, tmp.x, 2, -1;", //1 or -1 endmad ? "tmp" : outReg ); break; case WaveFunc::Triangle: prog.Append( //set up selectors to pick the part of the wave we want NL "SLT s0, tmp.xxxx, pConstsTriL;" NL "SGE s1, tmp.xxxx, pConstsTriG;" NL "MUL s0, s0, s1;" //get the 3 parts of the function NL "MAD tmp, tmp.x, pConstsTriM, pConstsTriA;" //pick the relevant one NL "DP3 %s, tmp, s0;", endmad ? "tmp" : outReg ); break; case WaveFunc::SawTooth: prog.Append( NL "MOV %s, tmp.x;", endmad ? "tmp" : outReg ); break; case WaveFunc::InvSawTooth: prog.Append( NL "SUB %s, 1, tmp.x;", endmad ? "tmp" : outReg ); break; default: fail( "invalid waveform in shader" ); } //finish the wave evaluation if( endmad ) prog.Append( NL "MAD %s, tmp, %f, %f;", outReg, wave.amplitude, wave.base ); } GLuint Shader::BuildShader( size_t iPass ) { shader_src prog = "!!ARBvp1.0" NL "ATTRIB iPos = vertex.position;" NL "ATTRIB iNorm = vertex.normal;" NL "ATTRIB iColor = vertex.color;" NL "ATTRIB iTexCoord = vertex.texcoord;" NL "OUTPUT oPos = result.position;" NL "OUTPUT oColor = result.color;" NL "OUTPUT oTexCoord = result.texcoord;" NL "TEMP pos, norm, texCoord, tmp, tmp2, tmp3;" NL "ALIAS s0 = tmp2;" NL "ALIAS s1 = tmp3;" NL "ADDRESS aNoise;" NL "PARAM pConsts = { 109.119200877, 18.1865334795, 0.159154943, 6.283185307 };" //63*sqrt(3), 63/(2*sqrt(3)), 1/(2*PI), 2*PI NL "PARAM pConstsEnv = { 0.5, -0.5, 0.5, 0.5 };" NL "PARAM pConstsSin = { 1, -0.166666667, 0.008333333, -0.000198413 };" //1, -1/(3!), 1/(5!), -1/(7!) NL "PARAM pConstsCos = { 1, -0.5, 0.0416666666667, -0.0013888888889 };" //1, -1/(2!), 1/(4!), -1/(6!) NL "PARAM pConstsTriM = { 4, -4, 4, 1 };" NL "PARAM pConstsTriA = { 0, 2, -4, 1 };" NL "PARAM pConstsTriL = { 0.25, 0.75, 1, 1 };" NL "PARAM pConstsTriG = { 0, 0.25, 0.75, 1 };" NL "PARAM pMatMVP[4] = { state.matrix.mvp };" NL "PARAM pEye = state.matrix.modelview.invtrans.row[3];" NL "PARAM pLitAmbient = state.light[0].ambient;" NL "PARAM pLitDiffuse = state.light[0].diffuse;" NL "PARAM pLitDir = state.light[0].position;" NL "PARAM pTime = program.local[0];" //time NL "PARAM pTexGenV[2] = { program.local[1..2] };" NL "PARAM pTexMat0[2] = { program.local[3..4] };" NL "PARAM pTexMat1[2] = { program.local[5..6] };" NL "PARAM pTexMat2[2] = { program.local[7..8] };" NL "PARAM pTexMat3[2] = { program.local[9..10] };" NL "PARAM pNoise[64] = { program.local[32..95] };" //some noisy data -- see FixMe in case Deformation::Normals NL "MOV pos, iPos;" NL "MOV norm, iNorm;"; //color and texCoord will be initialized by the stage's gen functions //process the deformation for( int i = 0; i < m_numDeforms; i++ ) { const DeformStage &ds = m_deforms[i]; switch( ds.type ) { case Deformation::None: // 0 instructions break; case Deformation::Normals: // 10 instructions //twitch the normals //the following is placeholder code //FixMe: read up on noise functions and redo this bit prog.Append( NL "DP3 tmp.w, pos, pos;" NL "RSQ tmp.w, tmp.w;" NL "MUL tmp.xyz, pos.xyz, tmp.w;" NL "DP3 tmp.w, tmp.xyz, pConsts.y;" NL "ADD tmp.w, tmp.w, pConsts.x;" NL "ARL aNoise.x, tmp.w;" NL "MAD norm, pNoise[aNoise.x], pWave.y, norm;" NL "DP3 norm.w, norm, norm;" NL "RSQ norm.w, norm.w;" NL "MUL norm.xyz, norm.xyz, norm.w;" ); break; case Deformation::Wave: //11 instructions if( ds.wave.type != WaveFunc::None ) { //get an offset into tmp.x if( ds.wave.frequency == 0 ) BuildWaveEval( ds.wave, "pTime.x", null, "tmp.x", prog ); else { prog.Append( NL "DP3 tmp.x, iPos, %f;", ds.spread ); BuildWaveEval( ds.wave, "pTime.x", "tmp.x", "tmp.x", prog ); } //tmp.x now has the wave value, apply it prog.Append( NL "MAD pos.xyz, norm, tmp.x, pos;" ); } break; case Deformation::Bulge: //11 instructions { WaveForm wave; wave.type = WaveFunc::Sin; wave.amplitude = ds.bulge.height; wave.frequency = 1.0F / (2.0F * (float)M_PI); wave.base = 0; wave.phase = 0; prog.Append( NL "MUL tmp.w, pTime.x, %f;" NL "MAD tmp.x, iTexCoord.x, %f, tmp.w;", ds.bulge.width, ds.bulge.speed * 0.001F ); BuildWaveEval( wave, null, "tmp.x", "tmp.x", prog ); //tmp.x now has the wave value, apply it prog.Append( NL "MAD pos.xyz, norm, tmp.x, pos;" ); } break; case Deformation::Move: //9 instructions if( ds.wave.type != WaveFunc::None ) { //the wave eval could (technically) be hoisted out //and evaluated on the CPU as it is constant, but it //would be too much work to track that sort of optimization //for a non real-time application BuildWaveEval( ds.wave, "pTime.x", null, "tmp.x", prog ); prog.Append( NL "MAD pos, { %f, %f, %f, 0 }, tmp.x, pos;", ds.moveVec[0], ds.moveVec[1], ds.moveVec[2] ); } break; case Deformation::AutoSprite: //FixMe: get cpu-side verts and get this rigged up break; case Deformation::AutoSprite2: //FixMe: get cpu-side verts and get this rigged up break; } } //have used max of 35 instructions thus far ShaderStage &stage = m_stages[iPass]; //rgb gen switch( stage.rgb.type ) { case RgbGen::Identity: case RgbGen::IdentityLighting: //1 instruction prog.Append( NL "MOV oColor, 1;" ); break; case RgbGen::DiffuseLit: //5 instructions prog.Append( NL "DP3 tmp, pLitDir, norm;" NL "MAX tmp, tmp, 0;" NL "MAD tmp2, pLitDiffuse, tmp, pLitAmbient;" NL "MAX tmp2, tmp2, 1;" NL "SWZ oColor, tmp2, x, y, z, 1;" ); break; case RgbGen::Vertex: //1 instruction case RgbGen::ExactVertex: prog.Append( NL "MOV oColor, iColor;" ); break; case RgbGen::InvVertex: //1 instruction prog.Append( NL "SUB oColor, 1, iColor;" ); break; case RgbGen::Const: //1 instruction prog.Append( NL "MOV oColor, { %f, %f, %f, 1 };", stage.rgb.c[0], stage.rgb.c[1], stage.rgb.c[2] ); break; case RgbGen::Wave: //10 instructions max if( stage.rgb.wave.type == WaveFunc::None ) { prog.Append( NL "MOV oColor, { %f, %f, %f, 1 };", stage.rgb.c[0], stage.rgb.c[1], stage.rgb.c[2] ); break; } if( stage.rgb.wave.type == WaveFunc::Noise ) { //another block that could go out onto the CPU prog.Append( NL "ADD tmp.x, pTime.x, %f;" NL "MUL tmp.x, tmp.x, %f;" NL "FRC tmp.x, tmp.x;" NL "MUL tmp.x, tmp.x, 63;" NL "ARL aNoise.x, tmp.x;" NL "MOV tmp.x, pNoise[aNoise.x];", stage.rgb.wave.phase, stage.rgb.wave.frequency ); } else BuildWaveEval( stage.rgb.wave, "pTime.x", null, "oColor.xyz", prog ); break; case RgbGen::Entity: case RgbGen::InvEntity: //we don't have an entity here, fudge this for now prog.Append( NL "MOV oColor, 1;" ); break; } //45 instructions switch( stage.alpha.type ) { case AlphaGen::Skip: //pass to identity as the last stage may not have set the alpha case AlphaGen::Identity: prog.Append( NL "MOV oColor.w, 1;" ); break; case AlphaGen::Const: prog.Append( NL "MOV oColor.w, %f;", stage.alpha.a ); break; case AlphaGen::Wave: //10 instructions if( stage.alpha.wave.type == WaveFunc::None ) { prog.Append( NL "MOV oColor.w, %f;", stage.alpha.a ); break; } else { BuildWaveEval( stage.alpha.wave, "pTime.x", null, "oColor.w", prog ); } break; case AlphaGen::Specular: //11 instructions prog.Append( NL "DP3 tmp.x, norm, pLitDir;" NL "ADD tmp.x, tmp.x, tmp.x;" NL "MAD tmp, norm, tmp.x, -pLitDir;" NL "SUB tmp2, pEye, pos;" NL "DP3 tmp2.w, tmp2, tmp2;" NL "RSQ tmp2.w, tmp2.w;" NL "MUL tmp2, tmp2, tmp2.w;" NL "DP3 tmp.x, tmp, tmp2;" NL "MAX tmp.x, tmp.x, 0;" NL "MUL tmp.x, tmp.x, tmp.x;" NL "MUL oColor.w, tmp.x, tmp.x;" ); break; case AlphaGen::Entity: case AlphaGen::InvEntity: //we don't have an entity here, fudge this for now prog.Append( NL "MOV oColor.w, 1;" ); break; case AlphaGen::Vertex: prog.Append( NL "MOV oColor.w, iColor.w;" ); break; case AlphaGen::InvVertex: prog.Append( NL "SUB oColor.w, 1, iColor.w;" ); break; case AlphaGen::Portal: //6 instructions prog.Append( NL "SUB tmp, pos, pEye;" NL "DP3 tmp, tmp, tmp;" NL "MUL tmp, tmp, %f;" NL "mov oColor.w, tmp;", 1.0F / m_portalRange ); } //56 instructions switch( stage.bundle.texGen ) { case TexGen::Identity: prog.Append( NL "MOV texCoord, 0;" ); break; case TexGen::Vector: prog.Append( NL "DP3 texCoord.x, pos, pTexGenV[0];" NL "DP3 texCoord.y, pos, pTexGenV[1];" ); break; case TexGen::EnvMap: //8 instructions prog.Append( NL "SUB tmp, pEye, pos;" NL "DP3 tmp.w, tmp, tmp;" NL "RSQ tmp.w, tmp.w;" NL "MUL tmp, tmp, tmp.w;" NL "DP3 tmp2.x, tmp, norm;" NL "ADD tmp2.x, tmp2.x, tmp2.x;" NL "MAD tmp, norm, tmp2.x, -tmp;" NL "MAD texCoord.xy, tmp, pConstsEnv, pConstsEnv.zwww;" ); break; case TexGen::Texture: default: prog.Append( NL "MOV texCoord, iTexCoord;" ); break; } //64 instructions for( int i = 0; i < stage.bundle.numTexMods; i++ ) { const ShaderStage::TextureBundle::TextureMod &tcMod = stage.bundle.texMods[i]; if( tcMod.type == TexMod::None ) break; if( tcMod.type & TexMod::Combine_Bits ) { if( (tcMod.type & TexMod::Combine_Bits) == TexMod::Combine_Forward ) //combined into an upcoming tcMod matrix continue; int iTexMat = tcMod.type & TexMod::Combine_Bits - TexMod::Combine_0; prog.Append( NL "SWZ tmp, texCoord, x, y, 1, 0;" NL "DP3 texCoord.x, tmp, pTexMat%i[0];" NL "DP3 texCoord.y, tmp, pTexMat%i[1];", iTexMat, iTexMat ); continue; } switch( tcMod.type & TexMod::Type_Bits ) { case TexMod::Turbulent: //21 instructions //turbulent is the problem child of the tcMods //its dependency on using geometric position as a paramater to a wave function //renders it impossible for us to fit this into a simple matrix multiply { WaveForm wave = tcMod.wave; wave.type = WaveFunc::Sin; wave.base = 0; prog.Append( NL "MUL texCoord.w, pTime.x, %f;" NL "ADD texCoord.z, pos.x, pos.z;" NL "MAD texCoord.z, texCoord.z, 0.0009765625, texCoord.w;" //constant is 1/1024, comes from Q3 code NL "MAD texCoord.w, pos.y, 0.0009765625, texCoord.w;", wave.frequency ); //unfortunately, the possibility of running the sin or triangle functions with //their DP3/4 bottlenecks means we can't (in general) combine both evaluations BuildWaveEval( wave, null, "texCoord.z", "texCoord.z", prog ); BuildWaveEval( wave, null, "texCoord.w", "texCoord.w", prog ); prog.Append( NL "ADD texCoord.xy, texCoord, texCoord.zwww;" ); } break; case TexMod::EntityTranslate: //we don't really have an entity here so skip this break; case TexMod::Scroll: //3 instructions prog.Append( NL "MUL tmp, pTime.x, { %f, %f, 1, 1 };" NL "FRC tmp, tmp;" NL "ADD texCoord.xy, texCoord, tmp;", tcMod.scroll[0], tcMod.scroll[1] ); break; case TexMod::Scale: //1 instruction - could pull out into a texture matrix prog.Append( NL "MUL texCoord.xy, texCoord, { %f, %f, 1, 1 };", tcMod.scale[0], tcMod.scale[1] ); break; case TexMod::Stretch: //15 instructions BuildWaveEval( tcMod.wave, "pTime.x", null, "tmp.x", prog ); prog.Append( NL "RCP tmp.x, tmp.x;" NL "MAD tmp.y, tmp.x, -0.5, 0.5;" NL "SWZ tmp2, tmp, x, 0, y, 1;" NL "SWZ tmp3, tmp, 0, x, y, 1;" NL "SWZ tmp, texCoord, x, y, 1, 0;" //set up texcorrd vector NL "DP3 texCoord.x, tmp, tmp2;" NL "DP3 texCoord.y, tmp, tmp3;" ); break; case TexMod::Transform: //3 instructoins prog.Append( NL "SWZ tmp, texCoord, x, y, 1, 0;" NL "DP3 texCoord.x, tmp, { %f, %f, %f, 0 };" NL "DP3 texCoord.y, tmp, { %f, %f, %f, 0 };", tcMod.matrix[0][0], tcMod.matrix[1][0], tcMod.translate[0], tcMod.matrix[0][1], tcMod.matrix[1][1], tcMod.translate[1] ); break; case TexMod::Rotate: //21 instructions prog.Append( //these will be packed into matrices, this is more here for reference NL "MUL tmp.w, pTime.x, %f;" //rotations per second * time NL "FRC tmp.w, tmp.w;" //cut it off at [0, 1) NL "MUL tmp.w, tmp.w, pConsts.w;" //scale into [0, 2pi) NL "MOV tmp2.x, 1;" NL "MUL tmp2.y, tmp.w, tmp.w;" NL "MUL tmp2.zw, tmp2.y, tmp2.y;" NL "MUL tmp2.w, tmp2.y, tmp2.z;" NL "DP4 tmp.y, tmp2, pConstsCos;" //tmp.y = cos(tmp2.w) NL "MUL tmp2, tmp2, tmp.w;" NL "DP4 tmp.x, tmp2, pConstsSin;" //tmp.x = sin(tmp2.w) NL "MUL tmp.zw, tmp.xyxy, pConstsEnv;" //pConstsEnv ends in 0.5, 0.5 //tmp now contains { sin, cos, 0.5 * sin, 0.5 * cos } NL "MOV tmp2.x, tmp.y;" NL "MOV tmp2.y, -tmp.x;" NL "ADD tmp2.z, -tmp.w, tmp.z;" NL "ADD tmp2.z, tmp2.z, 0.5;" //set up first matrix vector NL "MOV tmp3.xy, tmp;" NL "ADD tmp3.z, tmp.z, tmp.w;" NL "SUB tmp3.z, 0.5, tmp3.z;" //set up second matrix vector NL "SWZ tmp, texCoord, x, y, 1, 0;" //set up texcorrd vector NL "DP3 texCoord.x, tmp, tmp2;" NL "DP3 texCoord.y, tmp, tmp3;", //perform the rotation -(tcMod.rotateSpeed / 360.0F) ); break; default: fail( "invalid tcmod" ); } } //148 instructions -- NOT GOOD, must cut the rotate and turbulent back! prog.Append( NL "DP4 oPos.x, pMatMVP[0], pos;" NL "DP4 oPos.y, pMatMVP[1], pos;" NL "DP4 oPos.z, pMatMVP[2], pos;" NL "DP4 oPos.w, pMatMVP[3], pos;" NL "SUB texCoord.y, 1, texCoord;" //Q3 texcoords are mirrored to Maya's NL "SWZ oTexCoord, texCoord, x, y, 0, 1;" //must set oTexCoord.w to 1 ); //potentially up to 154 instructions //that's 26 over the ARB min instruction count, gotta cut this back demand( prog.Append( NL "END" ), "shader program exceeded text buffer" ); GLuint ret; glGenProgramsARB( 1, &ret ); glBindProgramARB( GL_VERTEX_PROGRAM_ARB, ret ); glProgramStringARB( GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, prog.Length(), prog ); const char *pErr = (const char*)glGetString( GL_PROGRAM_ERROR_STRING_ARB ); if( pErr && strlen( pErr ) > 0 ) fail( "program failed to compile" ); stage.program = ret; return ret; } void Shader::LoadGLResources( void ) { if( !GLEW_ARB_vertex_program ) warn( "ARB_vertex_program is missing, cannot render Q3 shader, falling back to shaded view" ); for( int i = 0; i < m_numStages; i++ ) { ShaderStage &stage = m_stages[i]; if( GLEW_ARB_vertex_program && !glIsProgramARB( stage.program ) ) BuildShader( i ); for( int i = 0; i < stage.bundle.numTextures; i++ ) { GLenum addrMode; switch( stage.state & StateBits::TexAddr_Bits ) { case StateBits::TexAddr_Clamp: addrMode = GL_CLAMP; break; case StateBits::TexAddr_ClampToEdge: addrMode = GL_CLAMP_TO_EDGE; break; default: addrMode = GL_REPEAT; break; } //this always returns a unique texture ShaderStage::TextureBundle &bundle = stage.bundle; if( !glIsTexture( stage.bundle.texHandles[i] ) ) bundle.texHandles[i] = LoadTexture( bundle.texNames[i], !m_noMips, addrMode, bundle.texWidths[i], bundle.texHeights[i] ); } } m_glCtx = gl::Context::GetCurrent(); } void Shader::UnloadGLResources( void ) { gl::Context ctx = gl::Context::GetCurrent(); if( ctx != m_glCtx ) { if( !m_glCtx.MakeCurrent() ) //couldn't set context, can't free return; } else //no need to set the context back after ctx = gl::Context::GetNull(); for( int i = 0; i < m_numStages; i++ ) { ShaderStage &stage = m_stages[i]; if( GLEW_ARB_vertex_program && glIsProgramARB( stage.program ) ) { glDeleteProgramsARB( 1, &stage.program ); stage.program = 0; } for( int i = 0; i < stage.bundle.numTextures; i++ ) if( glIsTexture( stage.bundle.texHandles[i] ) ) { glDeleteTextures( 1, &stage.bundle.texHandles[i] ); stage.bundle.texHandles[i] = 0; } } if( ctx ) ctx.MakeCurrent(); } size_t Shader::BeginShader( const ShaderContext & /* ctx */ ) { if( !GLEW_ARB_vertex_program ) return (size_t)~0; glPushAttrib( GL_ENABLE_BIT | GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glEnable( GL_TEXTURE_2D ); glEnable( GL_VERTEX_PROGRAM_ARB ); switch( m_cull ) { case CullMode::None: glDisable( GL_CULL_FACE ); break; case CullMode::Front: glEnable( GL_CULL_FACE ); glCullFace( GL_FRONT ); break; case CullMode::Back: glEnable( GL_CULL_FACE ); glCullFace( GL_BACK ); break; } if( m_polyOffset ) { glEnable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( -1.0F, -2.0F ); } glEnable( GL_BLEND ); glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL ); return m_numStages; } void Shader::EndShader( const ShaderContext & /* ctx */ ) { if( !GLEW_ARB_vertex_program ) return; glDisable( GL_VERTEX_PROGRAM_ARB ); glPopAttrib(); } static float g_noise[64 * 4]; static float *GetNoise( void ) { static bool noiseInit = false; if( !noiseInit ) { //get consistent results srand( 42 ); for( int i = 0; i < (sizeof( g_noise ) / sizeof( g_noise[0] )); i++ ) g_noise[i] = ((float)rand() / (float)RAND_MAX) * 2.0F - 1.0F; noiseInit = true; } return g_noise; } bool Shader::BeginStage( const ShaderContext &ctx, size_t idx ) { if( !GLEW_ARB_vertex_program ) return false; if( idx >= (size_t)m_numStages ) return false; const ShaderStage &stage = m_stages[idx]; const ShaderStage::TextureBundle &bundle = stage.bundle; if( stage.isDetail && !ctx.GetShowDetail() ) return false; glBindProgramARB( GL_VERTEX_PROGRAM_ARB, stage.program ); /* NL "PARAM pTime = program.local[0];" //time NL "PARAM pTexGenV[2] = program.local[1..2];" NL "PARAM pTexMat0[2] = state.local[3..4];" NL "PARAM pTexMat1[2] = state.local[5..6];" NL "PARAM pTexMat2[2] = state.local[7..8];" NL "PARAM pTexMat3[2] = state.local[9..10];" NL "PARAM pNoise[64] = program.local[32..95];" //noisy data... */ glProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0, ctx.GetTime() / 1000.0F, 0.0F, 0.0F, 0.0F ); glProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 1, bundle.texGenVecs[0][0], bundle.texGenVecs[0][1], bundle.texGenVecs[0][2], 0.0F ); glProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 2, bundle.texGenVecs[1][0], bundle.texGenVecs[1][1], bundle.texGenVecs[1][2], 0.0F ); float texmat[2][3] = { { 1, 0, 0 }, { 0, 1, 0 }, //0 0 1 }; for( int i = 0; i < bundle.numTexMods; i++ ) { const ShaderStage::TextureBundle::TextureMod &tm = bundle.texMods[i]; TexMod tmc = tm.type & TexMod::Combine_Bits; if( !tmc ) continue; bool combineForward = (tmc & TexMod::Combine_Forward) != 0; tmc &= ~TexMod::Combine_Forward; float currMat[2][3] = { { 0, 0, 0 }, { 0, 0, 0 } /* 0, 0, 1 */ }; switch( tm.type & TexMod::Type_Bits ) { case TexMod::Rotate: { float val = (ctx.GetTime() / 1000.0F) * (tm.rotateSpeed / 360.0F); val -= floorf( val ); val *= (float)(M_PI * 2.0F); float sinVal = sinf( val ); float cosVal = cosf( val ); currMat[0][0] = cosVal; currMat[1][0] = -sinVal; currMat[0][2] = 0.5F - (0.5F * cosVal) + (0.5F * sinVal); currMat[0][1] = sinVal; currMat[1][1] = cosVal; currMat[1][2] = 0.5F - (0.5F * sinVal) - (0.5F * cosVal); } break; case TexMod::Scale: currMat[0][0] = tm.scale[0]; currMat[1][1] = tm.scale[1]; break; case TexMod::Stretch: { //evaluate the wave float val = (ctx.GetTime() / 1000.0F) * tm.wave.frequency + tm.wave.phase; val -= floorf( val ); switch( tm.wave.type ) { case WaveFunc::Sin: val = sinf( val * (float)(M_PI * 2.0F) ); break; case WaveFunc::Square: val = (val < 0.5F) ? 1.0F : -1.0F; break; case WaveFunc::Triangle: if( val < 0.25F ) { //up from 0 to 1 val *= 4.0F; } else if( val < 0.75F ) { //down from 1 to -1 val = (val * -4.0F) + 2.0F; } else { //up from -1 to 0 val = (val * 4.0F) - 4.0F; } break; case WaveFunc::SawTooth: break; case WaveFunc::InvSawTooth: val = 1.0F - val; break; } val = tm.wave.base + val * tm.wave.frequency; float val2 = (val * -0.5F) + 0.5F; currMat[0][0] = val; currMat[0][2] = val2; currMat[1][1] = val; currMat[1][2] = val2; } break; case TexMod::Transform: currMat[0][0] = tm.matrix[0][0]; currMat[0][1] = tm.matrix[1][0]; currMat[0][2] = tm.translate[0]; currMat[1][0] = tm.matrix[0][1]; currMat[1][1] = tm.matrix[1][1]; currMat[1][2] = tm.translate[1]; break; } float newTexMat[2][3]; //remember, there's an implicit 0, 0, 1 row on these matrices newTexMat[0][0] = currMat[0][0] * texmat[0][0] + currMat[0][1] * texmat[1][0]; newTexMat[0][1] = currMat[0][0] * texmat[0][1] + currMat[0][1] * texmat[1][1]; newTexMat[0][2] = currMat[0][0] * texmat[0][2] + currMat[0][1] * texmat[1][2] + currMat[0][2]; newTexMat[1][0] = currMat[1][0] * texmat[0][0] + currMat[1][1] * texmat[1][0]; newTexMat[1][1] = currMat[1][0] * texmat[0][1] + currMat[1][1] * texmat[1][1]; newTexMat[1][2] = currMat[1][0] * texmat[0][2] + currMat[1][1] * texmat[1][2] + currMat[1][2]; memcpy( texmat, newTexMat, sizeof( texmat ) ); if( combineForward ) continue; //upload the matrix int iTexMat = tmc - TexMod::Combine_0; iTexMat = (iTexMat * 2) + 3; glProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, iTexMat + 0, texmat[0][0], texmat[0][1], texmat[0][2], 1.0F ); glProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, iTexMat + 1, texmat[1][0], texmat[1][1], texmat[1][2], 1.0F ); //clear the matrix to start the next combine memset( texmat, 0, sizeof( texmat ) ); texmat[0][0] = texmat[1][1] = 1.0F; } float *noise = GetNoise(); for( int i = 0; i < 64; i++ ) glProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 32 + i, noise + (i * 4) ); //ToDo: check this to make sure it lines up with waves! size_t texIdx = (size_t)(ctx.GetTime() * bundle.texAnimSpeed / 1000.0F); if( texIdx < 0 ) texIdx = 0; texIdx %= bundle.numTextures; glBindTexture( GL_TEXTURE_2D, bundle.texHandles[texIdx] ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); GLenum srcBlend = GL_ONE; GLenum dstBlend = GL_ZERO; switch( stage.state & StateBits::SrcBlend_Bits ) { case StateBits::SrcBlend_Zero: srcBlend = GL_ZERO; break; case StateBits::SrcBlend_One: srcBlend = GL_ONE; break; case StateBits::SrcBlend_DstColor: srcBlend = GL_DST_COLOR; break; case StateBits::SrcBlend_InvDstColor: srcBlend = GL_ONE_MINUS_DST_COLOR; break; case StateBits::SrcBlend_SrcAlpha: srcBlend = GL_SRC_ALPHA; break; case StateBits::SrcBlend_InvSrcAlpha: srcBlend = GL_ONE_MINUS_SRC_ALPHA; break; case StateBits::SrcBlend_DstAlpha: srcBlend = GL_DST_ALPHA; break; case StateBits::SrcBlend_InvDstAlpha: srcBlend = GL_ONE_MINUS_DST_ALPHA; break; case StateBits::SrcBlend_AlphaSat: srcBlend = GL_SRC_ALPHA_SATURATE; break; } switch( stage.state & StateBits::DstBlend_Bits ) { case StateBits::DstBlend_Zero: dstBlend = GL_ZERO; break; case StateBits::DstBlend_One: dstBlend = GL_ONE; break; case StateBits::DstBlend_SrcColor: dstBlend = GL_SRC_COLOR; break; case StateBits::DstBlend_InvSrcColor: dstBlend = GL_ONE_MINUS_SRC_COLOR; break; case StateBits::DstBlend_SrcAlpha: dstBlend = GL_SRC_ALPHA; break; case StateBits::DstBlend_InvSrcAlpha: dstBlend = GL_ONE_MINUS_SRC_ALPHA; break; case StateBits::DstBlend_DstAlpha: dstBlend = GL_DST_ALPHA; break; case StateBits::DstBlend_InvDstAlpha: dstBlend = GL_ONE_MINUS_DST_ALPHA; break; } switch( stage.state & StateBits::AlphaTest_Bits ) { case StateBits::AlphaTest_GT_0: glEnable( GL_ALPHA_TEST ); glAlphaFunc( GL_GREATER, 0 ); break; case StateBits::AlphaTest_GE_0x80: glEnable( GL_ALPHA_TEST ); glAlphaFunc( GL_GEQUAL, 0x80 ); break; case StateBits::AlphaTest_LT_0x80: glEnable( GL_ALPHA_TEST ); glAlphaFunc( GL_LESS, 0x80 ); break; default: glDisable( GL_ALPHA_TEST ); break; } glBlendFunc( srcBlend, dstBlend ); return true; } void Shader::EndStage( const ShaderContext &ctx ) { ctx; } }