Quake’s mipmap level selection

quake_mipmap3
Mip map switching in WinQuake 1.0

If you’re rendering texture mapped triangles you’ll want mipmaps because of a) you get less flickering in far away triangles, and b) sampling smaller textures is more cache friendly. To do it properly you’ll need local UV-coordinate derivatives with respect to screen pixels, which may not be easily computable if you’re rendering a single pixel at a time. A good example is Quake’s software renderer that only uses the triangles distance from camera with some multipliers when picking the mip map LOD level, totally ignoring the slope.

For some helpful diagrams see the “Surface Subsystem: Mipmapping” section of Fabien Sanglard’s Quake 2 code review.

We can see that at least for sprites the Quake software renderer calculates mip levels based on the nearest vertex of a mesh (s->nearzi), the screen resolution (scale_for_mip), and some fudge variable based on texture coordinate offset magnitudes (textinfo->mipadjust):

miplevel = D_MipLevelForScale (s->nearzi * scale_for_mip
   * pface->texinfo->mipadjust);
pcurrentcache = D_CacheSurface (pface, miplevel);

The calculated scale is passed to a function that picks the final mip level (comments added by me):

int D_MipLevelForScale (float scale)
{
   int lmiplevel;

 if (scale >= d_scalemip[0] ) // 1.0
   lmiplevel = 0;
 else if (scale >= d_scalemip[1] ) // 0.4
   lmiplevel = 1;
 else if (scale >= d_scalemip[2] ) // 0.2
   lmiplevel = 2;
 else
   lmiplevel = 3;

 if (lmiplevel < d_minmip)
   lmiplevel = d_minmip;

 return lmiplevel;
}

The values of d_scalemip are basically from an array called basemip:

static float basemip[NUM_MIPS-1] = {1.0, 0.5*0.8, 0.25*0.8};

The mip level stored in a surface (r_drawsurf.surfmip) is used to offset a texture pointer and also to scale texture dimensions correctly during sampling:

// unsigned char *r_source points to texture data
r_source = (byte *)mt + mt->offsets[r_drawsurf.surfmip];
 
texwidth = mt->width >> r_drawsurf.surfmip;
blocksize = 16 >> r_drawsurf.surfmip;
blockdivshift = 4 - r_drawsurf.surfmip;

Leave a comment