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