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()});
@schellingb
Copy link

Hello Alon
I'm the author of the article and as mentioned in my twitter response I'm very happy you took the time to look at it and respond!

Maybe I should try to clarify my intent with the article some more. It is intended for people who want to treat the web platform as an important build target and who aren't afraid of customizing the JavaScript layer to get full control over the application. People who don't want to engage in JavaScript and are happy with the SDL2 emulation of Emscripten are not the intended audience.

Even though file-size is not the main/only reason, I do mention about minifying the JavaScript code in the article in the last (advanced) demo. Taking the WebGL demo from the article used in this gist as is and just running the JavaScript file through the online tool UglifyJS 3, results in html+js+wasm at 19,962 bytes.

In 2012 I started using Emscripten a few months after adding NaCl target support to my game framework. Ever since I struggled with keeping up with all the changes on it and the web (damn you webaudio!) and kept hoping for NaCl to not die.
This lower level article from 2019 from @surma (https://surma.dev/things/c-to-webassembly/) showed me that wasm can be built with just two EXE files (clang and wasm-ld). A month later I deprecated Emscripten and NaCl targets and finally I could embrace WebAssembly as a platform by understanding most of what is going on while it executes my code.

This somehow became a blog post on its own... anyway, as much as I hoped that Emscripten would go away when llvm added wasm as officially supported, I'm glad for what it did and does and where we are today. Thank you Alon!

@kripken
Copy link
Author

kripken commented Apr 1, 2020

Even though file-size is not the main/only reason, I do mention about minifying the JavaScript code in the article in the last (advanced) demo. Taking the WebGL demo from the article used in this gist as is and just running the JavaScript file through the online tool UglifyJS 3, results in html+js+wasm at 19,962 bytes.

Thanks, I had assumed the downloaded loader.js was already optimized, but you're right, it wasn't. I did that too and got 19,964 (2 byte difference, maybe Uglify3 isn't deterministic?). I updated the gist now, also with an emscripten update using the latest optimizations, after which the emscripten size is 14,536.

Maybe I should try to clarify my intent with the article some more. It is intended for people who want to treat the web platform as an important build target and who aren't afraid of customizing the JavaScript layer to get full control over the application. People who don't want to engage in JavaScript and are happy with the SDL2 emulation of Emscripten are not the intended audience.

Thanks, yeah, I sort of guessed that's where you were coming from. As I wrote on Twitter I think the post came across as unnecessarily critical and generalizing. Good to know it wasn't intended as such!

I'd add that I think the Emscripten use case is broader than what you just mentioned, though. We put in a lot of work on size and speed, and on feature support like pthreads, exceptions, etc. It would take a lot of work to get those without Emscripten, and you're at risk of missing things (e.g. in your Makefile I don't see --low-memory-unused integrated). So even users that are ok with messing with JavaScript will often get the best results with Emscripten - but not always, certainly it depends.

@juj
Copy link

juj commented Apr 6, 2020

It is intended for people who want to treat the web platform as an important build target and who aren't afraid of customizing the JavaScript layer to get full control over the application.

I keep reading these "let's skip Emscripten for a smaller and better result" recommending articles. I wish that such approaches would prove something more complex than a hello world/webgl to showcase the superiority over and unrecoverable bloat about Emscripten. The gripe with these articles is that they will take the reader down a fallacious path of ending up re-writing what Emscripten already offers, with little gains.

The article introduction conflates Emscripten with asm.js/fastcomp, and uses asm.js/fastcomp as a "proof" of Emscripten's hackiness; while conveniently omitting the fact that Emscripten can well target the upstream LLVM wasm backend (in fact, these two are developed in conjunction). I am sorry to write, but that kind of rhetorics did not seem honest to me.

If you wanted to just develop software to the web, and not treat the web as one of your (many) build targets, then such an approach might be feasible. However if you are developing software to target many platforms, you end up wasting time reimplementing what is already there in Emscripten - e.g. your GL layer looks like it is copy-paste adapted from Emscripten libraries. If someone needs to target OpenGL ES on Android, and WebGL in browsers, saying that it's easy and smaller with DIY - it may not be that true..

Of course reimplementing the wheel is fine, for fun and learning; although the article was not quite cast in either light, but to make a point of how to improve over Emscripten.

Emscripten's current hurdle that is acknowledged here is that it does need configuring to get the minimal build size for trivial applications, but it is much less work to go through src/settings.js rather than e.g. rewrite Emscripten's GL layer from scratch; It is probably also lacking in documentation, which is perhaps the root cause why these kinds of articles keep coming up? I think we will need to improve on that part.

If you'd like to try out a comparable minimally sized WebGL application built with Emscripten, check out https://github.com/juj/webgl_render_test/ that renders WebGL sprites and text with minimal sized output (~5K JS, ~18K Wasm). It also does not generate any JS garbage to ensure as little as possible GC stuttering, enables easy co-targeting for WebGL 1 vs WebGL 2, is readily Closure compiler -capable out of the box and uses a runtime that is easily extended to multithreading and background rendering, to name a few.

Your final application was ~50K. Here is an online link similar features compiled with Emscripten, that is about half of that in size: http://clb.confined.space/greetings/ , with music, WebGL sprites and text, and audio.

It is great to see interest in directly tackling LLVM wasm tooling, but I think that could also be written about in standalone, without making a needless(?) stomp on Emscripten?

@schellingb
Copy link

Here's some of the output that I build for where WASM is one of the build targets:

  • Games (File size mentioned is gzipped including all assets)
  • Samples (55kb ~ 170kb gzipped with assets).

These are 40 programs that I build each for 10+ targets (WASM, Emscripten (deprecated), NaCl (deprecated), Win32, Win64, Linux32, Linux64, macOS, iOS and Android (up to 4 separate builds for 4 ABIs). So compiling 400 times, linking 400 times, collecting assets and packing gzips/ZIPs/APKs/etc 400 times. And I do that all automated in a few minutes.
Yes those are not giant commercial games, just small things made by me in my spare time. But so are most things on the web.

Now until I switched to building without Emscripten, maintenance of the web target was a big pain. Builds were slow, modifying the JavaScript library meant I had to rerun emcc and updating Emscripten was never smooth (yes I'm on Windows). It also never ran without flaws (scaling the web site resized the canvas incorrectly, fullscreen barely worked, etc.) thus I also deployed NaCl builds for Chrome (never had any problems with both GL rendering and audio output there). Sure web standards and browser support grew along the way, too. But so did WebAssembly.

I still stand by that I think Emscripten should be just one tool not THE tool for running C/C++ on the web. I hoped that with llvm officially gaining support for wasm that the part that Emscripten does (generating the layer between browser APIs and the wasm) would be an optional step. Maybe as one of the Binaryen executables.
And it basically is (the point of my blog post)! Yet the public at large doesn't seem to realize/know that.

Also I think the source code for the SDL2, glfw, OpenAL, etc. JavaScript libraries should by now be in the respective projects source repositories and not in Emscripten's. It feels like this closed platform where these few libraries are chosen to be the supported (and maintained) ones without giving others a clear path how to embrace the web platform. Shoutouts to the ones that still do like the sokol headers by @floooh.

Btw. that last link 'greetings' is 3.7 MB 😉

@kripken
Copy link
Author

kripken commented Apr 6, 2020

@juj

It is probably also lacking in documentation, which is perhaps the root cause why these kinds of articles keep coming up? I think we will need to improve on that part.

Good point, I agree. How about working together on a blogpost about MINIMAL_RUNTIME and related things? I think that could be very useful!

@schellingb

Also I think the source code for the SDL2, glfw, OpenAL, etc. JavaScript libraries should by now be in the respective projects source repositories and not in Emscripten's.

I think that's reasonable. Emscripten support for SDL2 has already been upstreamed into the Emscripten backend there, and others do similar things. It really just depends on volunteers stepping up to do it for the rest.

edit: if you want a non-Emscripten upstreamed version, then there's no obvious way to do that: You need a way to integrate JS and wasm in a way that's convenient for those projects to use (like a C API such as emscripten_*, EM_ASM/EM_JS, etc.). Perhaps in time wasi and/or interface types will help here, but those are far off.

It feels like this closed platform where these few libraries are chosen to be the supported (and maintained) ones without giving others a clear path how to embrace the web platform.

I don't think that's accurate at all! First, again, some already do. Second, the documented path is to use the emscripten_* APIs, as SDL2 and those others do (or, you can also use EM_ASM/EM_JS).

Btw. that last link 'greetings' is 3.7 MB 😉

No, I think @juj is right: JS is 6K, wasm is 24K. You may be measuring it wrong if you see MBs there.

@schellingb
Copy link

schellingb commented Apr 7, 2020

That's why I added the wink, I'm aware of the size of just the .wasm file. The original sentence was "with music, WebGL sprites and text, and audio." which is the size number I quoted.
Though I think that's important to consider. Why really bother making the WASM as small as 24 KB when you have two PNGs at 97 KB that could be ran through zopflipng and get 56 KB out (lossless recompression with zero impact in image quality). Also together with the 3.6 MB MP3 song that could use a more modern compression this makes a rather bad "hey look at how small this is" example.

@kripken
Copy link
Author

kripken commented Apr 7, 2020

Obviously the other things could be compressed better, but I think you are interpreting @juj's example in the least charitable way. The context here is of the size of JS+wasm.

But yes, sure, in some cases art assets are much bigger than the JS+wasm, and their size hardly matters. But in others it's the reverse. In emscripten we focus so much on code size because sometimes it really, really matters, even if not always.

@floooh
Copy link

floooh commented Apr 9, 2020

Just a little thought because I've been also thinking a lot about an "emscripten free" mode for my cross-platform headers:

Such a "freestanding mode" can be useful for integration with other languages. For instance, Zig has the ability to directly include C headers, and directly compile C code though the same Zig compiler (also to WASM), and it also has the entire build system integrated right into the compiler, no cmake or similar.

In such an environment, mixing C and Zig feels completely natural, but this convenience falls apart when an external build tool must be integrated.

This sort of problem isn't limited to the emscripten SDK, it's more like a general cross-platform problem. For instance macOS/iOS APIs are mostly Objective-C, which creates a similar set of problems like the Web-API and Javascript interop, or on Android where C <=> Java interop is needed (and TBH, emscripten provides the most convenient solution of those, EM_JS() is simply brilliant). But there are valid scenarios where not depending on the emscripten SDK makes a lot of sense.

The one small critique I have with the current emscripten SDK is that it is such a "hodge podge" of tooling technologies. There's python, node.js, Java and native tools. Thankfully that's all hidden under "emcc", but I wonder how much overhead is required to keep it all running. It "feels" a bit brittle :)

@kripken
Copy link
Author

kripken commented Apr 9, 2020

But there are valid scenarios where not depending on the emscripten SDK makes a lot of sense.

I definitely agree!

Specifically, if you need little Web API integration, then emscripten is probably not necessary - but may still be useful as it gives good default optimizations, otherwise you need to integrate with lld and binaryen manually, and it's easy to miss important optimizations (like --low-memory-unused). But if you do need significant JS or Web API stuff, or you need stuff like files, pthreads, exceptions, asyncify, etc. etc., then that's what emscripten is for.

The one small critique I have with the current emscripten SDK is that it is such a "hodge podge" of tooling technologies. There's python, node.js, Java and native tools. Thankfully that's all hidden under "emcc", but I wonder how much overhead is required to keep it all running. It "feels" a bit brittle :)

I would prefer to have fewer as well. However, it doesn't take much overhead, and isn't brittle (at least I see no signs of that? the one annoyance I can think of is python issues with certificates, but not sure another language would avoid those...). The one thing we could in theory remove is the python code, and rewrite that for node or for native code. But I'm not sure that would be worth it as the benefit would mainly be just that things feel simpler. If someone is interested though, it's worth talking about.

Java, btw, is no longer necessary. The one thing we used Java for was the closure compiler. As we use that from node now, Java isn't normally needed (on most platforms the closure package will install a native executable, which I believe is AOT compiled Java).

Otherwise, we need those things:

  • Node.js is necessary for JS parsing and optimization. We use acorn and other things. It would be very hard to do JS integration without those!
  • Native tools are necessary since without LLVM, clang, lld, and binaryen we would be completely lost!

@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