WindowGradient is slooooowwww

Posted by Fiendish on Wed 27 Oct 2010 02:57 PM — 10 posts, 19,529 views.

USA Global Moderator #0
If I take the following code:
-- replacement for WindowRectOp action 5, which allows for a 3D look while maintaining color theme
-- Requires global theme.HIGHLIGHT, theme.FACE, theme.INNERSHADOW, and theme.OUTERSHADOW rgb colors to be set.
function DrawThemed3DRect(Window, left, top, right, bottom)
    WindowRectOp(Window, 2, left, top, right, bottom, theme.FACE)
    WindowLine(Window, left, top, right, top, theme.HIGHLIGHT, 0 + 0x0200, 1)
    WindowLine(Window, left, top, left, bottom, theme.HIGHLIGHT, 0 + 0x0200, 1)
    WindowLine(Window, left, bottom-2, right, bottom-2, theme.INNERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, right-2, top, right-2, bottom-2, theme.INNERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, left, bottom-1, right, bottom-1, theme.OUTERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, right-1, top, right-1, bottom-1, theme.OUTERSHADOW, 0 + 0x0200, 1)    
end


and replace the call to WindowRectOp with a call to WindowGradient with a second color, my channel capture miniwindow (which redraws 5 pretty small 3D rects each time the resize drag handler is called) can no longer keep up with even very slow user motion when dragging around the resizing tab. The code in its condition as presented above works flawlessly, and keeps up with even the fastest user motion as long as the window doesn't get too large.

Is there any way to bridge the performance gap here?
Amended on Wed 27 Oct 2010 03:14 PM by Fiendish
USA #1
One thing I notice when I look at the code for drawing gradients is that it sets every pixel manually:
   // main loop is columns
   for (long col = 0; col < iWidth; col++)
     {
     r = (uint8) rval;
     g = (uint8) gval;
     b = (uint8) bval;
     unsigned char * p = pBuffer + col * 3;
     for (long row = 0; row < iHeight; row++)
       {
       p [0] = b;
       p [1] = g;
       p [2] = r;
       p += increment;
       }  // end of each row
     rval += rinc;
     gval += ginc;
     bval += binc;                      
     }  // end of each column

One thing I'd be inclined to try is replacing that inner loop with a call to CMiniWindow::Line() so GDI can handle it. After all, GDI is hardware accelerated to some degree, and it buffers drawing instructions when it can.
USA #2
I also found a GradientFill() function [1] that seems like it would be really useful to implement.

[1] http://msdn.microsoft.com/en-us/library/dd144957(VS.85).aspx
Amended on Wed 27 Oct 2010 04:38 PM by Twisol
Australia Forum Administrator #3
My first comment is that the problem is vaguely described. Five "pretty small" rectangles could be anything. And the difference between GDI drawing and manually setting bytes is likely to change from insignificant for small rectangles to quite noticeable for large ones.

However, doing my best to reproduce, I made this (for the Immediate window):


<aliases>
  <alias
   match="gradient"
   enabled="y"
   group="miniwindows"
   send_to="12"
   sequence="100"
  >
  <send>

-- replacement for WindowRectOp action 5, which allows for a 3D look while maintaining color theme
-- Requires global theme.HIGHLIGHT, theme.FACE, theme.INNERSHADOW, and theme.OUTERSHADOW rgb colors to be set.
function DrawThemed3DRect(Window, left, top, right, bottom)
   WindowRectOp(Window, 2, left, top, right, bottom, theme.FACE)
    --WindowGradient ( Window , left, top, right, bottom, ColourNameToRGB "red", ColourNameToRGB "yellow" , 1)
    WindowLine(Window, left, top, right, top, theme.HIGHLIGHT, 0 + 0x0200, 1)
    WindowLine(Window, left, top, left, bottom, theme.HIGHLIGHT, 0 + 0x0200, 1)
    WindowLine(Window, left, bottom-2, right, bottom-2, theme.INNERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, right-2, top, right-2, bottom-2, theme.INNERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, left, bottom-1, right, bottom-1, theme.OUTERSHADOW, 0 + 0x0200, 1)
    WindowLine(Window, right-1, top, right-1, bottom-1, theme.OUTERSHADOW, 0 + 0x0200, 1)    
end

Window = "test"

theme = {
   FACE = ColourNameToRGB ("cyan"),
   HIGHLIGHT = ColourNameToRGB ( "red"),
   INNERSHADOW = ColourNameToRGB ( "lightgray"),
   OUTERSHADOW = ColourNameToRGB ( "darkgray"),
}

