2

私が取り組んでいるプロジェクトでは、グラデーションを使用して線を描画する機能 (つまり、描画される間隔で色が変わる) は非常に便利です。以下に貼り付けるように、これにはアルゴリズムがありますが、非常に遅いことが判明しました。Bresenham アルゴリズムを使用して各点を見つけていますが、ソフトウェア レンダリングの限界に達したのではないかと心配しています。これまで SDL2 を使用してきましたが、私の線画アルゴリズムは より 200 倍遅く見えますSDL_RenderDrawLine。これは概算であり、2 つの関数が 10,000 行を描画する時間を比較して算出したものです。私の関数は500ミリ秒近くかかり、SDL_RenderDrawLine私のマシンでは2〜3ミリ秒でそれを行いました。私は水平線を使って関数をテストし、ブレゼンハム アルゴリズムが失敗しただけではなく、同様の速度低下が発生したことを確認しました。残念ながら、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. とを掛けyて、サーフェス内の位置を計算します。surface->pitchxsurface->format->BytesPerPixel
  3. を使用して、サーフェスの現在のカラー モデルを確認しSDL_PixelFormatます。
  4. 提供された色をこのカラー モデルの正しい形式に変換します。

上記のすべては、単一のピクセルごとに実行する必要があります。さらに、関数を呼び出すこと自体がオーバーヘッドになります。小さいかもしれませんが、表示されていなくても、個別のピクセルごとに実行する必要があります

あなたはできる:

  1. 線の始点と終点が常に表示されていることが確実な場合は、範囲チェックをx省略します。y
  2. アドレスへの変換ステップを行の開始に対して1 回計算することによって省略し、水平または垂直方向の移動に対してBytesPerPixelおよびを加算して更新します。pitch
  3. 正しい RGB 値を 1 回計算して、モデルをカラーに変換するステップを省略します (少なくとも、単一のカラー ラインの場合は -- グラデーションの場合は少し難しくなります)。
  4. lineコードをインライン化して関数呼び出しを省略し、ルーチン内で単一のピクセルを設定します。

別の -- 小さい -- 問題: 自分のルーチンを「Bresenham の ...」と呼んでいますが、そうではありません。Bresenham の最適化は、実際にはdouble計算を完全に回避することです (そして、その最大のポイントは、数学的に正しい出力が得られることです。double変数を使用する場合は当てになりません...)。

次のルーチンは、 rangecolor modelcolor values、または (実際に) サーフェスをロックする必要があるかどうかをチェックしません。これらの操作はすべて、タイトな描画ループの外で行うのが理想的です。現状では、赤バイトが最初の 24 ビット RGB カラー画面を想定しています。[*]

このコードは、まだ SDL-1.0 である現在の SDL 環境用に作成しましたが、新しいバージョンでも機能するはずです。

デルタレッド、デルタグリーン、デルタブルーの値にもブレゼンハムの計算を使用することができますが、ここでは意図的にそれらを省略しまし:)た。多くの余分な変数が追加されます。推測では、カラーチャネルごとに 3 つです。 -、余分なチェック、そして何よりも、目に見えて優れた品質ではありません. 赤の連続する 2 つの値、たとえば 127 と 128 の差は通常、1 ピクセル幅の線では小さすぎてわかりません。さらに、この小さなステップは、線が少なくとも 256 ピクセルの長さで、グラデーションで 0 から 255 までの赤の範囲全体をカバーしている場合にのみ発生します

[*] 独自のプログラムで特定のスクリーン モデルをターゲットにしていることが 100% 確実な場合は、これを使用できます (もちろん、その特定のスクリーン モデルに合わせて調整されます)。いくつかの異なる画面モデルをターゲットにすることも可能です。それぞれにカスタマイズされたルーチンを作成し、関数ポインタを使用して正しいルーチンを呼び出します。
ほとんどの場合、これがSDL_RenderDrawLine1 ミリ秒ごとのパフォーマンスを絞り出す方法です。ライブラリ (さまざまな画面設定で使用される) 用にすべてのコードを作成する価値はありますが、おそらく、あなたのような単一のプログラム用ではありません。プレーンにフォールバックする単一の範囲チェックをコメントアウトしたことに注意してくださいline必要に応じてルーチン。異常または予期しない画面設定に対して同じことを行うことができ、その場合は、独自の低速の描画ルーチンを呼び出すだけです。(ルーチンは SDL のネイティブ ルーチンを使用するため、より堅牢です。)

以下の元のlineルーチンは、10 年以上前にインターネットからコピーされたもので、私は長年使用してきまし。私は喜んでそれを誰かに帰したいと思います。誰かがコメントを認識している場合 (それらはほとんど元のコードに表示されているとおりです)、コメントを投稿してください。

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;
        }
    }
}

.. そして、これはランダムな色のランダムな線の画面全体の一部であり、右側にクローズアップがあります:

ぷくあトロンの画像

「ネイティブ」な SDL 線画、あなたの素朴な方法、純粋な無地の Bresenham の実装とこれの違いを計りませんでした。繰り返しますが、これは SDL ネイティブ ラインよりもそれほど遅くはないはずです。

于 2014-07-10T22:11:43.667 に答える