Material optimization

From Valve Developer Community
Revision as of 10:04, 16 September 2008 by TomEdwards (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

When working with materials, there are a couple performance bottlenecks to keep in mind: fillrate (which usually affects all DirectX levels), and reducing vertex processing (which only is a bottleneck on DirectX7). As a level designer, the only real choice that you've got to combat any of these performance bottlenecks is to either reduce the amount of a particular expensive material on screen, or to turn off some of the expensive features in the material (like bumpmapping, for example). Note that you can make different choices about which features to use at each separate DirectX support level.

One other important aspect of dealing with performance bottlenecks is that you're going to have to measure framerates to see how well you're doing. For Half-Life 2, we used 3 target machines (determined based on looking at our hardware survey stats) for measuring performance at various DirectX levels. These were:

DX (DirectX) Level Minimum Platform
DX9 ATI 9800, 1 GB, P4 3.0 GHz
DX8 NVidia GeForce4 Ti4600, 512 MB, P4 2.0 GHz
DX7 NVidia GeForce2 MX, 256MB, P4 1.2 GHz

Here's how you specify different material parameters for different DX support levels: Each shader has a specific fallback shader it uses if the shader determines it can't run on the particular DX level on the card currently being run. Within the definition of your material, you add a field with the same name as the fallback shader and a sub-block of fields which are the values of those fields that you want to use if that fallback shader is being used. A list of the most commonly used shaders and their fallback shaders is shown below.

Here's an example:

	"$basetexture" "Metal/metalwall063a"
	"$surfaceprop" "metal"
	"$envmap" "env_cubemap"

		"$bumpmap" "metal/metalwall063a_normal"
		"$normalmapalphaenvmapmask" 1

		"$basetexture" "metal/citadel_metalwall063a"
		"$basealphaenvmapmask" 1

		"$fallbackmaterial" "metal/metalwall063b"

In this example, the $envmap field is used no matter what DirectX level the game is being run at. The $basetexture field metal/metalwall063a is used for every fallback shader except LightmappedGeneric_dx8 (used for running under DX8), which uses metal/citadel_metalwall063a for its $basetexture. Also under DX8 only, we add a field specifying that the special base texture used by dx8 has an envmap mask in its alpha channel. When using the LightmappedGeneric_DX9 shader (used by DX9), we add a bumpmap and specify that the envmap mask is in the alpha channel of the bumpmap. Note that it's also possible to cause the material system to use a completely different material with a totally different set of shaders by specifying a $fallbackmaterial in a fallback block, which is done in this example when falling back to the LightmappedGeneric_DX6 shader (used by DX7 and DX6).

For a listing of all of the fallback shaders used in Half-Life 2, see the document Half-Life 2 Shader Fallbacks.


There are a couple of tricks to use to get performance back on levels that have water in them. Water is a pretty expensive shader, and you oftentimes can be fillrate bound when you use it. This can often be seen by typing +showbudget at the developer console: if the Swap Buffers bar is very high, you're likely running into a fill rate problem. To reduce this problem, make sure to cut the water brushes to eliminate parts that lie within other brushes or displacements.

Another performance problem is that any object that is partially inside and partially outside of the water is rendered twice: once for the refraction, and once for the part of the scene that lies on the same side of the water plane as the player. You can see which objects are being rendered into the refraction by typing mat_showwatertextures 1 in the developer console. Also typing mat_wireframe 1 in the console can help to highlight the problem areas. Try moving any prop_static so they don't touch the water, and cut your water brushes so as few as possible models actually cross the water plane.

Dealing with fillrate issues

The materials which are most expensive from a fillrate perspective are the water and refract shaders, and any material which uses bumpmaps. The way you can tell you've got a fillrate problem if you see a big bar for swapbuffers when using +showbudget. We'll talk here about solutions for a couple of the most common offenders for fillrate: refraction, water, and bumpmapped shaders.

For refractive shaders, the only real thing you can do is to use the $fallbackmaterial to make the shader not be refractive at the DX level where it's causing you fillrate issues. For water shaders, the fillrate cost of these shaders goes up when using refraction and reflection. You can turn both off by specifying $forcecheap 1 in the .VMT. You can also disable refraction by not specifying a $refracttexture in the .VMT, and you can disable reflection by not specifying a $reflecttexture. If you've got fillrate and CPU to burn, you can forcibly make the water do local reflections (regardless of video config settings) by specifying $forceexpensive 1, and you can forcibly make the water reflect entities by specifying $reflectentities 1.

For bumpmapping, usually the shader that's the fillrate offender is LightmappedGeneric. There are a couple things you can do to improve performance. Specifying $nodiffusebumplighting 1 turns off diffuse bumpmapping, but specular bumpmapping still is active. Turning off specular bumpmapping will also improve performance, although there is a pretty big visual quality hit you take for turning it off. One feature we added to LightmappedGeneric to help turn off bumpmapping on those DX8 cards that just weren't up to snuff was by introducing a fallback called LightmappedGeneric_NoBump_dx8. This fallback is automatically used on DX8 machines which are specified as using mat_reducefillrate 1 in the dxsupport.cfg file. In some materials used by the Citadel in Half-Life 2, we use this to allow us to specify the use of bumpmaps under dx9 and the normal dx8 path, but not under the low-fillrate DX8 path.

Low-end cards (DX7) also get both hosed from a fill-rate perspective and also from a vertex processing perspective by using unbumped specularity. As a result, by default, specularity is turned off on DX7 cards. To reactivate specularity for these materials under DX7, add $multipass 1 to the .VMT. The LightmappedGeneric_dx6, VertexLitGeneric_dx6, VertexLitGeneric_dx7, and UnlitGeneric_dx6 shaders will all respond to this material parameter.

<< Return to Optimization (level design)