Monday, March 15, 2010

A Generic Way to Render on GDI

In this post I'll introduce a generic way to do a DirectX rendering on Windows GDI.


In the previous post, I did an experiment with the texture created by calling the D3DXCreateTextureFromFile function from a file then obtain the level 0 surface of the texture and then get the DC of the surface, and finally, render this content in the DC to a screen DC. This is working because D3DXCreateTextureFromFile uses D3DPOOL_MANAGED by default, so it is possible to get the DC from its surface.


The key idea is then to create then render on a surface from which we can get its DC.


Notice it is not alway possible to get a DC from a surface, as specified by the DirectX document, the following restrictions apply when calling the GetDC method of a surface:


# IDirect3DSurface9::GetDC is valid on the following formats only: D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, D3DFMT_R8G8B8, and D3DFMT_X8R8G8B8. Formats that contain Alpha are not supported because the GDI implementations don't have a well-defined behavior on the alpha channel. For more information about formats, see D3DFORMAT.

# Only one device context per surface can be returned at a time.

# IDirect3DSurface9::GetDC will fail if the surface is already locked. If the surface is a member of a mipmap or cubemap, IDirect3DSurface9::GetDC fails if any other mipmap or cubemap member is locked.

# IDirect3DSurface9::GetDC fails on render targets unless they were created lockable (or, in the case of back buffers, with the D3DPRESENTFLAG_LOCKABLE_BACKBUFFER flag).

# For surfaces not created with IDirect3DDevice9::CreateOffscreenPlainSurface, IDirect3DSurface9::GetDC will fail on default pool (D3DPOOL_DEFAULT) surfaces unless they are dynamic (D3DUSAGE_DYNAMIC) or are lockable render targets.

# IDirect3DSurface9::GetDC will fail on D3DPOOL_SCRATCH surfaces.

# IDirect3DSurface9::GetDC causes an implicit lock; do not retain the device context for later use. Call IDirect3DSurface9::ReleaseDC to release it.
It is valid to call IDirect3DSurface9::GetDC/IDirect3DSurface9::ReleaseDC on levels of a mipmap or cubemap, however, these calls will be slow to all miplevels except the topmost level, and GDI operations to these miplevels will not be accelerated.


As a result, GetDC of a default BackBuffer will usually fail as the default BackBuffer uses D3DPOOL_DEFAULT. So I need to create a lockable surface with CreateRenderTarget method:


HRESULT CreateRenderTarget(
  UINT Width,
  UINT Height,
  D3DFORMAT Format,
  D3DMULTISAMPLE_TYPE MultiSample,
  DWORD MultisampleQuality,
  BOOL Lockable,
  IDirect3DSurface9** ppSurface,
  HANDLE* pSharedHandle
);

and specify Locable as true. In order to fill the first five parameters, I can get the default BackBuffer's description and use the same information for the new surface.


The following steps are straightforward, when doing the render, first set the render target as the new surface, then render. Finally, instead of calling Present, get the DC of the surface then use GDI to put the contents on the screen.


Till now, I used a surface without an alpha channel, if you want to use an alpha channel in the surface and you are on a Windows XP as me, be sure to get the SP3 which contains an important fix KB 937106 that allows GetDC from a surface with an alpha channel.

No comments: