Rendering Full Screen Images from Textures

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Related Articles of Interest:

A Simple Blit Function for DirectX 8
Rendering 3D Scenes to Large Image Formats
Saving a Screen Shot in Direct3D

Since the release of the DirectX 8 SDK, I have received a lot of questions regarding how to perform various 2D operations using DirectX Graphics, which is a 3D oriented API.  One such operation, which we will cover here, is the display of full screen images, such as those used for splash screens or background images.

This task can be somewhat troublesome, because the 4:3 aspect ratio and use of standard screen resolutions for full screen images does not comply with the dimensional requirements of texture surfaces, which must have dimensions that are powers of two.  Also, on some hardware, the maximum texture dimensions may be smaller than the required image size, requiring the image to be divided into multiple textures.

What are the Options?

There are two basic options here:

  1. The images can be loaded into an image surface, using the IDirect3DSurface8 interface, and IDirect3DDevice8::CopyRects() can be used to render the image to the screen.  However, this does not provide optimum performance, and the image cannot be resized during rendering to support different frame buffer dimensions.
     
  2. Resize the image in your media files, then load the image onto a texture surface and render the texture onto a primitive constructed from pre-transformed and lit vertices.  This provides superior performance, but some considerations must be made.

We will now take a look at how to implement the second option above.  This will require some modification to our media files, as well as a modest bit of code.

Preparing the Media

To prepare our media, we will need to resize the image so that its dimensions are powers of two.  To prevent loss of quality, choose dimensions that are larger than the original, corresponding to the smallest power of two that is larger than the original dimensions of the image.  For example, a 640x480 image would be stretched to 1024x768.  Use the best available resampling to stretch the image - bicubic sampling in Adobe Photoshop or comparable will do just fine.

This results in a larger image, but you can compensate for this by using the DirectX Texture Tool included in the SDK to compress the image into a DDS file.  The results will often be significantly smaller than the original (smaller) bitmap file.  For example, a 640x480x24 bit BMP file is 900K in size.  After resizing to 1024x512 and conversion to DXT1 texture format, the image takes up only 256K of drive space.

The Code

Below you will find source code for a class named CSplash.  This is a stripped down version of a class that I use to display splash screens. 

To use the class, first create an instance of CSplash, passing it the path to an image file (any file type the D3DX texture functions can load, such as DDS, BMP, JPG, etc), a pointer to the 3D device, and the dimensions of the frame buffer to be rendered to.  This will load the image file, subdividing it into multiple textures if required by hardware limitations.

To render to the screen, call the draw() member function with a pointer to the 3D device.  The image will be rendered into the frame buffer, from 0,0 to the dimensions specified during instance creation.  This function must be called from within a scene, and lighting (the D3DRS_LIGHTING state) must be turned off.  In addition, to insure the quality of the resized image, set the D3DTSS_MINFILTER and D3DTSS_MAGFILTER states for the first texture stage to D3DTEXF_LINEAR.

When done with the image, simply delete the class instance.

typedef struct _D3DTLVERTEX {
	D3DVALUE sx; /* Screen coordinates */
	D3DVALUE sy;
	D3DVALUE sz;
	D3DVALUE rhw; /* Reciprocal of homogeneous w */
	D3DCOLOR color; /* Vertex color */
	D3DVALUE tu; /* Texture coordinates */
	D3DVALUE tv;
	_D3DTLVERTEX() { }
	_D3DTLVERTEX(const D3DVECTOR& v, float _rhw,
		     D3DCOLOR _color, 
		     float _tu, float _tv)
		{ sx = v.x; sy = v.y; sz = v.z; rhw = _rhw;
		  color = _color; 
		  tu = _tu; tv = _tv;
		}
} D3DTLVERTEX, *LPD3DTLVERTEX;
 
#define SafeRelease(x) if (x) {x->Release(); x=NULL;}
 
class CSplash  
{
public:
	int surfCount;
	BOOL draw(LPDIRECT3DDEVICE8 lpDevice);
	CSplash(LPCSTR bmpName,LPDIRECT3DDEVICE8 lpDevice,int scr_width,int scr_height);
	virtual ~CSplash();
	LPDIRECT3DTEXTURE8 surf[16];
	RECT surfRects[16];
};

