So...
ID3DXSprite
D3DXCreateSprite
When you first create a sprite, with D3DXCreateSprite, it creates a vertex declaration with the following structure:
ID3DXSprite::Begin(D3DXSPRITE_ALPHABLEND)
Upon calling ID3DXSprite::Begin(), D3DX will allocate an index buffer of 24576 16-bit indices (Enough to render 4096 triangles as a triangle list) using D3DUSAGE_WRITEONLY as the usage, in the default pool.
It then goes on to create a vertex buffer of 393216 bytes, also in the default pool, passing D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC as the usage.
Next up, it calls IDirect3DDevice9::BeginStateBlock(), followed by IDirect3DDevice9::SetVertexDeclaration() to set the declaration to the one it created with the object, and then it sets the following render states and calls SetNPatchMode() too:
After that, it sets the following texture stage states:
Then the following sampler states:
After that, it calls IDirect3DDevice9::SetVertexDeclaration() again, passing the same vertex declaration as it did before (Your guess is as good as mine...), then SetIndices(NULL), SetStreamSource(0, NULL), SetTexture(0, NULL), and then it calls EndStateBlock().
After it's done that, it goes through the whole process again, starting a new state block. Then it starts a THIRD state block, and sets the following states for it:
Then ends the state block, and does the above process again. So both of the state blocks it builds gets created twice. Right...
Finally, it begins another state block, calls SetTransform() to set the world, view and projection matrices to the identity matrix, and ends the block.
Next, it begins another state block (Christ, how many does it need!?) sets the world and view matrices to the identity again, calls GetViewport() and then sets an appropriate projection matrix.
ID3DXSprite::Draw()
In my test I'm getting ID3DXFont to make the draw calls. ID3DXSprite didn't make any calls into the device until End() was called. Presumably it was filling up its internal data structures at this stage.
ID3DXSprite::End()
The moment ID3DXSprite::End() is called, everything flies into action again. ID3DXSprite sets up the device to render states by making calls to SetVertexDeclaration(), SetIndices(), SetStreamSource() and SetTexture().
It then locks the entire vertex buffer (Presumably the index buffer too - I haven't got around to testing that yet) with the D3DLOCK_DISCARD flag, fills it up with data, and then unlocks it.
Immediately after that, a DrawIndexedPrimitive() call comes along, in this case telling the device to draw a triangle list consisting of 396 vertices in 198 primitives.
ID3DXFont
When you create the font, with D3DXCreateFont, nothing is allocated from the device. I presume that all it does is remember the settings you pass to it, and perhaps do some stuff with GDI.
The first time you call DrawText(), even passing the DT_CALCRECT flag to it, ID3DXFont will allocate a 256x256 D3DFMT_A8R8G8B8 managed texture with 1 mip level. ID3DXFont will then proceed to lock the entire texture once for each letter added to the texture. Eww. It doesn't lock just the portion it needs, no. It locks the whole texture. I have no idea why, presumably the D3DX team know what they're doing though.
This texture is filled with glyphs, presumably the maximum size of a glyph is determined via some GDI call. In my case, using a 16 point (Not 16px) font, each glyph is given a 32x32 region of the texture.
Only the alpha channel is used for the text, the other three channels completely ignored.
Misc
I also noticed something interesting in debug mode. When using the debug runtimes, D3D calls QueryInterface() on any vertex buffer you pass to SetStreamSource(). The GUID it requested doesn't seem to exist anywhere (GUID = {DC05CFFC-88B7-4DFB-9CEA-9608D962D831}), and returning E_FAIL instead of D3D_OK from the QueryInterface() call causes the following debug output:
Quote:Direct3D9: (ERROR) :VertexBuffer not created with this Device
Direct3D9: (ERROR) :SetStreamSource failed
D3D9 Helper: IDirect3DDevice9::SetStreamSource failed: D3DERR_INVALIDCALL
This only happens in debug mode. Presumably, D3D uses some crazy COM stuff to check the interface is valid. No idea what it's doing though. In release mode, there's no call to QueryInterface at all. My guess is that the device casts the vertex buffer to some known internaly pointer, so it can get access to the vertices. My VB doesn't have that, so I get an access violation with the release D3D runtimes - Access violation reading location 0xcdcdcdfd. Since I have my own memory manager, I quickly knocked up a test to debug fill memory with another value - malloc() uses 0xcdcdcdcd - and the result was as expected - the memory location changed. I'll look into that tomorrow and try to see where this memory is actually getting dereferenced from.
Conclusion
No wonder I got better performance from writing my own code instead of using ID3DXSprite.
The only interfaces I hooked into were the device, texture and vertex buffer classes. So I can't tell when state blocks are applied or released, or when index buffers are used. Although it should be pretty easy to work it out.
I'd quite like to know why ID3DXSprite does everything twice. There's something very odd in there.
Well, this took a lot longer to get done that I thought, so I'm going to bed.