0

わかりました、ユーザーが 100x64 LCD にピクセルを設定できるクラスがあります。

// (U8 = unsigned char)
inline void pixelOn(const U8 X, const U8 Y) {
   *(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) |= (1 << (Y % LCD_DEPTH));
}


inline void pixelOff(const U8 X, const U8 Y) {
   *(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) &= !(1 << (Y % LCD_DEPTH));
}

これで、液晶に線を引くクラスができました。異なるピクセルの「設定機能」を使用する 3 つの機能があります: show、erease、invert

void NLine::show(bool update) const {
    if(lcd == 0) return;

    if (!NLcd::pixelInLcd(x, y) && !NLcd::pixelInLcd(endX, endY))
        return;
    if ((x == endX) || (y == endY)) {
        straight(&NLcd::pixelOn);
    } else {
        bresenham(&NLcd::pixelOn);
    }
    visible = true;
    if (update) {
        display_update();
    }
}

現時点では、関数ポインターでピクセルを設定するためにプライベート関数が呼び出されます。

void NLine::bresenham(void (NLcd::*fpPixelState)(const U8, const U8)) const {
    // predetermine function to avoid ifs during calculation!
    // low level pixel functions use U8!
    S8 ix = x;
    S8 iy = y;
    S8 sx = ix < endX ? 1 : -1;
    S8 sy = iy < endY ? 1 : -1;
    S16 err = width + (-height), e2;

    for (;;) {
        // how to get the compiler to inline this (template?)!
        (lcd->*fpPixelState)(static_cast<U8>(ix), static_cast<U8>(iy));
        if (ix == endX && iy == endY)
            break;
        e2 = 2 * err;
        if (e2 > (-height)) {
            err += (-height);
            ix += sx;
        }
        if (e2 < width) {
            err += width;
            iy += sy;
        }
    }
}

コンパイラが for ループでこの関数をインライン化することを望んでいるのは理解できると思います。テンプレートでこの問題を解決しようとしましたが、コンパイラがインラインを使用しているかどうかわからないという同じ問題があります。完全に異なる設計を使用する必要がありますか、またはこの問題をどのように解決できますか? 次の問題は、インラインが異なるために show erase と invert を呼び出すと、コンパイラが大量のコードを生成することです。別のコード設計を使用する必要があると思いますか?

編集:

まず、デザインの提案については、Dietmar Kühl に感謝します。だからここに結論があります:

これはテストコードです:

NLine line(lcd, 0, 0, 99, 0);
t0 = timer.now();
for(S8 i=0; i<NLcd::LCD_HEIGHT; ++i) {
    // x0, y0, xend, yend
    line.setPosition(NLine::keep, i, NLine::keep, i);
    // call only straight not the bigger bresenham function
    line.show();
    line.erase();
    line.invert();
}
t1 = timer.now();

方法 1: funciton-pointers を使用する (最初の)
メモリ: 26384
RT: 51 ms

方法 2: 関数オブジェクトを使用する (Dietmar
Kühl) メモリ: 26592
RT: 27 ミリ秒

方法 3: switch in for ループを使用して、ピクセル操作関数を決定します
。 メモリ: 26416
RT: 36 ms

方法 2: 最良の RT ですが、特にブレセンハムの場合、描画メソッド周辺の実装が非常に大きくなると、プログラムが非常に大きくなることが判明しました。これは、テンプレートの実装によって 3 つのピクセル関数すべての完全なコードが生成されるために発生します。

方法 3: 最も単純な方法が適切なトレードオフのようです。

さらなる提案を歓迎します。

4

2 に答える 2

1

最も効果的な方法は、関数ポインターを渡すのではなく、関数オブジェクトを指定するテンプレート引数を使用して関数をカスタマイズすることです。NLine次に、インライン関数呼び出し演算子を使用して関数オブジェクトを渡すことができます (明らかに、メンバー関数はクラスで対応して宣言する必要があります。

template <typename PixelState>
    // PixelState is a function object taking
    // - a reference to an `NLcd`
    // - two U8 parameters and returns nothing
void NLine::bresenham(PixelState pixelState) const {
    // ...
    pixelState(lcd,static_cast<U8>(ix), static_cast<U8>(iy));
    // ...
}

対応する関数オブジェクトは、適切な関数呼び出し演算子を持つクラスにすることができます。次に例を示します。

struct PixelOn
{
    void operator()(NLcd& lcd, U8 x, U8 y) const {
        lcd.pixelOn(x, y);
    }
};
// ...
bresenham(PixelOn());
// with C++ as of the 2011 revision:
bresenham([](Nlcd& lcd, U8 x, U8 y){ lcd.pixelOn(x, y); });

このような関数オブジェクトは通常、コンパイラによってインライン化できます。コンパイラは、標準の C++ アルゴリズムなどで頻繁に使用されるため、インライン化も得意とする傾向があります。特定の関数が実際にインライン化されるかどうかは、コンパイラがインライン化を決定するかどうかによって異なりますが、通常は最も効率的な選択を行います (もちろん、最適化レベルが適切であると仮定します)。インライン化できる場合NLcd::pixelOn、この関数もインライン化されます。

于 2013-11-09T11:02:58.417 に答える