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


![CVQGQQMU8AA0kGK.jpg](https://qiita-image-store.s3.amazonaws.com/0/54192/a7ebc279-e4e1-d910-7662-6df1b1bd6aa9.jpeg)

http://kyorohiro.github.io/umiuni2d/draw_snow/web/main.html


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

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


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

<br>

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

![texture_atlas_01.png](https://qiita-image-store.s3.amazonaws.com/0/54192/fdbc2027-fa4d-ce33-1d08-683165663a4f.png)

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

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

<br>

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

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

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

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

![drawimage.jpg](https://qiita-image-store.s3.amazonaws.com/0/54192/b98cfebf-b8f9-9fff-4de2-cbb046706dbb.jpeg)


表示したい画像ごとに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;
}

```



<br>
<br>

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

![drawrectimage.jpg](https://qiita-image-store.s3.amazonaws.com/0/54192/bfb65439-98a7-b28f-1662-17837fd90aac.jpeg)

ひとつの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側からみると、この設計は、とても自然な発想から生まれているのが解るでしょう。

<br>
<br>

# 体感してみよう。
![CVQGQQMU8AA0kGK.jpg](https://qiita-image-store.s3.amazonaws.com/0/54192/a7ebc279-e4e1-d910-7662-6df1b1bd6aa9.jpeg)

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

#### (丸) Bad know-how (※シークレットウインドウで開いてね)
##### [軽め]
http://kyorohiro.github.io/umiuni2d/draw_snow_badknowhow/web/main.html
##### [重め]
http://kyorohiro.github.io/umiuni2d/draw_snow_badknowhow_1000/web/main.html

#### (丸) Good know-how (※シークレットウインドウで開いてね)
##### [軽め]
http://kyorohiro.github.io/umiuni2d/draw_snow/web/main.html
##### [重め]
http://kyorohiro.github.io/umiuni2d/draw_snow_good_knowhow_1000/web/main.html

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

## ほそく

#### DartでWebGL入門-テクスチャ編
http://sbfl.net/blog/2014/10/05/webgl-tutorials-with-dart-texture/

#### Texture Atlas
https://en.wikipedia.org/wiki/Texture_atlas

#### デモのソース: Umiuni2D
https://github.com/kyorohiro/doc_2dgame

#### 2Dゲームに関する、kyorohiroのメモ(まだ何もない)
https://www.gitbook.com/book/kyorohiro/doc_2dgame/details

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

* https://play.google.com/store/apps/details?id=info.kyorohiro.umiuni2d.demo.mino

* http://kyorohiro.github.io/umiuni2d/mino2/web/main.html


# Thanks!

![umiuni2d_minon_screen.png](https://qiita-image-store.s3.amazonaws.com/0/54192/eac83355-712b-7c8e-858f-829b10d4ce94.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)




<br>
<br>
-------
Kyorohiro Work
http://kyorohiro.strikingly.com/
http://kyorohiro.github.io/