PUSH YOUR MAP to the limits - FPS optimization and some other stuff.

Some words before getting started.

Today I am going to talk about all ways of optimizing your goldsource(cs) map FPS wise (or probably just about most of them). Its helpful keep in mind these tips while building a new map and while optimizing existing projects. Those can be useful to improve FPS or just allow to add more detalization without major FPS losses.

But this is not another "how to" tutorial. In this article I mostly going point out important things and basically explain how they work. Its up to you to find out how to manipulate and use following information. Some previous knowledges are required for better understanding of some aspects, Ill try refer good external material along.


There are many articles about how you should build a map and what rules you should follow: avoid big open areas/ too much detalization etc... all that are just quick tips and have nothing to do with performance or fps directly.

If you want to make quite big open areas, good details in your map... push it to the limits and keep good performance/fps - the main rule for you is to control r_speeds (wPoly and ePoly) in your map, especially in most used/active areas (such as climbing path for kreedz maps for example, or high traffic/action places in multiplayer maps [de_ cs_ etc.]).

Loading map in developer mode.

The best way to check r_speeds is load your map in developer mode, commands you would need for that:
  • map <mapname> - Starts a map in developer mode. Example: map de_dust2
  • r_speeds 1 - This command will activate wPoly and ePoly calculations.
  • developer 1 - This will bring r_speeds output from console to your ingame screen basically.
  • gl_wireframe 2 - Shows you how faces are split into polygons and how much engine draws them for specific areas.
NB! render with "gl_wireframe 2" requires more resources, so don't panic if fps drops in this mode.

Some basics. What is what in here?

wPoly - is number of texture polygons that engine draws for you, or in other words - the number of them game can see. Old computers start loosing fps at 600-800 wPolys usually, and about 1500 is critical for most of them. 800-1000 of maximum wPolys in "used areas" would allow old computers to run the map with "ok" fps. If you want make your map playable even on "slow" computers – I suggest to keep in mind these numbers.

ePoly - is number of models polygons that engine draws for you. This has less affect on fps, which makes reasonable to make detailed objects with models. However, old computers loose fps when you go over ~10 000 ePoly. Also large models (file size), high polygon models and large sum of wPoly+ePoly requires a lot more memory and resources. So don't use too much high polygon models in a map and in one place, especially on multiplayer maps (there are already player models, hud, decals etc.)

Polygons (Texture polygons) - engine divides/cuts faces with textures into pieces (polygons). You can see these "pieces" with "gl_wireframe 2" in developer mode. The bigger polygons - the less wPolys engine draws and better it is for FPS. Very ofter engine makes "unnecessary" divides on faces. Controlling how texture polygons are divided/cut - the main way to decrease wPoly and improve FPS.

Model polygon - a face of a model, pretty much the same as texture polygon, but on a model. Basically you don't need to work on /improve models unless you are a model maker.

Face - in this article means side of a brush/model with a texture.

How faces are divided.

All faces are subdivided with 240 units. These are texture units (pixels). With texture scale 1.0 plane is cut after every 240 units, with scale 0.5 - after 120 units (240 pixels of a texture), and it doesn't matter what size is the texture 64 or 512 units for example. It is defined by BSP compilation command: -subdivide # (default 240). Increasing this number could be a way to lower wPoly, tho values over 240 do not work properly most times and map crashes on load.

Also extra divides are made in places where brushes touch each other. Columns, boxes and all other stuff that touch the floor or walls will cut them:

Links: Subdivide http://www.egir.dk/index.php?page=vhe_bsp.php

Things that affect r_speeds and performance

1. Architecture, Vis block, sky box, leaks.

Nothing gonna save a map from bad r_speeds and FPS if its detailed, with big open areas and has no good visibility blocking structures. Control visible areas and r_speeds. Always think about ways to block players visibility with map architecture and visBlocks in detailed or big open areas. It also requires to make correct sky box and a map with no leaks of course.

Tip1: never make a big sky box around your map (especially to fix leaks)
Tip2: don't connect two big detailed areas with wide hollow. Better make "L" or "Z" -like connection, so engine doesn't have to draw both areas at once.

