Ajustez le ciel étoilé sur WebGL en 1009 octets de JavaScript

Deux choses remplissent toujours l'âme de surprises et de révérences nouvelles et toujours plus fortes, plus nous y pensons souvent et plus longtemps - c'est le ciel étoilé au-dessus de moi et la loi morale en moi. Immanuel Kant

JS1k est un concours annuel où vous devez accueillir une démo, un jeu ou quoi que ce soit, en 1024 caractères en JavaScript. Cette année, ma démo a pris la quatrième place (jusqu'au troisième il n'y avait pas assez de deux points). Vous pouvez regarder la démo sur le site Web de JS1k . Qui ne s'ouvre pas ou ne fonctionne pas, devrait ressembler à ceci:



Le code source minifié et complet se trouve sur github . Et sous la coupe se trouve une analyse de la façon dont JavaScript est maintenant minimisé pour de tels concours.


Clause de non-responsabilité


La principale beauté de la démo est un fragment shader de Pablo Roman Andrioli. Pablo est un artiste qui travaille avec les fractales, et sur le forum fractalforums il donne quelques détails sur les calculs. Ma tâche consistait à emballer un shader et un code WebGL de 1024 octets.


Initialisation WebGL


L'encapsuleur JS1k au début de la démonstration fournit un contexte WebGL dans la variable globale g . Malgré cela, travailler avec WebGL est très bavard. Par exemple, pour ajouter un vertex shader à un programme, 159 caractères sont nécessaires:


// Define a new program p=g.createProgram(); // Basic vertex shader s=g.createShader(VERTEX_SHADER); g.shaderSource(s,"attribute vec2 p;void main(){gl_Position=vec4(p,0,1);}"); // Compile and attach it to the program g.compileShader(s); g.attachShader(p,s); 

Pour résoudre ce problème, toutes les solutions JS1k des dernières années utilisent une astuce avec des synonymes de fonctions:


 for(i in g){ g[i[0] + i[6]] = g[i]; } 

La boucle ajoute un synonyme pour chaque fonction (et pour tout membre) du contexte WebGL, qui se compose de la première et de 7 lettres. Par exemple, créer un programme devient cP , s hader S ource - sS , etc. De plus, en encadrant tout le code with(g) (qui ne peut pas être utilisée dans ces projets), nous obtenons:


 with(g){ p=cP(); sS(s=cS(35633),'attribute vec2 p;void main(){gl_Position=vec4(p,1,1);}'); ce(s); aS(p,s); } 

Minification du shader


Le shader d'origine prend 1100 caractères. Les abréviations principales: supprimer les variables inutiles et combiner des fragments similaires. Après tout, j'ai transmis le code via le minifieur en ligne . En conséquence, un peu plus de 500 octets sont restés du shader.


JSCrush


JSCrush est la norme de facto pour la compression de code dans de telles compétitions. L'utilitaire transforme le code en approximativement la séquence suivante:


_ = '(i a.style = ...
 _='(i a.style="widMj%;hEjvh;:left",g)g[i[0]+i[6]]=g[i];wiMO.u=g.G1f,x=y=k=g)p=cP(35633"tribute 2 p gl_Posit=4?FN"precis mediump ;G Zt,a,x,y Uf`ord.rg/64!-.f.=a;Zc=+xz,v=+yz;m2 m$cc-cc)s$vv-vv)fJf#Ur`Q,,r+`t*2.,t,-2.rJr#Zg=.1,b=Q;Ui`!Kl=Rl<2Rl++){Uo=r+f*;oQ)-mod(o,2.))Ze,n=e=!;Kd=Rd<2Rd++)oo)/dot(o,o)-3,n+o)-ee=oif(l>6)Q-max(!,.3-i+=b+g,g,g)*n*5*b;.73;g+=.1;}i=mix(i)i,.85lor=4(i*.01.lo?ug?bfO=34962,cB()eV(0vA(2,5120bDO,Tw Int8Array([|,|]35044o=,(Lt@-oa@TrHE/TrWidMx@xy@ydr(6,3requestAnimFrame(L)})(down=upk^=1},movek&&(xX,yY)};),3=funct(e){uOf?,"flo}@ce(saS?,slengM(onmouse ;void ma(){Tw De/1e5);incos(for=abs(gl_FragCo,1g*(sS(s=cS(n*n*.001at.5vecionb*=s(=e.page0,!0.#.r=s;$=m2(?(p@"EeightGunimJ.rm;K(t MthO(gQ1.R0;TneU Z `=j:100z/50!|-3';for(Y in $='|zj`ZUTRQOMKJGE@?$#! ')with(_.split($[Y]))_=join(pop());eval(_) 

Le principe de JSCrush peut être visualisé visuellement dans l' outil de conversion de code inverse . Ou lisez en détail dans l' article . En général, c'est un encodage avec un dictionnaire:


  1. On trouve un caractère qui n'est pas utilisé dans le code
  2. Nous trouvons des fragments répétitifs dans le code que nous remplaçons par le caractère du premier paragraphe
  3. Remplacez la chaîne par un caractère
  4. Répétez 1-3 jusqu'à ce que le résultat soit inférieur au code source.

Optimisation


Après toutes les opérations, il me restait environ 30 caractères, qui pourraient être utilisés pour optimiser les performances. Lors du chargement de la démo, vous pouvez spécifier la taille du canevas: plein écran ou taille fixe. Le lancement en plein écran est magnifique, mais le fragment shader est appelé pour chaque pixel et fonctionne lentement. La solution était de demander une taille fixe au canevas JS1k (j'ai choisi 640x640), puis de l'augmenter en plein écran dans le code:


 a.style='width:100%;height:100vh;float:left'; 

Ensuite, l'image occupe tout l'écran, mais le shader n'est exécuté que pour chaque pixel de la taille de la toile d'origine.

Source: https://habr.com/ru/post/fr462115/


All Articles