D3D #23: Texture creating/loading enumeration and

Published June 19, 2006
Advertisement
Right, been cracking on with the Forum FAQ again this evening. I seem to be doing an excellent job of creating more work for myself - for every paragraph I write I make notes to write another 2 [headshake]

Thoughts/opinions/complaints appreciated as usual...

D3D #23: Texture creating/loading enumeration and checking

Textures are a basic resource across all versions of Direct3D and can be used as both inputs and outputs for the pipeline. Despite being a fundamental part of the API they are subject to a number of constraints that an application developer needs to be aware of. For a more general discussion of enumeration and capabilities please refer to D3D #5: Hardware capabilities and enumeration.

This particular FAQ entry focuses on textures, stored as IDirect3DTexture9's, but the information applies equally to volume (IDirect3DVolumeTexture9) and cube (IDirect3DCubeTexture9) forms.

There are two common methods for creating texture
  1. Creating an empty texture and either rendering data to it or filling it procedurally using "Lock" operations.

  2. Using the D3DX functions to create a texture based on existing data (e.g. an image file stored on disk).


Creating regular textures is done via the IDirect3DDevice9::CreateTexture() function, whereas the Texturing Functions reference page contains a list of the D3DX functions.

The dimensions, measured in pixels, are of particular importance. The D3DCAPS9 structure (retrieved via IDirect3DDevice9::GetDeviceCaps()) reveals three maximum values: MaxTextureWidth, MaxTextureHeight and MaxVolumeExtent. You can create texture resources with dimensions between 1 and the appropriate maximum value. For most D3D9 hardware this will be either 2048 or 4096, it is rare to find hardware that supports dimensions greater than 4096.

For optimal performance powers of 2 dimensions (64, 128, 256, 512...) should be used. You should pay particular attention to the D3DCAPS9::TextureCaps flags (see the D3DCAPS9 documentation page for precise details). In particular these flags indicate whether non-2n dimensions are permitted, and if they are whether there are any restrictions. You should still check these, but according to the "Graphics Card Capabilities" spreadsheet no modern hardware restricts dimensions.

The D3DFORMAT enumeration contains a huge number of different resource formats, but most hardware only allows you to use a subset of these. Specifically, not all texture formats can be used for all types of usage. The IDirect3D9::CheckDeviceFormat() function is used to determine which formats are available for which resource types and formats. Depending on the intended usage it is often possible to create a number of "fallback" choices using this function such that a texture will always be created even if it is not necessarily the best/desired choice:

