webGLで4K Introに挑んでも無茶じゃないと思う

#いきなりですが、ここに追記です
結論からですが、webGLで4Kは無茶でした。crinkler.exeは、文字も圧縮してくれるようです。
せっかく書いたのだから削除せずに置いておきます。4kと言わず8kとかなら、何とかなるような気がします。

#4K introをwebGLでやってみます
4k introをC言語を書いてcrinkler.exeで実行ファイルを作るのが、余りにもニッチ過ぎるので、webGLで4Kをやる方法を模索してみた。今のところwebGLの方が0.2kばかり不利だけど、このくらいなら問題無いと思う。たぶん？！htmlもミニ化する方法もまだあると思うし、何しろ、お気楽と言う事で合格でしょ。
# とりあえずミニ化してないHTML
いきなりミニ化したwebGL載せても引かれるので、解りやすいとこから、とは言っても難解部分もあります。わかりやすくするのも面倒なところもあるので端折ってます。基本は、music shaderと一枚ポリゴンのgraphic shaderで作ってます。shaderはshadertoyでnewすれば出てくるテンプレートを、ちょっと直して使ってます。

```HAML
<html>
<head>
  <style>
*{
  margin: 0;
  padding: 0;
  overflow: hidden;
}
  </style>
</head>
<body>
<canvas id="c"></canvas>
<script id="ms" type="x-shader/x-vertex">#version 300 es
uniform float sampleRate; 
out vec2 gain; 
void main(){
  float time=float(gl_VertexID)/sampleRate;
  gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));
}
</script>
<script id="gs" type="x-shader/x-fragment">#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
void main() {
  vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y;
  vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4));
  fragColor=vec4(col,1);
}
</script>
<script>
c.width = window.innerWidth;
c.height = window.innerHeight;
GL = c.getContext("webgl2") || c.getContext("experimental-webgl2");
var duration = 120;
var audio = new AudioContext();
var sampleRate = audio.sampleRate;
var bufferSize = audio.sampleRate * duration;
var audioBuffer = audio.createBuffer(2, bufferSize, audio.sampleRate);
var node = audio.createBufferSource();
node.buffer = audioBuffer;
node.loop = false;
node.connect(audio.destination);
var aryBuffer = new ArrayBuffer(bufferSize*4*2);
var dataView = new DataView(aryBuffer);
with(GL) {
  p = createProgram ();
  for (t = 0; s = createShader(VERTEX_SHADER - t); compileShader(s), attachShader(p, s)) {
    shaderSource(s,t++ ? "#version 300 es\nvoid main(){}":ms.text);
  }
  transformFeedbackVaryings(p, ['gain'], SEPARATE_ATTRIBS);
  linkProgram(p);
  useProgram(p);
  uniform1f(getUniformLocation(p, "sampleRate"), sampleRate); 
  VBOs = [createBuffer(),createBuffer()];
  for (i = 0; i < 2; i++) {
    bindBuffer(ARRAY_BUFFER, VBOs[i]);
    bufferData(ARRAY_BUFFER, new Float32Array(bufferSize*2), STATIC_DRAW);
  }  
  bindBufferBase(TRANSFORM_FEEDBACK_BUFFER, 0, VBOs[0]);
  enable(RASTERIZER_DISCARD);
  beginTransformFeedback(POINTS);
  drawArrays(POINTS, 0, bufferSize);
  endTransformFeedback();
  disable(RASTERIZER_DISCARD);
  bindBufferBase(TRANSFORM_FEEDBACK_BUFFER, 0, null);
  bindBuffer(ARRAY_BUFFER, VBOs[0]);
  getBufferSubData(ARRAY_BUFFER, 0, dataView); 
  p = createProgram();
  for (t = 0; s = createShader(VERTEX_SHADER - t); compileShader(s), attachShader(p, s)) {
    shaderSource(s,t++ ? gs.text:"#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");
  }
  linkProgram(p);
  useProgram(p);
  uniform2f(getUniformLocation(p, "resolution"), c.width, c.height);
  l=function () { 
    uniform1f(getUniformLocation(p, "time"),Math.min(audio.currentTime,duration));
    drawArrays(TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(l);
  };
}
var buf = new Float32Array(aryBuffer);
var data0 = audioBuffer.getChannelData(0);
var data1 = audioBuffer.getChannelData(1);
for(var i = 0; i < bufferSize; i++) {
  data0[i] = buf[i*2];
  data1[i] = buf[i*2+1];
}
node.start(0);
l();
</script>
</body>
</html>
```

