There are several offerings by GPU vendors like AMD's CodeXL or NVIDIA's nSight/Linux GFX Debugger which allow stepping through shaders but are tied to the respective vendor's hardware.
Let me note that, although they are available under Linux, I always had very little success with using them there. I can't comment on the situation under Windows.
The option which I have come to use recently, is to modularize my shader code via #includes
and restrict the included code to a common subset of GLSL and C++&glm.
When I hit a problem I try to reproduce it on another device to see if the problem is the same which hints at a logic error (instead of a driver problem/undefined behavior). There is also the chance of passing wrong data to the GPU (e.g. by incorrectly bound buffers etc.) which I usually rule out either by output debugging like in cifz answer or by inspecting the data via apitrace.
When it is a logic error I try to rebuild the situation from the GPU on CPU by calling the included code on CPU with the same data. Then I can step through it on CPU.
Building upon the modularity of the code you can also try to write unittest for it and compare the results between a GPU run and a CPU run. However, you have to be aware that there are corner cases where C++ might behave differently than GLSL, thus giving you false positives in these comparisons.
Finally, when you can't reproduce the problem on another device, you can only start to dig down where the difference comes from. Unittests might help you to narrow down where that happens but in the end you will probably need to write out additional debug information from the shader like in cifz answer.
And to give you an overview here is a flowchart of my debugging process:
To round this off here is a list of random pros and cons:
pro
- step through with usual debugger
- additional (often better) compiler diagnostics
con
- subtle differences in some mathematic primitives (e.g. different return values in corner cases)
- additional code to reproduce call environment on CPU