Page 1 of 1

Burnings draw2DRectangle

Posted: Sun Jan 25, 2015 8:10 am
by chronologicaldot
I'm sure this topic may get old, and I hate starting a new thread, but this is different.

I changed the Burnings draw2DRectangle( four-corners coloring ) function to be accurate in the gradient color, and it looks very nice. This is good for if you want to use it for color-selection gradients (as I'm using it for). On the downside, it's slower (I didn't run an FPS check, but it's rather obvious when using a GUI skin), even after I inlined the color interpolation.

Anyone think they can optimize it, let me know. Here's the code I have so far (certain things have been left in but commented out so you can see the logic):

Code: Select all

 
//!Draws an 2d rectangle with a gradient.
void CBurningVideoDriver::draw2DRectangle(const core::rect<s32>& position,
    SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown,
    const core::rect<s32>* clip)
{
#if 1 // USE_BURNINGS_PRETTY_DRAW2DRECT
    if ( !RenderTargetSurface )
    {
        setRenderTarget(BackBuffer);
    }
 
    // Done in setRenderTarget
    //RenderTargetSize = RenderTargetSurface->getDimension();
 
    core::rect<s32> realClip( position );
    if ( clip )
        realClip.clipAgainst(*clip);
 
    if ( !realClip.isValid() )
        return;
 
    // Rectangle bounds given by %
    u32 x0 = (u32) ( realClip.UpperLeftCorner.X < 0 ? 0 : realClip.UpperLeftCorner.X );
    u32 y0 = (u32) ( realClip.UpperLeftCorner.Y < 0 ? 0 : realClip.UpperLeftCorner.Y );
    u32 x100 = (u32)realClip.LowerRightCorner.X < RenderTargetSize.Width ? (u32)realClip.LowerRightCorner.X : RenderTargetSize.Width-1;
    u32 y100 = (u32)realClip.LowerRightCorner.Y < RenderTargetSize.Height ? (u32)realClip.LowerRightCorner.Y : RenderTargetSize.Height-1;
 
    u32 x, y, w, h, rx, ry, rw, rh;
    f32 ryh, rxw;
    w = x100 - x0; // render area width
    h = y100 - y0; // render area height
    //s32 flatAlpha, flatRed, flatGreen, flatBlue;
 
    /* The rectangle colors should be drawn as though the rectangle were the same size,
    even if part of it is clipped. */
    rw = position.getWidth(); // real width
    rh = position.getHeight(); // real height
 
    u32 colorLeftUpColor = colorLeftUp.color;
    u32 colorLeftDownColor = colorLeftDown.color;
    u32 colorRightUpColor = colorRightUp.color;
    u32 colorRightDownColor = colorRightDown.color;
 
    //SColor pixelColor; = colorLeftUp;
    u32 pixelColor;
    //SColor leftColor, rightColor;
    u32 leftColor, rightColor;
 
    tVideoSample* dst; // render target surface (just a pointer to a u32 array of size RenderTargetSize)
 
    y=0;
    ry = position.UpperLeftCorner.Y < 0 ? (u32)( position.UpperLeftCorner.Y * -1 ) : 0;
    for ( ; y < h ; y++ )
    {
        ryh = (f32)ry/(f32)rh;
        //leftColor = colorLeftDown.getInterpolated( colorLeftUp, (f32)ry/(f32)rh );
 
        leftColor =
                    ( (u32( (colorLeftDownColor>>24)*ryh + (colorLeftUpColor>>24)*(1.f-ryh) )) << 24 )
                    + ( (u32( ((colorLeftDownColor>>16) & 0xff)*ryh + ((colorLeftUpColor>>16) & 0xff)*(1.f-ryh) )) << 16 )
                    + ( (u32( ((colorLeftDownColor>>8) & 0xff)*ryh + ((colorLeftUpColor>>8) & 0xff)*(1.f-ryh) )) << 8 )
                    + ( u32( (colorLeftDownColor & 0xff)*ryh + (colorLeftUpColor & 0xff)*(1.f-ryh) ) );
 
        //rightColor = colorRightDown.getInterpolated( colorRightUp, (f32)ry/(f32)rh );
        rightColor =
                    ( (u32( (colorRightDownColor>>24)*ryh + (colorRightUpColor>>24)*(1.f-ryh) )) << 24 )
                    + ( (u32( ((colorRightDownColor>>16) & 0xff)*ryh + ((colorRightUpColor>>16) & 0xff)*(1.f-ryh) )) << 16 )
                    + ( (u32( ((colorRightDownColor>>8) & 0xff)*ryh + ((colorRightUpColor>>8) & 0xff)*(1.f-ryh) )) << 8 )
                    + ( u32( (colorRightDownColor & 0xff)*ryh + (colorRightUpColor & 0xff)*(1.f-ryh) ) );
 
        // left and right side
        //RenderTargetSurface->setPixel( x0, y + y0, leftColor, false );
        //RenderTargetSurface->setPixel( x100, y + y0, rightColor, false );
        dst = (tVideoSample*)RenderTargetSurface->lock() + (y+y0)*RenderTargetSize.Width + x0;
        dst[0] = (tVideoSample)leftColor; //.color;
        dst[w-1] = (tVideoSample)rightColor; //.color;
 
        x = 1;
        rx = position.UpperLeftCorner.X < 0 ? (u32)( position.UpperLeftCorner.X * -1 ) : 0;
        for ( ; x < w-1; x++ )
        {
            rxw = (f32)x/(f32)rw;
 
            //pixelColor = rightColor.getInterpolated( leftColor, (f32)rx/(f32)rw );
            pixelColor =
                    ( (u32( (rightColor>>24)*rxw + (leftColor>>24)*(1.f-rxw) )) << 24 )
                    + ( (u32( ((rightColor>>16) & 0xff)*rxw + ((leftColor>>16) & 0xff)*(1.f-rxw) )) << 16 )
                    + ( (u32( ((rightColor>>8) & 0xff)*rxw + ((leftColor>>8) & 0xff)*(1.f-rxw) )) << 8 )
                    + ( u32( (rightColor & 0xff)*rxw + (leftColor & 0xff)*(1.f-rxw) ) );
 
            //RenderTargetSurface->setPixel( x + x0, y + y0, pixelColor, false ); // <-- Reeeally slow
            dst[x] = (tVideoSample)pixelColor; //.color;
 
            rx++;
        }
 
        ry++;
    }
    //RenderTargetSurface->unlock(); // unnecessary for Burnings
#endif
}
 
I used direct video samples (tVideoSamples) rather than SColor to make it faster, but I left in comments of where the color used to be - I hope that's not too distracting.
What I suppose I could do is make it optional by setting a video driver flag (now that I know about those, DOH!), but I'd rather make it faster if possible.

Re: Burnings draw2DRectangle

Posted: Sun Jan 25, 2015 9:44 am
by hendu
Well, the common optimization to such float multiplies is an adaptation of Bresenham's line algorithm. To change it to int math, you calculate when the pixels increase and decrease: instead of multiplying every iteration, you ++/-- every Nth iteration.

Re: Burnings draw2DRectangle

Posted: Sun Jan 25, 2015 10:28 pm
by chronologicaldot
I see. I had considered incrementing by a float, but my first implementation was messy. Thanks for the suggestion. I've probably seen Bresenham's line before and forgot. *sigh* I'll work at this again.

Re: Burnings draw2DRectangle

Posted: Tue Jan 27, 2015 2:45 am
by chronologicaldot
Okay, so I tried setting it up and as I put it together, I realized it wasn't going to work. Bresenham's line assumes you want to draw a line, but I'm only trying to do 1-point-per-1-point, i.e. 1 color value per pixel position. I'd have to average point values, which is more work. Plus, what do I do in the case of steep slopes? - I'd end up incrementing every pixel anyways.
Turns out to be cleaner and faster to do it my first way and not try (at least what I had in mind for) the Bresenham's line implementation. I may keep working at it, but I may just make it optional somehow (e.g. I only need it for the color selection gradient - everything else can use the scanline method). Inlining the color calculations to one may help, so I think I'll try that just as a last check. hm...

Re: Burnings draw2DRectangle

Posted: Tue Jan 27, 2015 6:32 am
by mongoose7
He didn't mean to *use* the line algorithm, just use the concept of representing a (fixed-point) floating point number as an integer result plus an integer remainder, where the remainder is folded into the result when it exceeds one. May be a bridge too far, eh?

Re: Burnings draw2DRectangle

Posted: Tue Jan 27, 2015 7:29 am
by chronologicaldot
Rereading my own post, I realize it's rather a mess. That's not what I meant. Oops. Sorry about that. My fault for rambling.

I got what she meant, and it looks like I tried relating it back to the original idea when I labeled it. My current "Bresenham's line" analogous implementation ran into some problems when dealing with steep slopes. When I have to change the color value over a range of 255 (the max) over a short distance, say 100 pixels, I end up incrementing up in an odd slope rather than have a nice, gradual change where I would only need to change the color every so-many pixels. Changing the color once (just incrementing by ++) every so-many pixels was the ideal I had in mind, and also of which I didn't clarify.
Anyways...

Clockspeed, my current algo is about half the normal speed of Burnings, *sigh*, even with optimizations. Maybe my PC is slow, but OpenGL came out to around 28 fps, Burnings normal came out to 14 fps, and Burnings with my algo came out to 8 fps, only doing GUI work with a skin.