WebAssembly not rendering

I’m trying to get a very simple SDL2 WebAssembly example to run, but it doesn’t render any output. The program could hardly be simpler:

#include <stdio.h>
#include "SDL2/SDL.h"
int main(int argc, char* argv[])
{
SDL_Window *window ;
SDL_Renderer *renderer ;
SDL_Event ev ;
int quit = 0 ;

SDL_Init(SDL_INIT_EVERYTHING);
SDL_CreateWindowAndRenderer(600, 400, 0, &window, &renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);

while (!quit)
    {
	if (SDL_PollEvent(&ev))
	    {
		if ((ev.type == SDL_QUIT) || (ev.type == SDL_KEYDOWN))
			quit = 1;
	    }
	SDL_Delay(20);
    }
SDL_DestroyRenderer(renderer) ;
SDL_DestroyWindow(window) ;
exit(0) ;
}

I’m compiling it (on Windows 10, using 32-bit Emscripten 1.38.12) with the command:

emcc hello.c -s WASM=1 -s USE_SDL=2 -o hello.html

There are no errors or warnings, and the program ‘runs’, but there’s no output and it does not quit on pressing a key as expected. If I take the event loop out entirely, so the program immediately quits, then the yellow window does appear! The compiled program can be found here if you want to try it yourself.

You must never block the main loop. Think of the web browser as a
single threaded VM that must always have control of the event loop.
(The latter event loop design is a constraint on the majority of the
newer platforms, e.g. Android, iOS, and even macOS to a degree.)

So you must wrap your main loop in a function (e.g. main_loop()) and
call the Emscripten built-in function in your setup code in your int
main():

#ifdef EMSCRIPTEN
emscripten_set_main_loop(main_loop, 0, 1);
#else
while (!g_appDone) {
main_loop();
}
#endif

(With the added benefit that you can later extend this design to other
platforms that impose a similar main loop constraint which you cannot
work around.)

There is also another compile flag (which I don’t remember off the top
of my head) which tells Emscripten to not quit/cleanup after int
main() returns. This will keep the web browser pumping the event loop
even though the program has finished running main().

-Eric

My app runs fine on Android, iOS and MacOS.

So you must wrap your main loop in a function (e.g. main_loop()) and
call the Emscripten built-in function in your setup code in your int
main():

I thought that was necessary only for Emscripten (i.e. translated to JavaScript). My understanding was that WebAssembly doesn’t have the same restriction, am I wrong?

If it’s helpful, I can modify the test case to be more similar to how my app works. But they currently fail in exactly the same way.

My app runs fine on Android, iOS and MacOS.

Yes, but my statement is still technically true.
The devil is in the details…
SDL dances through some hoops on those platforms. A traditional game
that does not need to interface with native features will generally
not have any problems and be fine. But if your app starts integrating
native features, such as in-app-purchases, Game Center, plugins like
Facebook or anything else that calls native UI, you start feeling
immense pain. The more you add, the more pain incurred.

Mac and iOS have some native support for pumping the native event loop
which allows SDL to get away with more. However these are not cases
Apple cares about so there have been bugs.

I recall there used to be some menu bar bugs with SDL if you tried to
use the native menu bar on Mac. I remember Game Center used to have
some show stopping bugs. SDL used to go through some heroics on iOS to
deal with suspend, resume, and low memory events because Apple
requires you to handle those events immediately, but if you are
pumping your own event loop, you won’t receive those messages until
after it is too late. (I think that code has changed…and there is a
special callback now to handle some of these events immediately when
they come in.)

In fact, I thought I remember SDL actually provided an optional
extension to use the native event loop (driven by CADisplayLink),
which architecturally is compatible with the Emscripten solution.

Android is the most painful because it is not possible to manually
pump the event loop. So SDL cheats and runs SDL on a background
thread. But this causes all kinds of hell when you need to integrate
with native Android features (e.g. the aforementioned in-app
purchases, many network services, any UI, and pretty much anything
that touches Java, which means any native API). The end result is the
thread rules leak out of the SDL abstraction layer, and both the
libraries and your application have to dance around the threading
model, which turns into a lot of tedious code. Java JNI also has some
weird rules about attaching/detaching threads and can adversely affect
performance if you are not careful.

