Optimizations
A good part of the work on 2.0 went into making the renderer fast. There was no single big trick to it; there were quite a few corners that got polished and honed, and it added up.
Batching
The old renderer drew a level face by face: for every single wall face it set the textures and the lights and then sent that one face to the graphics card.
Now the entire mine is one mesh that is built when the level loads and stays on the graphics card. Faces that can be drawn in the same way - the same kind of textures, with or without holes, with or without relief - are sorted next to each other and go out in one draw. While you play, only what changes gets patched.
The same goes for objects. All robots of one type are drawn in a single draw, no matter how many of them are on the screen, and the data of all models of a frame is sent to the graphics card in one go instead of model by model. The icons of the HUD come from one atlas and are drawn in one call, too.
Texture Arrays
Batching only works because of texture arrays. Normally, a change of texture between two faces forces a new draw. So all textures of the same size are now stacked as the layers of one texture array, which stays in place for all of them, and every face just tells the shader which layer it wants.
An animated texture or a door sliding open is no longer a change of textures either, but only another layer number in a table.
Geometry
Lighting a pixel is the expensive part of rendering a level, and in a Descent level a lot of walls are hidden behind other walls. So walls are drawn twice: first only their depth, which is cheap, and then with their lighting, but only where they are actually visible. Pixels you cannot see never run through the light computation.
The lights themselves got their share. Which lamps reach a segment is determined once per level and not in every frame, the lists are only updated where a light has changed, and whatever is the same for all pixels a light shines on is computed once per frame instead of once per pixel.
Which parts of the mine are visible is determined in every frame by looking through the portals between the segments, starting where you are. In very large levels the game switches by itself to a simpler test against your field of view when looking through all those portals gets too expensive.
Tessellation only refines walls that have relief and are on the screen; flat faces bypass it altogether.
Half Resolution
Soft things have no fine detail that could get lost. Smoke, the halos of lamps and the glow of lightning are therefore rendered at half the resolution, which is a quarter of the pixels, and then blended into the picture.
With smoke, the tricky part are the edges of whatever stands in front of it: simply scaling the smoke up would smear it over such an edge. So when the smoke is put back into the picture, the depth of every pixel is compared with the depth the smoke was rendered for, and where the two don't match, the smoke value belonging to the right depth is taken. The result is exact to the pixel.
Gloss Only Where You Can See It
The gloss static lamps put on surfaces cannot be baked into lightmaps, because it depends on where you are looking from. It has to be computed for every lamp on a segment's list, and most of the time nothing comes of it.
So the game now leaves that computation as early as possible: when the pixel lies behind the lamp, when the lamp lies behind the surface, or when the highlight would be too faint to notice from your point of view. In one of my test levels the first two of these checks alone took the frame rate from 200 to 266 fps.
Computing Ahead
Whatever can be computed ahead is computed when a level is loaded for the first time and then kept in a cache: lightmaps, the visibility of lights, the light in fog, tessellation data, reverberation and the outlines for the cartoon style. Shaders are prepared at program start, so nothing has to be compiled in the middle of a game.