2

对于我一直在从事的项目,使用渐变绘制线条的能力(即它们在绘制的间隔内改变颜色)将非常有用。我有一个算法,我将在下面粘贴,但结果非常慢。我正在使用 Bresenham 算法找到每个点,但我担心我已经达到了软件渲染的极限。到目前为止,我一直在使用 SDL2,我的画线算法看起来比SDL_RenderDrawLine. 这是一个估计值,是通过比较两个函数绘制 10,000 条线的时间得出的。我的功能将花费近 500 毫秒,并且SDL_RenderDrawLine在我的机器上在 2-3 毫秒内完成。我什至用水平线测试了这些函数,以确保它不仅仅是一个拙劣的 Bresenham 算法,并且类似的缓慢孵化。不幸的是,SDL 没有用于绘制渐变线的 API(或者如果有,我是盲人)。我知道任何软件渲染都会比硬件慢得多,但是速度的剪切幅度让我感到惊讶。有没有一种方法可以用来加快速度?我是不是把绘图系统搞砸了?我考虑过保存我希望绘制的像素数组,然后将它们一次全部推送到屏幕上,但我不知道如何使用 SDL2 执行此操作,而且我似乎无法在 wiki 中找到 API或允许这样做的文档。那会更快吗?

感谢您的考虑!

void DRW_LineGradient(SDL_Renderer* rend, SDL_Color c1, int x1, int y1, SDL_Color c2, int x2, int y2){
Uint8 tmpr, tmpg, tmpb, tmpa;
SDL_GetRenderDrawColor(rend, &tmpr, &tmpg, &tmpb, &tmpa);

int dy = y2 - y1;
int dx = x2 - x1;

/* Use doubles for a simple gradient */
double d = (abs(x1 - x2) > abs(y1 - y2) ? abs(x1 - x2) : abs(y1 - y2));
double dr = (c2.r - c1.r) / d;
double dg = (c2.g - c1.g) / d;
double db = (c2.b - c1.b) / d;
double da = (c2.a - c1.a) / d;

double r = c1.r, g = c1.g, b = c1.b, a = c1.a;

/* The line is vertical */
if (dx == 0) {
    int y;
    if (y2 >= y1) {
        for (y = y1; y <= y2; y++) {
            SDL_SetRenderDrawColor(rend, r, g, b, a);
            SDL_RenderDrawPoint(rend, x1, y);
            r += dr;
            g += dg;
            b += db;
            a += da;
        }
        return;
    }
    else{
        for (y = y1; y >= y2; y--) {
            SDL_SetRenderDrawColor(rend, r, g, b, a);
            SDL_RenderDrawPoint(rend, x1, y);

            r += dr;
            g += dg;
            b += db;
            a += da;
        }
        return;
    }
}
/* The line is horizontal */
if (dy == 0) {
    int x;
    if (x2 >= x1) {
        for (x = x1; x <= x2; x++) {
            SDL_SetRenderDrawColor(rend, r, g, b, a);
            SDL_RenderDrawPoint(rend, x, y1);
            r += dr;
            g += dg;
            b += db;
            a += da;
        }
        return;
    }
    else{
        for (x = x1; x >= x2; x--) {
            SDL_SetRenderDrawColor(rend, r, g, b, a);
            SDL_RenderDrawPoint(rend, x, y1);

            r += dr;
            g += dg;
            b += db;
            a += da;
        }
        return;
    }
}
/* The line has a slope of 1 or -1 */
if (abs(dy) == abs(dx)) {
    int xmult = 1, ymult = 1;
    if (dx < 0) {
        xmult = -1;
    }
    if (dy < 0) {
        ymult = -1;
    }
    int x = x1, y = y1;
    do {
        SDL_SetRenderDrawColor(rend, r, g, b, a);
        SDL_RenderDrawPoint(rend, x, y);
        x += xmult;
        y += ymult;
        r += dr;
        g += dg;
        b += db;
        a += da;
    } while (x != x2);
    return;
}

/* Use bresenham's algorithm to render the line */

int checky = dx >> 1;
int octant = findOctant((Line){x1, y1, x2, y2, dx, dy});

dy = abs(dy);
dx = abs(dx);
x2 = abs(x2 - x1) + x1;
y2 = abs(y2 - y1) + y1;

if (octant == 1 || octant == 2 || octant == 5 || octant == 6) {
    int tmp = dy;
    dy = dx;
    dx = tmp;
}

int x, y = 0;
for (x = 0; x <= dx; x++) {
    SDL_SetRenderDrawColor(rend, r, g, b, a);
    switch (octant) {
        case 0:
            SDL_RenderDrawPoint(rend, x + x1, y + y1);
            break;
        case 1:
            SDL_RenderDrawPoint(rend, y + x1, x + y1);
            break;
        case 2:
            SDL_RenderDrawPoint(rend, -y + x1, x + y1);
            break;
        case 3:
            SDL_RenderDrawPoint(rend, -x + x1, y + y1);
            break;
        case 4:
            SDL_RenderDrawPoint(rend, -x + x1, -y + y1);
            break;
        case 5:
            SDL_RenderDrawPoint(rend, -y + x1, -x + y1);
            break;
        case 6:
            SDL_RenderDrawPoint(rend, y + x1, -x + y1);
            break;
        case 7:
            SDL_RenderDrawPoint(rend, x + x1, -y + y1);
            break;
        default:
            break;
    }

    checky += dy;
    if (checky >= dx) {
        checky -= dx;
        y++;
    }

    r += dr;
    g += dg;
    b += db;
    a += da;
}

SDL_SetRenderDrawColor(rend, tmpr, tmpg, tmpb, tmpa);
}