So you must wrap your main loop in a function (e.g. main_loop()) and
call the Emscripten built-in function in your setup code in your int
main():

I thought that was necessary only for Emscripten (i.e. translated to
JavaScript). My understanding was that WebAssembly doesn’t have the same
restriction, am I wrong?

I could be out of date, on this, but historically, the web browser
itself is the limitation. It was not originally designed to give up
control of the event-loop. It has been hammered into web developers
for two decades to never block the event-loop, otherwise the browser
will say “It appears that the JavaScript on this page has stopped
responding. Would you like to stop it?”

I don’t know if the web browser specification has changed for Web
Assembly in this matter. However, I am cynical. The web developer
community considers blocking/non-responsive scripts to be an evil that
must be punished/purged. So this is a “feature” in their eyes, not a
bug.

If/when full-blown thread support comes to WebAseembly, then I can see
a way out for this case. (Though in reality, I think that will just
re-create the same problems we have with Android on a background
thread. emscripten_set_main_loop is by far the simpler, easier, and
better solution.)

I am very much in favour of that. To me, SDL is first and foremost an abstraction layer, its job is to present a compatible environment across multiple platforms. If that means jumping through hoops, making some native capabilities inaccessible, it’s a price worth paying in my opinion.

I thought WebAssembly already supported multi-threading. Certainly you get no error or warning from calling SDL_CreateThread().

My app is inherently multi-threaded. Indeed multi-threading is the only way to leverage the power of multi-core CPUs, so it will increasingly be the norm.

I am very much in favour of that. To me, SDL is first and foremost an abstraction layer, its job is to present a compatible environment across multiple platforms. If that means jumping through hoops, making some native capabilities inaccessible, it’s a price worth paying in my opinion.

There isn’t any meaningful workaround for “anything you render won’t go to the screen until your program returns from main()”

I’ve asked web browser people about adding an API call that blocks the current function until other pending functions have run and event queues have been pumped (which must exist at browser level anyhow, if alert() can block Javascript execution without drama), but it’s just not something they want to add.

So for now, it’s unavoidable to have a few small #ifdefs to set up an Emscripten mainloop (and possibly a lot of annoying redesign to support that form of control flow). In most cases, it’s not too bad to set up.

testsprite2.c shows what needs to be done if you grep for EMSCRIPTEN

https://hg.libsdl.org/SDL/file/5ce13990c5fb/test/testsprite2.c

Three #ifdefs:

  • One to #include <emscripten.h>
  • One to set up the mainloop callback
  • One to terminate the program (tell it not to call the callback again).

And the redesign: what would be in a tight loop instead gets moved to a function that emscripten uses as a callback, and the non-Emscripten builds just call that function in a tight loop.

As for threads: there are proposals to add threads to HTML at various stages of support, but right now there is no agreed upon standard. That being said, I believe Emscripten can use some of the non-standard options, enabled by some magic command line switches.

When threads are availble, they are exposed to Emscripten to look like pthreads; it’s possible the configure script found this, enabled it and then Emscripten is (incorrectly) reporting success. Our buildbot runs the configure scripte with --disable-threads for Emscripten builds, fwiw. Stripping out multithreading can be a much harder problem when moving to Emscripten. An option would be to change each to run a little at a time and return, and give them each a shot at running once at the start of every frame.

This part of Emscripten is hard and can be quite unexpected.

As a computer user first and a developer second, I have to agree with this perspective. Blocking the UI, whether it be in a Web browser or in an application, is obnoxious, and the sooner we move away from that method of thinking, the better.

I agree, but that is precisely why my app is multi-threaded. I can (and do) block in my worker thread, but the UI thread never blocks. Expecting you not to block the UI, but at the same time insisting on everything running in the same thread, is unreasonable in my opinion.

I agree, but we go to war with the army we have, and at the moment this is a choice between tolerating the quirks of web browsers or not porting to them at all…both choices are valid.

2 Likes

Good (?) news, everybody! Pthreads with WebAssembly:

1 Like

