WebGLで色々な描画ができるようにはなってきたのですが
複雑な描画の実装を進めていくとGPUの負荷が高くなり、速度が低下してきました。
その時に起きた低下原因と解消方を書いていこうと思います。
- Framebufferを使っていない
- コール数が多い(createしたbuffer情報を使い回せていない etc...)
- 必要最低限の領域に対して描画を行う
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);
}
まだまだ、やれる事はいっぱいあると思うのですが
大きな改善が見れたポイントを書きました。
明日は、テキスト入力をどうやって再現したかを書こうと思います。