4

レンダリング エンジンにガンマ補正を追加しようとしています。2 つの問題があります。

1) Math.pow は本当に遅いです (毎秒数千回呼び出されることに比べて)。そのため、その場で計算する代わりに、アクセスできる事前計算済みのガンマ テーブルを作成する必要があります。(これは追加情報であり、実際の問題ではありません)。

2) 現在、これを行うには、整数ピクセルをアンパックし、RGBA チャンネルを対応するガンマ修正値に置き換えてガンマを適用し、ピクセルを再パックして画像バッファーに送り返す必要があります。パフォーマンスへの影響はひどいもの ではありませんが、60fps の固定タイムステップが約 40fps にまで低下しています (いくつかの画像がレンダリングされています)。

整数のアンパック/パックをネイティブ コードに実装しようとしましたが、パフォーマンスの向上は見られず、VM がクラッシュしました (おそらくメモリ チェック エラーですが、今は修正する必要はありません)。

ピクセルをアンパック/パックせずにガンマを適用する方法はありますか? そうでない場合、これを行うためにどの方法を使用することをお勧めしますか?

注意 BufferedImageOp を使用するとは言わないでください。遅く、画像全体でしか操作できません (ピクセル固有が必要です)。

追加情報:

ピクセルのパッケージ:

public static int[] unpackInt(int argb, int type) {
    int[] vals = null;
    int p1 = 0;
    int p2 = 1;
    int p3 = 2;
    int p4 = 3;
    switch (type) {
    case TYPE_RGB:
        vals = new int[3];
        vals[p1] = argb >> 16 & 0xFF;
        vals[p2] = argb >> 8 & 0xFF;
        vals[p3] = argb & 0xFF;
        break;
    case TYPE_RGBA:
    case TYPE_ARGB:
        vals = new int[4];
        vals[p4] = argb & 0xFF;
        vals[p3] = argb >> 8 & 0xFF;
        vals[p2] = argb >> 16 & 0xFF;
        vals[p1] = argb >> 24 & 0xFF;
        break;
    default:
        throw (new IllegalArgumentException(
                "type must be a valid field defined by ColorUtils class"));
    }
    return vals;
}

public static int packInt(int... rgbs) {

    if (rgbs.length != 3 && rgbs.length != 4) {
        throw (new IllegalArgumentException(
                "args must be valid RGB, ARGB or RGBA value."));
    }
    int color = rgbs[0];
    for (int i = 1; i < rgbs.length; i++) {
        color = (color << 8) + rgbs[i];
    }
    return color;
}

以前にコードを破棄しましたが、ガンマ補正に次のアルゴリズムを使用していました。

protected int correctGamma(int pixel, float gamma) {
    float ginv = 1 / gamma;
    int[] rgbVals = ColorUtils.unpackInt(pixel, ColorUtils.TYPE_ARGB);
    for(int i = 0; i < rgbVals.length; i++) {
        rgbVals[i] = (int) Math.round(255 - Math.pow(rgbVals[i] / 255.0, ginv));
    }
    return ColorUtils.packInt(rgbVals);
}

解決

最終的に、GargantuChet が提案した多くのアイデアを組み合わせて、かなりうまく機能しているように見える (パフォーマンスの低下なし) システムに仕上げました。

GammaTable というクラスは、ガンマ値修飾子 (0.0 ~ 1.0 は暗く、>1.0 は明るくなります) でインスタンス化されます。コンストラクターは、この値のガンマ テーブルを構築する内部メソッドを呼び出します。このメソッドは、後でガンマをリセットするためにも使用されます。

/**
 * Called when a new gamma value is set to rebuild the gamma table.
 */
private synchronized void buildGammaTable() {
    table = new int[TABLE_SIZE];
    float ginv = 1 / gamma;
    double colors = COLORS;
    for(int i=0;i<table.length;i++) {
        table[i] = (int) Math.round(colors * Math.pow(i / colors, ginv)); 
    }
}

ガンマを適用するために、GammaTable は整数ピクセルを取り、それをアンパックし、変更されたガンマ値を検索し、再パックされた整数を返します*

/**
 * Applies the current gamma table to the given integer pixel.
 * @param color the integer pixel to which gamma will be applied
 * @param type a pixel type defined by ColorUtils
 * @param rgbArr optional pre-instantiated array to use when unpacking.  May be null.
 * @return the modified pixel value
 */
public int applyGamma(int color, int type, int[] rgbArr) {
    int[] argb = (rgbArr != null) ? ColorUtils.unpackInt(rgbArr, color):ColorUtils.unpackInt(color, type);
    for(int i = 0; i < argb.length; i++) {
        int col = argb[i];
        argb[i] = table[col];
    }
    int newColor = ColorUtils.packInt(argb);
    return newColor;
}

このapplyGammaメソッドは、画面上の各ピクセルに対して呼び出されます。

