// // 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. // //- // ========================================================================== // Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All // rights reserved. // // The coded instructions, statements, computer programs, and/or related // material (collectively the "Data") in these files contain unpublished // information proprietary to Autodesk, Inc. ("Autodesk") and/or its // licensors, which is protected by U.S. and Canadian federal copyright // law and by international treaties. // // The Data is provided for use exclusively by You. You have the right // to use, modify, and incorporate this Data into other products for // purposes authorized by the Autodesk software license agreement, // without fee. // // The copyright notices in the Software and this entire statement, // including the above license grant, this restriction and the // following disclaimer, must be included in all copies of the // Software, in whole or in part, and all derivative works of // the Software, unless such copies or derivative works are solely // in the form of machine-executable object code generated by a // source language processor. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. // AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED // WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF // NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR // PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR // TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS // BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, // DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK // AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY // OR PROBABILITY OF SUCH DAMAGES. // // ========================================================================== //+ #ifndef CGFXSHADER_VERSION #define CGFXSHADER_VERSION "4.4" #endif #include "cgfxShaderNode.h" #include "cgfxFindImage.h" #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... // PFNGLCLIENTACTIVETEXTUREARBPROC glStateCache::glClientActiveTexture = 0; PFNGLVERTEXATTRIBPOINTERARBPROC glStateCache::glVertexAttribPointer = 0; PFNGLENABLEVERTEXATTRIBARRAYARBPROC glStateCache::glEnableVertexAttribArray = 0; PFNGLDISABLEVERTEXATTRIBARRAYARBPROC glStateCache::glDisableVertexAttribArray = 0; PFNGLVERTEXATTRIB4FARBPROC glStateCache::glVertexAttrib4f = 0; PFNGLSECONDARYCOLORPOINTEREXTPROC glStateCache::glSecondaryColorPointer = 0; PFNGLSECONDARYCOLOR3FEXTPROC glStateCache::glSecondaryColor3f = 0; PFNGLMULTITEXCOORD4FARBPROC glStateCache::glMultiTexCoord4fARB = 0; int glStateCache::sMaxTextureUnits = 0; glStateCache::glStateCache() { reset(); } glStateCache glStateCache::gInstance; void glStateCache::activeTexture( int i) { if( i != fActiveTextureUnit) { fActiveTextureUnit = i; if( glStateCache::glClientActiveTexture) glStateCache::glClientActiveTexture( GL_TEXTURE0_ARB + i ); } } void glStateCache::enableVertexAttrib( int i) { if( !(fEnabledRegisters & (1 << (glRegister::kVertexAttrib + i)))) { if( glStateCache::glEnableVertexAttribArray) glStateCache::glEnableVertexAttribArray( i); fEnabledRegisters |= (1 << (glRegister::kVertexAttrib + i)); } fRequiredRegisters |= (1 << (glRegister::kVertexAttrib + i)); } void glStateCache::flushState() { // Work out which registers are enabled, but no longer required long redundantRegisters = fEnabledRegisters & ~fRequiredRegisters; //printf( "State requires %d, enabled %d, redundant %d\n", fRequiredRegisters, fEnabledRegisters, redundantRegisters); // Disable them if( redundantRegisters & (1 << glRegister::kPosition)) glDisableClientState(GL_VERTEX_ARRAY); if( redundantRegisters & (1 << glRegister::kNormal)) glDisableClientState(GL_NORMAL_ARRAY); if( redundantRegisters & (1 << glRegister::kColor)) glDisableClientState(GL_COLOR_ARRAY); if( redundantRegisters & (1 << glRegister::kSecondaryColor)) glDisableClientState(GL_SECONDARY_COLOR_ARRAY_EXT); for( int i = glRegister::kTexCoord; i <= glRegister::kLastTexCoord; i++) { if( redundantRegisters & (1 << i)) { activeTexture( i - glRegister::kTexCoord); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } } for( int i = glRegister::kVertexAttrib; i <= glRegister::kLastVertexAttrib; i++) { if( redundantRegisters & (1 << i)) { if( glStateCache::glDisableVertexAttribArray) glStateCache::glDisableVertexAttribArray( i - glRegister::kVertexAttrib); } } fEnabledRegisters = fRequiredRegisters; fRequiredRegisters = 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::sVertexAttributeList; MObject cgfxShaderNode::sVertexAttributeSource; 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, etcDataSet = -6, }; //--------------------------------------------------------------------// // Constructor: // cgfxShaderNode::cgfxShaderNode() : fEffect(0) , fAttrDefList(0) , fVertexAttributes( NULL) #ifdef TEXTURES_BY_NAME , fTexturesByName( true ) #else , fTexturesByName( false ) #endif , fNormalsPerVertex( 3 ) , fConstructed(false) , fErrorCount( 0 ) , fErrorLimit( 8 ) , fTechniqueHasBlending( false ) , fShaderFxFile() , fShaderFxFileChanged( false ) { // Set texCoordSource attribute to its default value. MStringArray sa; sa.append( "map1" ); sa.append( "tangent" ); sa.append( "binormal" ); setDataSources( &sa, NULL ); } // Post-constructor void cgfxShaderNode::postConstructor() { fConstructed = true; // ok to call MPxNode member functions } // 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: // 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 (uniform parameters) // 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 ); // // vertexAttributeList (varying parameters) // sVertexAttributeList = typedAttr.create("vertexAttributeList", "val", 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(sVertexAttributeList); M_CHECK( stat ); // // vertexAttributeSource // sVertexAttributeSource = typedAttr.create( "vertexAttributeSource", "vas", MFnData::kStringArray, MObject::kNullObj, &stat ); M_CHECK( stat ); stat = typedAttr.setInternal( true ); M_CHECK( stat ); stat = addAttribute( sVertexAttributeSource ); 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() ); setShaderFxFileChanged( true ); 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. // MString fileOptions; cgfxGetFxIncludePath( fileName, fileOptions ); const char *opts[2]; opts[0] = fileOptions.asChar(); opts[1] = NULL; CGeffect cgEffect = cgCreateEffectFromFile(sCgContext, fileName.asChar(), opts); 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 == sVertexAttributeList) { MDataHandle nonConstHandle(handle); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData(saData); const MStringArray& attributeList = fnSaData.array(); cgfxVertexAttribute* attributes = NULL; cgfxVertexAttribute** nextAttribute = &attributes; int numAttributes = attributeList.length() / 4; for( int i = 0; i < numAttributes; i++) { cgfxVertexAttribute* attribute = new cgfxVertexAttribute(); attribute->fName = attributeList[ i * 4 + 0]; attribute->fType = attributeList[ i * 4 + 1]; attribute->fUIName = attributeList[ i * 4 + 2]; attribute->fSemantic = attributeList[ i * 4 + 3]; *nextAttribute = attribute; nextAttribute = &attribute->fNext; } setVertexAttributes( attributes); } else if ( plug == sVertexAttributeSource ) { MDataHandle nonConstHandle( handle ); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData( saData ); MStringArray values = fnSaData.array(); setVertexAttributeSource( values); } else if ( plug == sTexCoordSource ) { MDataHandle nonConstHandle( handle ); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData( saData ); MStringArray values = fnSaData.array(); setDataSources( &values, NULL ); } else if ( plug == sColorSource ) { MDataHandle nonConstHandle( handle ); MObject saData = nonConstHandle.data(); MFnStringArrayData fnSaData( saData ); MStringArray values = fnSaData.array(); setDataSources( NULL, &values ); } 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 == sVertexAttributeList) { MStringArray attributeList; cgfxVertexAttribute* attribute = fVertexAttributes; while( attribute) { attributeList.append( attribute->fName); attributeList.append( attribute->fType); attributeList.append( attribute->fUIName); attributeList.append( attribute->fSemantic); attribute = attribute->fNext; } MFnStringArrayData saData; handle.set(saData.create( attributeList)); } else if ( plug == sVertexAttributeSource ) { MStringArray attributeSources; cgfxVertexAttribute* attribute = fVertexAttributes; while( attribute) { attributeSources.append( attribute->fSourceName); attribute = attribute->fNext; } MFnStringArrayData saData; handle.set( saData.create( attributeSources ) ); } 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 */ } // Handle a change in a connected texture // void textureChangedCallback( MNodeMessage::AttributeMessage msg, MPlug & plug, MPlug & otherPlug, void* aDef) { // Whenever there is a change in our texture's attributes (which // could also be texture node deleted or disconnected), remove // our callback to signify that this texture needs to be refreshed. // We don't release the GL texture here because there may not be // a valid GL context around when the DG is being updated. The // texture will get flushed at the next draw time when the bind // code determines there is a node but no callback. ((cgfxAttrDef*)aDef)->releaseCallback(); } #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, pGeomData[0].elementCount(), 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 // Tell Maya that Cg effects can be batched // bool cgfxShaderNode::supportsBatching() const { return true; } // Tell Maya to invert texture coordinates for this shader // bool cgfxShaderNode::invertTexCoords() const { return true; } /* 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. // glStateCache::instance().reset(); // 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); // 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. But only when it has // changed file name. In the case where the file cannot be found // we will not continuously search for the same file while refreshing. // The user will need to manually "refresh" the file name, or change // it to force a new attempt to load the file here. // if (shaderFxFileChanged()) { MString fileName = cgfxFindFile(shaderFxFile()); if(fileName.asChar() != NULL && strcmp(fileName.asChar(), "")) { // Compile and create the effect. MString fileOptions; cgfxGetFxIncludePath( fileName, fileOptions ); const char *opts[2]; opts[0] = fileOptions.asChar(); opts[1] = NULL; CGeffect cgEffect = cgCreateEffectFromFile(sCgContext, fileName.asChar(), opts); 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); } } setShaderFxFileChanged( false ); } } // One-time OpenGL initialization... if ( glStateCache::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 #define RESOLVE_GL_EXTENSION( fn, ext) wglGetProcAddress( #fn #ext) #elif defined LINUX #define RESOLVE_GL_EXTENSION( fn, ext) &fn ## ext #else #define RESOLVE_GL_EXTENSION( fn, ext) &fn #endif glStateCache::glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREARBPROC) RESOLVE_GL_EXTENSION( glClientActiveTexture, ARB); glStateCache::glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERARBPROC) RESOLVE_GL_EXTENSION( glVertexAttribPointer, ARB); glStateCache::glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC) RESOLVE_GL_EXTENSION( glEnableVertexAttribArray, ARB); glStateCache::glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) RESOLVE_GL_EXTENSION( glDisableVertexAttribArray, ARB); glStateCache::glVertexAttrib4f = (PFNGLVERTEXATTRIB4FARBPROC) RESOLVE_GL_EXTENSION( glVertexAttrib4f, ARB); glStateCache::glSecondaryColorPointer = (PFNGLSECONDARYCOLORPOINTEREXTPROC) RESOLVE_GL_EXTENSION( glSecondaryColorPointer, EXT); glStateCache::glSecondaryColor3f = (PFNGLSECONDARYCOLOR3FEXTPROC) RESOLVE_GL_EXTENSION( glSecondaryColor3f, EXT); glStateCache::glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC) RESOLVE_GL_EXTENSION( glMultiTexCoord4f, ARB); // Don't use GL_MAX_TEXTURE_UNITS as this does not provide a proper // count when the # of image or texcoord inputs differs // from the conventional (older) notion of texture unit. // // Instead take the minimum of GL_MAX_TEXTURE_COORDS_ARB and // GL_MAX_TEXUTRE_IMAGE_UNITS_ARB according to the // ARB_FRAGMENT_PROGRAM specification. // GLint tval; glGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &tval ); GLint mic = 0; glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &mic ); if (mic < tval) tval = mic; // Don't use this... //glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &tval ); glStateCache::sMaxTextureUnits = tval; if (!glStateCache::glClientActiveTexture || glStateCache::sMaxTextureUnits < 1) glStateCache::sMaxTextureUnits = 1; else if (glStateCache::sMaxTextureUnits > CGFXSHADERNODE_GL_TEXTURE_MAX) glStateCache::sMaxTextureUnits = CGFXSHADERNODE_GL_TEXTURE_MAX; } // Try and grab the first pass of our effect if( fNewEffect.fTechnique && fNewEffect.fTechnique->fPasses) { // Set up the uniform attribute values for the effect. bindAttrValues(); // Set depth function properly in case we have multi-pass if (fTechniqueHasBlending) glPushAttrib( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glGetBooleanv( GL_DEPTH_TEST, &fDepthEnableState); glGetIntegerv( GL_DEPTH_FUNC, &fDepthFunc); glDepthFunc(GL_LEQUAL); if( !fNewEffect.fTechnique->fMultiPass && fNewEffect.fTechnique->fPasses) cgSetPassState( fNewEffect.fTechnique->fPasses->fPass); } else { // 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. // glPushAttrib( GL_LIGHTING); 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); 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); } checkGlErrors("cgfxShaderNode::glBind"); } catch ( cgfxShaderCommon::InternalError* e ) { reportInternalError( __FILE__, (size_t)e ); stat = MS::kFailure; } catch ( ... ) { reportInternalError( __FILE__, __LINE__ ); stat = MS::kFailure; } return stat; } // cgfxShaderNode::bind static bool textureInitPowerOfTwo(unsigned int val, unsigned int & retval) { unsigned int res = 0; // May be we should return 1 when val == 0 if (val) { // Muliply all values by 2, to simplify the testing: // 3*(res/2) < val*2 <= 3*res val <<= 1; unsigned int low = 3; res = 1; while (val > low) { low <<= 1; res <<= 1; } } retval = res; return (res == (val>>1)) ? 1 : 0; } void cgfxShaderNode::bindAttrValues() { if ( !fEffect || !fTechnique.length() ) return; MStatus status; MObject oNode = thisMObject(); // This method should NEVER access the shape. If you find yourself tempted to access // any data from the shape here (like the matrices), be strong and resist! Any shape // dependent data should be set in bindAttrViewValues instead! // 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::kAttrTypeWorldDir: 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; switch (space) { /* case 0: object space, handled in view dependent method */ case 1: /* world space - do nothing, identity */ 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::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); } // attach a monitor to this texture if we don't already have one // Note that we don't need to worry about handling node destroyed // or disconnected, as both of these will trigger attribute changed // messages before going away, and we will deregister our callback // in the handler! if( aDef->fTextureMonitor == kNullCallback && textureNode != MObject::kNullObj) { // If we don't have a callback, this may mean our texture is dirty // and needs to be re-loaded (because we can't actually delete the // texture itself in the DG callback we need to wait until we // know we have a GL context - like right here) aDef->releaseTexture(); aDef->fTextureMonitor = MNodeMessage::addAttributeChangedCallback( textureNode, textureChangedCallback, aDef); } } } 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: // Only flip 2D textures if we're using right-handed texture // coordinates. Most of the time, we want to do the flipping // on the UV coordinates rather than the texture so that procedural // texture coordinates generated inside the shader work as well // (and if we just flip the texture to compensate for Maya's UV // coordinate system, these will get inverted) image.load(path.asChar(), !invertTexCoords() ); 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)) { // If we're using left handed texture coordinates, flip it upside down // (to undo the automatic flip it receives being read in by Maya) if( invertTexCoords() ) { img.verticalFlip(); } MStatus status = img.getSize( width, height); if (width > 0 && height > 0 && (status != MStatus::kFailure) ) { // If not power of two and NPOT is not supported, then we need // to resize the original system pixmap before binding. // if (width > 2 && height > 2) { unsigned int p2Width, p2Height; bool widthPowerOfTwo = textureInitPowerOfTwo(width, p2Width); bool heightPowerOfTwo = textureInitPowerOfTwo(height, p2Height); if(!widthPowerOfTwo || !heightPowerOfTwo) { width = p2Width; height = p2Height; img.resize( p2Width, p2Height, false /* preserverAspectRatio */); } } 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); } } 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: { int ival = timeGetTime() & 0xffffff; float val = (float)ival * 0.001f; cgSetParameter1f(aDef->fParameterHandle, val); break; } #endif case cgfxAttrDef::kAttrTypeOther: case cgfxAttrDef::kAttrTypeUnknown: break; case cgfxAttrDef::kAttrTypeObjectDir: case cgfxAttrDef::kAttrTypeViewDir: case cgfxAttrDef::kAttrTypeProjectionDir: case cgfxAttrDef::kAttrTypeScreenDir: case cgfxAttrDef::kAttrTypeObjectPos: case cgfxAttrDef::kAttrTypeViewPos: case cgfxAttrDef::kAttrTypeProjectionPos: case cgfxAttrDef::kAttrTypeScreenPos: case cgfxAttrDef::kAttrTypeWorldMatrix: 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 += currentPath().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::kAttrTypeObjectDir: case cgfxAttrDef::kAttrTypeViewDir: case cgfxAttrDef::kAttrTypeProjectionDir: case cgfxAttrDef::kAttrTypeScreenDir: case cgfxAttrDef::kAttrTypeObjectPos: 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 int base = cgfxAttrDef::kAttrTypeFirstPos; if (aDef->fType <= cgfxAttrDef::kAttrTypeLastDir) base = cgfxAttrDef::kAttrTypeFirstDir; if (base == cgfxAttrDef::kAttrTypeFirstPos) { MPoint point(tmp[0], tmp[1], tmp[2], tmp[3]); point *= wMatrix.inverse() * 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 *= wMatrix.inverse() * mat; tmp[0] = (float)vec.x; tmp[1] = (float)vec.y; tmp[2] = (float)vec.z; tmp[3] = 1.F; } cgSetParameterValuefc(aDef->fParameterHandle, aDef->fSize, tmp); break; } case cgfxAttrDef::kAttrTypeWorldMatrix: 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) { if( fNewEffect.fTechnique && fNewEffect.fTechnique->fPasses) { // If we've batched a single pass shader, reset the pass state if( !fNewEffect.fTechnique->fMultiPass) cgResetPassState( fNewEffect.fTechnique->fPasses->fPass); // Shaders have an uncanny ability to corrupt depth state if( fDepthEnableState) glEnable( GL_DEPTH_TEST); else glDisable( GL_DEPTH_TEST); glDepthFunc( fDepthFunc); if (fTechniqueHasBlending) glPopAttrib(); } else { // Restore material attributes glPopAttrib(); } glStateCache::instance().disableAll(); glStateCache::instance().activeTexture( 0); // glPopClientAttrib(); // glPopAttrib(); #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 { if( fNewEffect.fTechnique && fNewEffect.fTechnique->fPasses) { // Set up the uniform attribute values for the effect. bindViewAttrValues(shapePath); // If our input shape is dirty, clear any cached data if( dirtyMask() != kDirtyNone) fNewEffect.flushCache( shapePath); // Now render the passes for this effect cgfxPass* pass = fNewEffect.fTechnique->fPasses; while( pass) { pass->bind( shapePath, vertexCount, vertexArray, fNormalsPerVertex, normalCount, normalArrays, colorCount, colorArrays, texCoordCount, texCoordArrays); glStateCache::instance().flushState(); if( fNewEffect.fTechnique->fMultiPass) cgSetPassState( pass->fPass); glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray); if( fNewEffect.fTechnique->fMultiPass) cgResetPassState( pass->fPass); pass = pass->fNext; } } else // fEffect must be NULL { // Now call glDrawElements to put all the primitives on the // screen. See the comment above re: glDrawRangeElements. // glStateCache::instance().enablePosition(); glVertexPointer(3, GL_FLOAT, 0, vertexArray); if ( normalCount > 0 && normalArrays[ 0 ] ) { glStateCache::instance().enableNormal(); glNormalPointer(GL_FLOAT, 0, normalArrays[0]); } else { glStateCache::instance().disableNormal(); glNormal3f(0.0, 0.0, 1.0); } glStateCache::instance().flushState(); glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray); } checkGlErrors("After effects End"); } 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::getTexCoordSetNames( MStringArray& names ) { names = fUVSets; return names.length(); } // cgfxShaderNode::getTexCoordSetNames #if MAYA_API_VERSION >= 700 /* virtual */ int cgfxShaderNode::getColorSetNames( MStringArray& names ) { names = fColorSets; return names.length(); } #else /* virtual */ int cgfxShaderNode::colorsPerVertex() { fColorType.setLength(1); fColorIndex.setLength(1); fColorType[0] = 0; fColorIndex[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); } } // // Set the current per-vertex attributes the shader needs (replacing any existing set) // void cgfxShaderNode::setVertexAttributes( cgfxVertexAttribute* attributeList) { // Backward compatibility: if we have values set in the old texCoordSources // or colorSources, find any varying attributes that use that register // and inherit the maya source if( fTexCoordSource.length()) { int length = fTexCoordSource.length(); for( int i = 0; i < length; i++) { MString semantic( "TEXCOORD"); if( i) semantic += i; else semantic += "0"; MString source( fTexCoordSource[ i]); if( source.index( ':') < 0) source = "uv:" + source; cgfxVertexAttribute* newAttribute = attributeList; while( newAttribute) { if( newAttribute->fSemantic == semantic || (i == 6 && (newAttribute->fSemantic == "TANGENT" || newAttribute->fSemantic == "TANGENT0")) || (i == 7 && (newAttribute->fSemantic == "BINORMAL" || newAttribute->fSemantic == "BINORMAL0"))) newAttribute->fSourceName = source; newAttribute = newAttribute->fNext; } } fTexCoordSource.clear(); } if( fColorSource.length()) { int length = fColorSource.length(); for( int i = 0; i < length; i++) { MString semantic( "COLOR"); if( i) semantic += i; else semantic += "0"; MString source( fColorSource[ i]); if( source.index( ':') < 0) source = "color:" + source; cgfxVertexAttribute* newAttribute = attributeList; while( newAttribute) { if( newAttribute->fSemantic == semantic) newAttribute->fSourceName = source; newAttribute = newAttribute->fNext; } } fColorSource.clear(); } // If we have an existing attribute list, copy across the current values // the remove it cgfxVertexAttribute* oldAttributes = fVertexAttributes; while( oldAttributes) { cgfxVertexAttribute* oldAttribute = oldAttributes; oldAttributes = oldAttributes->fNext; cgfxVertexAttribute* newAttribute = attributeList; while( newAttribute) { if( newAttribute->fName == oldAttribute->fName) { newAttribute->fSourceName = oldAttribute->fSourceName; newAttribute->fSourceType = oldAttribute->fSourceType; } newAttribute = newAttribute->fNext; } delete oldAttribute; } // Now set our new attribute list fVertexAttributes = attributeList; // And determine the minimum set of data we need to request from Maya to // populate these values analyseVertexAttributes(); } // // Set the data set names that will be populating our vertex attributes // void cgfxShaderNode::setVertexAttributeSource( const MStringArray& sources) { // Flush any cached data our effect has - the inputs have changed fNewEffect.flushCache(); // Set the attributes sources as specified int i = 0; int numSources = sources.length(); cgfxVertexAttribute* attribute = fVertexAttributes; while( attribute) { attribute->fSourceName = ( i < numSources) ? sources[ i++] : ""; attribute = attribute->fNext; } // And determine the minimum set of data we need to request from Maya to // populate these values analyseVertexAttributes(); } inline int findOrInsert( const MString& value, MStringArray& list) { int length = list.length(); for( int i = 0; i < length; i++) if( list[ i] == value) return i; list.append( value); return length; } // // Analyse the per-vertex attributes to work out the minimum set of data we require // void cgfxShaderNode::analyseVertexAttributes() { fUVSets.clear(); fColorSets.clear(); fNormalsPerVertex = 0; cgfxVertexAttribute* attribute = fVertexAttributes; while( attribute) { // Work out where this attribute should come from MString source( attribute->fSourceName); source.toLowerCase(); if( attribute->fSourceName.length() == 0) { attribute->fSourceType = cgfxVertexAttribute::kNone; } else if( source == "position") { attribute->fSourceType = cgfxVertexAttribute::kPosition; } else if( source == "normal") { attribute->fSourceType = cgfxVertexAttribute::kNormal; if( fNormalsPerVertex < 1) fNormalsPerVertex = 1; } else { // Try and pull off the type MString set = attribute->fSourceName; int colon = set.index( ':'); MString type; if( colon >= 0) { if( colon > 0) type = source.substring( 0, colon - 1); set = set.substring( colon + 1, set.length() - 1); } // Now, work out what kind of set we have here if( type == "uv") { attribute->fSourceType = cgfxVertexAttribute::kUV; attribute->fSourceIndex = findOrInsert( set, fUVSets); } else if( type == "tangent") { attribute->fSourceType = cgfxVertexAttribute::kTangent; if( fNormalsPerVertex < 2) fNormalsPerVertex = 2; attribute->fSourceIndex = findOrInsert( set, fUVSets); } else if( type == "binormal") { attribute->fSourceType = cgfxVertexAttribute::kBinormal; if( fNormalsPerVertex < 3) fNormalsPerVertex = 3; attribute->fSourceIndex = findOrInsert( set, fUVSets); } else if( type == "color") { attribute->fSourceType = cgfxVertexAttribute::kColor; attribute->fSourceIndex = findOrInsert( set, fColorSets); } else { attribute->fSourceType = cgfxVertexAttribute::kUnknown; } } attribute = attribute->fNext; } //for( unsigned int i = 0; i < fUVSets.length(); i++) printf( "Requesting UVset[%d] = %s\n", i, fUVSets[i]); } // 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) { if( texCoordSources ) { int length_TC = texCoordSources->length(); if ( length_TC > CGFXSHADERNODE_GL_TEXTURE_MAX ) length_TC = CGFXSHADERNODE_GL_TEXTURE_MAX; fTexCoordSource.clear(); for ( int i = 0; i < length_TC; ++i ) { fTexCoordSource.append( (*texCoordSources)[ i ] ); } // This method is unstable and may causes crashes in the API // Don't use for now. //fTexCoordSource.setLength( length_TC ); //for ( int i = 0; i < length_TC; ++i ) // fTexCoordSource[ i ] = texCoordSources[ i ]; } if( colorSources ) { int length_CS = colorSources->length(); 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, fTexCoordType, fTexCoordIndex); updateDataSource( fColorSource, fColorType, fColorIndex); } void cgfxShaderNode::updateDataSource( MStringArray& v, MIntArray& typeList, 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(); typeList.setLength( nDataSets ); indexList.setLength( nDataSets ); for ( int iDataSet = 0; iDataSet < nDataSets; ++iDataSet ) { // iDataSet loop MString s; int iType = etcNull; int iBuf = 0; // 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 ) iType = 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"; iType = etcConstant; } } // UV set name or reserved word. else { s.set( bp, (int)(ep - bp) ); // Pull out any qualifiers (e.g. tangent:uvSet1) and register // the data set they require // MStringArray splitStrings; #define kDefaultUVSet "map1" if ((MStatus::kSuccess == s.split( ':', splitStrings)) && splitStrings.length() > 1) { s = splitStrings[0]; iBuf = findOrAppend( fDataSetNames, splitStrings[1]); } // Force reserved words to lower case. bp = s.asChar(); if ( 0 == stricmp( "normal", bp ) ) { s = "normal"; iType = etcNormal; } else if ( 0 == stricmp( "tangent", bp ) ) { s = "tangent"; if( splitStrings.length() < 2) { splitStrings.setLength( 2); splitStrings[ 1] = kDefaultUVSet; iBuf = findOrAppend( fDataSetNames, kDefaultUVSet); } s += ":" + splitStrings[1]; iType = etcTangent; if( fNormalsPerVertex < 2) fNormalsPerVertex = 2; } else if ( 0 == stricmp( "binormal", bp ) ) { s = "binormal"; if( splitStrings.length() < 2) { splitStrings.setLength( 2); splitStrings[ 1] = kDefaultUVSet; iBuf = findOrAppend( fDataSetNames, kDefaultUVSet); } s += ":" + splitStrings[1]; iType = etcBinormal; fNormalsPerVertex = 3; } // Data set name... tell Maya that we want to retrieve this data set. else { iType = etcDataSet; iBuf = findOrAppend( fDataSetNames, s ); } } // Tell our geometry() method where to get data. typeList[ iDataSet ] = iType; 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; fNewEffect.setEffect( fEffect); // fNewEffect.setupAttributes( this); this will happen when the technique gets set! // 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 /* virtual */ bool cgfxShaderNode::hasTransparency() { // Always return false, so that transparencyOptions() will be // called to give finer grain control. return false; } /* virtual */ unsigned int cgfxShaderNode::transparencyOptions() { if (fTechniqueHasBlending) { // Set as transparent, but we don't want any internal transparency algorithms // being used. return ( kIsTransparent | kNoTransparencyFrontBackCull | kNoTransparencyPolygonSort ); } return 0; } // // Scan the technique for passes which use blending // void cgfxShaderNode::setTechniqueHasBlending( CGtechnique technique) { // Assume not blending fTechniqueHasBlending = false; // Check for : BlendEnable=true, BlendFunc=something valid on the first pass only. // // We ignore any depth enable and functions for now... // CGpass cgPass = cgGetFirstPass(technique); bool foundBlendEnabled = false; bool foundBlendFunc = false; if (cgPass) { CGstateassignment stateAssignment = cgGetFirstStateAssignment(cgPass); while ( stateAssignment ) { CGstate state = cgGetStateAssignmentState( stateAssignment); const char *stateName = cgGetStateName(state); // Check for blend enabled. if (!foundBlendEnabled && stricmp( stateName, "BlendEnable") == 0) { int numValues = 0; const CGbool *values = cgGetBoolStateAssignmentValues(stateAssignment, &numValues); if (values && numValues) { if (values[0]) { foundBlendEnabled = true; } } } // Check for valid blend function else if (!foundBlendFunc && stricmp( stateName, "BlendFunc") == 0) { int numValues = 0; const int * values = cgGetIntStateAssignmentValues(stateAssignment, &numValues); if (values && numValues==2) { #if defined(CGFX_DEBUG_BLEND_FUNCTIONS) /* #define GL_SRC_COLOR 0x0300 = 768 #define GL_ONE_MINUS_SRC_COLOR 0x0301 = 769 #define GL_SRC_ALPHA 0x0302 = 770 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 = 771 #define GL_DST_ALPHA 0x0304 = 772 #define GL_ONE_MINUS_DST_ALPHA 0x0305 = 773 */ MString blendStringTable[6] = { "GL_SRC_COLOR", // SrcColor "GL_ONE_MINUS_SRC_COLOR", // OneMinusSrcColor "GL_SRC_ALPHA", // SrcAlpha "GL_ONE_MINUS_SRC_ALPHA", // OneMinusSrcAlpha "GL_DST_ALPHA", // DstAlpha "GL_ONE_MINUS_DST_ALPHA" // OneMinusDstAlpha }; #endif if ((values[0] >= GL_SRC_COLOR) && (values[0] <= GL_ONE_MINUS_DST_ALPHA) && (values[1] >= GL_SRC_COLOR) && (values[1] <= GL_ONE_MINUS_DST_ALPHA) ) { #if defined(CGFX_DEBUG_BLEND_FUNCTIONS) printf("Found blend function = %s, %s\n", blendStringTable[ values[0]-GL_SRC_COLOR].asChar(), blendStringTable[ values[1]-GL_SRC_COLOR].asChar()); #endif foundBlendFunc = true; } } } fTechniqueHasBlending = foundBlendEnabled && foundBlendFunc; if (fTechniqueHasBlending) break; stateAssignment = cgGetNextStateAssignment( stateAssignment); } } } void cgfxShaderNode::setTechnique( const MString& techn ) { // If effect not loaded, just store the technique name. if (!fEffect) { fTechnique = techn; fTechniqueHasBlending = false; return; } // Search for requested technique. CGtechnique technique = cgGetNamedTechnique(fEffect, techn.asChar()); if (cgValidateTechnique(technique) == CG_TRUE) { fTechnique = techn; setTechniqueHasBlending( technique ); // Setup the vertex parameters for this technique fNewEffect.setupAttributes( this); return; } // Requested technique was not found or not valid. Revert to the old one. technique = cgGetNamedTechnique(fEffect, fTechnique.asChar()); if (cgValidateTechnique(technique) == CG_TRUE) { setTechniqueHasBlending( technique ); return; } // Old technique is no good. Activate the first valid technique. technique = cgGetFirstTechnique(fEffect); while (technique) { if (cgValidateTechnique(technique) == CG_TRUE) { fTechnique = cgGetTechniqueName(technique); setTechniqueHasBlending( technique ); // Setup the vertex parameters for this technique fNewEffect.setupAttributes( this); 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; fTechniqueHasBlending = false; } // cgfxShaderNode::setTechnique MStatus cgfxShaderNode::shouldSave ( const MPlug & plug, bool & ret ) { if (plug == sAttributeList) { ret = true; return MS::kSuccess; } else if (plug == sVertexAttributeList) { 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)); }