summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-2.30.2/docs/README-emscripten.md
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-05-04 16:51:29 -0700
committer3gg <3gg@shellblade.net>2024-05-04 16:51:29 -0700
commit8222bfe56d4dabe8d92fc4b25ea1b0163b16f3e1 (patch)
tree763389e42276035ac134d94eb1dc32293b88d807 /src/contrib/SDL-2.30.2/docs/README-emscripten.md
Initial commit.
Diffstat (limited to 'src/contrib/SDL-2.30.2/docs/README-emscripten.md')
-rw-r--r--src/contrib/SDL-2.30.2/docs/README-emscripten.md374
1 files changed, 374 insertions, 0 deletions
diff --git a/src/contrib/SDL-2.30.2/docs/README-emscripten.md b/src/contrib/SDL-2.30.2/docs/README-emscripten.md
new file mode 100644
index 0000000..1a13eb1
--- /dev/null
+++ b/src/contrib/SDL-2.30.2/docs/README-emscripten.md
@@ -0,0 +1,374 @@
1# Emscripten
2
3## The state of things
4
5(As of September 2023, but things move quickly and we don't update this
6document often.)
7
8In modern times, all the browsers you probably care about (Chrome, Firefox,
9Edge, and Safari, on Windows, macOS, Linux, iOS and Android), support some
10reasonable base configurations:
11
12- WebAssembly (don't bother with asm.js any more)
13- WebGL (which will look like OpenGL ES 2 or 3 to your app).
14- Threads (see caveats, though!)
15- Game controllers
16- Autoupdating (so you can assume they have a recent version of the browser)
17
18All this to say we're at the point where you don't have to make a lot of
19concessions to get even a fairly complex SDL-based game up and running.
20
21
22## RTFM
23
24This document is a quick rundown of some high-level details. The
25documentation at [emscripten.org](https://emscripten.org/) is vast
26and extremely detailed for a wide variety of topics, and you should at
27least skim through it at some point.
28
29
30## Porting your app to Emscripten
31
32Many many things just need some simple adjustments and they'll compile
33like any other C/C++ code, as long as SDL was handling the platform-specific
34work for your program.
35
36First, you probably need this in at least one of your source files:
37
38```c
39#ifdef __EMSCRIPTEN__
40#include <emscripten.h>
41#endif
42```
43
44Second: assembly language code has to go. Replace it with C. You can even use
45[x86 SIMD intrinsic functions in Emscripten](https://emscripten.org/docs/porting/simd.html)!
46
47Third: Middleware has to go. If you have a third-party library you link
48against, you either need an Emscripten port of it, or the source code to it
49to compile yourself, or you need to remove it.
50
51Fourth: You still start in a function called main(), but you need to get out of
52it and into a function that gets called repeatedly, and returns quickly,
53called a mainloop.
54
55Somewhere in your program, you probably have something that looks like a more
56complicated version of this:
57
58```c
59void main(void)
60{
61 initialize_the_game();
62 while (game_is_still_running) {
63 check_for_new_input();
64 think_about_stuff();
65 draw_the_next_frame();
66 }
67 deinitialize_the_game();
68}
69```
70
71This will not work on Emscripten, because the main thread needs to be free
72to do stuff and can't sit in this loop forever. So Emscripten lets you set up
73a [mainloop](https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop).
74
75```c
76static void mainloop(void) /* this will run often, possibly at the monitor's refresh rate */
77{
78 if (!game_is_still_running) {
79 deinitialize_the_game();
80 #ifdef __EMSCRIPTEN__
81 emscripten_cancel_main_loop(); /* this should "kill" the app. */
82 #else
83 exit(0);
84 #endif
85 }
86
87 check_for_new_input();
88 think_about_stuff();
89 draw_the_next_frame();
90}
91
92void main(void)
93{
94 initialize_the_game();
95 #ifdef __EMSCRIPTEN__
96 emscripten_set_main_loop(mainloop, 0, 1);
97 #else
98 while (1) { mainloop(); }
99 #endif
100}
101```
102
103Basically, `emscripten_set_main_loop(mainloop, 0, 1);` says "run
104`mainloop` over and over until I end the program." The function will
105run, and return, freeing the main thread for other tasks, and then
106run again when it's time. The `1` parameter does some magic to make
107your main() function end immediately; this is useful because you
108don't want any shutdown code that might be sitting below this code
109to actually run if main() were to continue on, since we're just
110getting started.
111
112There's a lot of little details that are beyond the scope of this
113document, but that's the biggest intial set of hurdles to porting
114your app to the web.
115
116
117## Do you need threads?
118
119If you plan to use threads, they work on all major browsers now. HOWEVER,
120they bring with them a lot of careful considerations. Rendering _must_
121be done on the main thread. This is a general guideline for many
122platforms, but a hard requirement on the web.
123
124Many other things also must happen on the main thread; often times SDL
125and Emscripten make efforts to "proxy" work to the main thread that
126must be there, but you have to be careful (and read more detailed
127documentation than this for the finer points).
128
129Even when using threads, your main thread needs to set an Emscripten
130mainloop that runs quickly and returns, or things will fail to work
131correctly.
132
133You should definitely read [Emscripten's pthreads docs](https://emscripten.org/docs/porting/pthreads.html)
134for all the finer points. Mostly SDL's thread API will work as expected,
135but is built on pthreads, so it shares the same little incompatibilities
136that are documented there, such as where you can use a mutex, and when
137a thread will start running, etc.
138
139
140IMPORTANT: You have to decide to either build something that uses
141threads or something that doesn't; you can't have one build
142that works everywhere. This is an Emscripten (or maybe WebAssembly?
143Or just web browsers in general?) limitation. If you aren't using
144threads, it's easier to not enable them at all, at build time.
145
146If you use threads, you _have to_ run from a web server that has
147[COOP/COEP headers set correctly](https://web.dev/why-coop-coep/)
148or your program will fail to start at all.
149
150If building with threads, `__EMSCRIPTEN_PTHREADS__` will be defined
151for checking with the C preprocessor, so you can build something
152different depending on what sort of build you're compiling.
153
154
155## Audio
156
157Audio works as expected at the API level, but not exactly like other
158platforms.
159
160You'll only see a single default audio device. Audio capture also works;
161if the browser pops up a prompt to ask for permission to access the
162microphone, the SDL_OpenAudioDevice call will succeed and start producing
163silence at a regular interval. Once the user approves the request, real
164audio data will flow. If the user denies it, the app is not informed and
165will just continue to receive silence.
166
167Modern web browsers will not permit web pages to produce sound before the
168user has interacted with them (clicked or tapped on them, usually); this is
169for several reasons, not the least of which being that no one likes when a
170random browser tab suddenly starts making noise and the user has to scramble
171to figure out which and silence it.
172
173SDL will allow you to open the audio device for playback in this
174circumstance, and your audio callback will fire, but SDL will throw the audio
175data away until the user interacts with the page. This helps apps that depend
176on the audio callback to make progress, and also keeps audio playback in sync
177once the app is finally allowed to make noise.
178
179There are two reasonable ways to deal with the silence at the app level:
180if you are writing some sort of media player thing, where the user expects
181there to be a volume control when you mouseover the canvas, just default
182that control to a muted state; if the user clicks on the control to unmute
183it, on this first click, open the audio device. This allows the media to
184play at start, and the user can reasonably opt-in to listening.
185
186Many games do not have this sort of UI, and are more rigid about starting
187audio along with everything else at the start of the process. For these, your
188best bet is to write a little Javascript that puts up a "Click here to play!"
189UI, and upon the user clicking, remove that UI and then call the Emscripten
190app's main() function. As far as the application knows, the audio device was
191available to be opened as soon as the program started, and since this magic
192happens in a little Javascript, you don't have to change your C/C++ code at
193all to make it happen.
194
195Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385
196for some Javascript code to steal for this approach.
197
198
199## Rendering
200
201If you use SDL's 2D render API, it will use GLES2 internally, which
202Emscripten will turn into WebGL calls. You can also use OpenGL ES 2
203directly by creating a GL context and drawing into it.
204
205Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually
206present anything on the screen until your return from your mainloop
207function.
208
209
210## Building SDL/emscripten
211
212First: do you _really_ need to build SDL from source?
213
214If you aren't developing SDL itself, have a desire to mess with its source
215code, or need something on the bleeding edge, don't build SDL. Just use
216Emscripten's packaged version!
217
218Compile and link your app with `-sUSE_SDL=2` and it'll use a build of
219SDL packaged with Emscripten. This comes from the same source code and
220fixes the Emscripten project makes to SDL are generally merged into SDL's
221revision control, so often this is much easier for app developers.
222
223`-sUSE_SDL=1` will select Emscripten's JavaScript reimplementation of SDL
2241.2 instead; if you need SDL 1.2, this might be fine, but we generally
225recommend you don't use SDL 1.2 in modern times.
226
227
228If you want to build SDL, though...
229
230SDL currently requires at least Emscripten 3.1.35 to build. Newer versions
231are likely to work, as well.
232
233
234Build:
235
236This works on Linux/Unix and macOS. Please send comments about Windows.
237
238Make sure you've [installed emsdk](https://emscripten.org/docs/getting_started/downloads.html)
239first, and run `source emsdk_env.sh` at the command line so it finds the
240tools.
241
242(These configure options might be overkill, but this has worked for me.)
243
244```bash
245cd SDL
246mkdir build
247cd build
248emconfigure ../configure --host=wasm32-unknown-emscripten --disable-pthreads --disable-assembly --disable-cpuinfo CFLAGS="-sUSE_SDL=0 -O3"
249emmake make -j4
250```
251
252If you want to build with thread support, something like this works:
253
254```bash
255emconfigure ../configure --host=wasm32-unknown-emscripten --enable-pthreads --disable-assembly --disable-cpuinfo CFLAGS="-sUSE_SDL=0 -O3 -pthread" LDFLAGS="-pthread"
256```
257
258Or with cmake:
259
260```bash
261mkdir build
262cd build
263emcmake cmake ..
264emmake make -j4
265```
266
267To build one of the tests:
268
269```bash
270cd test/
271emcc -O2 --js-opts 0 -g4 testdraw2.c -I../include ../build/.libs/libSDL2.a ../build/libSDL2_test.a -o a.html
272```
273
274## Building your app
275
276You need to compile with `emcc` instead of `gcc` or `clang` or whatever, but
277mostly it uses the same command line arguments as Clang.
278
279Link against the SDL/build/.libs/libSDL2.a file you generated by building SDL,
280link with `-sUSE_SDL=2` to use Emscripten's prepackaged SDL2 build.
281
282Usually you would produce a binary like this:
283
284```bash
285gcc -o mygame mygame.c # or whatever
286```
287
288But for Emscripten, you want to output something else:
289
290```bash
291emcc -o index.html mygame.c
292```
293
294This will produce several files...support Javascript and WebAssembly (.wasm)
295files. The `-o index.html` will produce a simple HTML page that loads and
296runs your app. You will (probably) eventually want to replace or customize
297that file and do `-o index.js` instead to just build the code pieces.
298
299If you're working on a program of any serious size, you'll likely need to
300link with `-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1gb` to get access
301to more memory. If using pthreads, you'll need the `-sMAXIMUM_MEMORY=1gb`
302or the app will fail to start on iOS browsers, but this might be a bug that
303goes away in the future.
304
305
306## Data files
307
308Your game probably has data files. Here's how to access them.
309
310Filesystem access works like a Unix filesystem; you have a single directory
311tree, possibly interpolated from several mounted locations, no drive letters,
312'/' for a path separator. You can access them with standard file APIs like
313open() or fopen() or SDL_RWops. You can read or write from the filesystem.
314
315By default, you probably have a "MEMFS" filesystem (all files are stored in
316memory, but access to them is immediate and doesn't need to block). There are
317other options, like "IDBFS" (files are stored in a local database, so they
318don't need to be in RAM all the time and they can persist between runs of the
319program, but access is not synchronous). You can mix and match these file
320systems, mounting a MEMFS filesystem at one place and idbfs elsewhere, etc,
321but that's beyond the scope of this document. Please refer to Emscripten's
322[page on the topic](https://emscripten.org/docs/porting/files/file_systems_overview.html)
323for more info.
324
325The _easiest_ (but not the best) way to get at your data files is to embed
326them in the app itself. Emscripten's linker has support for automating this.
327
328```bash
329emcc -o index.html loopwave.c --embed-file=../test/sample.wav@/sounds/sample.wav
330```
331
332This will pack ../test/sample.wav in your app, and make it available at
333"/sounds/sample.wav" at runtime. Emscripten makes sure this data is available
334before your main() function runs, and since it's in MEMFS, you can just
335read it like you do on other platforms. `--embed-file` can also accept a
336directory to pack an entire tree, and you can specify the argument multiple
337times to pack unrelated things into the final installation.
338
339Note that this is absolutely the best approach if you have a few small
340files to include and shouldn't worry about the issue further. However, if you
341have hundreds of megabytes and/or thousands of files, this is not so great,
342since the user will download it all every time they load your page, and it
343all has to live in memory at runtime.
344
345[Emscripten's documentation on the matter](https://emscripten.org/docs/porting/files/packaging_files.html)
346gives other options and details, and is worth a read.
347
348
349## Debugging
350
351Debugging web apps is a mixed bag. You should compile and link with
352`-gsource-map`, which embeds a ton of source-level debugging information into
353the build, and make sure _the app source code is available on the web server_,
354which is often a scary proposition for various reasons.
355
356When you debug from the browser's tools and hit a breakpoint, you can step
357through the actual C/C++ source code, though, which can be nice.
358
359If you try debugging in Firefox and it doesn't work well for no apparent
360reason, try Chrome, and vice-versa. These tools are still relatively new,
361and improving all the time.
362
363SDL_Log() (or even plain old printf) will write to the Javascript console,
364and honestly I find printf-style debugging to be easier than setting up a build
365for proper debugging, so use whatever tools work best for you.
366
367
368## Questions?
369
370Please give us feedback on this document at [the SDL bug tracker](https://github.com/libsdl-org/SDL/issues).
371If something is wrong or unclear, we want to know!
372
373
374