边注:

我不愿意继续使用 OpenGL 3.0+(我听说 SDL2 支持),因为我不知道如何使用它。我发现的大多数教程都解释了使用 SDL 设置上下文然后将屏幕着色为纯色的过程,但在解释如何绘制形状等之前就停止了。如果有人可以提供一个开始学习这方面的好地方,那也将非常有帮助。

4

1 回答 1

3

您的函数的很多开销都在重复调用SDL_RenderDrawPoint. 这(很可能)是一个需要执行以下操作的通用函数:

  1. 检查当前表面是否在范围内xy
  2. 通过乘以 和 来计算表面ysurface->pitchx位置surface->format->BytesPerPixel
  3. 使用 ; 检查表面的当前颜色模型SDL_PixelFormat
  4. 将提供的颜色转换为此颜色模型的正确格式。

必须为每个单个像素完成以上所有操作。此外,调用一个函数本身是有开销的——尽管它可能很小,但它仍然需要为每个单独的像素完成,即使它不可见

你可以:

  1. 如果您确定线的起点和终点始终可见,则省略x和范围检查;y
  2. 通过在行的开头计算一次BytesPerPixel,然后通过添加和更新它来pitch进行水平或垂直移动,从而省略转换到地址的步骤;
  3. 通过计算一次正确的 RGB 值来省略转换为颜色模型的步骤(好吧,至少对于单色线 - 渐变有点困难);
  4. line通过内联代码以在例程中设置单个像素来省略函数调用。

另一个 - 较小的 - 问题:您将自己的例程称为“Bresenham 的......但事实并非如此。Bresenham 的优化实际上是它double完全避免了计算(它的强项是它仍然给出了数学上正确的输出;我使用double变量时不会指望...)。

以下例程不检查范围颜色模型颜色值或(实际上)是否应锁定表面。理想情况下,所有这些操作都应该在紧密的绘图循环之外完成。事实上,它假定一个 24 位 RGB 彩色屏幕,首先是红色字节。[*]

我为我当前的 SDL 环境编写了这段代码,它仍然是 SDL-1.0,但它也应该适用于较新的版本。

也可以使用 Bresenham 对 delta-Red、delta-Green 和 delta-Blue 值的计算,但我在这里故意省略了它们。:)它们会添加很多额外的变量——猜测,每个颜色通道三个—— -,额外的检查,而且,尤其是,并不是一个明显更好的质量。红色的两个连续值之间的差异,例如 127 和 128,通常太小而无法在单个像素宽的线条中注意到。此外,只有当您的线条长度至少为 256 像素并且您在渐变中覆盖了从 0 到 255 的整个红色范围时,才会发生这个小步骤。

[*] 如果您 100% 确定您使用自己的程序针对特定屏幕型号,您可以使用它(当然,针对该特定屏幕型号进行了调整)。针对几个不同的屏幕模型也是可行的;为每个编写一个自定义例程,并使用函数指针调用正确的例程。
很可能这就是如何SDL_RenderDrawLine能够挤出每一毫秒的性能。非常值得为库编写所有代码(将用于各种屏幕设置),但很可能不适用于像您这样的单个程序。注意我注释掉了一个单一的范围检查,它回退到一个普通的line必要时例行。您可以对不寻常或意外的屏幕设置执行相同的操作,在这种情况下,只需调用您自己的较慢的绘图例程即可。(您的例程更健壮,因为它使用 SDL 的本机例程。)

下面的原始line程序是十多年前从互联网上复制的,因为我已经使用了很多年。我很乐意将其归因于某人;如果有人认出评论(它们大多出现在原始代码中),发表评论。