Tip3: wall with even small holes/windows in it is a bad visibility blocker
Tip4: entities do not block visibility, use world brushes.

Just use your imagination and creativity.

correct sky box http://img448.imageshack.us/img448/8752/sky7ln.jpg
Leafs http://members.multimania.co.uk/EditHalfLife/tutorials/leafs.htm
Vis Block & leafs http://www.egir.dk/index.php?page=hlvis/hlvis.htm

2. Texture scales. 240 unit textures.

As it was mentioned before - Planes are subdivided according to texture scale:
with scale 2.0 - divided every 480 units
with scale 1.0 - its every 240 units
with scale 0.5 - every 120 units

Texture dimensions do not affect subdivide.

Tip1: Scale textures up to get less polygons on a plane.

Tip2: Manipulate textures. Fit 240 pixels of a texture (or less) onto a single surface. You can ether use textures with 240*240 dimensions (or less) instead of bigger textures (256*256+), or scale your texture so surface only contains 240 pixels of it (or less).

Example: Imagine we have 13 boxes in a room, with 5 visible faces each and 256*256 texture on all of them. This means each face going to be cut into 4 polygons instead of 1. If we fix this by resizing textures, we will get rid of: 3 polygons per face * 5 faces per box * 13 boxes = 195 polygons total. And thats a lot!

NB! Some people are getting used to 240*240 textures. This is not always good option since after scale (up/down) planes with such textures are divided just like 256*256 textures (with extra polygons). I personally prefer 224*224 textures for saving polygons.

3. NULL texture.

Nothing much to say about it. This method one of the most known tips to reduce wPolys. Just apply NULL or SKY texture to all faces that players cant see directly ingame, that would remove unwanted polygons from the map. The only differences of SKY texture - is that it looks better (but who needs that if its out of sight?) and you cant put it on entities. So I would just suggest to stick with NULL. I'm gonna end right here about it, there are enough tutorials about NULL on the Internet already.

4. "Func_wall" and other entities.

Another well known technique to lower wPoly count and also there are plenty info about this on the Internet.

There are just some few things I want to underline and add:
  1. This is technique to prevent extra face divides by turning brushes into solid entities (func_wall, func_illusionary and all others) pic1 pic2 pic3 pic4 pic5 pic6 pic7
  2. Use NULL textures! Entities are processed different way by engine and seen from grater distance (even around corners). So even back faces are still gonna add up polygons.
  3. Don't group too much objects around the map into one entity. If you see one of the objects - others are rendered too and add up wPoly, even if most objects are behind Vis Blockers or even on the other side of the map. Best choice would be to group all objects (into one entity) which are visible all together at once (from most directions).
  4. Its also not always good to group objects into one entity if they touch each other. Group of objects in one entity will cut each other's faces and add up wPoly. Ether leave some of the objects out, or make separate entities of them. pic1 pic2
  5. Avoid turning big/long objects into entity. The bigger object - the longer visible distance for them. Once such object was seen in my map through 3 big rooms (and rooms were connected with L and Z -like corridors).
NB! Material textures do not emit sounds if those are applied on entities.
NB! There is a low precache limit which can run out if func_wall strategy is overused.

5. 1 unit gap.

Yet again an old trick. Quite simple: don't want a box or any brush to make extra divides on another plane - leave 1 unit gap between them (or more). Its not as good as func_wall, because its not good looking mostly and it cause light bugs (ugly shadows). Tho this method has some advantages (texture sounds available, saving precache count and big brushes still can be used as Vis Blockers).
pic1 pic2

6. VIS -full.

Use -full option for VIS compiler. This will make calculations more precise and mostly reduce r_speeds.
Even it has no big effect - I suggest always use this option (as every other option recommended for final compile).

7. Hint brush.

There are plenty good tutorials about this, some of them:

Just wanna say that with modern compiling tools HINT brushes not that much useful. However its handy if you need to make "custom" polygon divides and HINT usage can reduce compile time or correct VIS blocks. Although misuse can increase wPoly.

