LoginSignup
1
0

More than 3 years have passed since last update.

Flash Advent Calendar 12日目 - WebGL導入への道のり05 -

Posted at

WebGLで色々な描画ができるようにはなってきたのですが
複雑な描画の実装を進めていくとGPUの負荷が高くなり、速度が低下してきました。

その時に起きた低下原因と解消方を書いていこうと思います。

  1. Framebufferを使っていない
  2. コール数が多い(createしたbuffer情報を使い回せていない etc...)
  3. 必要最低限の領域に対して描画を行う

Framebufferを使っていない

そもそもとして、メインのFramebufferだけで描画処理を行っていた...
オフスクリーンレンダリングができていない状態でした。

この時、WebGL1.0で実装したいたので、Framebufferを導入するとアンチエリアスが使えない!?
っという事態になりました。
WebGL2.0への移行が必須になりシェーダーをWebGL2.0に対応する課題が発生したのですが
それはまた別の機会に・・・orz

メインのFramebufferに描画するとcanvas側に描画が適用される為、無駄なI/Oが発生してしまいます。
サブのFramebufferは完全にメモリだけの描画になるため、負荷が軽減されます。

// 描画だけで使うFramebufferを生成
this.frameBuffer = this.gl.createFramebuffer();
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);

// 書き込むtextureを生成
this.texture = this.gl.createTexture();
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);

this.gl.framebufferTexture2D(
    this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0,
    this.gl.TEXTURE_2D, this.texture, 0
);

// 描画情報を書き込むRenderbufferを生成
this.stencilBuffer = this.gl.createRenderbuffer();

this.gl.renderbufferStorage(
    this.gl.RENDERBUFFER,
    this.gl.STENCIL_INDEX8,
    width, height
);

this.gl.framebufferRenderbuffer(
    this.gl.FRAMEBUFFER, this.gl.STENCIL_ATTACHMENT,
    this.gl.RENDERBUFFER, this.stencilBuffer
);

この仕組みを導入する事でGPUの負荷は結構軽減しました。
ただ、この仕組みの導入で別の問題が発生しました。

コール数が多い

WebGLの速度関連で検索するとよく見かける言葉です・・・

createしたbuffer情報を使い回せていない

  • createTexture
  • createRenderbuffer
  • createBuffer

などなど
作ったbuffer情報が不要になったら破棄していたのですが
そもそもcreate系の関数のコール負荷が非常に高かったです。

なので、使い終わったbufferをpoolして使い回す事でGPUへの負荷を軽減しました。

また、次に利用するbufferのサイズが一致していれば
さらに余計な処理を省く事ができます。


/**
 * @param  {number} width
 * @param  {number} height
 * @return {WebGLRenderbuffer}
 * @public
 */ 
getStencilBuffer (width, height)
{
    for (let idx = 0; idx < this.objectPool.length; idx++) {
        const stencilBuffer = this.objectPool[idx];
        if (stencilBuffer.width === width && stencilBuffer.height === height) {
            this.objectPool.splice(idx, 1);
            return stencilBuffer;
        }
    }

    const stencilBuffer  = this.gl.createRenderbuffer();
    stencilBuffer.width  = 0;
    stencilBuffer.height = 0;
    return stencilBuffer;
}

/**
 * @param  {number} width
 * @param  {number} height
 * @return {WebGLRenderbuffer}
 * @public
 */ 
create (width, height)
{
    const stencilBuffer = this.getStencilBuffer(width, height);

    if (stencilBuffer.width !== width || stencilBuffer.height !== height) {
        stencilBuffer.width  = width;
        stencilBuffer.height = height;

        this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, stencilBuffer);
        this.gl.renderbufferStorage(
            this.gl.RENDERBUFFER,
            this.gl.STENCIL_INDEX8,
            width, height
        );
    }

    return stencilBuffer;
}

この例だと

  • bindRenderbuffer
  • renderbufferStorage

が省略されます。
コール数が減るとGPUの負荷がかなり軽減されます。

必要最低限の描画領域に対して描画を行えていない

scissorなどで描画に必要な領域だけに対して描画したりclearする事でGPUの負担を減らす事ができました。

/**
 * @param  {number} x
 * @param  {number} y
 * @param  {number} w
 * @param  {number} h
 * @retuen {void}
 * @public
 */
clear (x, y, w, h)
{
    this.gl.enable(this.gl.SCISSOR_TEST);
    this.gl.scissor(x, y, w, h);
    this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.STENCIL_BUFFER_BIT);
    this.gl.disable(this.gl.SCISSOR_TEST);
}

まだまだ、やれる事はいっぱいあると思うのですが
大きな改善が見れたポイントを書きました。

明日は、テキスト入力をどうやって再現したかを書こうと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0