The best tutorial is to read through the code, however I'll give a few tips here.
You can look at the Winamp SDK to help understand how Winamp communicates with the visualization. If you aren't familiar with pointers to functions this might help clear your mind:
The DLL returns a "mod" structure to Winamp to inform Winamp of the call-back functions, how often to call Render, and whether to provide Spectrum or Oscillascope information.
Winamp will call the Init() method that it finds in the "mod", then start periodically calling the Render() method. When the visualization is killed the Quit() method is called. The Configuration() method is called to Configure your file.
This section is only useful if you want to get a better understanding of how Winamp communicates with the DLL.
I was having some problems with my Plugin at one point - winamp never called my Render method even though I returned success from the Init method. To test my code in depth I whiped up a quick MFC application to act as a calling app instead of Winamp. I won't bother distributing the whole code, basically it was just three buttons: "Load", "Render", "Close".
However it might be informative to look at my code for initializing the winamp visualization, This isn't necessarily the same as what Winamp does. This code only looks at the first mod - you could enumerate all the different mods in a DLL.
// in_pszDLL is the DLL file to load void CCallingAppDlg::LoadPlugin( LPCTSTR in_pszDLL ) { HINSTANCE l_hInstance = LoadLibrary( in_pszDLL ) ; if ( l_hInstance == NULL ) { MessageBox( _T("Failed DLL Load") ) ; return ; } winampVisGetHeaderType l_pProc = ( winampVisGetHeaderType ) GetProcAddress( l_hInstance, ( LPCSTR ) 1 ) ; if ( l_pProc == NULL ) { MessageBox( _T("Couldn't find Exported Function") ) ; return ; } winampVisHeader * l_pHeader = ( *l_pProc )() ; if ( l_pHeader == NULL ) { MessageBox( _T("Couldn't get VisHeader") ) ; return ; } // Get the first Plug in out of the DLL m_pMod = ( *l_pHeader->getModule )( 0 ) ; if ( m_pMod == NULL ) { MessageBox( _T("Couldn't get Module") ) ; return ; } int l_RetVal = (*m_pMod->Init)(m_pMod) ; if ( l_RetVal != 0 ) { MessageBox( _T("Initialization Failed") ) ; return ; } }
I won't try to explain COM in any depth here. Just warning you that its there! The Direct 3D API is defined completely with COM interfaces. That means sooner or later you'd better figure out Addref() and QueryInterface() or else you'll be leaking objects or crashing like crazy. Its really not that hard once you get used to it.
One quick "gotcha" that wasted hours of my time when I was using the first version of the then "Games SDK". The example code was all C and it referenced the COM interfaces using the "vtbl" pointer.
Eg. MyInterace->vtbl->Method() ;
But in C++ you call directly:
MyInterace->Method() ;
Either one or the other is available to you, depending on whether __cplusplus is defined.
Almost all Direct 3D sample code I looked at is in C. I perfer C++, but I also didn't want to fall into the trap of trying to wrap all the Direct 3D API with my own C++ wrappers - you want to learn Direct 3D, right? So I tried to compromise and write a C++ class to take care of the complete functionality of the Plug In, but which talks directly to the Direct 3D interfaces. Once the program gets bigger I'll have to keep my sanity by splitting out functionality into smaller classes, but for the moment everything is really straightforward.
The 3D landscape is a grid of polygons. Each render call slides all the row vertices down and then manipulates the vertices of the last row based on the array of spectrum analysis that Winamp provides.
I just move the landscape around, the camera around and some lights around to create different views of the resulting effect.