#ワンポイントjavaScriptスキル
```
<script id="ms" type="x-shader/x-vertex">...</script>
```
これの中にあるshaderのソースの文字列は

```
ms.text
```
で取り出せます。idが単純に変数になります。

```
<canvas id="c"></canvas>
```
と書いておけばjavaScript側で、あっさりと

```
GL = c.getContext("webgl2") || c.getContext("experimental-webgl2");
```
って処理できます。

#JavaScriptをミニ化していきます
本来ならば改行を消して1行が王道ですが可読性の為、改行は入れてあります。

```haml
<html>
<style>
*{
  margin: 0;
  padding: 0;
  overflow: hidden;
}
</style>
<body>
<canvas id="c"/>
<script>
c.width=window.innerWidth;
c.height=window.innerHeight;
G=c.getContext("webgl2")||c.getContext("experimental-webgl2");
for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];
D=120;
A=new AudioContext();
R=A.sampleRate;
S=R*D;
B=A.createBuffer(2,S,R);
N=A.createBufferSource();
N.buffer=B;
N.connect(A.destination);
a=new ArrayBuffer(S*8);
with(G){
  o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};
  p=rrm();
  o("#version 300 es\nvoid main(){}",
  `#version 300 es\n
  uniform float sampleRate;
  out vec2 gain;
  void main(){
    float time=float(gl_VertexID)/sampleRate;
    gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));
  }
  `);
  rrs(p,["gain"],EES);
  igm(p);
  srm(p);
  n1f(eon(p,"sampleRate"),R); 
  V=[rur(),rur()];
  for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);
  bindBufferBase(RRR,0,V[0]);
  enable(AZD);
  eak(POINTS);
  ras(POINTS,0,S);
  nsk();
  disable(AZD);
  bindBufferBase(RRR,0,null);
  ifr(RUR,V[0]);
  eea(RUR,0,new DataView(a)); 
  p=rrm();
  o(`#version 300 es\n
  precision highp float;
  out vec4 fragColor;
  uniform vec2 resolution;
  uniform float time;
  void main() {
    vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y;
    vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4));
    fragColor=vec4(col,1);
    }
  `,"#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");
  igm(p);
  srm(p);
  n2f(eon(p,"resolution"),c.width,c.height);
  l=function(){ 
    n1f(eon(p,"time"),Math.min(A.currentTime,D));
    ras(REP,0,4);
    requestAnimationFrame(l)
  };
}
b = new Float32Array(a);
for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]= b[i*2+j];
N.start(0);
l();
</script>
</body>
</html>
```

ここでのwebGLのミニ化については
http://jsdo.it/cx20/5ivN
ここに詳しく書かれてます。ポイントは

```
for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];
```
です。よく考えられてます。

javaScript限らずにカウントダウンしていく、たまに見かけるミニ化スキル

```
for(j=2;j--;)
```
も使ってます。これは、console.log()とかで追っかけみれば挙動が解ります。

```
for(j=2;--j;)
```
とすると挙動が変わります。最後を１にしたい時に使えます。

これの改行抜きは

```
<html><style>*{margin:0;padding:0;overflow:hidden;}</style><body><canvas id="c"/><script>c.width=window.innerWidth;c.height=window.innerHeight;G=c.getContext("webgl2")||c.getContext("experimental-webgl2");for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];D=120;A=new AudioContext();R=A.sampleRate;S=R*D;B=A.createBuffer(2,S,R);N=A.createBufferSource();N.buffer=B;N.connect(A.destination);a=new ArrayBuffer(S*8);with(G){o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};p=rrm();o("#version 300 es\nvoid main(){}","#version 300 es\nuniform float sampleRate; out vec2 gain; void main(){  float time=float(gl_VertexID)/sampleRate;  gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));}");rrs(p,["gain"],EES);igm(p);srm(p);n1f(eon(p,"sampleRate"),R);V=[rur(),rur()];for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);bindBufferBase(RRR,0,V[0]);enable(AZD);eak(POINTS);ras(POINTS,0,S);nsk();disable(AZD);bindBufferBase(RRR,0,null);ifr(RUR,V[0]);eea(RUR,0,new DataView(a));p=rrm();o("#version 300 es\nprecision highp float;out vec4 fragColor;uniform vec2 resolution;uniform float time;void main() {  vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y;  vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4));  fragColor=vec4(col,1);}","#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");igm(p);srm(p);n2f(eon(p,"resolution"),c.width,c.height);l=function(){n1f(eon(p,"time"),Math.min(A.currentTime,D));ras(REP,0,4);requestAnimationFrame(l)};}b=new Float32Array(a);for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]=b[i*2+j];N.start(0);l();</script></body></html>
```

