// // Copyright (C) 2002-2004 NVIDIA // // File: cgfxShaderNode.cpp // // Dependency Graph Node: cgfxShader // // Author: Jim Atkinson // // Changes: // 10/2003 Kurt Harriman - www.octopusgraphics.com +1-415-893-1023 // - Multiple UV sets; user-specified texcoord assignment. // - "tcs/texCoordSource", a new static attribute, is a // string array of up to 32 elements. Set it to specify // the source of each TEXCOORD vertex parameter as one of: // a UV set name; "tangent"; "binormal"; "normal"; an empty // string; or up to 4 float values "x y z w". Default is // {"map1","tangent","binormal"}. // - "-mtc/maxTexCoords" flag of cgfxShader command returns an // upper bound on the number of texcoord inputs per vertex // (GL_MAX_TEXTURE_UNITS) that can be passed from Maya thru // OpenGL to vertex shaders on the current workstation. // - The MEL command `pluginInfo -q -version cgfxShader` // returns the plug-in version and cgfxShaderNode.cpp // compile date. // - Improved error handling. // 12/2003 Kurt Harriman - www.octopusgraphics.com +1-415-893-1023 // - To load or reload an effect, use the cgfxShader command // "-fx/fxFile " flag. Setting the cgfxShader // node's "s/shader" attribute no longer loads the effect. // - To choose a technique, set the "t/technique" // attribute of the cgfxShader node. The effect is not // reloaded. There is no longer a message box requiring // the user to choose a technique when loading an effect. // - The techniques defined by the current effect are returned // by the cgfxShader command "-lt/-listTechniques" flag. // - Fixed incorrect transformation of direction/position // parameters to spaces other than world space. // - Dangling references to deleted dynamic attributes // caused exceptions in MObject destructor, terminating // the Maya process. This has been fixed. // - Improved error handling. // #ifndef CGFXSHADER_VERSION #define CGFXSHADER_VERSION "4.4" #endif #include "cgfxShaderCommon.h" #include "cgfxShaderNode.h" #include "cgfxFindImage.h" #include #if defined(WIN32) || defined(LINUX) #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_SWATCH_RENDERING_SUPPORTED_) // For swatch rendering #include #include #include #endif #include #include "nv_dds.h" #ifdef _WIN32 #else # include # include # # define stricmp strcasecmp # define strnicmp strncasecmp #endif // // Statics and globals... // #ifdef _WIN32 static PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = 0; static PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = 0; #endif int cgfxShaderNode::sMaxTextureUnits = 0; int cgfxShaderNode::sMaxColorUnits = 0; // This typeid must be unique across the universe of Maya plug-ins. // // TODO: Get a unique ID from NVIDIA if they have them or from A|W // if they do not. // #ifdef _WIN32 MTypeId cgfxShaderNode::sId( 4084862000 ); #else MTypeId cgfxShaderNode::sId( 0xF37A0C30 ); #endif CGcontext cgfxShaderNode::sCgContext; // Attribute declarations // MObject cgfxShaderNode::sShader; MObject cgfxShaderNode::sTechnique; MObject cgfxShaderNode::sAttributeList; MObject cgfxShaderNode::sTexCoordSource; MObject cgfxShaderNode::sColorSource; MObject cgfxShaderNode::sTexturesByName; // Codes used in ftexCoordList array enum ETexCoord { etcNull = -1, etcConstant = -2, etcNormal = -3, etcTangent = -4, etcBinormal = -5, }; //--------------------------------------------------------------------// // Constructor: // cgfxShaderNode::cgfxShaderNode() : fEffect(0) , fAttrDefList(0) #ifdef TEXTURES_BY_NAME , fTexturesByName( true ) #else , fTexturesByName( false ) #endif , fNormalsPerVertex( 3 ) , fConstructed(false) , fErrorCount( 0 ) , fErrorLimit( 8 ) { // Set texCoordSource attribute to its default value. MStringArray sa; sa.append( "map1" ); sa.append( "tangent" ); sa.append( "binormal" ); MStringArray emptyArray; setDataSources( sa, emptyArray ); } // Post-constructor void cgfxShaderNode::postConstructor() { fConstructed = true; // ok to call MPxNode member functions shaderEnabled( false ); // disable our shader until we get a valid effect } // cgfxShaderNode::postConstructor // Destructor: // cgfxShaderNode::~cgfxShaderNode() { #ifdef KH_DEBUG MString ss = " .. ~node "; if ( fConstructed ) { MFnDependencyNode fnNode( thisMObject() ); ss += fnNode.name(); } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif // Remove all the callbacks that we registered. MMessage::removeCallbacks( fCallbackIds ); fCallbackIds.clear(); if (fAttrDefList) { fAttrDefList->release(); fAttrDefList = 0; } // Make sure that any loaded shader is unloaded and freed // before going away. // if (fEffect) { cgDestroyEffect(fEffect); fEffect = 0; } } // // Description: // Enable or disable our shader // MStatus cgfxShaderNode::shaderEnabled( bool enabled) { MStatus status; MFnDependencyNode dgFn( thisMObject()); MPlug plug = dgFn.findPlug( "enableHwShading", &status); if( status == MS::kSuccess && !plug.isNull()) plug.setValue( enabled); return status; } // // Description: // This method computes the value of the given output plug based // on the values of the input attributes. // // Arguments: // plug - the plug to compute // data - object that provides access to the attributes for this node // MStatus cgfxShaderNode::compute( const MPlug& plug, MDataBlock& data ) { MStatus returnStatus; // Compute a color, so that Hypershade swatches do not render black. if ((plug == outColor) || (plug.parent() == outColor)) { MFloatVector color(.07f, .8f, .07f); MDataHandle outputHandle = data.outputValue( outColor ); outputHandle.asFloatVector() = color; outputHandle.setClean(); return MS::kSuccess; } return MS::kUnknownParameter; } // ========== cgfxShaderNode::creator ========== // // Description: // this method exists to give Maya a way to create new objects // of this type. // // Return Value: // a new object of this type. // /* static */ void* cgfxShaderNode::creator() { return new cgfxShaderNode(); } // ========== cgfxShaderNode::initialize ========== // // Description: // This method is called to create and initialize all of the attributes // and attribute dependencies for this node type. This is only called // once when the node type is registered with Maya. // // Return Values: // MS::kSuccess // MS::kFailure // /* static */ MStatus cgfxShaderNode::initialize() { MStatus ms; try { initializeNodeAttrs(); } catch ( cgfxShaderCommon::InternalError* e ) // internal error { size_t ee = (size_t)e; MString es = "cgfxShaderNode internal error "; es += (int)ee; MGlobal::displayError( es ); ms = MS::kFailure; } catch ( ... ) { MGlobal::displayError( "cgfxShaderNode internal error: Unhandled exception in initialize" ); ms = MS::kFailure; } return ms; } // Create all the attributes. /* static */ void cgfxShaderNode::initializeNodeAttrs() { MFnTypedAttribute typedAttr; MFnNumericAttribute numericAttr; MFnStringData stringData; MFnStringArrayData stringArrayData; MStatus stat, stat2; // The shader attribute holds the name of the .fx file that defines // the shader // sShader = typedAttr.create("shader", "s", MFnData::kString, stringData.create(&stat2), &stat); M_CHECK( stat2 ); M_CHECK( stat ); // Attribute is keyable and will show up in the channel box // stat = typedAttr.setKeyable(true); M_CHECK( stat ); // Mark it as internal so we can track changes to it and know when to // reload the .fx file // stat = typedAttr.setInternal(true); M_CHECK( stat ); // Add the attribute we have created to the node // stat = addAttribute(sShader); M_CHECK( stat ); // // technique // sTechnique = typedAttr.create("technique", "t", MFnData::kString, stringData.create(&stat2), &stat); M_CHECK( stat2 ); M_CHECK( stat ); typedAttr.setInternal(true); typedAttr.setKeyable(true); stat = addAttribute(sTechnique); M_CHECK( stat ); // // attributeList // sAttributeList = typedAttr.create("attributeList", "al", MFnData::kStringArray, stringArrayData.create(&stat2), &stat); M_CHECK( stat2 ); M_CHECK( stat ); // Attribute is NOT keyable and will NOT show up in the channel box // stat = typedAttr.setKeyable(false); M_CHECK( stat ); // Attribute is NOT connectable // stat = typedAttr.setConnectable(false); M_CHECK( stat ); // This attribute is an NOT an array. (It is a single valued attribute // whose value is a single MStringArray object.) // stat = typedAttr.setArray(false); M_CHECK( stat ); // Mark it as internal so we can track changes to it and know when to // reload the .fx file // stat = typedAttr.setInternal(true); M_CHECK( stat ); // This attribute is a hidden. // stat = typedAttr.setHidden(true); M_CHECK( stat ); // Add the attribute we have created to the node // stat = addAttribute(sAttributeList); M_CHECK( stat ); // // texCoordSource // sTexCoordSource = typedAttr.create( "texCoordSource", "tcs", MFnData::kStringArray, MObject::kNullObj, &stat ); M_CHECK( stat ); stat = typedAttr.setInternal( true ); M_CHECK( stat ); stat = addAttribute( sTexCoordSource ); M_CHECK( stat ); // // colorSource // sColorSource = typedAttr.create( "colorSource", "cs", MFnData::kStringArray, MObject::kNullObj, &stat ); M_CHECK( stat ); stat = typedAttr.setInternal( true ); M_CHECK( stat ); stat = addAttribute( sColorSource ); M_CHECK( stat ); // // texturesByName // sTexturesByName = numericAttr.create( "texturesByName", "tbn", MFnNumericData::kBoolean, 0, &stat ); M_CHECK( stat ); stat = numericAttr.setInternal(true); M_CHECK( stat ); // Hide this switch - TDs can recompile this to default to // different options, but we don't want to encourage users // to switch some shading nodes to use node textures, and // others named textures (and we definitely don't want to // try and handle converting configured shaders from one to // the other) // stat = numericAttr.setHidden(true); M_CHECK( stat ); numericAttr.setKeyable(false); stat = addAttribute( sTexturesByName ); M_CHECK( stat ); } // cgfxShaderNode::initializeNodeAttrs /* virtual */ void cgfxShaderNode::copyInternalData( MPxNode* pSrc ) { const cgfxShaderNode& src = *(cgfxShaderNode*)pSrc; setTexturesByName( src.getTexturesByName() ); setShaderFxFile( src.shaderFxFile() ); setTechnique( src.getTechnique() ); setDataSources( src.getTexCoordSource(), src.getColorSource() ); // Rebuild the shader from the fx file. // MString fileName = cgfxFindFile(shaderFxFile()); bool hasFile = (fileName.asChar() != NULL) && strcmp(fileName.asChar(), ""); if ( hasFile ) { // Create the effect for this node. // CGeffect cgEffect = cgCreateEffectFromFile(sCgContext, fileName.asChar(), 0); if (cgEffect) { cgfxAttrDefList* effectList = NULL; MStringArray attributeList; MDGModifier dagMod; // Update the node. // cgfxAttrDef::updateNode(cgEffect, this, &dagMod, effectList, attributeList); MStatus status = dagMod.doIt(); assert(status == MS::kSuccess); setAttrDefList(effectList); setAttributeList(attributeList); setEffect(cgEffect); } } } // cgfxShaderNode::copyInternalData bool cgfxShaderNode::setInternalValueInContext( const MPlug& plug, const MDataHandle& handle, MDGContext&) { bool retVal = true; try { #ifdef KH_DEBUG MString ss = " .. seti "; ss += plug.partialName( true, true, true, false, false, true ); if (plug == sShader || plug == sTechnique) { ss += " \""; ss += handle.asString(); ss += "\""; } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif if (plug == sShader) { setShaderFxFile(handle.asString()); } else if (plug == sTechnique) { setTechnique(handle.asString()); } else if (plug == sAttributeList) { MDataHandle nonConstHandle(handle); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData(saData); setAttributeList(fnSaData.array()); } else if ( plug == sTexCoordSource ) { MDataHandle nonConstHandle( handle ); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData( saData ); MStringArray emptyArray; setDataSources( fnSaData.array(), emptyArray ); } else if ( plug == sColorSource ) { MDataHandle nonConstHandle( handle ); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData( saData ); MStringArray emptyArray; setDataSources( emptyArray, fnSaData.array() ); } else if ( plug == sTexturesByName ) { setTexturesByName( handle.asBool(), !MFileIO::isReadingFile()); } else { retVal = MPxHwShaderNode::setInternalValue(plug, handle); } } catch ( cgfxShaderCommon::InternalError* e ) { reportInternalError( __FILE__, (size_t)e ); retVal = false; } catch ( ... ) { reportInternalError( __FILE__, __LINE__ ); retVal = false; } return retVal; } /* virtual */ bool cgfxShaderNode::getInternalValueInContext( const MPlug& plug, MDataHandle& handle, MDGContext&) { bool retVal = true; try { #ifdef KH_DEBUG MString ss = " .. geti "; ss += plug.partialName( true, true, true, false, false, true ); if ( plug == sShader ) ss += " \"" + fShaderFxFile + "\""; else if (plug == sTechnique) ss += " \"" + fTechnique + "\""; ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif if (plug == sShader) { handle.set(fShaderFxFile); } else if (plug == sTechnique) { handle.set(fTechnique); } else if (plug == sAttributeList) { MFnStringArrayData saData; handle.set(saData.create(fAttributeListArray)); } else if ( plug == sTexCoordSource ) { MFnStringArrayData saData; handle.set( saData.create( fTexCoordSource ) ); } else if ( plug == sColorSource ) { MFnStringArrayData saData; handle.set( saData.create( fColorSource ) ); } else if (plug == sTexturesByName) { handle.set(fTexturesByName); } else { retVal = MPxHwShaderNode::getInternalValue(plug, handle); } } catch ( cgfxShaderCommon::InternalError* e ) { reportInternalError( __FILE__, (size_t)e ); retVal = false; } catch ( ... ) { reportInternalError( __FILE__, __LINE__ ); retVal = false; } return retVal; } static void checkGlErrors(const char* msg) { #if defined(CGFX_DEBUG) #define MYERR(n) case n: OutputDebugStrings(" ", #n); break GLenum err; bool errors = false; while ((err = glGetError()) != GL_NO_ERROR) { if (!errors) { // Print this the first time through the loop // OutputDebugStrings("OpenGl errors: ", msg); } errors = true; switch (err) { MYERR(GL_INVALID_ENUM); MYERR(GL_INVALID_VALUE); MYERR(GL_INVALID_OPERATION); MYERR(GL_STACK_OVERFLOW); MYERR(GL_STACK_UNDERFLOW); MYERR(GL_OUT_OF_MEMORY); default: { char tmp[32]; sprintf(tmp, "%d", err); OutputDebugStrings(" GL Error #", tmp); } } } #undef MYERR #endif /* CGFX_DEBUG */ } #if defined(_SWATCH_RENDERING_SUPPORTED_) /* virtual */ MStatus cgfxShaderNode::renderSwatchImage( MImage & outImage ) { MStatus status = MStatus::kFailure; if( sCgContext == 0 ) return status; // Get the hardware renderer utility class MHardwareRenderer *pRenderer = MHardwareRenderer::theRenderer(); if (pRenderer) { const MString& backEndStr = pRenderer->backEndString(); // Get geometry // ============ unsigned int* pIndexing = 0; unsigned int numberOfData = 0; unsigned int indexCount = 0; MHardwareRenderer::GeometricShape gshape = MHardwareRenderer::kDefaultSphere; MGeometryData* pGeomData = pRenderer->referenceDefaultGeometry( gshape, numberOfData, pIndexing, indexCount ); if( !pGeomData ) { return MStatus::kFailure; } // Make the swatch context current // =============================== // unsigned int width, height; outImage.getSize( width, height ); unsigned int origWidth = width; unsigned int origHeight = height; MStatus status2 = pRenderer->makeSwatchContextCurrent( backEndStr, width, height ); if( status2 != MS::kSuccess ) { pRenderer->dereferenceGeometry( pGeomData, numberOfData ); } // Get the light direction from the API, and use it // ============================================= { float light_pos[4]; pRenderer->getSwatchLightDirection( light_pos[0], light_pos[1], light_pos[2], light_pos[3] ); } // Get camera // ========== { // Get the camera frustum from the API glMatrixMode(GL_PROJECTION); glLoadIdentity(); double l, r, b, t, n, f; pRenderer->getSwatchPerspectiveCameraSetting( l, r, b, t, n, f ); glFrustum( l, r, b, t, n, f ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float x, y, z, w; pRenderer->getSwatchPerspectiveCameraTranslation( x, y, z, w ); glTranslatef( x, y, z ); } // Get the default background color and clear the background // float r, g, b, a; MHWShaderSwatchGenerator::getSwatchBackgroundColor( r, g, b, a ); glClearColor( r, g, b, a ); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Draw The Swatch // =============== //drawTheSwatch( pGeomData, pIndexing, numberOfData, indexCount ); MDagPath dummyPath; glBind( dummyPath ); float *vertexData = (float *)( pGeomData[0].data() ); float *normalData = (float *)( pGeomData[1].data() ); float *uvData = (float *)( pGeomData[2].data() ); float *tangentData = (float *)( pGeomData[3].data() ); float *binormalData = (float *)( pGeomData[4].data() ); unsigned int normalCount = 0; // Stick normal, tangent, binormals into ptr array float ** normalArrays = new float * [3]; if (normalData) { normalArrays[0] = normalData; normalCount++; } else normalArrays[0] = NULL; if (tangentData) { normalArrays[1] = tangentData; normalCount++; } else normalArrays[1] = NULL; if (binormalData) { normalArrays[2] = binormalData; normalCount++; } else normalArrays[2] = NULL; // Stick uv data into ptr array unsigned int uvCount = 0; float ** texCoordArrays = new float * [1]; if (uvData) { texCoordArrays[0] = uvData; uvCount = 1; } else texCoordArrays[0] = NULL; glGeometry( dummyPath, GL_TRIANGLES, false, indexCount, pIndexing, 0, NULL, /* no vertex ids */ vertexData, normalCount, (const float **) normalArrays, 0, NULL, /* no colours */ uvCount, (const float **) texCoordArrays); glUnbind( dummyPath ); normalArrays[0] = NULL; normalArrays[1] = NULL; delete[] normalArrays; texCoordArrays[0] = NULL; delete[] texCoordArrays; // Read pixels back from swatch context to MImage // ============================================== pRenderer->readSwatchContextPixels( backEndStr, outImage ); // Double check the outing going image size as image resizing // was required to properly read from the swatch context outImage.getSize( width, height ); if (width != origWidth || height != origHeight) { status = MStatus::kFailure; } else { status = MStatus::kSuccess; } } return status; } #endif /* virtual */ MStatus cgfxShaderNode::glBind(const MDagPath& shapePath) { // This is the routine where you would do all the expensive, // one-time kind of work. Create vertex programs, load // textures, etc. // // In this case, we will evaluate all the attributes and store the // parameter values. In theory, there could be multiple calls to // geometry in between single calls to bind and unbind. Since we // only need to get the attribute values once per frame, get them // in bind. MStatus stat; #ifdef KH_DEBUG MString ss = " .. bind "; if ( this && fConstructed ) ss += name(); ss += " "; ss += request.multiPath().fullPathName(); ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif try { if (fEffect == NULL) { #ifdef _WIN32 ::OutputDebugString( "CGFX: fEffect was NULL\n"); #endif // When batch off-screen rendering through "mayabatch -command hwRender ...", // the effect will be uninitialized because there was no active OpenGL // context at the time "cgfxShader -e -fx ..." was executed. This setup // is delayed until now when hardware renderer guarantees a valid context // and requests the plug-in to bind its resources to it. -cdt // //HRESULT result; // Attempt to read the effect from the file MString fileName = cgfxFindFile(shaderFxFile()); bool hasFile = (fileName.asChar() != NULL) && strcmp(fileName.asChar(), ""); if (! hasFile ) { return stat; } // Compile and create the effect. // const char* errors = 0; CGeffect cgEffect = cgCreateEffectFromFile(sCgContext, fileName.asChar(), 0); if (cgEffect) { cgfxAttrDefList* effectList = NULL; MStringArray attributeList; MDGModifier dagMod; // updateNode does a fair amount of work. It determines which // attributes need to be added and which need to be deleted and // fills in all the changes in the MDagModifier. Then it builds // a new value for the attributeList attribute. Finally, it // builds a new value for the attrDefList internal value. All // these values are returned here where we can set them into the // node. cgfxAttrDef::updateNode(cgEffect, this, &dagMod, effectList, attributeList); MStatus status = dagMod.doIt(); assert(status == MS::kSuccess); // Actually update the node. setAttrDefList(effectList); setAttributeList(attributeList); setEffect(cgEffect); } } // One-time OpenGL initialization... if ( sMaxTextureUnits <= 0 ) { // Before this point, we never had a good OpenGL context. Now // we can check for extensions and set up pointers to the // extension procs. #ifdef _WIN32 glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB"); #endif GLint tval; glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &tval ); sMaxTextureUnits = tval; #ifdef _WIN32 if (!glClientActiveTextureARB || sMaxTextureUnits < 1) #else if (sMaxTextureUnits < 1) #endif sMaxTextureUnits = 1; else if (sMaxTextureUnits > CGFXSHADERNODE_GL_TEXTURE_MAX) sMaxTextureUnits = CGFXSHADERNODE_GL_TEXTURE_MAX; } // One-time OpenGL initialization... if ( sMaxColorUnits <= 0 ) { /// TODO add support for more than 1 colour unit sMaxColorUnits = 1; } // Set up the uniform attribute values for the effect. bindAttrValues( shapePath ); } catch ( cgfxShaderCommon::InternalError* e ) { reportInternalError( __FILE__, (size_t)e ); stat = MS::kFailure; } catch ( ... ) { reportInternalError( __FILE__, __LINE__ ); stat = MS::kFailure; } return stat; } // cgfxShaderNode::bind void cgfxShaderNode::bindAttrValues(const MDagPath& shapePath) { if ( !fEffect || !fTechnique.length() ) return; MStatus status; MObject oNode = thisMObject(); MMatrix wMatrix; { if (shapePath.isValid()) wMatrix = shapePath.inclusiveMatrix(); else wMatrix.setToIdentity(); } for ( cgfxAttrDefList::iterator it( fAttrDefList ); it; ++it ) { // loop over fAttrDefList cgfxAttrDef* aDef = *it; try { switch (aDef->fType) { case cgfxAttrDef::kAttrTypeBool: { bool tmp; aDef->getValue(oNode, tmp); cgSetParameter1i(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeInt: { int tmp; aDef->getValue(oNode, tmp); cgSetParameter1i(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeFloat: { float tmp; aDef->getValue(oNode, tmp); cgSetParameter1f(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeString: { MString tmp; aDef->getValue(oNode, tmp); cgSetStringParameterValue(aDef->fParameterHandle, tmp.asChar()); break; } case cgfxAttrDef::kAttrTypeVector2: { float tmp[2]; aDef->getValue(oNode, tmp[0], tmp[1]); cgSetParameter2fv(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeVector3: case cgfxAttrDef::kAttrTypeColor3: { float tmp[3]; aDef->getValue(oNode, tmp[0], tmp[1], tmp[2]); cgSetParameter3fv(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeVector4: case cgfxAttrDef::kAttrTypeColor4: { float tmp[4]; aDef->getValue(oNode, tmp[0], tmp[1], tmp[2], tmp[3]); cgSetParameter4fv(aDef->fParameterHandle, tmp); break; } case cgfxAttrDef::kAttrTypeObjectDir: case cgfxAttrDef::kAttrTypeWorldDir: case cgfxAttrDef::kAttrTypeObjectPos: case cgfxAttrDef::kAttrTypeWorldPos: { // Read the value float tmp[4]; if (aDef->fSize == 3) { aDef->getValue(oNode, tmp[0], tmp[1], tmp[2]); tmp[3] = 1.0; } else { aDef->getValue(oNode, tmp[0], tmp[1], tmp[2], tmp[3]); } // Find the coordinate space, and whether it is a // point or a vector int base = cgfxAttrDef::kAttrTypeFirstPos; if (aDef->fType <= cgfxAttrDef::kAttrTypeLastDir) base = cgfxAttrDef::kAttrTypeFirstDir; int space = aDef->fType - base; // Compute the transform matrix MMatrix mat = wMatrix.inverse(); switch (space) { case 0: /* object space */ break; case 1: mat = mat * wMatrix; /* world space */ break; /* case 2: eye space, unsupported yet */ /* case 3: clip space, unsupported yet */ /* case 4: screen space, unsupported yet */ } if (base == cgfxAttrDef::kAttrTypeFirstPos) { MPoint point(tmp[0], tmp[1], tmp[2], tmp[3]); point *= mat; tmp[0] = (float)point.x; tmp[1] = (float)point.y; tmp[2] = (float)point.z; tmp[3] = (float)point.w; } else { MVector vec(tmp[0], tmp[1], tmp[2]); vec *= mat; tmp[0] = (float)vec.x; tmp[1] = (float)vec.y; tmp[2] = (float)vec.z; tmp[3] = 1.F; } cgSetParameterValuefr(aDef->fParameterHandle, aDef->fSize, tmp); break; } case cgfxAttrDef::kAttrTypeMatrix: { MMatrix tmp; float tmp2[4][4]; aDef->getValue(oNode, tmp); if (aDef->fInvertMatrix) { tmp = tmp.inverse(); } if (!aDef->fTransposeMatrix) { tmp = tmp.transpose(); } tmp.get(tmp2); cgSetMatrixParameterfr(aDef->fParameterHandle, &tmp2[0][0]); break; } case cgfxAttrDef::kAttrTypeWorldMatrix: { MMatrix mat; if (aDef->fType == cgfxAttrDef::kAttrTypeWorldMatrix) { mat = wMatrix; } if (aDef->fInvertMatrix) { mat = mat.inverse(); } if (!aDef->fTransposeMatrix) { mat = mat.transpose(); } float tmp[4][4]; mat.get(tmp); cgSetMatrixParameterfr(aDef->fParameterHandle, &tmp[0][0]); break; } case cgfxAttrDef::kAttrTypeColor1DTexture: case cgfxAttrDef::kAttrTypeColor2DTexture: case cgfxAttrDef::kAttrTypeColor3DTexture: case cgfxAttrDef::kAttrTypeColor2DRectTexture: case cgfxAttrDef::kAttrTypeNormalTexture: case cgfxAttrDef::kAttrTypeBumpTexture: case cgfxAttrDef::kAttrTypeCubeTexture: case cgfxAttrDef::kAttrTypeEnvTexture: case cgfxAttrDef::kAttrTypeNormalizationTexture: { MString tmp; MObject textureNode = MObject::kNullObj; if( fTexturesByName) { aDef->getValue(oNode, tmp); } else { // If we have a fileTexture node connect, get the // filename it is using MPlug srcPlug; aDef->getSource(oNode, srcPlug); MObject srcNode = srcPlug.node(); if( srcNode != MObject::kNullObj) { MStatus rc; MFnDependencyNode dgFn( srcNode); MPlug filenamePlug = dgFn.findPlug( "fileTextureName", &rc); if( rc == MStatus::kSuccess) { filenamePlug.getValue( tmp); textureNode = filenamePlug.node(&rc); } } } if (aDef->fTextureId == 0 || tmp != aDef->fStringDef) { // If the texture object already exists, reuse // it. Otherwise, we create a new one. // if (aDef->fTextureId == 0) { GLuint val; glGenTextures(1, &val); aDef->fTextureId = val; } aDef->fStringDef = tmp; nv_dds::CDDSImage image; MString path = cgfxFindFile(tmp); if (path.asChar() != NULL && strcmp(path.asChar(), "")) { switch (aDef->fType) { case cgfxAttrDef::kAttrTypeEnvTexture: case cgfxAttrDef::kAttrTypeCubeTexture: case cgfxAttrDef::kAttrTypeNormalizationTexture: // we don't want to flip cube maps... image.load(path.asChar(),false); break; default: image.load(path.asChar()); break; } } // Our common stand-in "texture" // The code below creates a separate stand-in GL texture // for every attribute without a value (rather than sharing // the default across all node/attributes of a given type. // This is done because the current design does not support // GL texture id sharing across nodes/attributes AND because // we want to avoid checking disk every frame for missing // textures. Once this plugin is re-factored to support a // shared texture cache, we should revisit this to share // default textures too // static unsigned char whitePixel[ 4] = { 255, 255, 255, 255}; switch (aDef->fType) { case cgfxAttrDef::kAttrTypeColor1DTexture: glBindTexture(GL_TEXTURE_1D,aDef->fTextureId); if( image.is_valid()) { // Load the image if (image.get_num_mipmaps() == 0) glTexParameteri(GL_TEXTURE_1D, GL_GENERATE_MIPMAP_SGIS, true); image.upload_texture1D(); } else { // Create a dummy stand-in texture glTexImage1D( GL_TEXTURE_1D, 0, GL_RGBA, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); } break; case cgfxAttrDef::kAttrTypeColor2DTexture: case cgfxAttrDef::kAttrTypeNormalTexture: case cgfxAttrDef::kAttrTypeBumpTexture: #if !defined(WIN32) && !defined(LINUX) case cgfxAttrDef::kAttrTypeColor2DRectTexture: #endif glBindTexture(GL_TEXTURE_2D,aDef->fTextureId); if( image.is_valid()) { // Load the image if (image.get_num_mipmaps() == 0) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, true); image.upload_texture2D(); } else { // Try to use Maya's default file texture loading, // if the DDS loader failed. For now all that // we can support is 2D textures. // if (textureNode != MObject::kNullObj) { MImage img; unsigned int width, height; if (MS::kSuccess == img.readFromTextureNode(textureNode)) { img.getSize( width, height); glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, true); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.pixels()); } else { // Create a dummy stand-in texture glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); } } else { // Create a dummy stand-in texture glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); } } break; case cgfxAttrDef::kAttrTypeEnvTexture: case cgfxAttrDef::kAttrTypeCubeTexture: case cgfxAttrDef::kAttrTypeNormalizationTexture: { glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, aDef->fTextureId); if( image.is_valid()) { GLenum target; if (image.get_num_mipmaps() == 0) glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_GENERATE_MIPMAP_SGIS, true); // loop through cubemap faces and load them as 2D textures for (int n = 0; n < 6; ++n) { // specify cubemap face target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+n; image.upload_texture2D(image.is_cubemap() ? n : 0, target); } } // else Add default environment map here if needed break; } case cgfxAttrDef::kAttrTypeColor3DTexture: glBindTexture(GL_TEXTURE_3D,aDef->fTextureId); if( image.is_valid()) { image.upload_texture3D(); } break; #if defined(WIN32) || defined(LINUX) // No such thing as NV texture rectangle // on Mac. case cgfxAttrDef::kAttrTypeColor2DRectTexture: glBindTexture(GL_TEXTURE_RECTANGLE_NV, aDef->fTextureId); if( image.is_valid()) { // Load the image image.upload_textureRectangle(); } else { // Create a dummy stand-in texture glTexImage2D( GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); } break; #endif default: assert(false); } } checkGlErrors("After loading texture"); cgGLSetupSampler(aDef->fParameterHandle, aDef->fTextureId); break; } #ifdef _WIN32 case cgfxAttrDef::kAttrTypeTime: { cgSetParameter1f(aDef->fParameterHandle, (float)timeGetTime() * 1000.0f); break; } #endif case cgfxAttrDef::kAttrTypeOther: case cgfxAttrDef::kAttrTypeUnknown: break; case cgfxAttrDef::kAttrTypeViewDir: case cgfxAttrDef::kAttrTypeProjectionDir: case cgfxAttrDef::kAttrTypeScreenDir: case cgfxAttrDef::kAttrTypeViewPos: case cgfxAttrDef::kAttrTypeProjectionPos: case cgfxAttrDef::kAttrTypeScreenPos: case cgfxAttrDef::kAttrTypeViewMatrix: case cgfxAttrDef::kAttrTypeProjectionMatrix: case cgfxAttrDef::kAttrTypeWorldViewMatrix: case cgfxAttrDef::kAttrTypeWorldViewProjectionMatrix: // View dependent parameter break; default: M_CHECK( false ); } // switch (aDef->fType) } catch ( cgfxShaderCommon::InternalError* e ) { if ( ++fErrorCount <= fErrorLimit ) { size_t ee = (size_t)e; MFnDependencyNode fnNode( oNode ); MString sMsg = "cgfxShader warning "; sMsg += (int)ee; sMsg += ": "; sMsg += fnNode.name(); sMsg += " internal error while setting parameter \""; sMsg += aDef->fName; sMsg += "\" of effect \""; sMsg += fShaderFxFile; sMsg += "\" for shape "; sMsg += shapePath.partialPathName(); MGlobal::displayWarning( sMsg ); } } } // loop over fAttrDefList } // cgfxShaderNode::bindAttrValues void cgfxShaderNode::bindViewAttrValues(const MDagPath& shapePath) { if ( !fEffect || !fTechnique.length() ) return; MStatus status; MObject oNode = thisMObject(); MMatrix wMatrix, vMatrix, pMatrix, sMatrix; MMatrix wvMatrix, wvpMatrix, wvpsMatrix; { float tmp[4][4]; if (shapePath.isValid()) wMatrix = shapePath.inclusiveMatrix(); else wMatrix.setToIdentity(); glGetFloatv(GL_MODELVIEW_MATRIX, &tmp[0][0]); wvMatrix = MMatrix(tmp); vMatrix = wMatrix.inverse() * wvMatrix; glGetFloatv(GL_PROJECTION_MATRIX, &tmp[0][0]); pMatrix = MMatrix(tmp); wvpMatrix = wvMatrix * pMatrix; float vpt[4]; float depth[2]; glGetFloatv(GL_VIEWPORT, vpt); glGetFloatv(GL_DEPTH_RANGE, depth); // Construct the NDC -> screen space matrix // float x0, y0, z0, w, h, d; x0 = vpt[0]; y0 = vpt[1]; z0 = depth[0]; w = vpt[2]; h = vpt[3]; d = depth[1] - z0; // Make a reference to ease the typing // double* s = &sMatrix.matrix[0][0]; s[ 0] = w/2; s[ 1] = 0.0; s[ 2] = 0.0; s[ 3] = 0.0; s[ 4] = 0.0; s[ 5] = h/2; s[ 6] = 0.0; s[ 7] = 0.0; s[ 8] = 0.0; s[ 9] = 0.0; s[10] = d/2; s[11] = 0.0; s[12] = x0+w/2; s[13] = y0+h/2; s[14] = z0+d/2; s[15] = 1.0; wvpsMatrix = wvpMatrix * sMatrix; } for ( cgfxAttrDefList::iterator it( fAttrDefList ); it; ++it ) { // loop over fAttrDefList cgfxAttrDef* aDef = *it; try { switch (aDef->fType) { case cgfxAttrDef::kAttrTypeViewDir: case cgfxAttrDef::kAttrTypeProjectionDir: case cgfxAttrDef::kAttrTypeScreenDir: case cgfxAttrDef::kAttrTypeViewPos: case cgfxAttrDef::kAttrTypeProjectionPos: case cgfxAttrDef::kAttrTypeScreenPos: { float tmp[4]; if (aDef->fSize == 3) { aDef->getValue(oNode, tmp[0], tmp[1], tmp[2]); tmp[3] = 1.0; } else { aDef->getValue(oNode, tmp[0], tmp[1], tmp[2], tmp[3]); } // Maya's API only provides for vectors of size 3. // When we do the matrix multiply, it will only // work correctly if the 4th coordinate is 1.0 // MVector vec(tmp[0], tmp[1], tmp[2]); int space = aDef->fType - cgfxAttrDef::kAttrTypeFirstPos; if (space < 0) { space = aDef->fType - cgfxAttrDef::kAttrTypeFirstDir; } MMatrix mat; // initially the identity matrix. switch (space) { case 0: /* mat = identity */ break; case 1: mat = wMatrix; break; case 2: mat = wvMatrix; break; case 3: mat = wvpMatrix; break; case 4: mat = wvpsMatrix; break; } // Maya's transformation matrices are set up with // the translation in row 3 (like OpenGL) rather // than column 3. To transform a point or vector, // use V*M, not M*V. kh 11/2003 vec *= wMatrix.inverse() * mat; // Compute the 4th element of the vector. // tmp[3] = (float)(mat(0,3) * tmp[0] + mat(1,3) * tmp[1] + mat(2,3) * tmp[2] + mat(3,3) * tmp[3]); cgSetParameterValuefc(aDef->fParameterHandle, aDef->fSize, tmp); break; } case cgfxAttrDef::kAttrTypeViewMatrix: case cgfxAttrDef::kAttrTypeProjectionMatrix: case cgfxAttrDef::kAttrTypeWorldViewMatrix: case cgfxAttrDef::kAttrTypeWorldViewProjectionMatrix: { MMatrix mat; switch (aDef->fType) { case cgfxAttrDef::kAttrTypeWorldMatrix: mat = wMatrix; break; case cgfxAttrDef::kAttrTypeViewMatrix: mat = vMatrix; break; case cgfxAttrDef::kAttrTypeProjectionMatrix: mat = pMatrix; break; case cgfxAttrDef::kAttrTypeWorldViewMatrix: mat = wvMatrix; break; case cgfxAttrDef::kAttrTypeWorldViewProjectionMatrix: mat = wvpMatrix; break; default: break; } if (aDef->fInvertMatrix) { mat = mat.inverse(); } if (!aDef->fTransposeMatrix) { mat = mat.transpose(); } float tmp[4][4]; mat.get(tmp); cgSetMatrixParameterfr(aDef->fParameterHandle, &tmp[0][0]); break; } default: break; } // switch (aDef->fType) } catch ( cgfxShaderCommon::InternalError* e ) { if ( ++fErrorCount <= fErrorLimit ) { size_t ee = (size_t)e; MFnDependencyNode fnNode( oNode ); MString sMsg = "cgfxShader warning "; sMsg += (int)ee; sMsg += ": "; sMsg += fnNode.name(); sMsg += " internal error while setting parameter \""; sMsg += aDef->fName; sMsg += "\" of effect \""; sMsg += fShaderFxFile; sMsg += "\" for shape "; if (shapePath.isValid()) sMsg += shapePath.partialPathName(); else sMsg += "SWATCH GEOMETRY"; MGlobal::displayWarning( sMsg ); } } } // loop over fAttrDefList } /* virtual */ MStatus cgfxShaderNode::glUnbind(const MDagPath& shapePath) { // There is nothing to do in unbind because we did not change the // graphics state in bind. // #ifdef KH_DEBUG MString ss = " .. unbd "; if ( this && fConstructed ) ss += name(); ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif return MS::kSuccess; } /* virtual */ MStatus cgfxShaderNode::glGeometry(const MDagPath& shapePath, int prim, unsigned int writable, int indexCount, const unsigned int * indexArray, int vertexCount, const int * vertexIDs, const float * vertexArray, int normalCount, const float ** normalArrays, int colorCount, const float ** colorArrays, int texCoordCount, const float ** texCoordArrays) { MStatus stat; #ifdef KH_DEBUG MString ss = " .. geom "; if ( this && fConstructed ) ss += name(); ss += " "; ss += indexCount; ss += "i "; ss += vertexCount; ss += "v "; ss += normalCount; ss += "n "; ss += colorCount; ss += "c "; ss += texCoordCount; ss += "t "; ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif try { // unsigned int dirtyMask = this->dirtyMask(); // Here's where we do *all* the interesting work of the shader. // Normally some of it might be done in bind() and unbind() but // we just put it all here. // checkGlErrors("cgfxShaderNode::geometry"); // Since we have no idea what the effect may set, we have // to push everything. glPushAttrib(GL_ALL_ATTRIB_BITS); glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); // We know that the geometry is not going to change over the // course of the n passes. So we'll set it up initially and // then just leave it alone in the passes. // // TODO: Compiled vertex arrays would probably help this a lot. // glVertexPointer(3, GL_FLOAT, 0, vertexArray); glEnableClientState(GL_VERTEX_ARRAY); if ( normalCount > 0 && normalArrays[ 0 ] ) { glNormalPointer(GL_FLOAT, 0, normalArrays[0]); glEnableClientState(GL_NORMAL_ARRAY); } else { glDisableClientState(GL_NORMAL_ARRAY); glNormal3f(0.0, 0.0, 1.0); } // Colours int nColor = fColorList.length(); if (nColor > sMaxColorUnits ) nColor = sMaxColorUnits; for ( int iColor = 0; iColor < nColor; ++iColor ) { // TODO - add support for colorArrays other than 0! //if ( glClientActiveTextureARB ) // glClientActiveTextureARB( GL_TEXTURE0_ARB + iColor ); int iBuf = fColorList[ iColor ]; // Color or UV set if ( iBuf >= 0 && iBuf < colorCount ) { if( colorArrays && colorArrays[ iBuf ] ) { glColorPointer( 4, GL_FLOAT, 0, colorArrays[ iBuf ] ); glEnableClientState( GL_COLOR_ARRAY ); } // No point checking texture coordinates for colours // as Maya only provides 2D texture coordinates but // OpenGL requires at least 3 elements per colour } // binormal else if ( iBuf == etcBinormal && normalCount > 2 && normalArrays[ 2 ] ) { glColorPointer( 3, GL_FLOAT, 0, normalArrays[ 2 ] ); glEnableClientState( GL_COLOR_ARRAY ); } // tangent else if ( iBuf == etcTangent && normalCount > 1 && normalArrays[ 1 ] ) { glColorPointer( 3, GL_FLOAT, 0, normalArrays[ 1 ] ); glEnableClientState( GL_COLOR_ARRAY ); } // normal else if ( iBuf == etcNormal && normalCount > 0 && normalArrays[ 0 ] ) { glColorPointer( 3, GL_FLOAT, 0, normalArrays[ 0 ] ); glEnableClientState( GL_COLOR_ARRAY ); } // constant (4 float values) else if ( iBuf == etcConstant ) { float fv[4]; #ifdef _WIN32 int nv = sscanf( fColorSource[ iColor ].asChar(), " %f %f %f %f", fv, fv+1, fv+2, fv+3 ); M_CHECK( nv == 4 ); #endif glDisableClientState( GL_COLOR_ARRAY ); glColor4fv( fv ); } // User did not request any data for this texcoord, or // the shape does not have a UV set that matches the request, or // tangent/binormal unavailable because shape has no UV coords. else { glDisableClientState( GL_COLOR_ARRAY ); glColor4f( 1.0, 0.5, 1.0, 1.0 ); } } // color loop // Texture coords int nTex = fTexCoordList.length(); if (nTex > sMaxTextureUnits ) nTex = sMaxTextureUnits; for ( int iTex = 0; iTex < nTex; ++iTex ) { #if defined(_WIN32) || defined(LINUX) #if defined(_WIN32) if ( glClientActiveTextureARB ) #endif glClientActiveTextureARB( GL_TEXTURE0_ARB + iTex ); #else glClientActiveTexture( GL_TEXTURE0_ARB + iTex ); #endif int iBuf = fTexCoordList[ iTex ]; // UV or Color set if ( iBuf >= 0 && (iBuf < texCoordCount || iBuf < colorCount)) { if( texCoordArrays && texCoordArrays[ iBuf ] ) { // NOTE: Maya supports only 2D texture coords! glTexCoordPointer( 2, GL_FLOAT, 0, texCoordArrays[ iBuf ] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } else if( colorArrays && colorArrays[ iBuf ] ) { glTexCoordPointer( 4, GL_FLOAT, 0, colorArrays[ iBuf ] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } } // binormal else if ( iBuf == etcBinormal && normalCount > 2 && normalArrays[ 2 ] ) { glTexCoordPointer( 3, GL_FLOAT, 0, normalArrays[ 2 ] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } // tangent else if ( iBuf == etcTangent && normalCount > 1 && normalArrays[ 1 ] ) { glTexCoordPointer( 3, GL_FLOAT, 0, normalArrays[ 1 ] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } // normal else if ( iBuf == etcNormal && normalCount > 0 && normalArrays[ 0 ] ) { glTexCoordPointer( 3, GL_FLOAT, 0, normalArrays[ 0 ] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } // constant (4 float values) else if ( iBuf == etcConstant ) { float fv[4]; #ifdef _WIN32 int nv = sscanf( fTexCoordSource[ iTex ].asChar(), " %f %f %f %f", fv, fv+1, fv+2, fv+3 ); M_CHECK( nv == 4 ); #endif glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoord4fv( fv ); } // User did not request any data for this texcoord, or // the shape does not have a UV set that matches the request, or // tangent/binormal unavailable because shape has no UV coords. else { glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoord2f( 0.0, 0.0 ); } } // texture coords loop checkGlErrors("After setting attribute pointers"); if (fEffect) { // Set up the uniform attribute values for the effect. bindViewAttrValues(shapePath); CGpass cgPass = cgGetFirstPass(cgGetNamedTechnique(fEffect, fTechnique.asChar())); while (cgPass) { checkGlErrors("Before setting pass state"); cgSetPassState(cgPass); checkGlErrors("After setting pass state"); glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray); checkGlErrors("After glDrawElements"); cgResetPassState(cgPass); checkGlErrors("After resetting pass state"); cgPass = cgGetNextPass(cgPass); } } else // fEffect must be NULL { // There is no effect. Either they never set one or the one provided // failed to compile. Just use this default material which is sort // of a shiny salmon-pink color. It looks like nothing that Maya // creates by default but still lets you see your geometry. // static float diffuse_color[4] = {1.0, 0.5, 0.5, 1.0}; static float specular_color[4] = {1.0, 1.0, 1.0, 1.0}; glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); glDisable( GL_TEXTURE_2D); glDisableClientState(GL_COLOR_ARRAY); glColor4fv(diffuse_color); // Set up the specular color // glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular_color); // Set up a default shininess // glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0); // Now call glDrawElements to put all the primitives on the // screen. See the comment above re: glDrawRangeElements. // glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray); } checkGlErrors("After effects End"); glPopClientAttrib(); glPopAttrib(); } catch ( cgfxShaderCommon::InternalError* e ) { reportInternalError( __FILE__, (size_t)e ); stat = MS::kFailure; } catch ( ... ) { reportInternalError( __FILE__, __LINE__ ); stat = MS::kFailure; } return stat; } // cgfxShaderNode::geometry /* virtual */ int cgfxShaderNode::texCoordsPerVertex() { #ifdef KH_DEBUG MString ss = " .. tcpv "; if ( this && fConstructed ) ss += name(); ss += " "; ss += (int)fDataSetNames.length(); ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif return fDataSetNames.length(); // NB: Maya apparently calls texCoordsPerVertex() only if // getTexCoordSetNames() returns 0 or is not implemented. kh 9/03 } // cgfxShaderNode::texCoordsPerVertex /* virtual */ int cgfxShaderNode::getTexCoordSetNames( MStringArray& names ) { #ifdef KH_DEBUG MString ss = " .. tcsn "; if ( this && fConstructed ) ss += name(); ss += " "; for ( int i = 0; i < fDataSetNames.length(); ++i ) { ss += fDataSetNames[ i ]; ss += " "; } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif names = fDataSetNames; return names.length(); // NB: Maya calls getTexCoordSetNames() *before* bind(). // Therefore we set fDataSetNames as early as possible, // even before knowing what geometry will be rendered or // which UV set names are defined for the geometry. kh 9/03 } // cgfxShaderNode::getTexCoordSetNames #if MAYA_API_VERSION >= 700 /* virtual */ int cgfxShaderNode::getColorSetNames( MStringArray& names ) { // Rather than sorting the requested texCoords into // a list of TexCoord sets and a list of ColorSets // for each geometry being rendered, we just ask for // all sets as both UV and colour, and then use which // ever bit of data is returned. This is a little // ugly - but unless we cache this per-shape we're // applied to to determine which entry is UV vs CPV, // the performance of this is probably better than // querying the API every frame to sort these ourselves // names = fDataSetNames; return names.length(); } #else /* virtual */ int cgfxShaderNode::colorsPerVertex() { fColorList.setLength(1); fColorList[0] = 0; return 1; } // cgfxShaderNode::texCoordsPerVertex #endif /* virtual */ int cgfxShaderNode::normalsPerVertex() { #ifdef KH_DEBUG MString ss = " .. npv "; if ( this && fConstructed ) ss += name(); ss += " "; ss += fNormalsPerVertex; ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif return fNormalsPerVertex; // NB: Maya calls normalsPerVertex() both before and after bind(). // It appears that the normalCount passed to geometry() is // obtained *before* the call to bind(). Therefore we set // fNormalsPerVertex as early as possible. kh 9/03 } // cgfxShaderNode::normalsPerVertex void cgfxShaderNode::setAttrDefList( cgfxAttrDefList* list ) { if (fAttrDefList) cgfxAttrDef::purgeMObjectCache( fAttrDefList ); if ( list ) { list->addRef(); cgfxAttrDef::validateMObjectCache( thisMObject(), list ); } if (fAttrDefList) fAttrDefList->release(); fAttrDefList = list; } // cgfxShaderNode::setAttrDefList void cgfxShaderNode::getAttributeList(MStringArray& attrList) const { MString tmp; int len = fAttributeListArray.length(); attrList.clear(); for (int i = 0; i < len; ++i) { tmp = fAttributeListArray[i]; attrList.append(tmp); } } void cgfxShaderNode::setAttributeList(const MStringArray& attrList) { MString tmp; int len = attrList.length(); fAttributeListArray.clear(); for (int i = 0; i < len; ++i) { tmp = attrList[i]; fAttributeListArray.append(tmp); } } // Data accessors for the texCoordSource attribute. const MStringArray& cgfxShaderNode::getTexCoordSource() const { #ifdef KH_DEBUG MString ss = " .. gtcs "; if ( this && fConstructed ) ss += name(); ss += " "; for ( int ii = 0; ii < fTexCoordSource.length(); ++ii ) { ss += "\""; ss += fTexCoordSource[ii]; ss += "\" "; } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif return fTexCoordSource; } // cgfxShaderNode::getTexCoordSource // Data accessors for the colorSource attribute. const MStringArray& cgfxShaderNode::getColorSource() const { #ifdef KH_DEBUG MString ss = " .. gtcs "; if ( this && fConstructed ) ss += name(); ss += " "; for ( int ii = 0; ii < fColorSource.length(); ++ii ) { ss += "\""; ss += fColorSource[ii]; ss += "\" "; } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif return fColorSource; } // cgfxShaderNode::getColorSource void cgfxShaderNode::setDataSources( const MStringArray& texCoordSources, const MStringArray& colorSources) { int length_TC = texCoordSources.length(); if( length_TC > 0 ) { if ( length_TC > CGFXSHADERNODE_GL_TEXTURE_MAX ) length_TC = CGFXSHADERNODE_GL_TEXTURE_MAX; fTexCoordSource.setLength( length_TC ); for ( int i = 0; i < length_TC; ++i ) fTexCoordSource[ i ] = texCoordSources[ i ]; } int length_CS = colorSources.length(); if( length_CS > 0 ) { if ( length_CS > CGFXSHADERNODE_GL_COLOR_MAX ) length_CS = CGFXSHADERNODE_GL_COLOR_MAX; fColorSource.setLength( length_CS ); for ( int i = 0; i < length_CS; ++i ) fColorSource[ i ] = colorSources[ i ]; } fDataSetNames.clear(); fNormalsPerVertex = 1; updateDataSource( fTexCoordSource, fTexCoordList); updateDataSource( fColorSource, fColorList); } void cgfxShaderNode::updateDataSource( MStringArray& v, MIntArray& indexList) { #ifdef KH_DEBUG MString ss = " .. stcs "; if ( this && fConstructed ) ss += name(); ss += " "; for ( int ii = 0; ii < v.length(); ++ii ) { ss += "\""; ss += v[ii]; ss += "\" "; } ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif int nDataSets = v.length(); indexList.setLength( nDataSets ); for ( int iDataSet = 0; iDataSet < nDataSets; ++iDataSet ) { // iDataSet loop MString s; int iBuf = etcNull; // Strip leading and trailing spaces and control chars. const char* bp = v[ iDataSet ].asChar(); const char* ep = v[ iDataSet ].length() + bp; #ifdef _WIN32 while ( bp < ep && *bp <= ' ' && *bp >= '\0') ++bp; #else while ( bp < ep && *bp <= ' ') ++bp; #endif #ifdef _WIN32 while ( bp < ep && ep[-1] <= ' ' && ep[-1] >= '\0' ) --ep; #else while ( bp < ep && ep[-1] <= ' ' ) --ep; #endif // Empty? if ( bp == ep ) iBuf = etcNull; // Constant? (1, 2, 3 or 4 float values) else if ( (*bp >= '0' && *bp <= '9') || *bp == '-' || *bp == '+' || *bp == '.' ) { const char* cp = bp; int nValues = 0; while ( cp < ep && nValues < 4 ) { float x; int nc = 0; int nv = sscanf( cp, " %f%n", &x, &nc ); if ( nv != 1 ) break; ++nValues; cp += nc; } if ( nValues > 0 ) { s.set( bp, (int)(cp - bp) ); // drop trailing junk for ( ; nValues < 4; ++nValues ) s += " 0"; iBuf = etcConstant; } } // UV set name or reserved word. else { s.set( bp, (int)(ep - bp) ); // Force reserved words to lower case. bp = s.asChar(); if ( 0 == stricmp( "normal", bp ) ) { s = "normal"; iBuf = etcNormal; } else if ( 0 == stricmp( "tangent", bp ) ) { s = "tangent"; iBuf = etcTangent; if( fNormalsPerVertex < 2) fNormalsPerVertex = 2; } else if ( 0 == stricmp( "binormal", bp ) ) { s = "binormal"; iBuf = etcBinormal; fNormalsPerVertex = 3; } // Data set name... tell Maya that we want to retrieve this data set. else iBuf = findOrAppend( fDataSetNames, s ); } // Tell our geometry() method where to get data. indexList[ iDataSet ] = iBuf; // Store cleaned-up string. v[ iDataSet ] = s; } // iDataSet loop } // cgfxShaderNode::updateDataSource // Data accessor for list of empty UV sets. const MStringArray& cgfxShaderNode::getEmptyUVSets() const { static const MStringArray saNull; return saNull; } // cgfxShaderNode::getEmptyUVSets const MObjectArray& cgfxShaderNode::getEmptyUVSetShapes() const { static const MObjectArray oaNull; return oaNull; } // cgfxShaderNode::getEmptyUVSetShapes void cgfxShaderNode::setEffect(CGeffect pNewEffect) { #ifdef KH_DEBUG char ssbuf[64]; MString ss = " .. se "; ss += name(); ss += " "; sprintf( ssbuf, "new=%X old=%X", pNewEffect, fEffect ); ss += ssbuf; ss += "\n"; ::OutputDebugString( ss.asChar() ); #endif if (fEffect) cgDestroyEffect(fEffect); fEffect = pNewEffect; // Build string array containing technique names and descriptions. // Each item in the technique list has the form // "techniqueNamenumPasses" // where // numPasses is the number of passes defined by the // technique, or 0 if the technique is not valid. // (Future versions of the cgfxShader plug-in may append // additional tab-separated fields.) fTechniqueList.clear(); if (fEffect) { CGtechnique cgTechnique = cgGetFirstTechnique(fEffect); while (cgTechnique) { MString s; const char* techniqueName = cgGetTechniqueName(cgTechnique); if (techniqueName) { s += techniqueName; } if (cgValidateTechnique(cgTechnique) == CG_TRUE) { int numPasses = 0; CGpass cgPass = cgGetFirstPass(cgTechnique); while (cgPass) { ++numPasses; cgPass = cgGetNextPass(cgPass); } s += "\t"; s += numPasses; } else { s += "\t0"; } fTechniqueList.append(s); cgTechnique = cgGetNextTechnique(cgTechnique); } } } // cgfxShaderNode::setEffect void cgfxShaderNode::setTechnique( const MString& techn ) { // If effect not loaded, just store the technique name. if (!fEffect) { fTechnique = techn; return; } // Search for requested technique. CGtechnique technique = cgGetNamedTechnique(fEffect, techn.asChar()); if (cgValidateTechnique(technique) == CG_TRUE) { fTechnique = techn; return; } // Requested technique was not found or not valid. Revert to the old one. technique = cgGetNamedTechnique(fEffect, fTechnique.asChar()); if (cgValidateTechnique(technique) == CG_TRUE) { return; } // Old technique is no good. Activate the first valid technique. technique = cgGetFirstTechnique(fEffect); while (technique) { if (cgValidateTechnique(technique) == CG_TRUE) { fTechnique = cgGetTechniqueName(technique); return; } technique = cgGetNextTechnique(technique); } // No valid technique exists for the current effect. // Save requested technique name. We'll try to use it as the // initial technique the next time a valid effect is loaded. fTechnique = techn; } // cgfxShaderNode::setTechnique MStatus cgfxShaderNode::shouldSave ( const MPlug & plug, bool & ret ) { if (plug == sAttributeList) { ret = true; return MS::kSuccess; } return MPxNode::shouldSave(plug, ret); } void cgfxShaderNode::setTexturesByName(bool texturesByName, bool updateAttributes) { if( updateAttributes && fTexturesByName != texturesByName) { // We've been explicitly changed to a different // texture mode. // If we have any current texture attributes, destroy them // MDGModifier dgMod; cgfxAttrDefList* nodeList = attrDefList(); cgfxAttrDefList::iterator nmIt; bool foundTextures = false; for (nmIt = nodeList->begin(); nmIt; ++nmIt) { cgfxAttrDef* adef = (*nmIt); if(adef->fType >= cgfxAttrDef::kAttrTypeFirstTexture && adef->fType <= cgfxAttrDef::kAttrTypeLastTexture) { MObject theMObject = thisMObject(); adef->destroyAttribute( theMObject, &dgMod); foundTextures = true; } } // Switch across to the new texture mode (before creating the // new attributes) // fTexturesByName = texturesByName; // Now re-create our texture attributes // if( foundTextures) { dgMod.doIt(); for (nmIt = nodeList->begin(); nmIt; ++nmIt) { cgfxAttrDef* adef = (*nmIt); if( adef->fType >= cgfxAttrDef::kAttrTypeFirstTexture && adef->fType <= cgfxAttrDef::kAttrTypeLastTexture) { adef->createAttribute(thisMObject(), &dgMod, this); } } dgMod.doIt(); // Finally, if we just created new string attributes, we need to // set them to a sensible value or they won't show up // if( fTexturesByName) { for (nmIt = nodeList->begin(); nmIt; ++nmIt) { cgfxAttrDef* adef = (*nmIt); if( adef->fType >= cgfxAttrDef::kAttrTypeFirstTexture && adef->fType <= cgfxAttrDef::kAttrTypeLastTexture) { MObject theMObject = thisMObject(); adef->setTexture( theMObject, adef->fStringDef, &dgMod); } } } } } else { fTexturesByName = texturesByName; } } // Get cgfxShader version string. MString cgfxShaderNode::getPluginVersion() { MString sVer = "cgfxShader "; sVer += CGFXSHADER_VERSION; sVer += " for Maya "; sVer += (int)(MAYA_API_VERSION / 100); sVer += "."; sVer += (int)(MAYA_API_VERSION % 100 / 10); sVer += " ("; sVer += __DATE__; sVer += ")"; return sVer; } // cgfxShaderNode::getPluginVersion // Error reporting void cgfxShaderNode::reportInternalError( const char* function, size_t errcode ) { MString es = "cgfxShader"; try { if ( this && fConstructed ) { if ( ++fErrorCount > fErrorLimit ) return; MString s; s += "\""; s += name(); s += "\": "; s += typeName(); es = s; } } catch ( ... ) {} es += " internal error "; es += (int)errcode; es += " in "; es += function; #ifdef KH_DEBUG ::OutputDebugString( es.asChar() ); ::OutputDebugString( "\n" ); #endif MGlobal::displayError( es ); } // cgfxShaderNode::reportInternalError void cgfxShaderNode::cgErrorCallBack() { MGlobal::displayInfo(__FUNCTION__); CGerror cgLastError = cgGetError(); if(cgLastError) { MGlobal::displayError(cgGetErrorString(cgLastError)); MGlobal::displayError(cgGetLastListing(sCgContext)); } } // cgfxShaderNode::cgErrorCallBack void cgfxShaderNode::cgErrorHandler(CGcontext cgContext, CGerror cgError, void* userData) { MGlobal::displayError(cgGetErrorString(cgError)); MGlobal::displayError(cgGetLastListing(sCgContext)); }