Eww, ID3DXSprite. Eww.

posted in DruinkJournal
Published August 07, 2006
Advertisement
So, I'm digging around inside D3D's innards just now so I thought I'd look at how D3DX objects work.

So...

ID3DXSprite


D3DXCreateSprite


When you first create a sprite, with D3DXCreateSprite, it creates a vertex declaration with the following structure:
  • D3DDECLTYPE_FLOAT3 = D3DDECLUSAGE_POSITION
  • D3DDECLTYPE_D3DCOLOR = D3DDECLUSAGE_COLOR
  • D3DDECLTYPE_FLOAT2 = D3DDECLUSAGE_TEXCOORD
  • D3DDECLTYPE_UNUSED

    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:
  • D3DRS_FILLMODE = D3DFILL_SOLID
  • D3DRS_SHADEMODE = D3DSHADE_GOURAUD
  • D3DRS_CULLMODE = D3DCULL_NONE
  • D3DRS_WRAP0 = 0
  • D3DRS_CLIPPING = TRUE
  • D3DRS_VERTEXBLEND = D3DVBF_DISABLE
  • D3DRS_CLIPPLANEENABLE = 0
  • SetNPatchMode(0)
  • D3DRS_INDEXEDVERTEXBLENDENABLE = FALSE
  • D3DRS_ENABLEADAPTIVETESSELLATION = FALSE
  • D3DRS_SRGBWRITEENABLE = 0
  • D3DRS_COLORWRITEENABLE = 0x0F
  • D3DRS_FOGENABLE = FALSE
  • D3DRS_RANGEFOGENABLE = FALSE
  • D3DRS_SPECULARENABLE = FALSE
  • D3DRS_STENCILENABLE = FALSE
  • D3DRS_LIGHTING = FALSE
  • D3DRS_DIFFUSEMATERIALSOURCE = D3DMCS_COLOR1
    After that, it sets the following texture stage states:
  • Stage 0: D3DTSS_COLOROP = D3DTOP_MODULATE
  • Stage 0: D3DTSS_COLORARG1 = D3DTA_TEXTURE
  • Stage 0: D3DTSS_COLORARG2 = D3DTA_DIFFUSE
  • Stage 0: D3DTSS_ALPHAOP = D3DTOP_MODULATE
  • Stage 0: D3DTSS_ALPHAARG1 = D3DTA_TEXTURE
  • Stage 0: D3DTSS_ALPHAARG2 = D3DTA_DIFFUSE
  • Stage 0: D3DTSS_TEXCOORDINDEX = 0
  • Stage 0: D3DTSS_TEXTURETRANSFORMFLAGS = D3DTTFF_DISABLE
  • Stage 1: D3DTSS_COLOROP = D3DTOP_DISABLE
  • Stage 1: D3DTSS_ALPHAOP = D3DTOP_DISABLE
    Then the following sampler states:
  • Sampler 0: D3DSAMP_ADDRESSU = D3DTADDRESS_CLAMP
  • Sampler 0: D3DSAMP_ADDRESSV = D3DTADDRESS_CLAMP
  • Sampler 0: D3DSAMP_MAGFILTER = D3DTEXF_ANISOTROPIC
  • Sampler 0: D3DSAMP_MINFILTER = D3DTEXF_ANISOTROPIC
  • Sampler 0: D3DSAMP_MIPFILTER = D3DTEXF_LINEAR
  • Sampler 0: D3DSAMP_MIPMAPLODBIAS = 0
  • Sampler 0: D3DSAMP_MAXMIPLEVEL = 0
  • Sampler 0: D3DSAMP_MAXANISOTROPY = 16
  • Sampler 0: D3DSAMP_SRGBTEXTURE = 0
    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:
  • D3DRS_ALPHATESTENABLE = TRUE
  • D3DRS_ALPHAREF = 0
  • D3DRS_ALPHAFUNC = D3DCMP_GREATER
  • D3DRS_ALPHABLENDENABLE = TRUE
  • D3DRS_SEPARATEALPHABLENDENABLE = FALSE
  • D3DRS_SRCBLEND = D3DBLEND_SRCALPHA
  • D3DRS_DESTBLEND = D3DBLEND_INVSRCALPHA
  • D3DRS_BLENDOP = D3DBLENDOP_ADD
    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.
  • Previous Entry Untitled
    Next Entry Bugger
    0 likes 5 comments

    Comments

    Mushu
    This is what happens when you drink on the job.
    August 07, 2006 11:16 PM
    jollyjeffers
    Interesting write up - I like it [smile]

    I was digging around in PIX last night and I noticed a huge number of state-blocks being captured/applied/released. I was using the Effects framework though - I didn't check thoroughly, but I think it corresponded with each Begin()/BeginPass() block. Was still quite a lot of "stuff" going on [oh]

    I found it fairly easy to create a stupidly fast text renderer - it took quite a bit of time to get correct, but it wasn't challenging to write. It runs circles around D3DX's method - I can render an entire screen of 10pt text at over a 1000fps [grin]

    Jack
    August 08, 2006 05:02 AM
    Evil Steve
    Quote:Original post by jollyjeffers
    Interesting write up - I like it [smile]

    I was digging around in PIX last night and I noticed a huge number of state-blocks being captured/applied/released. I was using the Effects framework though - I didn't check thoroughly, but I think it corresponded with each Begin()/BeginPass() block. Was still quite a lot of "stuff" going on [oh]

    I found it fairly easy to create a stupidly fast text renderer - it took quite a bit of time to get correct, but it wasn't challenging to write. It runs circles around D3DX's method - I can render an entire screen of 10pt text at over a 1000fps [grin]
    Yeah, far too much "stuff" for my liking [smile]

    I didn't notice your post when I wrote the latest journal entry, I'll be writing a TTF renderer of my own over the next week I expect. And it'll use my texture manager's texture sheets directly, no copying textures into it and stuff. And it'll support unicode. And other stuff. Yeah. Stuff...
    August 08, 2006 07:57 AM
    GameEngineer_gi
    Evil Steve,
    Great write up. Very appreciated. I've been using ID3DXSprite for some time now and never really gave it a second thought until I asked one of the instructors at the Game Institute a question about it. I'm going to piece together my own sprite class and do some benchmarking with ID3DXSprite. Seems they create new buffers on every frame, seems wasteful to me.

    Steve
    March 30, 2008 05:02 PM
    Tom
    This is one of the most useful and informative write-ups I've seen. Here I thought D3DSprite was optimized beyond anything I could hope to write myself, and now I find it's hideously slow compared to even the most basic sprite renderer. That's what I get for having a relatively low sense of self-worth. I'll be cranking out another sprite renderer in the following days. Thanks for the heads-up.

    I may also be poking Jack for details on how he made his text renderer...
    March 17, 2009 01:20 PM
    You must log in to join the conversation.
    Don't have a GameDev.net account? Sign up!
    Advertisement
    Advertisement