I had hoped (assumed) that the version of WebAssembly that I’ve been using already supports threads, hence no warnings being raised by the compiler/linker, but I’m not certain. I am well aware of the limitations of Emscripten+JavaScript, and I know that my app can never work in that environment, but my understanding is that Emscripten+WebAssembly relaxes many of them.

I would actually prefer to always give a callbacks to SDL for drawing a frame etc. instead of insisting on having full control of the main loop. These days there are so many environments that don’t like to give apps full control of their own main loop - emscripten being the worst example requiring some ugly ifdef kludge and leading to confusion like this.

In other words, instead of providing special callbacks only for emscripten, SDL would allow the user to set similar callbacks that are used in all environments. If no callbacks are set, SDL works as it used to and the user still has full control of the main loop (so control freaks can still write their own loop, but the app would be less portable).

A bit off topic, but in the same vain I think the stuff related to redefining main and the whole SDL_main library create more confusion for the minor benefit they provide. I digged up an old post I made about this:

This could be added already and maybe in the next major version the whole SDL_main library could be dropped (You probably figured out already that I really hate libSDL_main).

1 Like

I want to clarify my earlier points:

There are two separate issues:

  1. Some platforms are designed around event-driven main loops instead
    of polling main-loops
  2. Multithreading capabilities and rules and limitations

I have no interest in debating which is better or worse.
My position is articulated by Ryan’s response, “we go to war with the
army we have”.

Focusing issue (1), my feeling is (similar to Olli) that a future SDL
version (e.g. 2.1) should start introducing a new main-loop callback
model that allows all platforms to work the same way. This callback
model would be similar to how the Emscripten and SDL/iOS extension
works.

This issue is never going to fully go away (read my comments about how
some of the other platforms work…this is not just Emscripten/web
browser.). But there also could be potential future benefits for some
platforms, such as those who wish to provide a hardware synced draw
refresh callback.

But more to the point, it is always possible for us to build (i.e.
fake) a callback/event model from a polling model. But it is not
possible to go the other way.

It should also be possible to make a transitional migration so we
don’t break anybody. We can default on the existing polling design,
but if the user opts into frame/loop callback, we run that instead.
Those who are happy with the current polling design can stick with it.
But those who want new platforms or more native capabilities on other
platforms can activate the new model. And for most people, it
shouldn’t be that hard to switch to the new model. (Most projects
using Emscripten that I’ve seen didn’t have any serious problems
switching to the main loop callback. Remember, I’m only talking about
issue (1) here, not issue (2) which is a different thing.)

I’ve actually done a talk on this and implemented a similar transition
for the native cross-platform GUI library, IUP. If any body is
interested, I can refer you to more information about it. But it
actually not hard to convert a polling system to emulate event driven.
Also, related to Olli’s comment about SDL_main, I was able to make
some accommodations to support not having an “int main” entry point on
all platforms (e.g. Android).

Thanks,
Eric

I agree in principle, but it’s the practical implications that concern me. My application (and I know it’s not typical, but I have no room for manoeuvre) requires very low latency messaging between my worker thread and the GUI thread. I achieve this by (some of the time, not continuously) running a busy-wait loop in the GUI thread, i.e. it calls SDL_PeepEvents and very little else in a tight loop. This works extremely well on a multi-core CPU.

In a callback-style environment I can’t run a busy-wait loop in the GUI thread, and I know from experience that an event callback (using SDL’s synchronisation primitives) cannot achieve anywhere near as low a latency. Indeed I’ve read learned papers that demonstrate that there is no other way of achieving as low a latency as a busy-wait loop. This would kill the performance of my app.

I know that I cannot expect the maintainers of SDL to take into account the requirements of one untypical application, but if and when SDL2 removes the polling option that will be the end for my app.

I don’t like the handful of #ifdefs Emscripten needs, but I’m not planning to change the whole paradigm of SDL for it.

4 Likes

Well that was not what I was suggesting.

What I’m suggesting is that as there is already the need to have the callbacks for emscripten why not allow using them for other environments too. Now I’m forced to using two different paradigms in the same app as I need to support emscripten along other environments, which is a lot worse in my mind than having SDL support 2 different paradigms globally from which I can choose one to use in my app.