WindowCreate (Window , 
  0, -- left
  0, -- top
  500, -- width
  500, -- height
  6, -- mode
  0, -- flags
  ColourNameToRGB ("navajowhite"))

start = utils.timer ( )
DrawThemed3DRect(Window, 5, 5, 250, 250)
DrawThemed3DRect(Window, 100, 100, 350, 350)
DrawThemed3DRect(Window, 200, 200, 450, 450)
DrawThemed3DRect(Window, 250, 250, 500, 500)
DrawThemed3DRect(Window, 350, 10, 600, 250)
print (string.format ("time take = %%0.6f", utils.timer () - start))

WindowShow (Window , true)

</send>
  </alias>
</aliases>


I am guessing here quite large rectangles (around 250 x 250) which is hardly "pretty small".

Anyway, with the timing I get this for the WindowGradient line uncommented (and hitting Ctrl+R to repeat the alias):


time take = 0.001843
time take = 0.001861
time take = 0.001781
time take = 0.001763
time take = 0.001949
time take = 0.001775
time take = 0.001759
time take = 0.001813


And with that commented out and the WindowRectOp instead, I get:


time take = 0.000406
time take = 0.000388
time take = 0.000410
time take = 0.000410
time take = 0.000385
time take = 0.000418


Certainly it is quicker doing one GDI call than mucking around setting bytes, but not by an order of magnitude. Four times slower I would say, roughly.

I'll look into making the function more efficient, but more detail about the rectangle sizes and comparative timing would help to know if the problem is acceptably solved.

The other question is, do you write on top of these rectangles, or are they decoration? In any case, it could be quicker to pre-render them (if their size doesn't change) into another window and just blit them onto the visible one.
Australia Forum Administrator #4
Another thing that could speed up resizing is to do what Windows used to do - during a resize operation draw just the outline of the window (not filling it) thus reducing considerably the amount of drawing involved. Then when they let go of the mouse redraw everything.
USA Global Moderator #5
My pretty small rectangles are in fact quite small:

15 x 15 exactly
15 x 15 exactly
15 x 15 exactly
15 x 150 with the second value resizing
450 x 20 with the first value resizing

I could potentially prerender the first three, but not the other two. The key for me here is that I'm trying to do this many times per second with other processing also going on. The whole thing works reasonably well without gradients, but falls over with them.
Amended on Wed 27 Oct 2010 08:47 PM by Fiendish
USA Global Moderator #6
Nick Gammon said:

Another thing that could speed up resizing is to do what Windows used to do - during a resize operation draw just the outline of the window (not filling it) thus reducing considerably the amount of drawing involved. Then when they let go of the mouse redraw everything.


I'll spend another month working out performance hacks before I let the interface go that far backward in time. It's likely that I can pre-render other complex bits of the drawing to make up for this slowness.
Amended on Wed 27 Oct 2010 08:53 PM by Fiendish
Australia Forum Administrator #7
Well I've taken both of Twisol's suggestions and improved the speed. For older operating systems drawing lines rather than doing the bytes myself results in a smallish increase in speed.


time take = 0.001546
time take = 0.001473
time take = 0.001491
time take = 0.001487
time take = 0.001490
time take = 0.001543
time take = 0.001469
time take = 0.001514


It's interesting it isn't better but there you are.

However for Windows 2000 up (which includes XP, Vista etc.) then the GradientFill call gives significantly better results:


time take = 0.000452
time take = 0.000525
time take = 0.001296
time take = 0.001269
time take = 0.001198
time take = 0.000456
time take = 0.000519
time take = 0.001187


It seems to vary a bit, interestingly, but overall it appears we are back to about the speed of the rectangle, or maybe slightly slower.
USA #8
Nick Gammon said:
It's interesting it isn't better but there you are.

Theoretically, if the distance between the two colors is smaller than the length of the gradient (for some definition of "smaller"), there will be multiple columns where the color is precisely the same. If I have a 100px gradient from #FF0000 to #EF0000, for example, there will be large divisions (I'd imagine of equal size) where the RGB values are identical.

If you wanted to, you could probably make that into an optimization, and draw rectangles instead of lines where possible.

Nick Gammon said:
However for Windows 2000 up (which includes XP, Vista etc.) then the GradientFill call gives significantly better results:

Excellent!
Amended on Wed 27 Oct 2010 10:31 PM by Twisol
USA Global Moderator #9
Hey cool. It's interesting that pre-2000 has different performance. I wonder if they started adding in some sort of special hardware acceleration for 2D shading. Or maybe they just have a really great way of calculating gradients quickly. Anyway, thanks tons, Nick. Turns out I might not have needed it after all, but I like optimization everywhere I can get it. *grin*