可以优化此线绘制算法吗? - SDL

时间:2014-07-10 06:28:12

标签: c performance algorithm sdl-2 bresenham

对于我一直在研究的项目,使用渐变绘制线条的能力(I.E.它们在绘制的区间内改变颜色)将非常有用。我有一个算法,因为我将在下面粘贴,但事实证明它是DREADFULLY慢。我使用Bresenham算法找到每个点,但我担心我已经达到了软件渲染的极限。到目前为止,我一直在使用SDL2,我的线条绘制算法比SDL_RenderDrawLine慢200倍。这是一个估计,并通过比较两个函数来收集。抽取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设置上下文然后将屏幕着色为纯色的过程,但在解释如何绘制形状等之前停止。如果有人可以提供一个开始学习这个的好地方,那也非常有帮助。

1 个答案:

答案 0 :(得分:3)

您的函数的大量开销是重复调用SDL_RenderDrawPoint。这(很可能)是一个泛型函数,需要执行以下操作:

  1. 检查xy是否在当前表面的范围内;
  2. ysurface->pitchxsurface->format->BytesPerPixel相乘,计算表面内的位置;
  3. 使用SDL_PixelFormat;
  4. 检查曲面的当前颜色模型
  5. 将提供的颜色转换为此颜色模型的正确格式。
  6. 必须对每个像素执行以上所有操作。此外,调用函数本身就是开销 - 虽然可能很小,但仍需要为每个单独的像素完成,即使它不可见

    你可以:

    1. 省略xy范围检查,如果您确定行起点和终点始终可见;
    2. 通过为行开头计算一次来省略转换为地址的步骤,然后通过为水平或垂直添加BytesPerPixelpitch来更新它运动;
    3. 通过计算一次正确的RGB值来省略转换为颜色的模型步骤(好吧,对于单个颜色线,至少 - 对于渐变来说有点难度);
    4. 通过内联代码在line例程中设置单个像素来省略函数调用。
    5. 另一个 - 较小 - 问题:你称自己的例行程序为“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 image

      我没有时间区分“原生”SDL线条画,你天真的方法,纯色的Bresenham的实现和这个;再说一遍,这不应该比SDL本机线慢得多