Search public documentation:
- Content Profiling and Optimization
- Example Profiling and Optimization Process
- Guided Content Optimization for Gears 3
- Content Optimization Workflow for Gears 2
- General Commandlets
- General Browsers and Tools
- Static Meshes
- Particle Systems
- Audio and Sounds
- Optimizing Content for Rendering
When adding new content, often it will be added with incorrect settings or will be more expensive compared to existing content. Usually these mistakes are just innocent oversight. But with the amount of content games are utilizing these days it is hard to track. You can try to speed up your game by optimizing content and/or optimizing code. From experience, we have found the biggest bang for the buck is almost always optimizing content. Because of this, a fair amount of time is spent creating tools to help content creators quickly find and fix slow use cases.
East Coast Game Conference. Slides for this presentation are available here.
- QA plays through entire game, takes PIX GPU captures whenever GPU time is above 33ms, trying to capture unique scenarios (not 10 captures of the same area)
- Programmers look through GPU captures
- Identify slow but low visual impact cases
- Ideally find enough potential optimizations that if they were all implemented, the scene would be < 33ms
- Use detailed draw event information to quickly identify what each draw call is in the editor
- Recorded Actor name, Resource name (static mesh, particle emitter, etc), Material name
- Provide LD's with suggestions on what to change, with clear directions and screenshots
- LD's and artists decide which optimizations are worthwhile, make content changes
- This ensures that art is responsible for quality decisions
- Repeat steps 1-3 as needed.
Unreal Engine provides many commandlets, or small embedded programs, several of which are very useful to use on a regular basis to ensure a smooth development cycle.
GAMENAME AnalyzeReferencedContent MAPNAMESThis will result in a number of .csv files being create with the contents of those maps. Both on a per map basis and on a global basis. From that you want to look at the data and find outliers, the most expensive objects, objects used the most, and optimize those. Examples
- Looking at the StaticMesh .csv: Look for any static meshes that are used only once or twice. If those meshes are not a hero piece then they can usually be removed from the level / replaced with a more commonly used object with no impact. What happens is that they get placed in the level to add some visual uniqueness, but when you look at the level as a whole they are not needed (especially for the cost)
- Looking at the StaticMesh .csv, find the biggest memory costing objects. How many are used? Can we optimize the object?
- Looking at the summary .csv, we can calculate the total number of "instanced triangles" across the entire game BY multiplying columns NumInstances by NumTriangles into a new column called "InstancedTriangles" for specific StaticMeshes. Sorting on that "InstancedTriangles" column we can see the meshes that are accounting for the most triangles across the entire game. Optimizing them will usually give us the biggest return on the cost of optimizing them. (e.g. A small shrubbery is used ALL over the place in most every level. Optimizing that is going to get a lot more value than a really expensive fence mesh that is used in a few levels only.) (Click for full size)
- Looking at the SoundNodeWaves .csv we can sort by the size of the SoundNodeWaves and see that there is one ambient sound that is 4x as expensive as the others. We want to find out why that is so expensive :-)
- Looking at the SoundNodeWaves .csv we see that there are 10 BirdCall SoundNodeWaves referenced. We can probably reduce the variety and not notice the difference.
- Type - Type of the resource, either skeletal mesh, static mesh, terrain or BSP (model)
- Count - Number of instances of that mesh in the level.
- Triangles - Triangle count per instance.
- Resource Size - Size of resource in KByte as reported by "view resource usage", only relevant for static and skeletal meshes
- Lights (avg LM/ other/ total) - Average number of lightmapped (LM), non lightmapped (other) and total lights affecting each instance.
- Obj/ Light cost - The object/ light interaction cost. This number indicates how many times this static mesh is going to be re-rendered for lighting purposes. It is the number of non-lightmapped lights times the total sections count of all instances. Lightmaps are not included in this number, as they are rendered as part of the emissive pass and are therefore can considered "free".
- Triangle cost - The number of triangles that need to be rendered for lighting purposes. This number excludes the z-prepass, emissive and lightmaps as those are constant overhead regardless of the number of lights.
- Lightmap - The amount of memory used by lightmap data.
- Shadowmap - The amount of memory used by shadowmap data.
- LM Res - The average lightmap resolution per instance.
- Sections - The number of sections this mesh has.
- Radius (min/max/avg) - Min/max/average radius of the bounding box for each instance of the mesh.
A common source of performance problems is in Static Meshes. In general, you should replace any Static Meshes that are used only once or twice, and replace them with similar ones that are already used frequently. Try to avoid using unique meshes for skyboxes whenever possible. Whenever possible, apply the proper trade-off for vertex lighting vs. lightmap lighting. Mesh Simplification Tool page for complete details over using this tool.
When an AnimSet is referenced by the game, it will load all animations in the AnimSet. This can be waste if you only use a part of them. If all animations in the AnimSet aren't needed, it is generally recommended to split them into sub-AnimSets and load them when only needed. For example, different weapons can have their own AnimSet, unless you need all the weapon animations all the time. Matinee: This problem can be worse when it comes to Matinee. It can be very cumbersome to maintain different AnimSets per Matinee sequence or per level, depending on your workflow (i.e. different person working on different Matinee sequences). Ideally, you would like to have one AnimSet per level, but maintaining this could be very time consuming between different groups of developers (LDs/Animators/Scripters). The flag,
bShouldBakeAndPrune(CL 259515) in InterpData, helps in this situation. It creates new AnimSet for the level and duplicates only the animations that are used by the level and re-links the reference. If the AnimSet is cooked/loaded anyway, then this flag needs to be off because it will duplicate the the same animation. This needs to be used if the AnimSet you're baking and pruning doesn't have to be loaded by the level. The flag can be globally disabled by setting the bBakeAndPruneDuringCook value to FALSE in the Cooker.MatineeOptions section of *Editor.ini. In
[Cooker.MatineeOptions] bBakeAndPruneDuringCook=falseAnimation Compression page for more information on the available compression algorithms and how to apply them to animations.
- What animation has not been used
- What animation is most visible to player during the gameplay
The complexity of the materials in a scene can greatly affect the GPU overhead of the scene. The more dynamic lights that are used, the more costly an expensive material becomes.
- Try and limit the amount of Dependent Texture reads, this means modifying UVs with a texture, such as BumpOffset.
- Use Material Instancing, and use StaticSwitchParameters for expensive effects so that they can be toggled on/off easier to make performance tradeoffs.
- Consider removing specular from materials that do not have very much specular. This removes a significant amount of pixel shader instructions and in many cases is not noticeable.
- Try and limit the number of texture lookups in each material, the more texture lookups the longer it will take the GPU to get the textures it needs.
- Instruction count
- Watch usage flags - control number of shaders per material
Unreal Engine 3 Uses a Texture Streaming Pool. This means that all streaming textures have a total known memory footprint, and the engine will do its best to load only textures that are large/near the player at the highest resolution. However it still leaves the potential for you to add too many textures and cause blurryness everywhere. In this case, the only way to fix it is to cut your texture usage down to realistic levels. Here are some tips for optimizing textures:
- Check STAT STREAMING and streaming fudge factor. If this number is at 1.0 then you have nothing to worry about, anything higher than 1.0 means that the textures do not fit and some of them will be blurry.
- Use LISTTEXTURES to see what is loaded. This will spit out a list of textures that are loaded into the log. This information can be copied into excel, and automatically organized into rows/columns by pressing the "Text to Columns" button (under Data), and choosing Delimited->Comma-> finish. Then press sort to allow you to sort each row by ascending or descending order.
- Once you have a listtextures output into excel, sorting by DESCENDING in the "Current Size" row, and you can use that to start analyzing the worst-case-offenders..... the textures that are taking up the most memory.
GAMENAME FindDuplicateTexturesCommandletThis will list out all of the Duplicate textures in your game content. NOTE: You will want to work with a programmer to look at the various options this commandlet can do. Specifically look at: UnPackageUtilities.cpp UFindDuplicateTexturesCommandlet::Startup()
There are usually lots of particles in a scene and not all of them have the same costs. We would like to know if there are any particles that are significantly more costly than others. Additionally, we want to know if there are any particles doing work when they don't need to be. Setting #define TRACK_DETAILED_PARTICLE_TICK_STATS 1 will out put a number of particle tick stats to the log Additionally, you will wan to look for particles that are updating their bounds every frame. The ContentAudit will have tagged those ParticleSystems, but if you ever see Particles having their bounds being dynamically updated each frame, those are always a good candidate to have a FixedRelativeBoundingBox Here are some tips for optimizing particles:
- Disable collision on minor stuff (in UT the Enforcer weapon sparks used collision!).
- Avoid overdraw.
- Use PIX and VIEWMODE SHADERCOMPLEXITY.
- Use STAT PARTICLES to see count.
- Manually set bounding boxes for common effects or large ones.
- Ensure Editor generated max active counts are sane.
- See the Particle System Reference page for more details
- Use MEMORYSPLIT, it will show how much particle data is currently loaded as Active and how much is could be loaded as Peak (the max possible memory that could be allocated to particles at one time).
Here are some tips for optimizing audio:
- Ensure that multiple music tracks aren't being used unless they are absolutely necessary. It's a good idea to keep music tracks in their own streaming maps so that they can be streamed in and out when they are needed(this will not affect performance, but it will affect the overall level budget).
- Check budget via STAT AUDIO, STAT MEMORY.
- Use LISTSOUNDS to see what is the currently loaded sounds - displays waves stored on a per group basis so you can see what types of sounds take up the most memory.
- LISTWAVES lists the currently playing sounds (PC only).
- LISTAUDIOCOMPONENTS lists the currently considered sounds.