Firstly, let me say "good luck" - I can say from experience that writing your own software rasterizer is both fun and rewarding.
My own software rasterizer, Rasterizr, is written in C#. The performance is nothing to write home about, but that's partly because I prioritised understandable code over speed. I chose to follow the Direct3D API quite closely, so I have the following stages:
- Input assembler
- Vertex shader
- Geometry shader
- Rasterizer
- Pixel shader
- Output merger
I found that this architecture helped keep the code clean and understandable.
For shaders, I did more or less what cgrant suggested. I implemented an HLSL bytecode parser (called SlimShader: https://github.com/tgjones/slimshader). Then I wrote a virtual machine that can execute HLSL shaders on the CPU. Then I wrote a jitter for the virtual machine, to keep performance within reasonable bounds - it produces C# code for a given shader and compiles it on the fly. The end result is that my software rasterizer uses "real" HLSL shaders. (Originally, I used C# for the shaders, but handling pixel shader gradient calculations, which is required for texture mipmapping, was a problem. Also using HLSL shaders is more fun.)
(It's awesome to see Jason Zink replying in this thread - his book "Practical Rendering and Computation with Direct3D 11" was an invaluable reference for me while I was figuring out how Direct3D 11 works, so that I could borrow some of its API for Rasterizr. Thank you Jason!)
I wrote a visual debugging tool, based on Visual Studio's graphics tools - here's a screenshot. It was helpful just yesterday in tracking down a bug related to geometry shaders and render target arrays!
If you want to look at my code, you'll find it here: https://github.com/tgjones/rasterizr. I'd be happy to help if you have any questions.
Good luck!