73 |
74 |
78 |
81 |
82 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 |
5 | fna-wasm:
6 | build: .
7 | image: fna-wasm-build
8 | ports:
9 | - "3030:8080"
10 | volumes:
11 | - ./dist:/var/output/bin/Release/net5.0/dist
--------------------------------------------------------------------------------
/docs/bsol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/docs/bsol.png
--------------------------------------------------------------------------------
/docs/fna-wasm-instructions.md.txt:
--------------------------------------------------------------------------------
1 | ## How to build your FNA game for WebAssembly
2 |
3 | **WARNING: This process is EXTREMELY experimental and not officially supported yet!**
4 |
5 | Thanks to the ongoing work on .NET WebAssembly support, it is now possible to build FNA games for the web!
6 |
7 | If you decide to give this a try, be sure to tell us about it in the [FNA Discord](https://discord.gg/fna-xna)! I'm happy to help if you run into problems or have any further questions that are not answered here.
8 |
9 | # The Basics
10 |
11 | **How does it work?**
12 |
13 | FNA browser games run on .NET 5 with the help of [Uno.Wasm.Bootstrap](https://github.com/unoplatform/Uno.Wasm.Bootstrap). As per usual, all of the platform-specific code on the native side is handled by SDL2. For the graphics backend we use FNA3D's OpenGL ES3 renderer, which Emscripten helpfully translates to WebGL 2.
14 |
15 | Just like all the other platforms FNA supports, the WebAssembly platform does not require a special version of FNA. It's just the regular old FNA.dll that you've come to know and love. Single-assembly portability, now on the web!
16 |
17 | In order to remain performant (and to statically link with the Emscripten-compiled native libraries), FNA browser games must be AOT compiled. However, reflection-heavy games are still feasible thanks to "mixed mode" compilation that enables the .NET interpreter on top of the AOT'd code, just for dynamic special cases!
18 |
19 | .NET's WebAssembly AOT support is still very much a WIP, so it's almost certain you'll run into runtime bugs. Thankfully there's almost always a workaround if you're willing to persevere, but still beware -- there be dragons.
20 |
21 | **What works?**
22 | * Graphics (via WebGL 2)
23 | * Sound Effects
24 | * Mouse / Keyboard / Gamepad Input
25 | * Asset loading (Content.Load<>, TitleContainer.OpenStream, File.Open)
26 |
27 | **What doesn't work?**
28 | * [Anything with threads.](https://github.com/unoplatform/Uno.Wasm.Bootstrap#threads-support) (XACT, threaded resource loading, etc.)
29 | * Calling `GraphicsDeviceManager.ApplyChanges()` in the game constructor. Because of [a bug in Emscripten](https://github.com/emscripten-ports/SDL2/issues/92), this will break mouse input.
30 | * APIs and assembly references that aren't compatible with .NET CoreCLR.
31 | * ContentReaders that use generics, such as `ListReader`. (There is a workaround though, which I'll describe in the Q+A.)
32 | * WebGL 1, since FNA3D does not have a GLES2 renderer.
33 | * Probably a lot of other stuff.
34 |
35 | **What's untested?**
36 | * Video, since I can't get Theorafile to build with Emscripten...
37 |
38 | # Prereqs
39 |
40 | The first thing you'll need is a compatible build OS. Currently only Linux and [Windows 10 + WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) are supported by Uno.Wasm.Bootstrap. I've personally been using WSL with Ubuntu 18.04 LTS.
41 |
42 | Next, [download, install, and set up Emscripten on Linux / your WSL partition](https://emscripten.org/docs/getting_started/downloads.html). **Don't** use a package manager! Use the officially recommended method of cloning from git!
43 |
44 | You will also need to install [.NET 5 on Linux/WSL](https://docs.microsoft.com/en-us/dotnet/core/install/linux).
45 |
46 | And finally, you'll need a basic FNA game to test with. I suggest you build the ol' reliable [Cornflower Blue sample app](https://gist.github.com/flibitijibibo/1ce4b7899b3cf1805a420330f0d2faf3#the-first-game-object) first to make sure everything's in order, before you try to build your own game for WebAssembly. You can do this part on Windows or on Linux.
47 |
48 | Now that's out of the way, let's build the fnalibs.
49 |
50 | # Building fnalibs
51 |
52 | EDIT: Thanks to clarvalon, we now have [automatically-built fnalibs](https://github.com/clarvalon/FNA-WASM-Build) that you can grab and use instead of building them yourself! But if you do want to build them manually, here's how you can do it.
53 |
54 | All of these steps must be done on Linux (or your WSL instance).
55 | ```bash
56 | # First, make sure you've added the emsdk to your path, per the Emscripten instructions!
57 |
58 | # Create the fnalibs repo directory
59 | mkdir fnalibs
60 | cd fnalibs
61 |
62 | # SDL2
63 | git clone https://github.com/libsdl-org/SDL
64 | cd SDL
65 | mkdir emscripten-build
66 | cd emscripten-build
67 | emconfigure ../configure --host=wasm32-unknown-emscripten --disable-assembly --disable-threads --disable-cpuinfo CFLAGS="-O2 -Wno-warn-absolute-paths -Wdeclaration-after-statement -Werror=declaration-after-statement" --prefix="$PWD/emscripten-sdl2-installed"
68 | emmake make
69 | emmake make install
70 | cd ../..
71 |
72 | # FNA3D
73 | git clone --recursive https://github.com/FNA-XNA/FNA3D
74 | cd FNA3D
75 | mkdir build
76 | cd build
77 | emcmake cmake .. -DSDL2_INCLUDE_DIRS=/include -DSDL2_LIBRARIES=/emscripten-build/emscripten-sdl2-installed/lib/libSDL2.a
78 | emmake make
79 | cd ../..
80 |
81 | # FAudio
82 | git clone https://github.com/FNA-XNA/FAudio
83 | cd FAudio
84 | mkdir build
85 | cd build
86 | emcmake cmake .. -DSDL2_INCLUDE_DIRS=/include -DSDL2_LIBRARIES=/emscripten-build/emscripten-sdl2-installed/lib/libSDL2.a
87 | emmake make
88 | cd ../..
89 |
90 | # Theorafile
91 | # Uh, instructions coming soon...?
92 | ```
93 |
94 | Now that you have all your libraries, it's time to copy them over to your FNA game project directory, like so:
95 | ```bash
96 | # Assuming WSL, remove the /mnt/c/Users/ if you're running native Linux.
97 | cp ./SDL/emscripten-build/emscripten-sdl2-installed/lib/libSDL2.a /mnt/c/Users///SDL2.a
98 | cp ./FNA3D/build/libFNA3D.a /mnt/c/Users///FNA3D.a
99 | cp ./FNA3D/build/libmojoshader.a /mnt/c/Users///libmojoshader.a
100 | cp ./FAudio/build/libFAudio.a /mnt/c/Users///FAudio.a
101 | ```
102 |
103 | Notice something very important in that command -- we are _renaming_ the SDL2, FNA3D, and FAudio libraries when we copy them! (e.g. `libSDL2.a` to just `SDL2.a`) This is unfortunately necessary for DllImport to work correctly.
104 |
105 | That's it for the fnalibs! Now to set up the project.
106 |
107 | # Setting up the C# project
108 |
109 | In your game's project directory, make a new .csproj file and copy-paste the following into it:
110 |
111 | ```xml
112 |
113 |
114 |
115 | Exe
116 | net5.0
117 | InterpreterAndAOT
118 | index.html
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | ```
147 | Much of this should be self-explanatory, but for more information on what these various attributes do, please see the very descriptive [Uno.Wasm.Bootstrap readme](https://github.com/unoplatform/Uno.Wasm.Bootstrap#readme).
148 |
149 | Additionally we need to create the index.html file that's referenced in the .csproj:
150 | ```html
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
204 |
205 | $(ADDITIONAL_CSS)
206 | $(ADDITIONAL_HEAD)
207 |
208 |
209 |
210 |
213 |
214 |
215 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
228 |
231 |
232 |
233 | ```
234 | And finally, we need our [LinkerConfig.xml](https://github.com/mono/linker/blob/main/docs/data-formats.md#xml-examples) file, which makes sure that the .NET linker doesn't get too excited and rip out stuff we actually use.
235 | ```
236 |
237 |
238 |
239 |
240 |
241 |
242 | ```
243 |
244 | And with that, we're done with the setup!
245 |
246 | # Building the game
247 |
248 | To run your game, you can either use Visual Studio or call `msbuild /t:restore` then `msbuild` directly in the command line in your project directory. I recommend the latter, as it gives you far more descriptive info about the build, so if something goes wrong you'll get an actual error message.
249 |
250 | You may encounter a build error that starts with: "The Windows subsystem for Linux dotnet environment may not be properly setup, and you may need to run the environment setup script." If you see this, just follow the instructions it gives you.
251 |
252 | Once the build is complete (which might take a while), we need to test the game!
253 |
254 | If you used the VS IDE to build+run it, it will automatically start up a local server. Don't put too much faith in it though. Its server has a habit of caching and running old builds, which can lead to a lot of confusion and frustration when debugging. (Speaking from experience here...)
255 |
256 | Instead, I recommend starting up a local server manually. My personal favorite is [live-server](https://www.npmjs.com/package/live-server), but you're welcome to use whatever you like. (I know `python -m http.server` is another popular one.) The path you'll want to serve on your server is `./bin/Debug/net5.0/dist/`.
257 |
258 | Finally, open up the browser and visit the address given by your server. With luck, you'll see the Cornflower Blue screen of life!
259 |
260 | ## Content
261 |
262 | To include Content in your game, add this to your WasmShellExtraEmccFlags Include string in the .csproj file:
263 | `--preload-file /mnt/c/Users///Content@Content`
264 |
265 | (If you're on Linux, remove the /mnt/ junk.)
266 |
267 | This will compile your whole Content directory into an asset bundle called "dotnet.data". Note that the path is relative to WSL. The "@Content" part of the string [re-maps the directory's name in the virtual file system](https://emscripten.org/docs/porting/files/packaging_files.html#packaging-files-packaged-file-location) so that we can use "Content/" as our root directory, just like on PC builds.
268 |
269 | By default, Emscripten will generate the dotnet.data file inside the `bin/dist/package_xxx` folder, but we need it to be in `bin/dist/` instead. To fix this, add this little MSBuild task into your .csproj. This automatically moves the content bundle to where it's supposed to go, saving you the trouble of manually dragging it into the right directory.
270 | ```xml
271 |
272 |
273 |
274 | ```
275 |
276 | Try adding some text files, images, or audio files into your Content directory and build! See what happens!
277 |
278 | ## Q+A
279 |
280 | **My builds take forever. Is that normal?**
281 |
282 | Yup... I've seen builds take upwards of 15 minutes for large projects. For smaller games, you should expect build times along the lines of 1-5 minutes, which is much more reasonable. But of course you'll probably want to stick with PC builds for rapid, iterative development.
283 |
284 | **My project hit a build error, but it doesn't actually say what the error is...?**
285 |
286 | If you're using Visual Studio, try using msbuild on the command line instead. It will be much more verbose.
287 |
288 | If the error came relatively early in the build cycle, try re-building. In rare cases, msbuild only spews the error message on the second run.
289 |
290 | If the error came very late in the build cycle, it's probably a linker error. Its output can be pretty cryptic sometimes, but if you study the msbuild output hard enough it might contain some clues as to what's gone wrong. If you're totally stumped, ask for help on the Discord.
291 |
292 | **Why can't we just use the Emscripten port of SDL2?**
293 |
294 | Because the Emscripten version of SDL2 is forked from upstream for no apparent reason and is perpetually out of date. As a result it's currently incompatible with FNA.
295 |
296 | **Why am I getting a mysterious "Uncaught: (some number)" exception without a stack trace in the JS console?**
297 |
298 | Most likely there's something in your code (or in FNA) that has the following structure:
299 | ```
300 | try { Foo(); }
301 | catch (SomeSpecificException e) { /* deal with the exception */ }
302 | ```
303 | without a generic `catch` block at the end that can handle any exception. Foo() might not be throwing the exception you expected to catch, and as a result the exception isn't properly caught by anything. This causes the .NET runtime to freak out, resulting in the indescipherable message you see here. Unfortunately, without better debugging support, the best thing to do is just start plopping Console.WriteLine statements around the codebase to see where it goes haywire.
304 |
305 | **How do I work around the generic ContentReader type limitation?**
306 |
307 | Modify FNA's source, of course! Print the name of the type it's trying to load so you know what it's called internally (see https://github.com/FNA-XNA/FNA/blob/master/src/Content/ContentTypeReaderManager.cs#L196) and then edit the code like so:
308 | ```
309 | Type l_readerType = Type.GetType(readerTypeString);
310 | if (l_readerType == null)
311 | {
312 | if (readerTypeString == "The.Type.You.Want`1[[whatever]]")
313 | {
314 | l_readerType = typeof(The.Type.You.Want);
315 | }
316 | }
317 | ```
--------------------------------------------------------------------------------
/lib/fnalibs/README.txt:
--------------------------------------------------------------------------------
1 | This is fnalibs, an archive containing the native libraries used by FNA.
2 |
3 | These are the folders included:
4 |
5 | - x86: 32-bit Windows
6 | - x64: 64-bit Windows
7 | - lib64: Linux (64-bit only)
8 | - osx: macOS (64-bit only)
9 | - vulkan: MoltenVK ICD for macOS
10 | - Place this at Game.app/Contents/Resources/vulkan/
11 |
12 | The library dependency list is as follows:
13 |
14 | - SDL2, used as the platform layer
15 | - FNA3D, used in the Graphics namespace
16 | - FAudio, used in the Audio/Media namespaces
17 | - libtheorafile, only used for VideoPlayer
18 |
--------------------------------------------------------------------------------
/lib/fnalibs/lib64/libFAudio.so.0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/lib64/libFAudio.so.0
--------------------------------------------------------------------------------
/lib/fnalibs/lib64/libFNA3D.so.0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/lib64/libFNA3D.so.0
--------------------------------------------------------------------------------
/lib/fnalibs/lib64/libSDL2-2.0.so.0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/lib64/libSDL2-2.0.so.0
--------------------------------------------------------------------------------
/lib/fnalibs/lib64/libtheorafile.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/lib64/libtheorafile.so
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libFAudio.0.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libFAudio.0.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libFNA3D.0.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libFNA3D.0.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libMoltenVK.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libMoltenVK.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libSDL2-2.0.0.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libSDL2-2.0.0.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libtheorafile.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libtheorafile.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/osx/libvulkan.1.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wattsyart/fna-wasm/991a838d42044af2aa3c64ba8d4dd6eb0c89a194/lib/fnalibs/osx/libvulkan.1.dylib
--------------------------------------------------------------------------------
/lib/fnalibs/vulkan/icd.d/MoltenVK_icd.json:
--------------------------------------------------------------------------------
1 | {
2 | "file_format_version" : "1.0.0",
3 | "ICD": {
4 | "library_path": "../../../MacOS/osx/libMoltenVK.dylib",
5 | "api_version" : "1.1.0"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/.env:
--------------------------------------------------------------------------------
1 | DOCKER_BUILDKIT=1
--------------------------------------------------------------------------------
/src/FnaWasm.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.32510.428
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FnaWasm", "FnaWasm\FnaWasm.csproj", "{D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FNA.Core", "..\lib\FNA\FNA.Core.csproj", "{A7826979-BC80-4AAB-B130-74FEE345AC92}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Release|Any CPU = Release|Any CPU
15 | Release|x64 = Release|x64
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Debug|x64.ActiveCfg = Debug|x64
21 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Debug|x64.Build.0 = Debug|x64
22 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Release|x64.ActiveCfg = Release|Any CPU
25 | {D9DF7A01-46B0-4C2C-847A-5ED2B842D3C1}.Release|x64.Build.0 = Release|Any CPU
26 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Debug|Any CPU.ActiveCfg = Debug|x64
27 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Debug|x64.ActiveCfg = Debug|x64
28 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Debug|x64.Build.0 = Debug|x64
29 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Release|Any CPU.ActiveCfg = Release|x64
30 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Release|x64.ActiveCfg = Release|x64
31 | {A7826979-BC80-4AAB-B130-74FEE345AC92}.Release|x64.Build.0 = Release|x64
32 | EndGlobalSection
33 | GlobalSection(SolutionProperties) = preSolution
34 | HideSolutionNode = FALSE
35 | EndGlobalSection
36 | GlobalSection(ExtensibilityGlobals) = postSolution
37 | SolutionGuid = {E09A6A46-5BA3-44DB-AA40-17E1FC05EF5E}
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/src/FnaWasm/.dockerignore:
--------------------------------------------------------------------------------
1 | FnaWasm.csproj
--------------------------------------------------------------------------------
/src/FnaWasm/FnaWasm.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | AnyCPU;x64
8 |
9 |
10 |
11 |
12 | x86\%(RecursiveDir)%(Filename)%(Extension)
13 | PreserveNewest
14 |
15 |
16 | x64\%(RecursiveDir)%(Filename)%(Extension)
17 | PreserveNewest
18 |
19 |
20 | osx\%(RecursiveDir)%(Filename)%(Extension)
21 | PreserveNewest
22 |
23 |
24 | lib\%(RecursiveDir)%(Filename)%(Extension)
25 | PreserveNewest
26 |
27 |
28 | lib64\%(RecursiveDir)%(Filename)%(Extension)
29 | PreserveNewest
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/FnaWasm/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace FnaWasm
6 | {
7 | static class Program
8 | {
9 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
10 | [return: MarshalAs(UnmanagedType.Bool)]
11 | private static extern bool SetDllDirectory(string lpPathName);
12 |
13 | [STAThread]
14 | private static void Main(string[] args)
15 | {
16 | // https://github.com/FNA-XNA/FNA/wiki/4:-FNA-and-Windows-API#64-bit-support
17 | if (Environment.OSVersion.Platform == PlatformID.Win32NT)
18 | {
19 | SetDllDirectory(Path.Combine(
20 | AppDomain.CurrentDomain.BaseDirectory,
21 | Environment.Is64BitProcess ? "x64" : "x86"
22 | ));
23 | }
24 |
25 | using (WasmGame g = new WasmGame())
26 | {
27 | g.Run();
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/FnaWasm/WasmGame.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace FnaWasm
4 | {
5 | class WasmGame : Game
6 | {
7 | private readonly GraphicsDeviceManager graphics;
8 |
9 | public WasmGame()
10 | {
11 | graphics = new GraphicsDeviceManager(this);
12 | }
13 |
14 | protected override void Draw(GameTime gameTime)
15 | {
16 | graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------