Update: This function has been updated from its original form to utilize the D3DXGetImageInfoFromFileInMemory() function introduced in the DirectX 8.1 SDK.  If using the 8.0 SDK, you can find the original source code here.

 
CSplash::CSplash(LPCSTR bmpName,LPDIRECT3DDEVICE8 lpDevice,int scr_width,int scr_height)
{
	// clear the surface count and surface pointer array
	surfCount=0;
	ZeroMemory(surf,sizeof(surf));
	// open image file
	HANDLE file=CreateFile(bmpName,
			   GENERIC_READ,
			   0,
			   NULL,
			   OPEN_EXISTING,
			   FILE_FLAG_SEQUENTIAL_SCAN,
			   NULL);
	if (file==INVALID_HANDLE_VALUE)
		return;
	// get file size and allocate buffer for image
	DWORD imageSize=GetFileSize(file,NULL);
	BYTE *imageBuf=new BYTE[imageSize];
	if (!imageBuf) {
		CloseHandle(file);
		return;
	}
	// read in image data
	DWORD bytesRead;
	ReadFile(file,imageBuf,imageSize,&bytesRead,NULL);
	CloseHandle(file);
	
	// get image information
	D3DXIMAGE_INFO info;
	if (FAILED(D3DXGetImageInfoFromFileInMemory(imageBuf,bytesRead,&info))) {
		delete imageBuf;
		return;
	}
 
	// can it be fit in a single texture?
	if (info.Width<=g_devCaps.MaxTextureWidth&&info.Height<=g_devCaps.MaxTextureHeight) {
 
		// yes, load it
		if (FAILED(D3DXCreateTextureFromFileInMemoryEx(lpDevice, imageBuf,bytesRead ,
								 D3DX_DEFAULT,D3DX_DEFAULT,1,0,
								 D3DFMT_UNKNOWN,D3DPOOL_MANAGED,
								 D3DX_FILTER_NONE,D3DX_FILTER_NONE,
								 0,&info,NULL,&surf[0]))) {
				delete imageBuf;
				return;
		}
		surfCount=1;
		delete imageBuf;
		surfRects[0].left=0;
		surfRects[0].right=scr_width;
		surfRects[0].top=0;
		surfRects[0].bottom=scr_height;
		return;
	}
	// get the texture format
	D3DSURFACE_DESC surfDesc;
	surf[0]->GetLevelDesc(0,&surfDesc);
	// release the texture
	surf[0]->Release();

	// create a surface to hold the entire file
	LPDIRECT3DSURFACE8 tempSurf;
	if (FAILED(lpDevice->CreateImageSurface(surfDesc.Width,surfDesc.Height,
						surfDesc.Format,&tempSurf))) {
		delete imageBuf;
		return;
	}
	// load the image into the surface
	D3DXLoadSurfaceFromFileInMemory(tempSurf,NULL,NULL,imageBuf,bytesRead,
					NULL,D3DX_FILTER_NONE,0,NULL);

	// de-allocate the image buffer
	delete imageBuf;

	// determine number of textures needed on each axis
	int numX=surfDesc.Width/g_devCaps.MaxTextureWidth;
	int numY=surfDesc.Height/g_devCaps.MaxTextureHeight;
	// loop through the rows
	for (int i=0;i<numY;i++) {

		// loop through the columns
		for (int j=0;j<numX;j++) {

			// create the texture
			if (FAILED(D3DXCreateTexture(lpDevice,
							  g_devCaps.MaxTextureWidth,
							  g_devCaps.MaxTextureHeight,
							  1,
							  0,
							  surfDesc.Format,
							  D3DPOOL_MANAGED,
							  &surf[surfCount]))) {
				tempSurf->Release();
				return;
			}
			// generate the screen target rectangle
			surfRects[surfCount].left=j*scr_width/numX;
			surfRects[surfCount].right=(j+1)*scr_width/numX-1;
			surfRects[surfCount].top=i*scr_height/numY;
			surfRects[surfCount].bottom=(i+1)*scr_height/numY-1;

			// generate the image source rectangle
			RECT src;
			src.left=j*surfDesc.Width/numX;
			src.right=(j+1)*surfDesc.Width/numX;
			src.top=i*surfDesc.Height/numY;
			src.bottom=(i+1)*surfDesc.Height/numY;
			// get texture surface
			LPDIRECT3DSURFACE8 targSurf;
			surf[surfCount]->GetSurfaceLevel(0,&targSurf);
			// copy region to texture surface
			D3DXLoadSurfaceFromSurface(targSurf,NULL,NULL,tempSurf,NULL,&src,D3DX_FILTER_NONE,0);

			// release texture surface
			targSurf->Release();

			// increment the surface counter
			surfCount++;
		}
	}

	// release the temp surface
	tempSurf->Release();
}

CSplash::~CSplash()
{
	// clear the image surfaces
	for (int i=0;i<surfCount;i++)
		SafeRelease(surf[i]);
}

BOOL CSplash::draw(LPDIRECT3DDEVICE8 lpDevice)
{
	D3DTLVERTEX rect[4];
 
	// loop through the surfaces
	for (int i=0;i<surfCount;i++) {
		// set the texture
		SetCurrentTexture(surf[i]);
	
		// set the target rectangle
		float l=surfRects[i].left-0.5f;
		float r=surfRects[i].right+0.5f;
		float t=surfRects[i].top-0.5f;
		float b=surfRects[i].bottom+0.5f;
		rect[0]=D3DTLVERTEX(D3DXVECTOR3(l,b,0.1f),1.0f,col,0.0f,1.0f);
		rect[1]=D3DTLVERTEX(D3DXVECTOR3(l,t,0.1f),1.0f,col,0.0f,0.0f);
		rect[2]=D3DTLVERTEX(D3DXVECTOR3(r,b,0.1f),1.0f,col,1.0f,1.0f);
		rect[3]=D3DTLVERTEX(D3DXVECTOR3(r,t,0.1f),1.0f,col,1.0f,0.0f);
		// draw the rectangle
		lpDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, rect, sizeof(D3DTLVERTEX));
	}
}

Related Articles of Interest:

A Simple Blit Function for DirectX 8
Using a Blit Queue
Saving a Screen Shot in Direct3D

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.