Skip to content

Instantly share code, notes, and snippets.

@kripken
Last active December 30, 2022 06:54
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kripken/cbffda150b4e1583bdad832d070944da to your computer and use it in GitHub Desktop.
Save kripken/cbffda150b4e1583bdad832d070944da to your computer and use it in GitHub Desktop.

Sizes

(html+js+wasm)

Non-emscripten: 19,964 bytes
Emscripten:     11,086 bytes

The biggest factor here is that the emscripten version uses emscripten's standard minification features (like closure compiler, --low-memory-unused, etc.) which the other one doesn't (it was minified using Uglify3 as mentioned in that post).

Please let me know if I missed something or got anything wrong!

Building

Notes:

  • The non-emscripten version uses JS to do Math.asin etc., which is smaller than emscripten's default behavior which is to use compiled musl code, which is faster. For a more apples-to-apples comparison, the emscripten version sets JS_MATH which does something similar. This makes both versions about 25% smaller, but also both suffer a speed penalty because of this, so it's not recommended in general.

Summary

Emscripten aims to provide a simple interface for users (at the cost of internal complexity inside the tool), and to integrate the most powerful minification and optimization tools. So there are tradeoffs to using it versus a more barebones approach like in that post.

It's good we have options! And that post does a good job of explaining the technical details of one type of approach. We just need to be aware of the tradeoffs.

In this case, it's less work to set up the emscripten version (everything in a single compile command on one line; no need for the 100+ line Makefile), and it's smaller (and likely faster, see the note above on musl vs JS). On the other hand, the barebones approach gives you more control, and if you put in enough work, you can get interesting results for specific use cases.