*結局のところ、ピクセルをアンパックして再パッケージ化しても速度はまったく低下しませんでした。なんらかの理由で、呼び出しをネストすると (つまりColorUtils.packInt(ColorUtils.unpackInt))、メソッドの実行時間が大幅に長くなります。興味深いことに、事前にインスタンス化された配列の使用も停止するColorUtils.unpackInt必要がありました。これは、パフォーマンスに大きな打撃を与えるように思われたためです。アンパック メソッドが各呼び出しでの新しい配列は、現在のコンテキストでのパフォーマンスに影響を与えないようです。

4

2 に答える 2

3

オーバーヘッドを引き起こしているのは数学演算なのだろうか。unpackInt を呼び出すたびに、JVM が割り当ててゼロに初期化する必要がある新しい配列を作成しています。これにより、実際には必要のない多くのヒープ アクティビティが発生している可能性があります。

unpackInt が宛先配列をパラメーターとして受け取るアプローチを検討することもできます。最初のパスとして、使用例は次のようになります

int[] rgbVals = new int[4];

protected int correctGamma(int pixel, float gamma) {
    float ginv = 1 / gamma;
    ColorUtils.unpackInt(pixel, ColorUtils.TYPE_ARGB, rgbVals);
    for(int i = 0; i &lt; rgbVals.length; i++) {
        rgbVals[i] = (int) Math.round(255 - Math.pow(rgbVals[i] / 255.0, ginv));
    }
    return ColorUtils.packInt(rgbVals);
}

これにより、unpackInt への呼び出しごとに (correctGamma を介して) 1 回ではなく、新しい配列を 1 回だけ作成するため、オブジェクト作成のオーバーヘッドが大幅に削減されます。唯一の注意点は、int を再パックするときに配列の長さを使用できなくなったことです。これは、タイプをパラメーターとして渡すか、unpackInt の TYPE_RGB ケースで未使用の要素を 0 に設定することで、簡単に解決できます。

case TYPE_RGB:
    vals[p1] = 0;
    vals[p2] = argb >> 16 & 0xFF;
    vals[p3] = argb >> 8 & 0xFF;
    vals[p4] = argb & 0xFF;

これは、この動作をすべてカプセル化したガンマ補正用のより特殊なクラスを作成する良い機会でもあります。

class ScreenContent {

    // ...

    GammaCorrector gammaCorrector = new GammaCorrector();

    // ...

    int[][] image;

    void correctGamma() {
        for (int[] row : image) {
            for (int i = 0; i &lt; row.length; i++) {
                row[i] = gammaCorrector.correct(row[i], gamma);
            }
        }
    }
}

class GammaCorrector {
    private int[] unpacked = new int[4];

    public int correct(int pixel, float gamma) {
        float ginv = 1 / gamma;
        ColorUtils.unpackInt(pixel, ColorUtils.TYPE_ARGB, unpacked);
        for(int i = 0; i &lt; rgbVals.length; i++) {
            rgbVals[i] = (int) Math.round(255 - Math.pow(unpacked[i] / 255.0, ginv));
        }
        return ColorUtils.packInt(unpacked);
    }
}

structアンパックされた値を保持するようなクラスを作成することで、配列とループを排除できます。最も内側のfor()ループは、毎秒数十万回実行されますが、ループが実行されるたびに実行される反復回数はわずかです。最新の CPUはこのケースをうまく処理できるはずですが、試してみる価値はあります。

バインドされたスレッド プールを使用して、行を並列にイメージすることもできます。CPU コアごとに 1 つのスレッドという制限は理にかなっているかもしれません。グラフィックス ハードウェアの設計では、通常、各ピクセルの操作は類似しているが独立しており、優れたパフォーマンスを実現するために大規模な並列処理が行われるという事実に重点が置かれています。

また、JVM のデバッグ ビルドを使用して、生成された命令を調べて、洞察を深めることも検討してください。理想的には、コードの変更をできるだけ少なくし、JVM が最適化の機会を逃した箇所のみに変更を加えます。

最終的にネイティブ コードに移行する場合は、必要に応じて SSE 命令のいくつかを使用することを検討してください。パックされた整数で機能する操作があり、基本的にはパックされた整数の各バイトに同じ操作を適用し、アンパック、計算、および再パックする必要はないと思います。これにより、かなりの時間を節約できますが、ガンマの計算方法が変更される可能性があります。利点は、高速であることです。1 つの SSE レジスタを使用すると、1 つの命令で 16 バイトを操作できます。この種の並列処理は、労力をかけて活用する価値があります。

于 2013-01-13T00:40:57.410 に答える
0

これを行う別の方法は、OpenGL を使用することです。(LWJGL は Java でそれを許可すると思います。) ストレートからガンマに補正されたテーブルを含む 1D テクスチャをアップロードし、ガンマ テーブルをピクセルに適用する glsl シェーダーを作成できます。それが現在の処理モデルにどのように適合するかはわかりませんが、1920x1080 HD RGBA フレームを常にリアルタイムで処理するために使用しています。

于 2013-01-13T01:36:50.283 に答える