if( FAILED( CheckDeviceFormat( D3DFMT_A8R8G8B8 ) ) ){	if( FAILED( CheckDeviceFormat( D3DFMT_A8B8G8R8 ) ) )	{		if( FAILED( CheckDeviceFormat( D3DFMT_A2R10G10B10 ) ) )		{			// We don't support *any* of the three textures we			// just tested. Either continue trying different ones			// or return an error at this point...		}		else		{			// Third Choice: We can use A2R10G10B10		}	}	else	{		// Second Choice: We can use A8B8G8R8	}}else{	// First Choice: We can use A8R8G8B8}


The above method is particularly important when targetting multiple types of hardware - not all configurations allow the same texture formats, so providing a fallback can be important.

Another important part to check is the intended usage of the texture resource. This is done by passing one (or more) of the D3DUSAGE or D3DUSAGE_QUERY enumerations into the Usage parameter of CheckDeviceFormat(). A common usage that you must check is when creating a render target (a texture that can be used as both an input and output for the pipeline, useful in many types of effect) - D3DUSAGE_RENDERTARGET should be used here.

When doing any High Dynamic Range Rendering (HDRR) you will find that there are 16bit (half) precision and 32bit (single) precision formats available. Two basic features available to almost every other format are often lacking with these texture formats - filtering and blending. Using CheckDeviceFormat() and passing in D3DUSAGE_QUERY_FILTER and/or D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING can determine whether any filtering and frame-buffer blending is supported. For D3D9 generation hardware it is common for the half-precision formats to support filtering/blending but single-precision won't - thus it might be better to choose a half-precision format instead of single-precision.

The core Direct3D runtime has no functionality for creating textures from images stored on disk or in memory, but D3DX has comprehensive support via its Texturing Functions. Familiarity with the D3DX functions is useful and can save you a lot of time! It is worth noting that the "FromFileInMemory" functions can be very useful when paired with a virtual file system; if, for example, you store your files in a compressed/encrypted archive you can load them into memory as appropriate and still have D3DX load/create a texture.

A common confusion with the D3DX functions is that they can change the parameters passed in, or if defaults are requested they might not give the expected results. In particular, if a source image is stored in a non-2n dimension the D3DX functions will round the image up (with filtering) to the nearest 2n dimension. In some cases this can introduce blurry results or unexpected values when manipulating the resource. Specifying D3DX_DEFAULT_NONPOW2 for height and/or width will avoid this characteristic, but be prepared for the call to fail if the device does not allow non-2n dimensions (although, as previously remarked, this is unlikely).

D3DX only supports the image formats listed in the D3DXIMAGE_FILEFORMAT enumeration. This covers the majority of uses, but it is worth noting that not all formats are supported - GIF being one that some people still insist on using. In some cases D3DX will have to convert the incoming data into a pixel format supported by Direct3D - specifying D3DFMT_UNKNOWN will allow D3DX to choose the most appropriate (and fail if none can be determined). Specifying a particular format does not guarantee the returned texture is of that format - if a conversion or support is unavailable an alternative will be selected. Using the D3DFMT_FROM_FILE flag can be useful, but if conversion or support is unavailable the call will fail.

As mentioned in the previous paragraphs, D3DX may well choose different parameters where appropriate - this causes people problems if they assume that data will be loaded in a known format (although remember that assumptions about device capabilities are not sensible!). It may be useful to use the D3DXGetImageInfoFromFile() and/or D3DXCheckTextureRequirements() functions before attempting to load a texture. Several of the D3DX functions will return a D3DXIMAGE_INFO structure that contains the final parameters that the texture was actually created with. Inspecting this output and checking for differences can be useful.
0 likes 6 comments

Comments

jamesw
Very good article, keep it up. You might want to specify what you mean by 'modern hardware'. If someone reads this in 2 years when D3D10 hardware is modern, they might get confused.
June 19, 2006 06:27 PM
Pipo DeClown
I can't imagine how this huge amount of information can fit in a simple Forum FAQ. Have you thought about publishing it? [lol]

Great work!
June 20, 2006 04:02 AM
Evil Steve
All looks good to me. One thing it might be wortwhile mentioning is that you can use GetLevelDesc() on level 0 to find out the size of the texture D3DX actually created, so you can still use non-power-of-2 textures and just adjust the texture coordinates.

E.g.

LPDIRECT3DTEXTURE9 pTexture; // Your texture returned from D3DX
D3DXIMAGE_INFO theInfo;      // Information about the image D3DX loaded

D3DSURFACE_DESC theDesc;
if(FAILED(pTexture->GetLevelDesc(0, &theDesc)))
{
   // Error
}

float tuMax = (float)theInfo.Width / (float)theDesc.Width;
float tvMax = (float)theInfo.Height / (float)theDesc.Height;


Then you can render a quad using (tu, tv) as texture coordinates where you'd normally use (1.0f, 1.0f).

Not sure if it fits in here, but I've seen the question on the DX forum 2 or 3 times in the past few weeks.
June 20, 2006 05:23 AM
jollyjeffers
Thanks for the comments!

Quote:You might want to specify what you mean by 'modern hardware'
A very good point, I shall clarify that immediately!

Quote:I can't imagine how this huge amount of information can fit in a simple Forum FAQ
Have you seen the size of the current FAQ? [oh]

MH did send me the word-format version that I've since lost, but I'm pretty sure it was still very long [smile]

I'm probably around 50% complete and its weighing in at 12,000 words. Some of my GDNet articles have been twice as long... so I'm not worried just yet!

Quote:One thing it might be wortwhile mentioning is that you can use GetLevelDesc() on level 0 to find out the size of the texture D3DX actually created
Good point, I'll see about adding that in. I was going to cover the GetLevelDesc() parts in the context of (rough) memory consumption and mip-map chains, but decided against it in the end.

Cheers,
Jack
June 20, 2006 10:45 AM
jamesw
I think it would also be a good idea to have a section on manually generating textures, like how to use dynamic textures but mainly how to lock a texture and fill it with system memory. That can be tricky because of the pitch variable that is not explained at all in the docs.

Another suggestion that I see you partly already doing is to organize things under headings so that people can find what they're looking for quickly. Most people will just look for a specific topic and not read through the whole thing. For this topic I think the headings should be:

D3D #23: Texture creating/loading enumeration and checking
Introduction
Creating
Limitations
Usage
D3DX support
June 20, 2006 11:21 AM
jollyjeffers
Quote:Original post by jamesw
I think it would also be a good idea to have a section on manually generating textures, like how to use dynamic textures but mainly how to lock a texture and fill it with system memory. That can be tricky because of the pitch variable that is not explained at all in the docs.
Yup, good point - but that one risks verging on being a tutorial on procedural content generation [smile]

I was just working on "D3D #21: How to interpret the data returned by a Lock() operation" which might cover what you're suggesting. It's got various comments on how to encode and decode the data without covering anything you might want to do to it...

Quote:Original post by jamesw
Another suggestion that I see you partly already doing is to organize things under headings so that people can find what they're looking for quickly.
That's a good idea, I hadn't really thought of that in the way you suggested. I think I'll try that out with the entry I'm writing now. Thanks!

Cheers,
Jack
June 20, 2006 12:11 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement