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