/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#define GL_GLEXT_PROTOTYPES
#define EGL_EGLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GLES2/gl2.h>
#include <math.h>
static const char* vertex_shader_text =
"precision lowp float;"
"uniform mat4 uMVP;"
"attribute vec4 aPos;"
"attribute vec3 aCol;"
"varying vec3 vCol;"
"void main()"
"{"
"vCol = aCol;"
"gl_Position = uMVP * aPos;"
"}";
static const char* fragment_shader_text =
"precision lowp float;"
"varying vec3 vCol;"
"void main()"
"{"
"gl_FragColor = vec4(vCol, 1.0);"
"}";
typedef struct Vertex { float x, y, r, g, b; } Vertex;
static GLuint program, vertex_buffer;
static GLint uMVP_location, aPos_location, aCol_location;
int WAFNDraw(double f, void*);
// This function is called at startup
int main(int argc, char *argv[])
{
emscripten_set_canvas_element_size("canvas", 640, 480);
EmscriptenWebGLContextAttributes attrs;
emscripten_webgl_init_context_attributes(&attrs);
attrs.alpha = 0;
auto glContext = emscripten_webgl_create_context("canvas", &attrs);
emscripten_webgl_make_context_current(glContext);
glViewport(0, 0, 640, 480);
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
glCompileShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
glCompileShader(fragment_shader);
program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
uMVP_location = glGetUniformLocation(program, "uMVP");
aPos_location = glGetAttribLocation(program, "aPos");
aCol_location = glGetAttribLocation(program, "aCol");
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glEnableVertexAttribArray(aPos_location);
glVertexAttribPointer(aPos_location, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(aCol_location);
glVertexAttribPointer(aCol_location, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(sizeof(float) * 2));
emscripten_request_animation_frame_loop(&WAFNDraw, 0);
return 0;
}
// This function is called by loader.js every frame
int WAFNDraw(double f, void*)
{
f /= 1000.0;
glClear(GL_COLOR_BUFFER_BIT);
Vertex vertices[3] =
{
{ -0.6f, -0.4f, 1.f, 0.f, 0.f },
{ 0.6f, -0.4f, 0.f, 0.f, 1.f },
{ 0.f, 0.6f, 1.f, 1.f, 1.f },
};
vertices[0].r = 0.5f + sinf(f * 3.14159f * 2.0f) * 0.5f;
vertices[1].b = 0.5f + cosf(f * 3.14159f * 2.0f) * 0.5f;
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
GLfloat mvp[4*4] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 };
glUseProgram(program);
glUniformMatrix4fv(uMVP_location, 1, GL_FALSE, mvp);
glDrawArrays(GL_TRIANGLES, 0, 3);
return EM_TRUE;
}
<!doctypehtml><html lang=en-us><head><meta charset=utf-8></head><body><canvas style=display:block;margin:auto></canvas><script>var Module={};function binary(e){return new Promise((n,r)=>{var a=new XMLHttpRequest;a.open("GET",e,!0),a.responseType="arraybuffer",a.onload=()=>{n(a.response)},a.send(null)})}function script(e){return new Promise((n,r)=>{var a=document.createElement("script");a.src=e,a.onload=()=>{n()},document.body.appendChild(a)})}Promise.all([binary("main_ems.js"),binary("main_ems.wasm")]).then(e=>{Module.wasm=e[1];var n=URL.createObjectURL(new Blob([e[0]],{type:"application/javascript"}));script(n).then(()=>{URL.revokeObjectURL(n)})})</script></body></html>
if(typeof Module==="undefined"){var Module={};}
var h;h||(h=Module);var k="function"===typeof read;if("object"===typeof process){var fs=require("fs");h.wasm=fs.readFileSync(__dirname+"/main_ems.wasm")}k&&(h.wasm=read("main_ems.wasm","binary"));var l="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0;
function m(a,b){var c=n,d=a+b;for(b=a;c[b]&&!(b>=d);)++b;if(16<b-a&&c.subarray&&l)return l.decode(c.subarray(a,b));for(d="";a<b;){var e=c[a++];if(e&128){var f=c[a++]&63;if(192==(e&224))d+=String.fromCharCode((e&31)<<6|f);else{var g=c[a++]&63;e=224==(e&240)?(e&15)<<12|f<<6|g:(e&7)<<18|f<<12|g<<6|c[a++]&63;65536>e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}
var p=new WebAssembly.Memory({initial:256,maximum:256}),r=p.buffer,t=new WebAssembly.Table({initial:2,maximum:2,element:"anyfunc"});new Int8Array(r);new Int16Array(r);var u=new Int32Array(r),n=new Uint8Array(r);new Uint16Array(r);new Uint32Array(r);var v=new Float32Array(r);new Float64Array(r);u[808]=5246272;function w(a){return a===a+0?a?m(a,void 0):"":a}var x=[0,"undefined"!==typeof document?document:0,"undefined"!==typeof window?window:0];
function z(a){return x[a]||("undefined"!==typeof document?document.querySelector(w(a)):void 0)}function A(a){var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=function(c,d){b.vertexAttribDivisorANGLE(c,d)},a.drawArraysInstanced=function(c,d,e,f){b.drawArraysInstancedANGLE(c,d,e,f)},a.drawElementsInstanced=function(c,d,e,f,g){b.drawElementsInstancedANGLE(c,d,e,f,g)})}
function B(a){var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=function(){return b.createVertexArrayOES()},a.deleteVertexArray=function(c){b.deleteVertexArrayOES(c)},a.bindVertexArray=function(c){b.bindVertexArrayOES(c)},a.isVertexArray=function(c){return b.isVertexArrayOES(c)})}function D(a){var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=function(c,d){b.drawBuffersWEBGL(c,d)})}var E=1,F=0,G=[],H=[],I=[],J=[],K=[],L=null,M={};
function N(a){for(var b=E++,c=a.length;c<b;c++)a[c]=null;return b}var O=[0];
function P(a){a||(a=L);if(!a.H){a.H=!0;var b=a.B;2>a.version&&(A(b),B(b),D(b));b.L=b.getExtension("EXT_disjoint_timer_query");var c="OES_texture_float OES_texture_half_float OES_standard_derivatives OES_vertex_array_object WEBGL_compressed_texture_s3tc WEBGL_depth_texture OES_element_index_uint EXT_texture_filter_anisotropic EXT_frag_depth WEBGL_draw_buffers ANGLE_instanced_arrays OES_texture_float_linear OES_texture_half_float_linear EXT_blend_minmax EXT_shader_texture_lod EXT_texture_norm16 WEBGL_compressed_texture_pvrtc EXT_color_buffer_half_float WEBGL_color_buffer_float EXT_sRGB WEBGL_compressed_texture_etc1 EXT_disjoint_timer_query WEBGL_compressed_texture_etc WEBGL_compressed_texture_astc EXT_color_buffer_float WEBGL_compressed_texture_s3tc_srgb EXT_disjoint_timer_query_webgl2 WEBKIT_WEBGL_compressed_texture_pvrtc".split(" ");(b.getSupportedExtensions()||
[]).forEach(function(d){-1!=c.indexOf(d)&&b.getExtension(d)})}}for(var Q=["default","low-power","high-performance"],R,S=new Float32Array(256),T=0;256>T;T++)O[T]=S.subarray(0,T+1);for(T=0;256>T;T++);var U,V;
WebAssembly.instantiate(h.wasm,{a:{m:function(a){return Math.cos(a)},l:function(a){return Math.sin(a)},t:function(a,b){function c(d){V(a,d,b)&&requestAnimationFrame(c)}return requestAnimationFrame(c)},y:function(a,b,c){a=z(a);if(!a)return-4;a.width=b;a.height=c;return 0},q:function(a,b){var c={};b>>=2;c.alpha=!!u[b];c.depth=!!u[b+1];c.stencil=!!u[b+2];c.antialias=!!u[b+3];c.premultipliedAlpha=!!u[b+4];c.preserveDrawingBuffer=!!u[b+5];c.powerPreference=Q[u[b+6]];c.failIfMajorPerformanceCaveat=!!u[b+
7];c.I=u[b+8];c.P=u[b+9];c.C=u[b+10];c.G=u[b+11];c.R=u[b+12];c.S=u[b+13];a=z(a);if(!a||c.G)c=0;else if(a=a.getContext("webgl",c)){b=N(K);var d={M:b,attributes:c,version:c.I,B:a};a.canvas&&(a.canvas.J=d);K[b]=d;("undefined"===typeof c.C||c.C)&&P(d);c=b}else c=0;return c},x:function(a){a>>=2;for(var b=0;14>b;++b)u[a+b]=0;u[a]=u[a+1]=u[a+3]=u[a+4]=u[a+8]=u[a+10]=1},k:function(a){L=K[a];h.K=R=L&&L.B;return!a||R?0:-5},a:function(a,b){R.attachShader(H[a],J[b])},g:function(a,b){R.bindBuffer(a,G[b])},r:function(a,
b,c,d){R.bufferData(a,c?n.subarray(c,c+b):b,d)},s:function(a){R.clear(a)},b:function(a){R.compileShader(J[a])},i:function(){var a=N(H),b=R.createProgram();b.name=a;H[a]=b;return a},d:function(a){var b=N(J);J[b]=R.createShader(a);return b},n:function(a,b,c){R.drawArrays(a,b,c)},f:function(a){R.enableVertexAttribArray(a)},u:function(a,b){for(var c=0;c<a;c++){var d=R.createBuffer(),e=d&&N(G);d?(d.name=e,G[e]=d):F||(F=1282);u[b+4*c>>2]=e}},h:function(a,b){return R.getAttribLocation(H[a],b?m(b,void 0):
"")},v:function(a,b){b=b?m(b,void 0):"";var c=0;if("]"==b[b.length-1]){var d=b.lastIndexOf("[");c="]"!=b[d+1]?parseInt(b.slice(d+1)):0;b=b.slice(0,d)}return(a=M[a]&&M[a].F[b])&&0<=c&&c<a[0]?a[1]+c:-1},w:function(a){R.linkProgram(H[a]);var b=H[a];a=M[a]={F:{},D:0,N:-1,O:-1};for(var c=a.F,d=R.getProgramParameter(b,35718),e=0;e<d;++e){var f=R.getActiveUniform(b,e),g=f.name;a.D=Math.max(a.D,g.length+1);"]"==g.slice(-1)&&(g=g.slice(0,g.lastIndexOf("[")));var q=R.getUniformLocation(b,g);if(q){var y=N(I);
c[g]=[f.size,y];I[y]=q;for(var C=1;C<f.size;++C)q=R.getUniformLocation(b,g+"["+C+"]"),y=N(I),I[y]=q}}},c:function(a,b,c,d){for(var e="",f=0;f<b;++f){var g=d?u[d+4*f>>2]:-1,q=u[c+4*f>>2];g=q?m(q,0>g?void 0:g):"";e+=g}R.shaderSource(J[a],e)},o:function(a,b,c,d){if(256>=16*b){var e=O[16*b-1];d>>=2;for(var f=0;f<16*b;f+=16){var g=d+f;e[f]=v[g];e[f+1]=v[g+1];e[f+2]=v[g+2];e[f+3]=v[g+3];e[f+4]=v[g+4];e[f+5]=v[g+5];e[f+6]=v[g+6];e[f+7]=v[g+7];e[f+8]=v[g+8];e[f+9]=v[g+9];e[f+10]=v[g+10];e[f+11]=v[g+11];e[f+
12]=v[g+12];e[f+13]=v[g+13];e[f+14]=v[g+14];e[f+15]=v[g+15]}}else e=v.subarray(d>>2,d+64*b>>2);R.uniformMatrix4fv(I[a],!!c,e)},p:function(a){R.useProgram(H[a])},e:function(a,b,c,d,e,f){R.vertexAttribPointer(a,b,c,!!d,e,f)},j:function(a,b,c,d){R.viewport(a,b,c,d)},memory:p,table:t}}).then(function(a){a=a.instance.exports;U=a.A;V=a.ya;a.z();U()});
@floooh
Copy link

floooh commented Apr 9, 2020

Java, btw, is no longer necessary.

That is most excellent news :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment