search
LoginSignup
9

More than 5 years have passed since last update.

posted at

updated at

Dart x WebGL DrawCallを体感してみよう。こんなにも速度が違うのだ。のだのだ。

CVQGQQMU8AA0kGK.jpg

本文では、SpriteSheetを使用して、描画パフォーマンスを改善する方法について解説します。

また、実際に、このパフォーマンスの違いを体感できるように、Webアプリを用意しました。

※ Dartで書きましたが、Dart以外の方も読めるようになっています。


SpriteSheetへの違和感がありませんでしたか?

皆さんはSpriteSheet(Texture Atlas)を利用したプログラムに違和感をもった事はありませんでしたか?

texture_atlas_01.png

「なんで、こんな面倒な事するの?」「別々のファイルの方が変更楽でシンプルではないか?」

はい、その判断は良いと思います。私もそう思います。しかし。これは、GPUに即した設計がAPIに反映されているだけなのです。


それは、DrawCallを減らすテクニックなのだ!!

実際の描画はGPUを通して行われます。そのCPUとGPU間のデータの転送が、DrawCallです。

このDrawCallは比較的に処理が重いのです。ですから、このDrawCallを何度も読んでしまうと処理が重くなるわけです。

画像を表示するたびに毎回DrawCallする場合。

drawimage.jpg

表示したい画像ごとにDrawする場合のWebGLのコードを思い出してみましょう。

※こんなのだったかな、くらいの感じで!!

import 'dart:html';
import 'dart:async';
import 'dart:web_gl';
import 'dart:typed_data';

List<String> vsSource = [
  "attribute vec3 vertexPosition;",
  "attribute vec2 texCoord;",
  "varying vec2 textureCoord;",
  "void main() {",
  "  textureCoord = texCoord;",
  "  gl_Position = vec4(vertexPosition, 1.0);",
  "}"];

List<String> fsSource = [
  "precision mediump float;",
  "uniform sampler2D texture;",
  "varying vec2 textureCoord;",
  "void main() {",
  " gl_FragColor = texture2D(texture, textureCoord);",
  "}"
  ];


main() async {
  ImageElement elm = await loadImage("chara.jpeg");
  CanvasElement canvasElement = new CanvasElement(width: 600, height: 500);
  document.body.append(canvasElement);

  RenderingContext GL = canvasElement.getContext3d();
  GL.clearColor(0.5, 0.8, 0.8, 0.7);
  GL.clearDepth(1.0);
  GL.clear(COLOR_BUFFER_BIT);
  GL.flush();

  //
  // shader
  Shader vs = GL.createShader(VERTEX_SHADER);
  GL.shaderSource(vs, vsSource.join("\n"));
  GL.compileShader(vs);

  Shader fs = GL.createShader(FRAGMENT_SHADER);
  GL.shaderSource(fs, fsSource.join("\n"));
  GL.compileShader(fs);

  Program pro = GL.createProgram();
  GL.attachShader(pro, vs);
  GL.attachShader(pro, fs);
  GL.linkProgram(pro);
  GL.useProgram(pro);

  //
  // texture
  Texture tex = GL.createTexture();
  GL.bindTexture(TEXTURE_2D, tex);
  GL.texImage2D(TEXTURE_2D, 0, RGBA, RGBA, UNSIGNED_BYTE, elm);
  GL.bindTexture(TEXTURE_2D, null);
  GL.bindTexture(TEXTURE_2D, tex);

  GL.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S,  CLAMP_TO_EDGE);
  GL.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T,  CLAMP_TO_EDGE);
  GL.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST);
  GL.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST);


  //
  // vertex
  Buffer vertexBuffer = GL.createBuffer();
  Buffer indexBuffer = GL.createBuffer();
  int vertexPositionLocation = GL.getAttribLocation(pro, "vertexPosition");
  int texCoordLocation     = GL.getAttribLocation(pro, "texCoord");


   GL.bindBuffer(ARRAY_BUFFER, vertexBuffer);

   GL.enableVertexAttribArray(vertexPositionLocation);
   GL.enableVertexAttribArray(texCoordLocation);

   GL.vertexAttribPointer(vertexPositionLocation, 3, FLOAT, false, (3+2)*Float32List.BYTES_PER_ELEMENT, 0);
   GL.vertexAttribPointer(texCoordLocation, 2, FLOAT, false, (3+2)*Float32List.BYTES_PER_ELEMENT, 3 * Float32List.BYTES_PER_ELEMENT);

   Float32List vertices = new Float32List.fromList(<double>[
     -1.0,  1.0, 0.0, 0.0, 0.0,       
      1.0,  1.0, 0.0, 1.0, 0.0,
     -1.0, -1.0, 0.0, 0.0, 1.0,
      1.0, -1.0, 0.0, 1.0, 1.0]);

   Uint16List indices = new Uint16List.fromList(<int>[
      0, 1, 2, 2, 3, 1]);

   GL.bindBuffer(ARRAY_BUFFER, vertexBuffer);
   GL.bufferDataTyped(ARRAY_BUFFER, vertices, STATIC_DRAW);

   GL.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer);
   GL.bufferDataTyped(ELEMENT_ARRAY_BUFFER, indices, STATIC_DRAW);

   GL.drawElements(TRIANGLES, indices.length, UNSIGNED_SHORT, 0);
}

Future<ImageElement> loadImage(String resName) async {
  ImageElement elm = new ImageElement(src: resName);
  Completer c = new Completer();
  elm.onLoad.listen(c.complete);
  await c.future;
  return elm;
}




一度のGPUの処理で同時に表示できれば、一度のDrawCallで済む。

drawrectimage.jpg

ひとつのTextureに複数の画像が含まれていれば、それをGPU側で取捨選択して、複数の画像を表示できるわけです。

さきほどのコードの以下の部分だけ変えてみましょう。画面に画像が2つ表示されます。

   Float32List vertices = new Float32List.fromList(<double>[
     -0.5,  0.5, 0.0, 0.0, 0.0,       
     0.5,  0.5, 0.0, 1.0, 0.0,
     -0.5, -0.5, 0.0, 0.0, 1.0,
     0.5, -0.5, 0.0, 1.0, 1.0,
     //
     -1.0,  -0.5, 0.0, 0.25, 0.25,       
     -0.5,  -0.5, 0.0, 0.75, 0.25,
     -1.0, -1.0, 0.0, 0.25, 0.75,
     -0.5, -1.0, 0.0, 0.75, 0.75
      ]);

   Uint16List indices = new Uint16List.fromList(<int>[
      0, 1, 2, 2, 3, 1,
      //
      4, 5, 6, 6, 7, 5]);

これが、SpriteSheet的な手法が用いられ続けてる一つ回答です。GPU側からみると、この設計は、とても自然な発想から生まれているのが解るでしょう。




体感してみよう。

CVQGQQMU8AA0kGK.jpg

bad know-how を実行するとPCがフリーズするかもしれないので注意!!ニコニコ動画とかは一度停止していからクリックしてね!!

(丸) Bad know-how (※シークレットウインドウで開いてね)

[軽め]

[重め]

(丸) Good know-how (※シークレットウインドウで開いてね)

[軽め]

[重め]

※ 僕のPCだと、このくらいでも明らかな差がでるのですが、出てなかったらごめん。Chromeよりも、Firefoxの方が顕著に差がでるかも。

ほそく

DartでWebGL入門-テクスチャ編

Texture Atlas

デモのソース: Umiuni2D

2Dゲームに関する、kyorohiroのメモ(まだ何もない)

今回のノウハウを生かしたゲームのデモ

Thanks!

umiuni2d_minon_screen.png

最後まで、読んでいただきまして、ありがとうございました。
(ref http://pixiv.me/kyorohiro)
(ref https://play.google.com/store/apps/details?id=info.kyorohiro.umiuni2d.demo.mino)
(ref http://kyorohiro.github.io/umiuni2d/mino2/web/main.html)



Kyorohiro Work
http://kyorohiro.strikingly.com/
http://kyorohiro.github.io/

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
What you can do with signing up
9