Sunday, March 7, 2010

A Simple Example for Rending Texture with GDI

In this example, I'll demonstrate how to render a texture with GDI. You may wonder why I need to do that anyway as texture is generally used to render shapes in DirectX. The idea here is if I use the layered window with a transparent key color, the color in the texture, when finally rendered using GDI, will be transparent so I see everything behind my program window. Using only DirectX will not produce the same effect.

Let's start with a quick introduction to the layered window. Layed window is created by calling CreateWindowEx with label WS_EX_LAYERED. After the window is created, I can use either SetLayeredWindowAttributes or UpdateLayeredWindow to actually show the window. By calling SetLayeredWindowAttributes, I need to process the rending in the WM_PAINT message. As I'm going to use GDI mixed with DirectX, I want to have total control on the rending process, I'll use the UpdateLayeredWindow instead. This function saves me from the WM_PAINT message. I can call it whenever I finished the drawing.


Next I'll do the DirectX initialization, where I'll initialize the divece and especially, I'll create the texture. For simplicity, I create the texture from a bmp file using D3DXCreateTextureFromFile. The bmp file I'm using contains in the part where I want it transparent a special color, e.g. RGB(255, 128, 64). I'll specify the same color as the color key when calling the UpdateLayeredWindow.

After the texture is created, I'll retrieve the level 0 surface associated to it. This is done by simply calling the texture's GetSurfaceLevel method.

As I'm going to render the texture with GDI, I need two DC handles to call the UpdateLayeredWindow, one is the source and the other is the destination. I already have a surface ready to be presented on the screen, I need to get the DC associated with it. This is done by calling the surface's GetDC method. If the call succeeds, the source is ready. Then I still need to prepare the destination. That is simply a call to global GetDC.

There are still some parameters to prepare in order to call UpdateLayeredWindow. The BLENDFUNCTION sturcture contains information about the blend I want to do with the contents to be rendered. I give the following values:
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.AlphaFormat = 0;
blend.SourceConstantAlpha = 255;

I need to specify also the new position, size of the new window. Be careful here, I need to retrieve the size of the bmp file I have loaded as it will be my entire window. What's more, give a size larger than the actual size will not work. Currently, I load the bitmap and get the size information from it, I'm not aware the way to get this information from the texture which is created from the bmp also. So I actually load twice the bmp file.

Next, the location of the layer in the source DC, which is usually (0,0).

Finally, I set the transparent color as the color key and the ULW_COLORKEY as the flag and call the UpdateLayeredWindow.

The whole process is:
  • Create a layered window.
  • Load bmp into texture.
  • Get surface 0 of the texture.
  • Get DC of the surface.
  • Get DC of the destination (Desktop)
  • UpdateLayeredWindow with source DC as the DC of the surface, destination DC as the Desktop.

Something I need to note, with D3DXCreateTextureFromFile, I can always get the DC of the surface 0 associated. But with a texture created by CreateTexture, I can fail sometimes when get the DC. This is due to the usage and memory pool used by the methods. D3DXCreateTextureFromFile always uses D3DPOOL_MANAGED. (Cited from DirectX Doc for this method: "Note that a resource created with this function will be placed in the memory class denoted by D3DPOOL_MANAGED.) And the GetDC method of the surface has several limitations and especially, it will fail on D3DPOOL_DEFAULT unless the usage is dynamic (D3DUSAGE_DYNAMIC). 

I'll investigate more on this issue. But the idea is clear, once I have surface from which I can get its device context, I'll be able to render it to a layered window. On the other side, I need also be able to render on the surface, obviously. So I'll need a surface that I can render on it and can get DC from it. I need to see if this is actually possible, otherwise, maybe I can render on on surface and put the result to another one from which I can get the DC.

2 comments:

CAI Qian said...

What is a GDI?

icequake said...

Hi,
GDI is really the basis of Windows. As per the MS SDK:
"The Microsoft® Windows® graphics device interface (GDI) enables applications to use graphics and formatted text on both the video display and the printer. Windows-based applications do not access the graphics hardware directly. Instead, GDI interacts with device drivers on behalf of applications."

"GDI can be used in all Windows-based applications."