The 3ds Max 8 SDK shader interface required that the shader be loaded and compiled by the material. The parser simply got the compiled object. NVIDIA has a different compile system, so without fundamental changes to the shader interface design, the material would have to include a special case for Cg support - a definite limitation. Moving the loading and compilation of the shaders removes the restriction of the type of shader the system can support. The material simply supplies a filename or buffer to compile.
Beyond the need to introduce Cg support, new shader languages are often introduced with the release of new gaming platforms. The new 3ds Max 9 shader language interface breaks a material's reliance on any specific interface, in this case the LPD3DEffect interface, allowing runtime bindings to take place in the parser DLLs. This approach makes the materials and the 3ds Max-centric aspects of shading independent of specific shader languages with D3D runtime support.
Within the 3ds Max 9 SDK itself, changes were made to define parameter access by name, and not by handle. Plug-in developers can maintain a list of handles internally for use directly with the runtime, but any access to 3ds Max is by name. Even though, in theory, the D3DXHANDLE is a pointer to a string, it can be ambiguous as to whether it is a handle or an actual string - and this could cause problems if the interface assumes a string.
Previously all data that 3ds Max provided was set by 3ds Max in the material. This is now impossible as the parser maintains the link to the runtime, so it needs to set the data itself. The new system now provides all the data by means of theIParameterManager. This is a new interface that provides access to the data requested by the parsers. The following code sample shows how to access data either by name or iteratively. The choice of approach depends on how the parser holds its internal data.
switch(pm->GetParamType(i)) { case IParameterManager::kPType_Float: { float fval; pm->GetParamData((void*)&fval,i); pEffect->SetFloat(pm->GetParamName(i), fval); } break; case IParameterManager::kPType_Color: case IParameterManager::kPType_Point4: { D3DCOLORVALUE cval; pm->GetParamData((void*)&cval,i); pEffect->SetVector(pm->GetParamName(i), (D3DXVECTOR4*)&cval); } break; case IParameterManager::kPType_Bool: { BOOL bval; pm->GetParamData((void*)&bval,i); pEffect->SetBool(pm->GetParamName(i), bval); } break; case IParameterManager::kPType_Int: { int ival; pm->GetParamData((void*)&ival,i); pEffect->SetInt(pm->GetParamName(i), ival); } break; }
As more shader types become available, materials must be able to display these in the File Opendialog. To accommodate this, new methods have been added to the EffectDescriptor. The parser defines supported file extensions in the descriptor since the actual parser has not been loaded until the file has been accessed to determine which parser to load. File extensions are supplied as a localizable strings.
To allow the future support of DirectX10, some methods now use void data types, which are then cast accordingly. For example:
bool MaxEffectParser::LoadTexture(LPVOID pDevice, LPD3DXEFFECT pEffect,TCHAR * paramName, TCHAR * filename, bool forceReload) { LPDIRECT3DDEVICE9 pd3dDevice = static_cast<LPDIRECT3DDEVICE9>(pDevice); }
Plug-in developers can query which version of DirectX is running so they can perform the appropriate cast. This approach kept the API changes smaller, otherwise a copy of the IEffectParser interface would be needed ( IEffectParser10) that had the same methods but with Dx10 counterparts.
The material is responsible for displaying any errors generated, so additional parser methods need to provide access to error buffers.
As noted above, the parser needs to compile and set all the parameters of the shader. All data is transferred around the system by using the void data type. The calling function must make sure that the buffer is large enough for the data to be copied. Currently, 3ds Max does not check this condition, it just fills the buffer.
Here is an example of data defined asLPVOID:
D3DXMATRIXMatInvWorld; D3DXMatrixInverse(&MatInvWorld,NULL,&MatWorld); *(D3DXMATRIX*)data = MatInvWorld;
The internal D3DXMATRIXinternally is used for convenience and is defined as follows:
typedef struct D3DXMATRIX { FLOAT _11, FLOAT _12, FLOAT _13, FLOAT _14, FLOAT _21, FLOAT _22, FLOAT _23, FLOAT _24, FLOAT _31, FLOAT _32, FLOAT _33, FLOAT _34, FLOAT _41, FLOAT _42, FLOAT _43, FLOAT _44 ); } D3DXMATRIX;
This is a fairly standard representation. All loading and parsing for the shaders now takes place in the parser. A typical function that uses the new method of IEffectParser is shown in the following code sample:
bool MaxEffectParser::LoadEffectFile(LPVOID pDevice, IEffectManager * em,TCHAR * fileName, bool forceReload) { int index; HRESULT hr; LPDIRECT3DDEVICE9 pd3dDevice = static_cast<LPDIRECT3DDEVICE9>(pDevice); //Used later when we have a Dx10 version of the parser. bDirectX9 = em->GetDirectXVersion() == IEffectManager::kDirectX9?true:false; index = IDxShaderCache::GetIDxShaderCache()->FindShader(fileName); if(index<0) { D3DXMACRO macros[5 = { { "_3DSMAX_", NULL }, { "_MAX_",NULL }, { "MAX",NULL }, { "3DSMAX",NULL }, { NULL,NULL } }; hr = D3DXCreateEffectFromFile(pd3dDevice,fileName,macros,NULL,NULL,NULL,&m_pEffect,NULL); if(FAILED(hr) || !m_pEffect){ return false ; } IDxShaderCache::GetIDxShaderCache()->SetShader(fileName,(LPCVOID)m_pEffect); } else { m_pEffect = (LPD3DXEFFECT) IDxShaderCache::GetIDxShaderCache()->GetShader(index); } return true; }
Parameter Access is By Name Only
The most the developer will need to do is the following (assuming D3D):
texHandle = pEffect->GetParameterByName(NULL,paramName);
from then on, the internal method can be used as it was before. This is exactly 3ds Max 9 works internally. The only other major code change is to use the IParameterManagerto access all the data held by 3ds Max.
Any D3DXHANDLEs in the interfaces point to strings, not handles.
As the DirectX Shader material is no longer responsible for loading and maintaining the effect, the IDxMaterial3::GetCurrentEffect() method is no longer available. Plug-in developers should use the filename to load the correct effect interface using the appropriate run time.