8. Viewable distance.

VHE menu > Map > Map properties > Max viewable distance
Obvious one, forces to drop polygons that beyond specified distance. It doesn't look good tho.
I cant imagine any good example to use this feature... just maybe if there is a thick fog.

9. Sky texture trick.

Its about making a world brush that doesn't divide plane it touches (just like func_wall).

Create a "box" with 6 pyramids - each pyramid per one box side, and pyramid top in the center of the "box". Cover all inner surfaces with SKY texture. Remove the pyramid on side where box is going to touch another surface (or just make it CLIP-Brush to save clipnodes). After such box is set - its not going to divide touching faces.
This trick apply for more complex brushes and with touching more than 1 sides.

10. Brush in brush effect.

Extra divides appear if part of a brush is inside of another brush, but that does not always happen actually. Such divides can appear even on the other side of the brush, also near brushes could be affected by this divide.

11. Rafters.

All in here:

Just be careful since rafters add some divides in the other room.

12. World global divide every 1024 units.

The whole map is divided after every 1024 units... Entities don't get divided by this.
Just try to keep world brushes away from such places, if you don't like divides you get.

13. Max node size.

HLBSP compiler option: -maxnodesize # (Sets the maximum portal node size). Reducing it to 512 from the 1024 default makes VIS more accurate and can limit number of polygons to be processed from different "unseen" areas. pic1 pic2
But usually it just increases r_speeds actually.

14. Water.

Subdivide works a bit in a different way for water, no matter if its done with func_water or func_illusionary. Water surface is divided every 64 units, no matter what texture scale is set. Additionally there is another divide according to texture scale (240 subdivide for scale 1.0 textures). Scaling water textures up can drop 240 divide.

Avoid large water brushes - those are rendered from bigger distance and creates much more polygons!
Perfect water is a model - less polygons and less engine resources required.
You probably already know that func_water is known as "lag maker", its not because of polygons, this entity is just f#cked up
(high resource consumption), so better use func_illusionary (+contents: water) instead... if you don't need waves.
I would recommend to use vluzacn compiler because it removes water backfaces and fixes other water related bugs.

15. Models.

Models increase ePoly count and do not affect wPoly, nor divide any surfaces. That makes them good for detailed objects especially. If you are a model maker and you use a lot of models (or high polygon count models) in your map – you might remove/improve unnecessary model polygons. Model polygons have long visible distances and engine processes every polygon of every model in every seen leafnode (even in opposite direction of player's sight), keep that in mind if you are placing many models in a single area.

16. Sprites, Fog, Rain.

Those don't affect r_speed at all, but still require computer resources. I wouldn't bother about fog and rain since players can turn them off (cl_weather 0/1/2).
Tho many sprites in one place can even crash clients.

17. File sizes, Detail textures.

Big files (like sounds, models etc.) and detail textures also have nothing to do with r_speeds, but still require computer resources.
Despite all other advantages – detail textures are good when texture scaling trick is used to reduce wPoly. Many old computer handle detailed textures better rather higher wPoly count.

PS. precache limit

Precache limit is a number of objects your map can "precache", in other words – how many solid entities (like func_wall), models, sprites and sounds you can include in your map. This limit is quite low – 512. Counter-Strike clients require to precache plenty of stuff like weapons, models, sprites... almost ~200 objects total. Also servers with amxx plugins usually add some objects to precache (like hats plugin from example), so be careful when you are reaching 300 number of additional files (sounds, models, sprites...) and solid entities (VHE menu > Map > Show Information > SolidEntities: ). Such map can become unplayable on servers with AMXX. Its always good to test a map on dedicated server for such kind of bugs.


As I said before - this is not another "how to" tutorial. Above mentioned information should not be considered as strict rules and not always going to work the way you want it to, or just won't fit. There are plenty ways of reducing r_speeds, use your own brain to decide what should be improved and how. Developer mode is good for finding weak spots.


This article is mostly a sum up of my own experience and some others people findings and r_speeds related tutorials, so big thanks to everybody who ever made a related tutorial or just published some explanations and ideas about this.