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