Saturday, November 13, 2010

Bug Fixes for the sched-perf Branch

I just pushed a rebased version of the sched-perf branch to a new branch called sched-perf-rebase at git://

This new branch contains bug fixes for the old branch and has no piglit regression vs. master on my RC410 and RV515 cards. In fact this branch has +1 passes on both cards.

This new branch should reduce fragment shader program size by about 10-20%. Shaders with branches should see the most improvement. There are three major changes to the compiler that are driving these improvements.

The first change is that the dataflow analysis for the optimization passes has been unified in a single function: rc_get_readers() which saves us from having to redo dataflow analysis for every passes and made it really easy to add the new optimization passes in this branch.

Fragment shader instructions for R300-R500 cards are actually composed of two sub instructions: one vector and one scalar. The vector instruction writes to the xyz components of a register and the scalar instruction writes to the w component. Currently, in the master branch an instruction like: MOV Temp[0].x, Temp[1].x is treated as a vector instruction, since it writes to the x component. This wastes the vector unit on what is actually a scalar instruction. One of the optimizations I added converts MOV Temp[0].x, Temp[1].x to MOV Temp[0].w Temp[1].x which allows us to make use of the scalar unit and leaves the vector unit free for actual vector instructions. Since there are usually more vector instructions than scalar we can usually fill this empty vector slot with another instruction which reduces the overall program size by one.

The third big change is converting the code to a quasi static single assignment (SSA) form prior to instruction scheduling. SSA basically means that each register is only written once. The main advantage of SSA is that it makes dataflow analysis much easier, however in the r300 compiler we aren't really using it for dataflow analysis. We are using it because it helps our scheduler do a better job pairing instructions and making use of the vector and scalar units on every cycle. I say quasi-SSA because you can't really turn vector instructions into SSA unless you break them apart into individual scalar instructions. For example, with vector instructions you might run into cases like this:

MOV Temp[4].x, Temp[5].x
MOV Temp[4].y, Temp[6].x
MOV Temp[7].xy, Temp[4].xy

In true SSA, each register is only written one time so we would need to rewrite the 2nd instruction like this:

MOV Temp[4].x, Temp[5].x
MOV Temp[5].y, Temp[6].x
MOV Temp[7].xy, Temp[4].xy

Oops, now we broke the program. Instruction 3 reads from Temp[4].x, but that component is never written. We could change instruction 3 to
MOV Temp[7].xy, Temp[5].xy, but then it would read from Temp[5].y which isn't written either. So, in the r300 compiler we convert everything to SSA unless we see code like the example above. In that case we just ignore it and don't bother trying to rewrite it.

As I mentioned earlier, these compiler optimizations reduce program size by about 10 - 20% Here is an example from the piglit test glsl-fs-atan3:

Total Instructions1119360
Vector Instructions816547
Scalar Instructions273747
Flow Control Instructions20207
Presubtract Operations344
Temporary Registers1096

The fglrx results come from the AMD Shader Analyzer v1.42.

So about a 15% decrease in shader size for this test, but we are still quite far away from fglrx. The good news is, however, that I can see lots of areas for improvement. The big gap between the r300 compiler and fglrx is mostly because the way we use flow control instructions is very inefficient, and in this shader, it costs us about 16 instructions. There are a few other optimization we could be doing better too.

I'm really not a GPU performance expert, so I don't know how smaller shader programs will translate to better performance at least in terms of frames per second. Smaller shaders means less data needs to be submitted to the graphics processor so that should help, but I think most of the performance bottlenecks are other places in the driver.

I'm going to do more testing of the sched-perf-rebase branch before I merge it with master, but I feel pretty good about it now. Also, as a bonus while working on these performance improvements I found and fixed 5 non-performance related bugs, which I hope will resolve some of the outstanding r300g fdo bugs.


  1. The shader size and the number of temporaries are the two key attributes that impact shader performance in my opinion. Latency hiding works better if the number of temporaries is low.

  2. I've tested it on my RV530. It works great so far.
    I didn't notice any regressions. Even better, almost all rendering glitches in Assassin's Creed are fixed now. The only remaining issue seems to be a vertex shader bug, but it also shows up with RADEON_NO_TCL=1 so it must be a glsl compiler problem.

  3. I did also some quick testing with my RV530 and it looks very good, there is some noticeable speedup and it fixes fdo bug 31255, however it also breaks shadows in Unigine again (first bad commit: 73285160fc4013766213515855027652f4ff0d10). If you would like some more data send me an email, however it's OK for me if you merge your patches now and fix it after.

  4. Pavel:
    If you send me the output of RADEON_DEBUG=fp,pstat from the good and the bad commit, I'll take a look at it.

  5. I tested it with OpenArena, Nexuiz and some other games on my RV530. I noticed no regressions, however I only see less than 1% improvement in fps.