Within the software realm, if the number of distinct brightness settings one needs isn't too large, it may be helpful to store the data in "bit-planar" format (as described in my other hardware-based answer) and then have the output routines use Boolean operators to act on 8 pixels at once. For maximum efficiency, this will require having multiple separate output routines, used for different parts of the PWM cycle; for example, if one wishes to use 4-bit brightness values, one would use eight routines of the form:
movf bit0Comp,w ; Should be 00 or FF depending upon bit 0 of comparand (FF if clear)
iorwf POSTINCF,w ; Bit 0 of data; always use IORWF
andwf POSTINCF,w ; Bit 1 of data; use IORWF if bit 1 of comparand is set; ANDWF if clear
andwf POSTINCF,w ; Bit 2 of data; Use IORWF if bit 1 of comparand is set; ANDWF if clear
andwf POSTINCF,w ; Bit 2 of data; Use IORWF if bit 1 of comparand is set; ANDWF if clear
movwf SPIREG ; Store resulting byte (bits set if >= comparand)
One would use different combinations of IORWF and ANDWF, depending upon the value of the comparand. Note that using this approach as illustrated, one may update pixel brightness values at any point in the PWM cycle without flicker provided that all four bits are written between calls to the display-shift routine, or by having the pixel-update routine determine whether the next shift will output a "1" or a "0" for the pixel, and either setting or clearing all bits of the pixel (whichever operation would make it do whatever it was going to do anyway) and then writing any bits whose value should be opposite. Note also that one may achieve arbitrary non-linear brightness scales by varying the timing of the display updates, or by using some comparand values more than once in a PWM cycle. Non-linear brightness scales are often useful with LEDs, since the difference in brightness between 1/128 duty and 2/128 duty is generally far more visible than the difference between 127/128 and 128/128 (or even between 7/8 and 8/8).