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

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

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

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


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

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


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

```javascript
// 描画だけで使う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のサイズが一致していれば
さらに余計な処理を省く事ができます。

```javascript

/**
 * @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の負担を減らす事ができました。

```javascript
/**
 * @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);
}
```

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

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