it might be a bit too much work to get out of software rendering
For the beginning you can keep the software-engine as it is and just call "someone else's" polyfiller.
On the long run it's inevitable to rearrange most of the data-structure, though.
It can be quite deflating to see how much faster that damn hardware is in comparism to a seemingly well-optimized software-polyfiller...
shadow maps [...] looked to me like a lot of additional work
Since you need to draw a z-pass from the light's point of view, it requires some sensible concept to use a framebuffer's zbuffer as a texture without making the renderloop a complete mess.
Unluckily inheritance isn't implemented in FreeBasic yet, so I can't really suggest an aesthetic solution here.
look up a pixel's surrounding...
to add more interpolation as well as multiple lookups from the z buffer and then calcs to work out the shadow value could add a fair bit.
For hard-edged shadows you'd test if the current pixel's distance from the light is greater than the distance stored in the shadowmap, so it's a simple compare (to avoid the compare it's even just the sign-bit of the difference). Usually the z-values can be stored with enough precision in 16bits, so a 2x2 area only requires reading of two dwords. There's no interpolation required here, you just count how many samples are lit. Creating a few levels of mipmaps from the shadowmap can increase the sampling area without much cost.
To avoid out-of-range tests when accessing the shadowmap in the innerloop, I clipped all polygons at the borders of the shadowmap.