LoginSignup
8
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-06

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/

8
9
6

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
8
9