void gradient_line (int x1,int y1,int x2,int y2,
    int r1,int g1, int b1,
    int r2,int g2, int b2)
{
    int     d;                      /* Decision variable                */
    int     dx,dy;                  /* Dx and Dy values for the line    */
    int     Eincr,NEincr;           /* Decision variable increments     */
    int     t;                      /* Counters etc.                    */
    unsigned char *ScrPos;
    int LineIncr;

    int rd,gd,bd;

    if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 ||
        x1 >= SCREEN_WIDE || x2 >= SCREEN_WIDE ||
        y1 >= SCREEN_HIGH || y2 >= SCREEN_HIGH)
    {
        line (x1,y1, x2,y2, (r1<<16)+(g1<<8)+b1);
        return;
    }

    rd = (r2-r1)<<8;
    gd = (g2-g1)<<8;
    bd = (b2-b1)<<8;

    dx = x2 - x1;
    if (dx < 0)
        dx = -dx;
    dy = y2 - y1;
    if (dy < 0)
        dy = -dy;

    if (dy <= dx)
    {
        /* We have a line with a slope between -1 and 1
         *
         * Ensure that we are always scan converting the line from left to
         * right to ensure that we produce the same line from P1 to P0 as the
         * line from P0 to P1.
         */
        if (x2 < x1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
            /* Swap colors */
            r1 = r2;
            g1 = g2;
            b1 = b2;
            rd = -rd;
            gd = -gd;
            bd = -bd;
        }
        r1 <<= 8;
        g1 <<= 8;
        b1 <<= 8;

        if (y2 > y1)
        {
            LineIncr = screen->pitch;
        } else
        {
            LineIncr = -screen->pitch;
        }

        d = 2*dy - dx;              /* Initial decision variable value  */
        Eincr = 2*dy;               /* Increment to move to E pixel     */
        NEincr = 2*(dy - dx);       /* Increment to move to NE pixel    */

        ScrPos = (unsigned char *)(screen->pixels+screen->pitch*y1+x1*screen->format->BytesPerPixel);

        rd /= dx;
        gd /= dx;
        bd /= dx;

        /* Draw the first point at (x1,y1)  */
        ScrPos[0] = r1 >> 8;
        ScrPos[1] = g1 >> 8;
        ScrPos[2] = b1 >> 8;

        r1 += rd;
        g1 += gd;
        b1 += bd;

        /* Incrementally determine the positions of the remaining pixels */
        for (x1++; x1 <= x2; x1++)
        {
            if (d < 0)
            {
                d += Eincr;         /* Choose the Eastern Pixel         */
            } else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                ScrPos += LineIncr;
            }
            ScrPos[0] = r1>>8;
            ScrPos[1] = g1>>8;
            ScrPos[2] = b1>>8;

            ScrPos += screen->format->BytesPerPixel;

            r1 += rd;
            g1 += gd;
            b1 += bd;
        }
    } else
    {
        /* We have a line with a slope between -1 and 1 (ie: includes
         * vertical lines). We must swap our x and y coordinates for this.
         *
         * Ensure that we are always scan converting the line from left to
         * right to ensure that we produce the same line from P1 to P0 as the
         * line from P0 to P1.
         */

        if (y2 < y1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
            /* Swap colors */
            r1 = r2;
            g1 = g2;
            b1 = b2;
            rd = -rd;
            gd = -gd;
            bd = -bd;
        }

        r1 <<= 8;
        g1 <<= 8;
        b1 <<= 8;

        if (x2 > x1)
        {
            LineIncr = screen->format->BytesPerPixel;
        } else
        {
            LineIncr = -screen->format->BytesPerPixel;
        }

        d = 2*dx - dy;              /* Initial decision variable value  */
        Eincr = 2*dx;               /* Increment to move to E pixel     */
        NEincr = 2*(dx - dy);       /* Increment to move to NE pixel    */

        rd /= dy;
        gd /= dy;
        bd /= dy;

        /* Draw the first point at (x1,y1)  */
        ScrPos = (unsigned char *)(screen->pixels+screen->pitch*y1+x1*screen->format->BytesPerPixel);

        ScrPos[0] = r1 >> 8;
        ScrPos[1] = g1 >> 8;
        ScrPos[2] = b1 >> 8;

        r1 += rd;
        g1 += gd;
        b1 += bd;

        /* Incrementally determine the positions of the remaining pixels
         */

        for (y1++; y1 <= y2; y1++)
        {
            ScrPos += screen->pitch;
            if (d < 0)
            {
                d += Eincr;         /* Choose the Eastern Pixel         */
            } else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                ScrPos += LineIncr; /* (or SE pixel for dx/dy < 0!)     */
            }
            ScrPos[0] = r1 >> 8;
            ScrPos[1] = g1 >> 8;
            ScrPos[2] = b1 >> 8;

            r1 += rd;
            g1 += gd;
            b1 += bd;
        }
    }
}

..这是一屏随机颜色随机线条的一部分,右侧是特写:

puke-a-tron 图片

我没有区分“本机” SDL 线条图、您的天真方法、纯色 Bresenham 的实现和这个之间的区别;话又说回来,这不应该比 SDL 原生线路慢很多

于 2014-07-10T22:11:43.667 回答