これのファイルサイズは1.57 KB (1,608 バイト)

#ちょっと　new Audio("data:Audio/WAV;base64...が失敗した件
ここ　http://www.p01.org/JS1K_2016_lrnz_snglrt/  にAudio()を使ったミニ化のスキルが載っていた。しかし、試したところ、firefoxでは走ったが、chromeでは動かなかった。残念。今後の動向を見守りたいです。TDFのデフォルトがchromeであるからミニ化の魅力はあるけど見送りですかね。


#crinkler.exeで作った実行ファイルと比べてみる
以前書いた記事
お試し 4K intro　https://qiita.com/gaziya5/items/667a23b3bfe453a90cc1
で使ったshaderでwebGLを書くと

```
<html><style>*{margin:0;padding:0;overflow:hidden;}</style><body><canvas id="c"/><script>c.width=window.innerWidth;c.height=window.innerHeight;G=c.getContext("webgl2")||c.getContext("experimental-webgl2");for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];D=120;A=new AudioContext();R=A.sampleRate;S=R*D;B=A.createBuffer(2,S,R);N=A.createBufferSource();N.buffer=B;N.connect(A.destination);a=new ArrayBuffer(S*8);with(G){o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};p=rrm();o("#version 300 es\nvoid main(){}","#version 300 es\nout vec2 gain;uniform float sampleRate;void main(){float time = float(gl_VertexID) / sampleRate;gain = vec2(sin(6.2831*440.0*fract(time)));}");rrs(p,["gain"],EES);igm(p);srm(p);n1f(eon(p,"sampleRate"),R);V=[rur(),rur()];for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);bindBufferBase(RRR,0,V[0]);enable(AZD);eak(POINTS);ras(POINTS,0,S);nsk();disable(AZD);bindBufferBase(RRR,0,null);ifr(RUR,V[0]);eea(RUR,0,new DataView(a));p=rrm();o("#version 300 es\nprecision highp float;out vec4 fragColor;uniform vec2 resolution;uniform float time;void main(){vec2 p = (2.0 * gl_FragCoord.xy - resolution.xy) / resolution.y;p += vec2(cos(time), sin(time)) * 0.5;float g = exp(-1.5 * dot(p,p));fragColor = vec4(g, g, g, 1.0);}","#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");igm(p);srm(p);n2f(eon(p,"resolution"),c.width,c.height);l=function(){n1f(eon(p,"time"),Math.min(A.currentTime,D));ras(REP,0,4);requestAnimationFrame(l)};}b=new Float32Array(a);for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]=b[i*2+j];N.start(0);l();</script></body></html>
```

これのファイルサイズは、1.60 KB (1,639 バイト)
記事の中に、この実行ファイルのサイズは、1.43 KBと書いてあります。実際crinkler.exeを使うのが面倒なので記事の内容を鵜呑みです。その差は0.17Kです。
なんとなくjavaScriptでもいいかな？って感じです。
ちなみにshader部分を除いたファイルサイズは1192byte。4kのファイルサイズは4096byte。なのでmusic shaderとgraphic shaderを合わせて2904chars使える。shadertoyのエディターだとcharsのカウントが解るので目安になります。

#実際のdemosceneの細かいルールは把握してない。
個人的にdemosceneに面白くて首を突っ込んでるけど、実際の細かいルールは把握していない。もしかして、これはJS4kというジャンルになるかもしれない。でもTDFは、これを問題なく受け入れてくれるし、４Kを超えてもエントリー可能です。
javaScriptで、4k introならエントリーも増えるような気がする。やりましょう。8k introもokだよ。結構、縛りは緩いTDFだよね。これを参考にしてもらえれば幸いです。

#追記
このソースをfirefoxで試したところ、音がでませんでした。TDFのデフォルトがchromeのままなら使えます。ミニ化のせいなのか、ちょっと不明。TDFの時のブラウザのヴァージョンとかチェックしないと怖い気はする。
htmlで```<body></body>```を消してもchromeでは動いてました。まあ、やり過ぎもキケンなので、探り探りやってほうが良いのかな？