概要
webgl2.0からtransformFeedbackが利用できるようになったので、GPUに計算させた結果をjavascript側に戻すことができます。
これを利用して、最近話題になっているGPGPUをすることができます。
まぁwebgl1.0の場合でもgl.ReadPixelsでfragmentShaderの計算結果を戻すことはできますけどね。
非常に簡単なサンプル
とりあえず簡単なサンプルはこんな感じです。
<!DOCTYPE html>
<html style="width:100%;height:100%">
<head>
<meta charset="UTF-8" />
<title>bsTest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<canvas id="canvas"></canvas>
<div id="result"></div>
<script type="text/vertex-shader" id="vs">#version 300 es
out float result;
void main() {
result = 1.0 + 1.0;
}
</script>
<script type="text/fragment-shader" id="fs">#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1.0);
}
</script>
<script type="text/javascript">
window.onload = () => {
var canvas = document.getElementById("canvas");
var result = document.getElementById("result");
var gl2 = canvas.getContext("webgl2");
if(!gl2) {
throw new Error("webgl2.0 is not supported.");
}
// vertex shader
var vs = gl2.createShader(gl2.VERTEX_SHADER);
gl2.shaderSource(vs, document.getElementById("vs").innerText);
gl2.compileShader(vs);
if(!gl2.getShaderParameter(vs, gl2.COMPILE_STATUS)) {
throw new Error("failed to make shader.");
}
// fragment shader
var fs =gl2.createShader(gl2.FRAGMENT_SHADER);
gl2.shaderSource(fs, document.getElementById("fs").innerText);
gl2.compileShader(fs);
if(!gl2.getShaderParameter(fs, gl2.COMPILE_STATUS)) {
throw new Error("failed to make shader.");
}
// programつくる
var program = gl2.createProgram();
gl2.attachShader(program, vs);
gl2.attachShader(program, fs);
gl2.transformFeedbackVaryings(program, ["result"], gl2.SEPARATE_ATTRIBS);
gl2.linkProgram(program);
if(!gl2.getProgramParameter(program, gl2.LINK_STATUS)) {
throw new Error("failed to make program.");
}
gl2.useProgram(program);
var vTransformFeedback = gl2.createBuffer();
var transformFeedback = gl2.createTransformFeedback();
gl2.bindBuffer(gl2.ARRAY_BUFFER, vTransformFeedback);
gl2.bufferData(gl2.ARRAY_BUFFER, Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
gl2.bindBuffer(gl2.ARRAY_BUFFER, null);
// あとは描画を実施する
gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vTransformFeedback);
gl2.beginTransformFeedback(gl2.POINTS);
gl2.drawArrays(gl2.POINTS, 0, 1);
gl2.endTransformFeedback();
// 結果を取り出す
var array = new Float32Array(1);
gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, array);
console.log(array);
result.innerText = "ok:" + array[0];
}
</script>
</body>
</html>
とりあえずこのコードでは、GPUに1.0 + 1.0を計算させてます。
もちろん答えは2.0になります。
解説
- vertex演算のコードを作成し、GPUに託す
- fragment演算のコードを作成し、GPUに託す
- 情報データを準備してGPUに渡す
- 実際の描画を実施する(普通はここで描画する)
- 描画での計算結果をtransformFeedbackでjs側に戻す
という動作をします。
vertex shader
<script type="text/vertex-shader" id="vs">#version 300 es
out float result;
void main() {
result = 1.0 + 1.0;
}
</script>
main関数の中身が実施されるもの。
out float resultで出力で利用する変数を定義
ということをやってます。
今回は1.0 + 1.0を計算してますが
void main() {
result = 2.0 * 3.0;
}
とかにすると2 x 3を計算させることができます。
fragment shader
<script type="text/fragment-shader" id="fs">#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1.0);
}
</script>
実際の色を出力するときに利用するプログラムです。
一応定義しますが、今回は利用しないので、どうでもいいです。
とりあえずrgba = (1.0, 1.0, 1.0, 1.0);真っ白にしてあります。
下準備
var canvas = document.getElementById("canvas");
var result = document.getElementById("result");
var gl2 = canvas.getContext("webgl2");
if(!gl2) {
throw new Error("webgl2.0 is not supported.");
}
// vertex shader
var vs = gl2.createShader(gl2.VERTEX_SHADER);
gl2.shaderSource(vs, document.getElementById("vs").innerText);
gl2.compileShader(vs);
if(!gl2.getShaderParameter(vs, gl2.COMPILE_STATUS)) {
throw new Error("failed to make shader.");
}
// fragment shader
var fs =gl2.createShader(gl2.FRAGMENT_SHADER);
gl2.shaderSource(fs, document.getElementById("fs").innerText);
gl2.compileShader(fs);
if(!gl2.getShaderParameter(fs, gl2.COMPILE_STATUS)) {
throw new Error("failed to make shader.");
}
// programつくる
var program = gl2.createProgram();
gl2.attachShader(program, vs);
gl2.attachShader(program, fs);
gl2.transformFeedbackVaryings(program, ["result"], gl2.SEPARATE_ATTRIBS);
gl2.linkProgram(program);
if(!gl2.getProgramParameter(program, gl2.LINK_STATUS)) {
throw new Error("failed to make program.");
}
gl2.useProgram(program);
var vTransformFeedback = gl2.createBuffer();
var transformFeedback = gl2.createTransformFeedback();
gl2.bindBuffer(gl2.ARRAY_BUFFER, vTransformFeedback);
gl2.bufferData(gl2.ARRAY_BUFFER, Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
gl2.bindBuffer(gl2.ARRAY_BUFFER, null);
これらはwebgl2.0の下準備処理です。
コンテキストをcanvasから取得して
vertexとfragmentのshaderプログラムを作成 -> プログラムソース設定 -> コンパイル -> 処理確認として
動画プログラムを2つのshaderからつくって
transformFeedbackを準備するということをやってます。
実際に計算させる。
// あとは描画を実施する
gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vTransformFeedback);
gl2.beginTransformFeedback(gl2.POINTS);
gl2.drawArrays(gl2.POINTS, 0, 1);
gl2.endTransformFeedback();
// 結果を取り出す
var array = new Float32Array(1);
gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, array);
console.log(array);
result.innerText = "ok:" + array[0];
普通のwebglの場合はrequestAnimationFrameやsetIntervalの処理の中で実施するwebglの描画処理の部分です。
今回はいきなりtransformFeedbackを発動させてdrawArraysで描画を1つの頂点についてのみ実施しています。
これでとりあえずGPUに計算を実施させてその結果をjavascript側(CPU側)で取得できました。