"Hm, I wonder why it was software only."
Because of graphics programming.
When you write your own software renderer, you have complete control over how things render. When you use an API like OpenGL, 1.1 at the time, you get a lot of features, however at the same time, you can't get the
easy per-pixel control as you'd have with a software renderer. (it gets a bit complicated)
One would either have to write a pixel shader (which wasn't available back then), or maybe modify the current texture for the current wpoly, before rendering it.
I'm guessing the algorithm went something like this for each pixel in the texture:
if ( texturePalette < 224 )
color = textureColor * lightmap;
else
color = textureColor;
This is the kind of 'control' that I'm talking about. We're basically modifying the texture before uploading it to draw the current wpoly. But, this isn't my area yet and I'm likely incorrect.
Modern Quake sourceports like Quakespasm and Mark V (the ones I've used) support this feature in OpenGL mode, so it's probably not that much of a trouble.